30 changed files with 1169 additions and 656 deletions
@ -0,0 +1,69 @@
@@ -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; } |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,62 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,125 @@
@@ -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 @@
@@ -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 @@
@@ -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
|
||||
} |
||||
} |
Loading…
Reference in new issue