Add basic rich-text support to AvalonEdit.
This commit is contained in:
Родитель
12e7a07ac4
Коммит
04b796ccb6
|
@ -25,9 +25,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
{
|
||||
var segment = new TextSegment { StartOffset = 0, Length = document.TextLength };
|
||||
string html = HtmlClipboard.CreateHtmlFragment(document, highlighter, segment, new HtmlOptions());
|
||||
Assert.AreEqual("<span style=\"color: #008000; font-weight: bold; \">using</span> System.Text;<br>" + Environment.NewLine +
|
||||
" <span style=\"color: #ff0000; \">string</span> " +
|
||||
"text = <span style=\"color: #191970; font-weight: bold; \">SomeMethod</span>();", html);
|
||||
Assert.AreEqual("<span style=\"color: #008000; font-weight: bold; \">using</span> System.Text;<br>" + Environment.NewLine +
|
||||
" <span style=\"color: #ff0000; \">string</span> " +
|
||||
"text = <span style=\"color: #191970; font-weight: bold; \">SomeMethod</span>();", html);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -30,12 +30,7 @@ namespace ICSharpCode.AvalonEdit.Utils
|
|||
list.InsertRange(0, billion, "A");
|
||||
list.InsertRange(1, billion, "B");
|
||||
Assert.AreEqual(2 * billion, list.Count);
|
||||
try {
|
||||
list.InsertRange(2, billion, "C");
|
||||
Assert.Fail("Expected OverflowException");
|
||||
} catch (OverflowException) {
|
||||
// expected
|
||||
}
|
||||
Assert.Throws<OverflowException>(delegate { list.InsertRange(2, billion, "C"); });
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -103,5 +98,34 @@ namespace ICSharpCode.AvalonEdit.Utils
|
|||
list.RemoveRange(0, 3);
|
||||
Assert.AreEqual(new[] { 2, 3, 3 }, list.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Transform()
|
||||
{
|
||||
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
|
||||
list.AddRange(new[] { 0, 1, 1, 0 });
|
||||
int calls = 0;
|
||||
list.Transform(i => { calls++; return i + 1; });
|
||||
Assert.AreEqual(3, calls);
|
||||
Assert.AreEqual(new[] { 1, 2, 2, 1 }, list.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TransformToZero()
|
||||
{
|
||||
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
|
||||
list.AddRange(new[] { 0, 1, 1, 0 });
|
||||
list.Transform(i => 0);
|
||||
Assert.AreEqual(new[] { 0, 0, 0, 0 }, list.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TransformRange()
|
||||
{
|
||||
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
|
||||
list.AddRange(new[] { 0, 1, 1, 1, 0, 0 });
|
||||
list.TransformRange(2, 3, i => 0);
|
||||
Assert.AreEqual(new[] { 0, 1, 0, 0, 0, 0 }, list.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ICSharpCode.NRefactory.Editor;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// A TextWriter implementation that directly inserts into a document.
|
||||
/// </summary>
|
||||
public class DocumentTextWriter : TextWriter
|
||||
{
|
||||
readonly IDocument document;
|
||||
int insertionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new DocumentTextWriter that inserts into document, starting at insertionOffset.
|
||||
/// </summary>
|
||||
public DocumentTextWriter(IDocument document, int insertionOffset)
|
||||
{
|
||||
this.insertionOffset = insertionOffset;
|
||||
if (document == null)
|
||||
throw new ArgumentNullException("document");
|
||||
this.document = document;
|
||||
var line = document.GetLineByOffset(insertionOffset);
|
||||
if (line.DelimiterLength == 0)
|
||||
line = line.PreviousLine;
|
||||
if (line != null)
|
||||
this.NewLine = document.GetText(line.EndOffset, line.DelimiterLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the current insertion offset.
|
||||
/// </summary>
|
||||
public int InsertionOffset {
|
||||
get { return insertionOffset; }
|
||||
set { insertionOffset = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(char value)
|
||||
{
|
||||
document.Insert(insertionOffset, value.ToString());
|
||||
insertionOffset++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(char[] buffer, int index, int count)
|
||||
{
|
||||
document.Insert(insertionOffset, new string(buffer, index, count));
|
||||
insertionOffset += count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(string value)
|
||||
{
|
||||
document.Insert(insertionOffset, value);
|
||||
insertionOffset += value.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Encoding Encoding {
|
||||
get { return Encoding.UTF8; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -117,9 +117,9 @@ namespace ICSharpCode.AvalonEdit.Document
|
|||
/// <summary>
|
||||
/// Gets the newline sequence used in the document at the specified line.
|
||||
/// </summary>
|
||||
public static string GetNewLineFromDocument(TextDocument document, int lineNumber)
|
||||
public static string GetNewLineFromDocument(IDocument document, int lineNumber)
|
||||
{
|
||||
DocumentLine line = document.GetLineByNumber(lineNumber);
|
||||
IDocumentLine line = document.GetLineByNumber(lineNumber);
|
||||
if (line.DelimiterLength == 0) {
|
||||
// at the end of the document, there's no line delimiter, so use the delimiter
|
||||
// from the previous line
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using ICSharpCode.NRefactory.Editor;
|
||||
using ICSharpCode.AvalonEdit.Utils;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document
|
||||
|
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
@ -73,11 +74,11 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
stateChanges.Add(new HighlightingState());
|
||||
}
|
||||
|
||||
HighlightedInlineBuilder(string text, int[] offsets, HighlightingState[] states)
|
||||
HighlightedInlineBuilder(string text, List<int> offsets, List<HighlightingState> states)
|
||||
{
|
||||
this.text = text;
|
||||
stateChangeOffsets.AddRange(offsets);
|
||||
stateChanges.AddRange(states);
|
||||
stateChangeOffsets = offsets;
|
||||
stateChanges = states;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -207,8 +208,8 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
public HighlightedInlineBuilder Clone()
|
||||
{
|
||||
return new HighlightedInlineBuilder(this.text,
|
||||
stateChangeOffsets.ToArray(),
|
||||
stateChanges.Select(sc => sc.Clone()).ToArray());
|
||||
stateChangeOffsets.ToList(),
|
||||
stateChanges.Select(sc => sc.Clone()).ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
}
|
||||
#endregion
|
||||
|
||||
#region ToHtml
|
||||
#region WriteTo / ToHtml
|
||||
sealed class HtmlElement : IComparable<HtmlElement>
|
||||
{
|
||||
internal readonly int Offset;
|
||||
|
@ -203,21 +203,21 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces HTML code for the line, with <span class="colorName"> tags.
|
||||
/// Writes the highlighted line to the RichTextWriter.
|
||||
/// </summary>
|
||||
public string ToHtml(HtmlOptions options)
|
||||
public void WriteTo(RichTextWriter writer)
|
||||
{
|
||||
int startOffset = this.DocumentLine.Offset;
|
||||
return ToHtml(startOffset, startOffset + this.DocumentLine.Length, options);
|
||||
WriteTo(writer, startOffset, startOffset + this.DocumentLine.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces HTML code for a section of the line, with <span class="colorName"> tags.
|
||||
/// Writes a part of the highlighted line to the RichTextWriter.
|
||||
/// </summary>
|
||||
public string ToHtml(int startOffset, int endOffset, HtmlOptions options)
|
||||
public void WriteTo(RichTextWriter writer, int startOffset, int endOffset)
|
||||
{
|
||||
if (options == null)
|
||||
throw new ArgumentNullException("options");
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException("writer");
|
||||
int documentLineStartOffset = this.DocumentLine.Offset;
|
||||
int documentLineEndOffset = documentLineStartOffset + this.DocumentLine.Length;
|
||||
if (startOffset < documentLineStartOffset || startOffset > documentLineEndOffset)
|
||||
|
@ -237,32 +237,49 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
elements.Sort();
|
||||
|
||||
IDocument document = this.Document;
|
||||
StringWriter w = new StringWriter(CultureInfo.InvariantCulture);
|
||||
int textOffset = startOffset;
|
||||
foreach (HtmlElement e in elements) {
|
||||
int newOffset = Math.Min(e.Offset, endOffset);
|
||||
if (newOffset > startOffset) {
|
||||
HtmlClipboard.EscapeHtml(w, document.GetText(textOffset, newOffset - textOffset), options);
|
||||
document.WriteTextTo(writer, textOffset, newOffset - textOffset);
|
||||
}
|
||||
textOffset = Math.Max(textOffset, newOffset);
|
||||
if (options.ColorNeedsSpanForStyling(e.Color)) {
|
||||
if (e.IsEnd) {
|
||||
w.Write("</span>");
|
||||
} else {
|
||||
w.Write("<span");
|
||||
options.WriteStyleAttributeForColor(w, e.Color);
|
||||
w.Write('>');
|
||||
}
|
||||
}
|
||||
if (e.IsEnd)
|
||||
writer.EndSpan();
|
||||
else
|
||||
writer.BeginSpan(e.Color);
|
||||
}
|
||||
HtmlClipboard.EscapeHtml(w, document.GetText(textOffset, endOffset - textOffset), options);
|
||||
return w.ToString();
|
||||
document.WriteTextTo(writer, textOffset, endOffset - textOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces HTML code for the line, with <span class="colorName"> tags.
|
||||
/// </summary>
|
||||
public string ToHtml(HtmlOptions options = null)
|
||||
{
|
||||
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
|
||||
using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) {
|
||||
WriteTo(htmlWriter);
|
||||
}
|
||||
return stringWriter.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces HTML code for a section of the line, with <span class="colorName"> tags.
|
||||
/// </summary>
|
||||
public string ToHtml(int startOffset, int endOffset, HtmlOptions options = null)
|
||||
{
|
||||
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
|
||||
using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) {
|
||||
WriteTo(htmlWriter, startOffset, endOffset);
|
||||
}
|
||||
return stringWriter.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[" + GetType().Name + " " + ToHtml(new HtmlOptions()) + "]";
|
||||
return "[" + GetType().Name + " " + ToHtml() + "]";
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
|
|
@ -75,6 +75,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
{
|
||||
info.AddValue("color", brush.Color.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
SimpleHighlightingBrush other = obj as SimpleHighlightingBrush;
|
||||
if (other == null)
|
||||
return false;
|
||||
return this.brush.Color.Equals(other.brush.Color);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return brush.Color.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -113,5 +126,18 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
{
|
||||
info.AddValue("propertyName", property.Name);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
SystemColorHighlightingBrush other = obj as SystemColorHighlightingBrush;
|
||||
if (other == null)
|
||||
return false;
|
||||
return object.Equals(this.property, other.property);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return property.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ using System.Security.Permissions;
|
|||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using ICSharpCode.NRefactory.TypeSystem;
|
||||
using ICSharpCode.NRefactory.TypeSystem.Implementation;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
|
@ -15,8 +17,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// A highlighting color is a set of font properties and foreground and background color.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class HighlightingColor : ISerializable
|
||||
public class HighlightingColor : ISerializable, IFreezable, ICloneable, IEquatable<HighlightingColor>
|
||||
{
|
||||
internal static readonly HighlightingColor Empty = FreezableHelper.FreezeAndReturn(new HighlightingColor());
|
||||
|
||||
string name;
|
||||
FontWeight? fontWeight;
|
||||
FontStyle? fontStyle;
|
||||
|
@ -175,7 +179,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// <summary>
|
||||
/// Prevent further changes to this highlighting color.
|
||||
/// </summary>
|
||||
public void Freeze()
|
||||
public virtual void Freeze()
|
||||
{
|
||||
frozen = true;
|
||||
}
|
||||
|
@ -186,5 +190,69 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
public bool IsFrozen {
|
||||
get { return frozen; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones this highlighting color.
|
||||
/// If this color is frozen, the clone will be unfrozen.
|
||||
/// </summary>
|
||||
public virtual HighlightingColor Clone()
|
||||
{
|
||||
HighlightingColor c = (HighlightingColor)MemberwiseClone();
|
||||
c.frozen = false;
|
||||
return c;
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return Clone();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override sealed bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as HighlightingColor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool Equals(HighlightingColor other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
return this.name == other.name && this.fontWeight == other.fontWeight && this.fontStyle == other.fontStyle && object.Equals(this.foreground, other.foreground) && object.Equals(this.background, other.background);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = 0;
|
||||
unchecked {
|
||||
if (name != null)
|
||||
hashCode += 1000000007 * name.GetHashCode();
|
||||
hashCode += 1000000009 * fontWeight.GetHashCode();
|
||||
hashCode += 1000000021 * fontStyle.GetHashCode();
|
||||
if (foreground != null)
|
||||
hashCode += 1000000033 * foreground.GetHashCode();
|
||||
if (background != null)
|
||||
hashCode += 1000000087 * background.GetHashCode();
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overwrites the properties in this HighlightingColor with those from the given color;
|
||||
/// but maintains the current values where the properties of the given color return <c>null</c>.
|
||||
/// </summary>
|
||||
public void MergeWith(HighlightingColor color)
|
||||
{
|
||||
FreezableHelper.ThrowIfFrozen(this);
|
||||
if (color.fontWeight != null)
|
||||
this.fontWeight = color.fontWeight;
|
||||
if (color.fontStyle != null)
|
||||
this.fontStyle = color.fontStyle;
|
||||
if (color.foreground != null)
|
||||
this.foreground = color.foreground;
|
||||
if (color.background != null)
|
||||
this.background = color.background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// Gets whether the color is empty (has no effect on a VisualLineTextElement).
|
||||
/// For example, the C# "Punctuation" is an empty color.
|
||||
/// </summary>
|
||||
bool IsEmptyColor(HighlightingColor color)
|
||||
internal static bool IsEmptyColor(HighlightingColor color)
|
||||
{
|
||||
if (color == null)
|
||||
return true;
|
||||
|
@ -223,14 +223,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// Applies a highlighting color to a visual line element.
|
||||
/// </summary>
|
||||
protected virtual void ApplyColorToElement(VisualLineElement element, HighlightingColor color)
|
||||
{
|
||||
ApplyColorToElement(element, color, CurrentContext);
|
||||
}
|
||||
|
||||
internal static void ApplyColorToElement(VisualLineElement element, HighlightingColor color, ITextRunConstructionContext context)
|
||||
{
|
||||
if (color.Foreground != null) {
|
||||
Brush b = color.Foreground.GetBrush(CurrentContext);
|
||||
Brush b = color.Foreground.GetBrush(context);
|
||||
if (b != null)
|
||||
element.TextRunProperties.SetForegroundBrush(b);
|
||||
}
|
||||
if (color.Background != null) {
|
||||
Brush b = color.Background.GetBrush(CurrentContext);
|
||||
Brush b = color.Background.GetBrush(context);
|
||||
if (b != null)
|
||||
element.BackgroundBrush = b;
|
||||
}
|
||||
|
|
|
@ -94,108 +94,5 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
}
|
||||
return html.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes text and writes the result to the StringBuilder.
|
||||
/// </summary>
|
||||
internal static void EscapeHtml(StringWriter w, string text, HtmlOptions options)
|
||||
{
|
||||
int spaceCount = -1;
|
||||
foreach (char c in text) {
|
||||
if (c == ' ') {
|
||||
if (spaceCount < 0)
|
||||
w.Write(" ");
|
||||
else
|
||||
spaceCount++;
|
||||
} else if (c == '\t') {
|
||||
if (spaceCount < 0)
|
||||
spaceCount = 0;
|
||||
spaceCount += options.TabSize;
|
||||
} else {
|
||||
if (spaceCount == 1) {
|
||||
w.Write(' ');
|
||||
} else if (spaceCount >= 1) {
|
||||
for (int i = 0; i < spaceCount; i++) {
|
||||
w.Write(" ");
|
||||
}
|
||||
}
|
||||
spaceCount = 0;
|
||||
switch (c) {
|
||||
case '<':
|
||||
w.Write("<");
|
||||
break;
|
||||
case '>':
|
||||
w.Write(">");
|
||||
break;
|
||||
case '&':
|
||||
w.Write("&");
|
||||
break;
|
||||
case '"':
|
||||
w.Write(""");
|
||||
break;
|
||||
default:
|
||||
w.Write(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < spaceCount; i++) {
|
||||
w.Write(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds options for converting text to HTML.
|
||||
/// </summary>
|
||||
public class HtmlOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a default HtmlOptions instance.
|
||||
/// </summary>
|
||||
public HtmlOptions()
|
||||
{
|
||||
this.TabSize = 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HtmlOptions instance that copies applicable options from the <see cref="TextEditorOptions"/>.
|
||||
/// </summary>
|
||||
public HtmlOptions(TextEditorOptions options)
|
||||
: this()
|
||||
{
|
||||
if (options == null)
|
||||
throw new ArgumentNullException("options");
|
||||
this.TabSize = options.IndentationSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of spaces a tab gets converted to.
|
||||
/// </summary>
|
||||
public int TabSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes the HTML attribute for the style to the text writer.
|
||||
/// </summary>
|
||||
public virtual void WriteStyleAttributeForColor(TextWriter writer, HighlightingColor color)
|
||||
{
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException("writer");
|
||||
if (color == null)
|
||||
throw new ArgumentNullException("color");
|
||||
writer.Write(" style=\"");
|
||||
writer.Write(color.ToCss());
|
||||
writer.Write("\"");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the color needs to be written out to HTML.
|
||||
/// </summary>
|
||||
public virtual bool ColorNeedsSpanForStyling(HighlightingColor color)
|
||||
{
|
||||
if (color == null)
|
||||
throw new ArgumentNullException("color");
|
||||
return !string.IsNullOrEmpty(color.ToCss());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds options for converting text to HTML.
|
||||
/// </summary>
|
||||
public class HtmlOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a default HtmlOptions instance.
|
||||
/// </summary>
|
||||
public HtmlOptions()
|
||||
{
|
||||
this.TabSize = 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HtmlOptions instance that copies applicable options from the <see cref="TextEditorOptions"/>.
|
||||
/// </summary>
|
||||
public HtmlOptions(TextEditorOptions options) : this()
|
||||
{
|
||||
if (options == null)
|
||||
throw new ArgumentNullException("options");
|
||||
this.TabSize = options.IndentationSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of spaces a tab gets converted to.
|
||||
/// </summary>
|
||||
public int TabSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes the HTML attribute for the style to the text writer.
|
||||
/// </summary>
|
||||
public virtual void WriteStyleAttributeForColor(TextWriter writer, HighlightingColor color)
|
||||
{
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException("writer");
|
||||
if (color == null)
|
||||
throw new ArgumentNullException("color");
|
||||
writer.Write(" style=\"");
|
||||
WebUtility.HtmlEncode(color.ToCss(), writer);
|
||||
writer.Write('"');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the color needs to be written out to HTML.
|
||||
/// </summary>
|
||||
public virtual bool ColorNeedsSpanForStyling(HighlightingColor color)
|
||||
{
|
||||
if (color == null)
|
||||
throw new ArgumentNullException("color");
|
||||
return !string.IsNullOrEmpty(color.ToCss());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using ICSharpCode.AvalonEdit.Utils;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
/// <summary>
|
||||
/// RichTextWriter implementation that produces HTML.
|
||||
/// </summary>
|
||||
public class HtmlRichTextWriter : RichTextWriter
|
||||
{
|
||||
readonly TextWriter htmlWriter;
|
||||
readonly HtmlOptions options;
|
||||
Stack<string> endTagStack = new Stack<string>();
|
||||
bool spaceNeedsEscaping = true;
|
||||
bool hasSpace;
|
||||
bool needIndentation = true;
|
||||
int indentationLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HtmlRichTextWriter instance.
|
||||
/// </summary>
|
||||
/// <param name="htmlWriter">
|
||||
/// 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!
|
||||
/// </param>
|
||||
/// <param name="options">Options that control the HTML output.</param>
|
||||
public HtmlRichTextWriter(TextWriter htmlWriter, HtmlOptions options = null)
|
||||
{
|
||||
if (htmlWriter == null)
|
||||
throw new ArgumentNullException("htmlWriter");
|
||||
this.htmlWriter = htmlWriter;
|
||||
this.options = options ?? new HtmlOptions();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Encoding Encoding {
|
||||
get { return htmlWriter.Encoding; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush()
|
||||
{
|
||||
FlushSpace(true); // next char potentially might be whitespace
|
||||
htmlWriter.Flush();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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 <br/> with the following \n
|
||||
case '\n':
|
||||
htmlWriter.Write("<br/>");
|
||||
needIndentation = true;
|
||||
break;
|
||||
default:
|
||||
WebUtility.HtmlEncode(c.ToString(), htmlWriter);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
WebUtility.HtmlEncode(value, htmlWriter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Indent()
|
||||
{
|
||||
indentationLevel++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unindent()
|
||||
{
|
||||
if (indentationLevel == 0)
|
||||
throw new NotSupportedException();
|
||||
indentationLevel--;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void BeginUnhandledSpan()
|
||||
{
|
||||
endTagStack.Push(null);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void EndSpan()
|
||||
{
|
||||
htmlWriter.Write(endTagStack.Pop());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(Color foregroundColor)
|
||||
{
|
||||
BeginSpan(new HighlightingColor { Foreground = new SimpleHighlightingBrush(foregroundColor) });
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(FontFamily fontFamily)
|
||||
{
|
||||
BeginUnhandledSpan(); // TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(FontStyle fontStyle)
|
||||
{
|
||||
BeginSpan(new HighlightingColor { FontStyle = fontStyle });
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(FontWeight fontWeight)
|
||||
{
|
||||
BeginSpan(new HighlightingColor { FontWeight = fontWeight });
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(HighlightingColor highlightingColor)
|
||||
{
|
||||
WriteIndentationAndSpace();
|
||||
if (options.ColorNeedsSpanForStyling(highlightingColor)) {
|
||||
htmlWriter.Write("<span");
|
||||
options.WriteStyleAttributeForColor(htmlWriter, highlightingColor);
|
||||
htmlWriter.Write('>');
|
||||
endTagStack.Push("</span>");
|
||||
} else {
|
||||
endTagStack.Push(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginHyperlinkSpan(Uri uri)
|
||||
{
|
||||
WriteIndentationAndSpace();
|
||||
htmlWriter.Write("<a href=\"" + WebUtility.HtmlEncode(uri.ToString()) + "\">");
|
||||
endTagStack.Push("</a>");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
/// <summary>
|
||||
/// A colorizer that applies the highlighting from a <see cref="RichTextModel"/> to the editor.
|
||||
/// </summary>
|
||||
public class RichTextColorizer : DocumentColorizingTransformer
|
||||
{
|
||||
readonly RichTextModel richTextModel;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RichTextColorizer instance.
|
||||
/// </summary>
|
||||
public RichTextColorizer(RichTextModel richTextModel)
|
||||
{
|
||||
if (richTextModel == null)
|
||||
throw new ArgumentNullException("richTextModel");
|
||||
this.richTextModel = richTextModel;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ColorizeLine(DocumentLine line)
|
||||
{
|
||||
var sections = richTextModel.GetHighlightedSections(line.Offset, line.Length);
|
||||
foreach (HighlightedSection section in sections) {
|
||||
if (HighlightingColorizer.IsEmptyColor(section.Color))
|
||||
continue;
|
||||
ChangeLinePart(section.Offset, section.Offset + section.Length,
|
||||
visualLineElement => HighlightingColorizer.ApplyColorToElement(visualLineElement, section.Color, CurrentContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using ICSharpCode.NRefactory.Editor;
|
||||
using ICSharpCode.NRefactory.TypeSystem.Implementation;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Utils;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores rich-text formatting.
|
||||
/// </summary>
|
||||
public sealed class RichTextModel
|
||||
{
|
||||
CompressingTreeList<HighlightingColor> list = new CompressingTreeList<HighlightingColor>(object.Equals);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the document.
|
||||
/// This has an effect on which coordinates are valid for this RichTextModel.
|
||||
/// </summary>
|
||||
public int DocumentLength {
|
||||
get { return list.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RichTextModel that needs manual calls to <see cref="UpdateOffsets(DocumentChangeEventArgs)"/>.
|
||||
/// </summary>
|
||||
public RichTextModel(int documentLength)
|
||||
{
|
||||
list.InsertRange(0, documentLength, HighlightingColor.Empty);
|
||||
}
|
||||
|
||||
#region UpdateOffsets
|
||||
/// <summary>
|
||||
/// Updates the start and end offsets of all segments stored in this collection.
|
||||
/// </summary>
|
||||
/// <param name="e">DocumentChangeEventArgs instance describing the change to the document.</param>
|
||||
public void UpdateOffsets(DocumentChangeEventArgs e)
|
||||
{
|
||||
if (e == null)
|
||||
throw new ArgumentNullException("e");
|
||||
OffsetChangeMap map = e.OffsetChangeMapOrNull;
|
||||
if (map != null) {
|
||||
foreach (OffsetChangeMapEntry entry in map) {
|
||||
UpdateOffsetsInternal(entry);
|
||||
}
|
||||
} else {
|
||||
UpdateOffsetsInternal(e.CreateSingleChangeMapEntry());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the start and end offsets of all segments stored in this collection.
|
||||
/// </summary>
|
||||
/// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
|
||||
public void UpdateOffsets(OffsetChangeMapEntry change)
|
||||
{
|
||||
UpdateOffsetsInternal(change);
|
||||
}
|
||||
|
||||
void UpdateOffsetsInternal(OffsetChangeMapEntry entry)
|
||||
{
|
||||
HighlightingColor color;
|
||||
if (entry.RemovalLength > 0) {
|
||||
color = list[entry.Offset];
|
||||
list.RemoveRange(entry.Offset, entry.RemovalLength);
|
||||
} else if (list.Count > 0) {
|
||||
color = list[Math.Max(0, entry.Offset - 1)];
|
||||
} else {
|
||||
color = HighlightingColor.Empty;
|
||||
}
|
||||
list.InsertRange(entry.Offset, entry.InsertionLength, color);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HighlightingColor for the specified offset.
|
||||
/// </summary>
|
||||
public HighlightingColor GetHighlighting(int offset)
|
||||
{
|
||||
return list[offset];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the HighlightingColor to the specified range of text.
|
||||
/// If the color specifies <c>null</c> for some properties, existing highlighting is preserved.
|
||||
/// </summary>
|
||||
public void ApplyHighlighting(int offset, int length, HighlightingColor color)
|
||||
{
|
||||
list.TransformRange(offset, length, c => {
|
||||
var newColor = c.Clone();
|
||||
newColor.MergeWith(color);
|
||||
newColor.Freeze();
|
||||
return newColor;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the HighlightingColor for the specified range of text,
|
||||
/// completely replacing the existing highlighting in that area.
|
||||
/// </summary>
|
||||
public void SetHighlighting(int offset, int length, HighlightingColor color)
|
||||
{
|
||||
list.SetRange(offset, length, FreezableHelper.GetFrozenClone(color));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the highlighted sections in the specified range.
|
||||
/// The highlighted sections will be sorted by offset, and there will not be any nested or overlapping sections.
|
||||
/// </summary>
|
||||
public IEnumerable<HighlightedSection> GetHighlightedSections(int offset, int length)
|
||||
{
|
||||
int pos = offset;
|
||||
int endOffset = offset + length;
|
||||
while (pos < endOffset) {
|
||||
int endPos = Math.Min(endOffset, list.GetEndOfRun(pos));
|
||||
yield return new HighlightedSection {
|
||||
Offset = pos,
|
||||
Length = endPos - pos,
|
||||
Color = list[pos]
|
||||
};
|
||||
pos = endPos;
|
||||
}
|
||||
}
|
||||
|
||||
#region WriteDocumentTo
|
||||
/// <summary>
|
||||
/// Writes the specified document, with the formatting from this rich text model applied,
|
||||
/// to the RichTextWriter.
|
||||
/// </summary>
|
||||
public void WriteDocumentTo(ITextSource document, RichTextWriter writer)
|
||||
{
|
||||
WriteDocumentTo(document, new SimpleSegment(0, DocumentLength), writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a segment of the specified document, with the formatting from this rich text model applied,
|
||||
/// to the RichTextWriter.
|
||||
/// </summary>
|
||||
public void WriteDocumentTo(ITextSource document, ISegment segment, RichTextWriter writer)
|
||||
{
|
||||
if (document == null)
|
||||
throw new ArgumentNullException("document");
|
||||
if (segment == null)
|
||||
throw new ArgumentNullException("segment");
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException("writer");
|
||||
|
||||
int pos = segment.Offset;
|
||||
int endOffset = segment.EndOffset;
|
||||
while (pos < endOffset) {
|
||||
int endPos = Math.Min(endOffset, list.GetEndOfRun(pos));
|
||||
writer.BeginSpan(list[pos]);
|
||||
document.WriteTextTo(writer, pos, endPos - pos);
|
||||
writer.EndSpan();
|
||||
pos = endPos;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using ICSharpCode.NRefactory.Editor;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Utils;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
/// <summary>
|
||||
/// A RichTextWriter that writes into a document and .
|
||||
/// </summary>
|
||||
public class RichTextModelWriter : PlainRichTextWriter
|
||||
{
|
||||
readonly RichTextModel richTextModel;
|
||||
readonly DocumentTextWriter documentTextWriter;
|
||||
readonly Stack<HighlightingColor> colorStack = new Stack<HighlightingColor>();
|
||||
HighlightingColor currentColor;
|
||||
int currentColorBegin = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RichTextModelWriter that inserts into document, starting at insertionOffset.
|
||||
/// </summary>
|
||||
public RichTextModelWriter(RichTextModel richTextModel, IDocument document, int insertionOffset)
|
||||
: base(new DocumentTextWriter(document, insertionOffset))
|
||||
{
|
||||
if (richTextModel == null)
|
||||
throw new ArgumentNullException("richTextModel");
|
||||
this.richTextModel = richTextModel;
|
||||
this.documentTextWriter = (DocumentTextWriter)base.textWriter;
|
||||
if (richTextModel.DocumentLength == 0)
|
||||
currentColor = HighlightingColor.Empty;
|
||||
else
|
||||
currentColor = richTextModel.GetHighlighting(Math.Max(0, insertionOffset - 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the current insertion offset.
|
||||
/// </summary>
|
||||
public int InsertionOffset {
|
||||
get { return documentTextWriter.InsertionOffset; }
|
||||
set { documentTextWriter.InsertionOffset = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void BeginUnhandledSpan()
|
||||
{
|
||||
colorStack.Push(currentColor);
|
||||
}
|
||||
|
||||
void BeginColorSpan()
|
||||
{
|
||||
WriteIndentationIfNecessary();
|
||||
colorStack.Push(currentColor);
|
||||
currentColor = currentColor.Clone();
|
||||
currentColorBegin = documentTextWriter.InsertionOffset;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void EndSpan()
|
||||
{
|
||||
currentColor = colorStack.Pop();
|
||||
currentColorBegin = documentTextWriter.InsertionOffset;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void AfterWrite()
|
||||
{
|
||||
base.AfterWrite();
|
||||
richTextModel.SetHighlighting(currentColorBegin, documentTextWriter.InsertionOffset - currentColorBegin, currentColor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(Color foregroundColor)
|
||||
{
|
||||
BeginColorSpan();
|
||||
currentColor.Foreground = new SimpleHighlightingBrush(foregroundColor);
|
||||
currentColor.Freeze();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(FontFamily fontFamily)
|
||||
{
|
||||
BeginUnhandledSpan(); // TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(FontStyle fontStyle)
|
||||
{
|
||||
BeginColorSpan();
|
||||
currentColor.FontStyle = fontStyle;
|
||||
currentColor.Freeze();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(FontWeight fontWeight)
|
||||
{
|
||||
BeginColorSpan();
|
||||
currentColor.FontWeight = fontWeight;
|
||||
currentColor.Freeze();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void BeginSpan(HighlightingColor highlightingColor)
|
||||
{
|
||||
BeginColorSpan();
|
||||
currentColor.MergeWith(highlightingColor);
|
||||
currentColor.Freeze();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,6 +102,7 @@
|
|||
<Compile Include="Document\DocumentChangeOperation.cs">
|
||||
<DependentUpon>UndoStack.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Document\DocumentTextWriter.cs" />
|
||||
<Compile Include="Document\ILineTracker.cs" />
|
||||
<Compile Include="Document\SimpleSegment.cs" />
|
||||
<Compile Include="Document\RopeTextSource.cs" />
|
||||
|
@ -196,6 +197,8 @@
|
|||
<Compile Include="Highlighting\HighlightingEngine.cs" />
|
||||
<Compile Include="Highlighting\HighlightingManager.cs" />
|
||||
<Compile Include="Highlighting\HtmlClipboard.cs" />
|
||||
<Compile Include="Highlighting\HtmlOptions.cs" />
|
||||
<Compile Include="Highlighting\HtmlRichTextWriter.cs" />
|
||||
<Compile Include="Highlighting\IHighlighter.cs" />
|
||||
<Compile Include="Highlighting\IHighlightingDefinition.cs" />
|
||||
<Compile Include="Highlighting\HighlightingRule.cs" />
|
||||
|
@ -204,6 +207,9 @@
|
|||
<Compile Include="Highlighting\HighlightingSpan.cs" />
|
||||
<Compile Include="Highlighting\IHighlightingDefinitionReferenceResolver.cs">
|
||||
</Compile>
|
||||
<Compile Include="Highlighting\RichTextColorizer.cs" />
|
||||
<Compile Include="Highlighting\RichTextModel.cs" />
|
||||
<Compile Include="Highlighting\RichTextModelWriter.cs" />
|
||||
<Compile Include="Highlighting\Xshd\HighlightingLoader.cs" />
|
||||
<Compile Include="Highlighting\Xshd\IXshdVisitor.cs" />
|
||||
<Compile Include="Highlighting\Xshd\SaveXshdVisitor.cs" />
|
||||
|
@ -354,7 +360,9 @@
|
|||
<Compile Include="Utils\FileReader.cs" />
|
||||
<Compile Include="Utils\NullSafeCollection.cs" />
|
||||
<Compile Include="Utils\ObserveAddRemoveCollection.cs" />
|
||||
<Compile Include="Utils\PlainRichTextWriter.cs" />
|
||||
<Compile Include="Utils\PropertyChangedWeakEventManager.cs" />
|
||||
<Compile Include="Utils\RichTextWriter.cs" />
|
||||
<Compile Include="Utils\Rope.cs" />
|
||||
<Compile Include="Utils\RopeNode.cs" />
|
||||
<Compile Include="Utils\RopeTextReader.cs" />
|
||||
|
|
|
@ -198,6 +198,7 @@ namespace ICSharpCode.AvalonEdit.Search
|
|||
{
|
||||
if (textArea == null)
|
||||
throw new ArgumentNullException("textArea");
|
||||
#pragma warning disable 618
|
||||
SearchPanel panel = new SearchPanel();
|
||||
panel.AttachInternal(textArea);
|
||||
panel.handler = new SearchInputHandler(textArea, panel);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
#define DATACONSISTENCYTEST
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -411,8 +411,26 @@ namespace ICSharpCode.AvalonEdit.Utils
|
|||
}
|
||||
prevNode = n;
|
||||
}
|
||||
CheckProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the conversion function to the elements in the specified range.
|
||||
/// </summary>
|
||||
public void TransformRange(int index, int length, Func<T, T> converter)
|
||||
{
|
||||
if (root == null)
|
||||
return;
|
||||
int endIndex = index + length;
|
||||
int pos = index;
|
||||
while (pos < endIndex) {
|
||||
int endPos = Math.Min(endIndex, GetEndOfRun(pos));
|
||||
T oldValue = this[pos];
|
||||
T newValue = converter(oldValue);
|
||||
SetRange(pos, endPos - pos, newValue);
|
||||
pos = endPos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the specified <paramref name="item"/> at <paramref name="index"/>
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// RichTextWriter implementation that writes plain text only
|
||||
/// and ignores all formatted spans.
|
||||
/// </summary>
|
||||
public class PlainRichTextWriter : RichTextWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The text writer that was passed to the PlainRichTextWriter constructor.
|
||||
/// </summary>
|
||||
protected readonly TextWriter textWriter;
|
||||
string indentationString = "\t";
|
||||
int indentationLevel;
|
||||
char prevChar;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PlainRichTextWriter instance that writes the text to the specified text writer.
|
||||
/// </summary>
|
||||
public PlainRichTextWriter(TextWriter textWriter)
|
||||
{
|
||||
if (textWriter == null)
|
||||
throw new ArgumentNullException("textWriter");
|
||||
this.textWriter = textWriter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the string used to indent by one level.
|
||||
/// </summary>
|
||||
public string IndentationString {
|
||||
get {
|
||||
return indentationString;
|
||||
}
|
||||
set {
|
||||
indentationString = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void BeginUnhandledSpan()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void EndSpan()
|
||||
{
|
||||
}
|
||||
|
||||
void WriteIndentation()
|
||||
{
|
||||
for (int i = 0; i < indentationLevel; i++) {
|
||||
textWriter.Write(indentationString);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the indentation, if necessary.
|
||||
/// </summary>
|
||||
protected void WriteIndentationIfNecessary()
|
||||
{
|
||||
if (prevChar == '\n') {
|
||||
WriteIndentation();
|
||||
prevChar = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called after a write operation.
|
||||
/// </summary>
|
||||
protected virtual void AfterWrite()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(char value)
|
||||
{
|
||||
if (prevChar == '\n')
|
||||
WriteIndentation();
|
||||
textWriter.Write(value);
|
||||
prevChar = value;
|
||||
AfterWrite();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Indent()
|
||||
{
|
||||
indentationLevel++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unindent()
|
||||
{
|
||||
if (indentationLevel == 0)
|
||||
throw new NotSupportedException();
|
||||
indentationLevel--;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Encoding Encoding {
|
||||
get { return textWriter.Encoding; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IFormatProvider FormatProvider {
|
||||
get { return textWriter.FormatProvider; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string NewLine {
|
||||
get {
|
||||
return textWriter.NewLine;
|
||||
}
|
||||
set {
|
||||
textWriter.NewLine = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// A text writer that supports creating spans of highlighted text.
|
||||
/// </summary>
|
||||
public abstract class RichTextWriter : TextWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets called by the RichTextWriter base class when a BeginSpan() method
|
||||
/// that is not overwritten gets called.
|
||||
/// </summary>
|
||||
protected abstract void BeginUnhandledSpan();
|
||||
|
||||
/// <summary>
|
||||
/// Begin a colored span.
|
||||
/// </summary>
|
||||
public virtual void BeginSpan(Color foregroundColor)
|
||||
{
|
||||
BeginUnhandledSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin a span with modified font weight.
|
||||
/// </summary>
|
||||
public virtual void BeginSpan(FontWeight fontWeight)
|
||||
{
|
||||
BeginUnhandledSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin a span with modified font style.
|
||||
/// </summary>
|
||||
public virtual void BeginSpan(FontStyle fontStyle)
|
||||
{
|
||||
BeginUnhandledSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin a span with modified font family.
|
||||
/// </summary>
|
||||
public virtual void BeginSpan(FontFamily fontFamily)
|
||||
{
|
||||
BeginUnhandledSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin a highlighted span.
|
||||
/// </summary>
|
||||
public virtual void BeginSpan(Highlighting.HighlightingColor highlightingColor)
|
||||
{
|
||||
BeginUnhandledSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin a span that links to the specified URI.
|
||||
/// </summary>
|
||||
public virtual void BeginHyperlinkSpan(Uri uri)
|
||||
{
|
||||
BeginUnhandledSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the end of the current span.
|
||||
/// </summary>
|
||||
public abstract void EndSpan();
|
||||
|
||||
/// <summary>
|
||||
/// Increases the indentation level.
|
||||
/// </summary>
|
||||
public abstract void Indent();
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the indentation level.
|
||||
/// </summary>
|
||||
public abstract void Unindent();
|
||||
}
|
||||
}
|
|
@ -1,439 +0,0 @@
|
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.AvalonEdit.Utils;
|
||||
using ICSharpCode.NRefactory.Utils;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Xml
|
||||
{
|
||||
class TagMatchingHeuristics
|
||||
{
|
||||
const int maxConfigurationCount = 10;
|
||||
|
||||
AXmlParser parser;
|
||||
TrackedSegmentCollection trackedSegments;
|
||||
string input;
|
||||
List<AXmlObject> tags;
|
||||
|
||||
public TagMatchingHeuristics(AXmlParser parser, string input, List<AXmlObject> tags)
|
||||
{
|
||||
this.parser = parser;
|
||||
this.trackedSegments = parser.TrackedSegments;
|
||||
this.input = input;
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public AXmlDocument ReadDocument()
|
||||
{
|
||||
AXmlDocument doc = new AXmlDocument() { Parser = parser };
|
||||
|
||||
// AXmlParser.Log("Flat stream: {0}", PrintObjects(tags));
|
||||
List<AXmlObject> valid = MatchTags(tags);
|
||||
// AXmlParser.Log("Fixed stream: {0}", PrintObjects(valid));
|
||||
IEnumerator<AXmlObject> validStream = valid.GetEnumerator();
|
||||
validStream.MoveNext(); // Move to first
|
||||
while(true) {
|
||||
// End of stream?
|
||||
try {
|
||||
if (validStream.Current == null) break;
|
||||
} catch (InvalidCastException) {
|
||||
break;
|
||||
}
|
||||
doc.AddChild(ReadTextOrElement(validStream));
|
||||
}
|
||||
|
||||
if (doc.Children.Count > 0) {
|
||||
doc.StartOffset = doc.FirstChild.StartOffset;
|
||||
doc.EndOffset = doc.LastChild.EndOffset;
|
||||
}
|
||||
|
||||
// Check well formed
|
||||
foreach(AXmlTag xmlDeclaration in doc.Children.OfType<AXmlTag>().Where(t => t.IsProcessingInstruction && string.Equals(t.Name, "xml", StringComparison.OrdinalIgnoreCase))) {
|
||||
if (xmlDeclaration.StartOffset != 0)
|
||||
TagReader.OnSyntaxError(doc, xmlDeclaration.StartOffset, xmlDeclaration.StartOffset + 5,
|
||||
"XML declaration must be at the start of document");
|
||||
}
|
||||
int elemCount = doc.Children.OfType<AXmlElement>().Count();
|
||||
if (elemCount == 0)
|
||||
TagReader.OnSyntaxError(doc, doc.EndOffset, doc.EndOffset,
|
||||
"Root element is missing");
|
||||
if (elemCount > 1) {
|
||||
AXmlElement next = doc.Children.OfType<AXmlElement>().Skip(1).First();
|
||||
TagReader.OnSyntaxError(doc, next.StartOffset, next.StartOffset,
|
||||
"Only one root element is allowed");
|
||||
}
|
||||
foreach(AXmlTag tag in doc.Children.OfType<AXmlTag>()) {
|
||||
if (tag.IsCData)
|
||||
TagReader.OnSyntaxError(doc, tag.StartOffset, tag.EndOffset,
|
||||
"CDATA not allowed in document root");
|
||||
}
|
||||
foreach(AXmlText text in doc.Children.OfType<AXmlText>()) {
|
||||
if (!text.ContainsOnlyWhitespace)
|
||||
TagReader.OnSyntaxError(doc, text.StartOffset, text.EndOffset,
|
||||
"Only whitespace is allowed in document root");
|
||||
}
|
||||
|
||||
|
||||
AXmlParser.Log("Constructed {0}", doc);
|
||||
trackedSegments.AddParsedObject(doc, null);
|
||||
return doc;
|
||||
}
|
||||
|
||||
static AXmlObject ReadSingleObject(IEnumerator<AXmlObject> objStream)
|
||||
{
|
||||
AXmlObject obj = objStream.Current;
|
||||
objStream.MoveNext();
|
||||
return obj;
|
||||
}
|
||||
|
||||
AXmlObject ReadTextOrElement(IEnumerator<AXmlObject> objStream)
|
||||
{
|
||||
AXmlObject curr = objStream.Current;
|
||||
if (curr is AXmlText || curr is AXmlElement) {
|
||||
return ReadSingleObject(objStream);
|
||||
} else {
|
||||
AXmlTag currTag = (AXmlTag)curr;
|
||||
if (currTag == StartTagPlaceholder) {
|
||||
return ReadElement(objStream);
|
||||
} else if (currTag.IsStartOrEmptyTag) {
|
||||
return ReadElement(objStream);
|
||||
} else {
|
||||
return ReadSingleObject(objStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AXmlElement ReadElement(IEnumerator<AXmlObject> objStream)
|
||||
{
|
||||
AXmlElement element = new AXmlElement();
|
||||
element.IsProperlyNested = true;
|
||||
|
||||
// Read start tag
|
||||
AXmlTag startTag = ReadSingleObject(objStream) as AXmlTag;
|
||||
AXmlParser.DebugAssert(startTag != null, "Start tag expected");
|
||||
AXmlParser.DebugAssert(startTag.IsStartOrEmptyTag || startTag == StartTagPlaceholder, "Start tag expected");
|
||||
if (startTag == StartTagPlaceholder) {
|
||||
element.HasStartOrEmptyTag = false;
|
||||
element.IsProperlyNested = false;
|
||||
TagReader.OnSyntaxError(element, objStream.Current.StartOffset, objStream.Current.EndOffset,
|
||||
"Matching openning tag was not found");
|
||||
} else {
|
||||
element.HasStartOrEmptyTag = true;
|
||||
element.AddChild(startTag);
|
||||
}
|
||||
|
||||
// Read content and end tag
|
||||
if (startTag == StartTagPlaceholder || // Check first in case the start tag is null
|
||||
element.StartTag.IsStartTag)
|
||||
{
|
||||
while(true) {
|
||||
AXmlTag currTag = objStream.Current as AXmlTag; // Peek
|
||||
if (currTag == EndTagPlaceholder) {
|
||||
TagReader.OnSyntaxError(element, element.LastChild.EndOffset, element.LastChild.EndOffset,
|
||||
"Expected '</{0}>'", element.StartTag.Name);
|
||||
ReadSingleObject(objStream);
|
||||
element.HasEndTag = false;
|
||||
element.IsProperlyNested = false;
|
||||
break;
|
||||
} else if (currTag != null && currTag.IsEndTag) {
|
||||
if (element.HasStartOrEmptyTag && currTag.Name != element.StartTag.Name) {
|
||||
TagReader.OnSyntaxError(element, currTag.StartOffset + 2, currTag.StartOffset + 2 + currTag.Name.Length,
|
||||
"Expected '{0}'. End tag must have same name as start tag.", element.StartTag.Name);
|
||||
}
|
||||
element.AddChild(ReadSingleObject(objStream));
|
||||
element.HasEndTag = true;
|
||||
break;
|
||||
}
|
||||
AXmlObject nested = ReadTextOrElement(objStream);
|
||||
|
||||
AXmlElement nestedAsElement = nested as AXmlElement;
|
||||
if (nestedAsElement != null) {
|
||||
if (!nestedAsElement.IsProperlyNested)
|
||||
element.IsProperlyNested = false;
|
||||
element.AddChildren(Split(nestedAsElement).ToList());
|
||||
} else {
|
||||
element.AddChild(nested);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
element.HasEndTag = false;
|
||||
}
|
||||
|
||||
element.StartOffset = element.FirstChild.StartOffset;
|
||||
element.EndOffset = element.LastChild.EndOffset;
|
||||
|
||||
AXmlParser.Assert(element.HasStartOrEmptyTag || element.HasEndTag, "Must have at least start or end tag");
|
||||
|
||||
AXmlParser.Log("Constructed {0}", element);
|
||||
trackedSegments.AddParsedObject(element, null); // Need all elements in cache for offset tracking
|
||||
return element;
|
||||
}
|
||||
|
||||
IEnumerable<AXmlObject> Split(AXmlElement elem)
|
||||
{
|
||||
int myIndention = GetIndentLevel(elem);
|
||||
// Has start tag and no end tag ? (other then empty-element tag)
|
||||
if (elem.HasStartOrEmptyTag && elem.StartTag.IsStartTag && !elem.HasEndTag && myIndention != -1) {
|
||||
int lastAccepted = 0; // Accept start tag
|
||||
while (lastAccepted + 1 < elem.Children.Count) {
|
||||
AXmlObject nextItem = elem.Children[lastAccepted + 1];
|
||||
if (nextItem is AXmlText) {
|
||||
lastAccepted++; continue; // Accept
|
||||
} else {
|
||||
// Include all more indented items
|
||||
if (GetIndentLevel(nextItem) > myIndention) {
|
||||
lastAccepted++; continue; // Accept
|
||||
} else {
|
||||
break; // Reject
|
||||
}
|
||||
}
|
||||
}
|
||||
// Accepted everything?
|
||||
if (lastAccepted + 1 == elem.Children.Count) {
|
||||
yield return elem;
|
||||
yield break;
|
||||
}
|
||||
AXmlParser.Log("Splitting {0} - take {1} of {2} nested", elem, lastAccepted, elem.Children.Count - 1);
|
||||
AXmlElement topHalf = new AXmlElement();
|
||||
topHalf.HasStartOrEmptyTag = elem.HasStartOrEmptyTag;
|
||||
topHalf.HasEndTag = elem.HasEndTag;
|
||||
topHalf.AddChildren(elem.Children.Take(1 + lastAccepted)); // Start tag + nested
|
||||
topHalf.StartOffset = topHalf.FirstChild.StartOffset;
|
||||
topHalf.EndOffset = topHalf.LastChild.EndOffset;
|
||||
TagReader.OnSyntaxError(topHalf, topHalf.LastChild.EndOffset, topHalf.LastChild.EndOffset,
|
||||
"Expected '</{0}>'", topHalf.StartTag.Name);
|
||||
|
||||
AXmlParser.Log("Constructed {0}", topHalf);
|
||||
trackedSegments.AddParsedObject(topHalf, null);
|
||||
yield return topHalf;
|
||||
for(int i = lastAccepted + 1; i < elem.Children.Count; i++) {
|
||||
yield return elem.Children[i];
|
||||
}
|
||||
} else {
|
||||
yield return elem;
|
||||
}
|
||||
}
|
||||
|
||||
int GetIndentLevel(AXmlObject obj)
|
||||
{
|
||||
int offset = obj.StartOffset - 1;
|
||||
int level = 0;
|
||||
while(true) {
|
||||
if (offset < 0) break;
|
||||
char c = input[offset];
|
||||
if (c == ' ') {
|
||||
level++;
|
||||
} else if (c == '\t') {
|
||||
level += 4;
|
||||
} else if (c == '\r' || c == '\n') {
|
||||
break;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
offset--;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stack of still unmatched start tags.
|
||||
/// It includes the cost and backtack information.
|
||||
/// </summary>
|
||||
class Configuration
|
||||
{
|
||||
/// <summary> Unmatched start tags </summary>
|
||||
public ImmutableStack<AXmlTag> StartTags { get; set; }
|
||||
/// <summary> Properly nested tags </summary>
|
||||
public ImmutableStack<AXmlObject> Document { get; set; }
|
||||
/// <summary> Number of needed modificaitons to the document </summary>
|
||||
public int Cost { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary which stores the cheapest configuration
|
||||
/// </summary>
|
||||
class Configurations: Dictionary<ImmutableStack<AXmlTag>, Configuration>
|
||||
{
|
||||
public Configurations()
|
||||
{
|
||||
}
|
||||
|
||||
public Configurations(IEnumerable<Configuration> configs)
|
||||
{
|
||||
foreach(Configuration config in configs) {
|
||||
this.Add(config);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Overwrite only if cheaper </summary>
|
||||
public void Add(Configuration newConfig)
|
||||
{
|
||||
Configuration oldConfig;
|
||||
if (this.TryGetValue(newConfig.StartTags, out oldConfig)) {
|
||||
if (newConfig.Cost < oldConfig.Cost) {
|
||||
this[newConfig.StartTags] = newConfig;
|
||||
}
|
||||
} else {
|
||||
base.Add(newConfig.StartTags, newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach(var kvp in this) {
|
||||
sb.Append("\n - '");
|
||||
foreach(AXmlTag startTag in kvp.Value.StartTags.Reverse()) {
|
||||
sb.Append('<');
|
||||
sb.Append(startTag.Name);
|
||||
sb.Append('>');
|
||||
}
|
||||
sb.AppendFormat("' = {0}", kvp.Value.Cost);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// Tags used to guide the element creation
|
||||
readonly AXmlTag StartTagPlaceholder = new AXmlTag();
|
||||
readonly AXmlTag EndTagPlaceholder = new AXmlTag();
|
||||
|
||||
/// <summary>
|
||||
/// Add start or end tag placeholders so that the documment is properly nested
|
||||
/// </summary>
|
||||
List<AXmlObject> MatchTags(IEnumerable<AXmlObject> objs)
|
||||
{
|
||||
Configurations configurations = new Configurations();
|
||||
configurations.Add(new Configuration {
|
||||
StartTags = ImmutableStack<AXmlTag>.Empty,
|
||||
Document = ImmutableStack<AXmlObject>.Empty,
|
||||
Cost = 0,
|
||||
});
|
||||
foreach(AXmlObject obj in objs) {
|
||||
configurations = ProcessObject(configurations, obj);
|
||||
}
|
||||
// Close any remaining start tags
|
||||
foreach(Configuration conifg in configurations.Values) {
|
||||
while(!conifg.StartTags.IsEmpty) {
|
||||
conifg.StartTags = conifg.StartTags.Pop();
|
||||
conifg.Document = conifg.Document.Push(EndTagPlaceholder);
|
||||
conifg.Cost += 1;
|
||||
}
|
||||
}
|
||||
// AXmlParser.Log("Configurations after closing all remaining tags:" + configurations.ToString());
|
||||
Configuration bestConfig = configurations.Values.OrderBy(v => v.Cost).First();
|
||||
AXmlParser.Log("Best configuration has cost {0}", bestConfig.Cost);
|
||||
|
||||
return bestConfig.Document.Reverse().ToList();
|
||||
}
|
||||
|
||||
/// <summary> Get posible configurations after considering given object </summary>
|
||||
Configurations ProcessObject(Configurations oldConfigs, AXmlObject obj)
|
||||
{
|
||||
AXmlParser.Log("Processing {0}", obj);
|
||||
|
||||
AXmlTag objAsTag = obj as AXmlTag;
|
||||
AXmlElement objAsElement = obj as AXmlElement;
|
||||
AXmlParser.DebugAssert(objAsTag != null || objAsElement != null || obj is AXmlText, obj.GetType().Name + " not expected");
|
||||
if (objAsElement != null)
|
||||
AXmlParser.Assert(objAsElement.IsProperlyNested, "Element not properly nested");
|
||||
|
||||
Configurations newConfigs = new Configurations();
|
||||
|
||||
foreach(var kvp in oldConfigs) {
|
||||
Configuration oldConfig = kvp.Value;
|
||||
var oldStartTags = oldConfig.StartTags;
|
||||
var oldDocument = oldConfig.Document;
|
||||
int oldCost = oldConfig.Cost;
|
||||
|
||||
if (objAsTag != null && objAsTag.IsStartTag) {
|
||||
newConfigs.Add(new Configuration { // Push start-tag (cost 0)
|
||||
StartTags = oldStartTags.Push(objAsTag),
|
||||
Document = oldDocument.Push(objAsTag),
|
||||
Cost = oldCost,
|
||||
});
|
||||
} else if (objAsTag != null && objAsTag.IsEndTag) {
|
||||
newConfigs.Add(new Configuration { // Ignore (cost 1)
|
||||
StartTags = oldStartTags,
|
||||
Document = oldDocument.Push(StartTagPlaceholder).Push(objAsTag),
|
||||
Cost = oldCost + 1,
|
||||
});
|
||||
if (!oldStartTags.IsEmpty && oldStartTags.Peek().Name != objAsTag.Name) {
|
||||
newConfigs.Add(new Configuration { // Pop 1 item (cost 1) - not mathcing
|
||||
StartTags = oldStartTags.Pop(),
|
||||
Document = oldDocument.Push(objAsTag),
|
||||
Cost = oldCost + 1,
|
||||
});
|
||||
}
|
||||
int popedCount = 0;
|
||||
var startTags = oldStartTags;
|
||||
var doc = oldDocument;
|
||||
foreach(AXmlTag poped in oldStartTags) {
|
||||
popedCount++;
|
||||
if (poped.Name == objAsTag.Name) {
|
||||
newConfigs.Add(new Configuration { // Pop 'x' items (cost x-1) - last one is matching
|
||||
StartTags = startTags.Pop(),
|
||||
Document = doc.Push(objAsTag),
|
||||
Cost = oldCost + popedCount - 1,
|
||||
});
|
||||
}
|
||||
startTags = startTags.Pop();
|
||||
doc = doc.Push(EndTagPlaceholder);
|
||||
}
|
||||
} else {
|
||||
// Empty tag or other tag type or text or properly nested element
|
||||
newConfigs.Add(new Configuration { // Ignore (cost 0)
|
||||
StartTags = oldStartTags,
|
||||
Document = oldDocument.Push(obj),
|
||||
Cost = oldCost,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Log("New configurations:" + newConfigs.ToString());
|
||||
|
||||
Configurations bestNewConfigurations = new Configurations(
|
||||
newConfigs.Values.OrderBy(v => v.Cost).Take(maxConfigurationCount)
|
||||
);
|
||||
|
||||
// AXmlParser.Log("Best new configurations:" + bestNewConfigurations.ToString());
|
||||
|
||||
return bestNewConfigurations;
|
||||
}
|
||||
|
||||
#region Helper methods
|
||||
/*
|
||||
string PrintObjects(IEnumerable<AXmlObject> objs)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach(AXmlObject obj in objs) {
|
||||
if (obj is AXmlTag) {
|
||||
if (obj == StartTagPlaceholder) {
|
||||
sb.Append("#StartTag#");
|
||||
} else if (obj == EndTagPlaceholder) {
|
||||
sb.Append("#EndTag#");
|
||||
} else {
|
||||
sb.Append(((AXmlTag)obj).OpeningBracket);
|
||||
sb.Append(((AXmlTag)obj).Name);
|
||||
sb.Append(((AXmlTag)obj).ClosingBracket);
|
||||
}
|
||||
} else if (obj is AXmlElement) {
|
||||
sb.Append('[');
|
||||
sb.Append(PrintObjects(((AXmlElement)obj).Children));
|
||||
sb.Append(']');
|
||||
} else if (obj is AXmlText) {
|
||||
sb.Append('~');
|
||||
} else {
|
||||
throw new InternalException("Should not be here: " + obj);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
*/
|
||||
#endregion
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче