// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
#if DOTNET4
using System.Net;
#else
using System.Web;
#endif
using System.Text;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Highlighting
{
///
/// RichTextWriter implementation that produces HTML.
///
class HtmlRichTextWriter : RichTextWriter
{
readonly TextWriter htmlWriter;
readonly HtmlOptions options;
Stack endTagStack = new Stack();
bool spaceNeedsEscaping = true;
bool hasSpace;
bool needIndentation = true;
int indentationLevel;
///
/// Creates a new HtmlRichTextWriter instance.
///
///
/// The text writer where the raw HTML is written to.
/// The HtmlRichTextWriter does not take ownership of the htmlWriter;
/// disposing the HtmlRichTextWriter will not dispose the underlying htmlWriter!
///
/// Options that control the HTML output.
public HtmlRichTextWriter(TextWriter htmlWriter, HtmlOptions options = null)
{
if (htmlWriter == null)
throw new ArgumentNullException("htmlWriter");
this.htmlWriter = htmlWriter;
this.options = options ?? new HtmlOptions();
}
///
public override Encoding Encoding {
get { return htmlWriter.Encoding; }
}
///
public override void Flush()
{
FlushSpace(true); // next char potentially might be whitespace
htmlWriter.Flush();
}
///
protected override void Dispose(bool disposing)
{
if (disposing) {
FlushSpace(true);
}
base.Dispose(disposing);
}
void FlushSpace(bool nextIsWhitespace)
{
if (hasSpace) {
if (spaceNeedsEscaping || nextIsWhitespace)
htmlWriter.Write(" ");
else
htmlWriter.Write(' ');
hasSpace = false;
spaceNeedsEscaping = true;
}
}
void WriteIndentation()
{
if (needIndentation) {
for (int i = 0; i < indentationLevel; i++) {
WriteChar('\t');
}
needIndentation = false;
}
}
///
public override void Write(char value)
{
WriteIndentation();
WriteChar(value);
}
static readonly char[] specialChars = { ' ', '\t', '\r', '\n' };
void WriteChar(char c)
{
bool isWhitespace = char.IsWhiteSpace(c);
FlushSpace(isWhitespace);
switch (c) {
case ' ':
if (spaceNeedsEscaping)
htmlWriter.Write(" ");
else
hasSpace = true;
break;
case '\t':
for (int i = 0; i < options.TabSize; i++) {
htmlWriter.Write(" ");
}
break;
case '\r':
break; // ignore; we'll write the
with the following \n
case '\n':
htmlWriter.Write("
");
needIndentation = true;
break;
default:
#if DOTNET4
WebUtility.HtmlEncode(c.ToString(), htmlWriter);
#else
HttpUtility.HtmlEncode(c.ToString(), htmlWriter);
#endif
break;
}
// If we just handled a space by setting hasSpace = true,
// we mustn't set spaceNeedsEscaping as doing so would affect our own space,
// not just the following spaces.
if (c != ' ') {
// Following spaces must be escaped if c was a newline/tab;
// and they don't need escaping if c was a normal character.
spaceNeedsEscaping = isWhitespace;
}
}
///
public override void Write(string value)
{
int pos = 0;
do {
int endPos = value.IndexOfAny(specialChars, pos);
if (endPos < 0) {
WriteSimpleString(value.Substring(pos));
return; // reached end of string
}
if (endPos > pos)
WriteSimpleString(value.Substring(pos, endPos - pos));
WriteChar(value[pos]);
pos = endPos + 1;
} while (pos < value.Length);
}
void WriteIndentationAndSpace()
{
WriteIndentation();
FlushSpace(false);
}
void WriteSimpleString(string value)
{
if (value.Length == 0)
return;
WriteIndentationAndSpace();
#if DOTNET4
WebUtility.HtmlEncode(value, htmlWriter);
#else
HttpUtility.HtmlEncode(value, htmlWriter);
#endif
}
///
public override void Indent()
{
indentationLevel++;
}
///
public override void Unindent()
{
if (indentationLevel == 0)
throw new NotSupportedException();
indentationLevel--;
}
///
protected override void BeginUnhandledSpan()
{
endTagStack.Push(null);
}
///
public override void EndSpan()
{
htmlWriter.Write(endTagStack.Pop());
}
///
public override void BeginSpan(Color foregroundColor)
{
BeginSpan(new HighlightingColor { Foreground = new SimpleHighlightingBrush(foregroundColor) });
}
///
public override void BeginSpan(FontFamily fontFamily)
{
BeginUnhandledSpan(); // TODO
}
///
public override void BeginSpan(FontStyle fontStyle)
{
BeginSpan(new HighlightingColor { FontStyle = fontStyle });
}
///
public override void BeginSpan(FontWeight fontWeight)
{
BeginSpan(new HighlightingColor { FontWeight = fontWeight });
}
///
public override void BeginSpan(HighlightingColor highlightingColor)
{
WriteIndentationAndSpace();
if (options.ColorNeedsSpanForStyling(highlightingColor)) {
htmlWriter.Write("');
endTagStack.Push("");
} else {
endTagStack.Push(null);
}
}
///
public override void BeginHyperlinkSpan(Uri uri)
{
WriteIndentationAndSpace();
#if DOTNET4
string link = WebUtility.HtmlEncode(uri.ToString());
#else
string link = HttpUtility.HtmlEncode(uri.ToString());
#endif
htmlWriter.Write("");
endTagStack.Push("");
}
}
}