// Copied from http://codingeditor.googlecode.com/svn/trunk/libs/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/
#region Header
//
//
//
//
// $Revision: 1971 $
//
#endregion Header
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
///
/// Holds information about the start of a fold in an xml string.
///
class XmlFoldStart
{
#region Fields
readonly int _col;
string _foldText = string.Empty;
readonly int _line;
readonly string _name = string.Empty;
readonly string _prefix = string.Empty;
#endregion Fields
#region Constructors
public XmlFoldStart(string prefix, string name, int line, int col)
{
_line = line;
_col = col;
_prefix = prefix;
_name = name;
}
#endregion Constructors
#region Properties
///
/// The column where the fold should start. Columns start from 0.
///
public int Column
{
get
{
return _col;
}
}
///
/// The text to be displayed when the item is folded.
///
public string FoldText
{
get
{
return _foldText;
}
set
{
_foldText = value;
}
}
///
/// The line where the fold should start. Lines start from 0.
///
public int Line
{
get
{
return _line;
}
}
///
/// The name of the xml item with its prefix if it has one.
///
public string Name
{
get
{
return _prefix.Length > 0 ? string.Concat(_prefix, ":", _name) : _name;
}
}
#endregion Properties
}
///
/// Determines folds for an xml string in the editor.
///
public class XmlFoldingStrategy : IFoldingStrategyEx
{
#region Fields
///
/// Flag indicating whether attributes should be displayed on folded elements.
///
public bool ShowAttributesWhenFolded = false;
private List _foldingErrors = new List();
#endregion Fields
#region Methods
public List GetFoldingErrors()
{
return _foldingErrors;
}
///
/// Adds folds to the text editor around each start-end element pair.
///
///
/// If the xml is not well formed then no folds are created.
/// Note that the xml text reader lines and positions start
/// from 1 and the SharpDevelop text editor line information starts from 0.
///
public List GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
_foldingErrors = new List();
//showAttributesWhenFolded = XmlEditorAddInOptions.ShowAttributesWhenFolded;
var foldMarkers = new List();
var stack = new Stack();
try
{
string xml = document.TextContent;
var reader = new XmlTextReader(new StringReader(xml));
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (!reader.IsEmptyElement)
{
XmlFoldStart newFoldStart = CreateElementFoldStart(reader);
stack.Push(newFoldStart);
}
break;
case XmlNodeType.EndElement:
var foldStart = (XmlFoldStart)stack.Pop();
CreateElementFold(document, foldMarkers, reader, foldStart);
break;
case XmlNodeType.Comment:
CreateCommentFold(document, foldMarkers, reader);
break;
}
}
}
catch (Exception ex)
{
_foldingErrors.Add(ex.Message);
// If the xml is not well formed keep the foldings that already exist in the document.
return new List(document.FoldingManager.FoldMarker);
}
return foldMarkers;
}
///
/// Xml encode the attribute string since the string returned from
/// the XmlTextReader is the plain unencoded string and .NET
/// does not provide us with an xml encode method.
///
static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
{
var encodedValue = new StringBuilder(attributeValue);
encodedValue.Replace("&", "&");
encodedValue.Replace("<", "<");
encodedValue.Replace(">", ">");
if (quoteChar == '"')
{
encodedValue.Replace("\"", """);
}
else
{
encodedValue.Replace("'", "'");
}
return encodedValue.ToString();
}
///
/// Creates a comment fold if the comment spans more than one line.
///
/// The text displayed when the comment is folded is the first
/// line of the comment.
void CreateCommentFold(IDocument document, List foldMarkers, XmlTextReader reader)
{
if (reader.Value != null)
{
string comment = reader.Value.Replace("\r\n", "\n");
string[] lines = comment.Split('\n');
if (lines.Length > 1)
{
// Take off 5 chars to get the actual comment start (takes
// into account the '
int endCol = lines[lines.Length - 1].Length + startCol + 3;
int endLine = startLine + lines.Length - 1;
string foldText = string.Concat("");
var foldMarker = new FoldMarker(document, startLine, startCol, endLine, endCol, FoldType.TypeBody, foldText);
foldMarkers.Add(foldMarker);
}
}
}
///
/// Create an element fold if the start and end tag are on
/// different lines.
///
void CreateElementFold(IDocument document, List foldMarkers, XmlTextReader reader, XmlFoldStart foldStart)
{
int endLine = reader.LineNumber - 1;
if (endLine > foldStart.Line)
{
int endCol = reader.LinePosition + foldStart.Name.Length;
var foldMarker = new FoldMarker(document, foldStart.Line, foldStart.Column, endLine, endCol, FoldType.TypeBody, foldStart.FoldText);
foldMarkers.Add(foldMarker);
}
}
///
/// Creates an XmlFoldStart for the start tag of an element.
///
XmlFoldStart CreateElementFoldStart(XmlTextReader reader)
{
// Take off 2 from the line position returned
// from the xml since it points to the start
// of the element name and not the beginning
// tag.
var newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2);
if (ShowAttributesWhenFolded && reader.HasAttributes)
{
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, " ", GetAttributeFoldText(reader), ">");
}
else
{
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, ">");
}
return newFoldStart;
}
///
/// Gets the element's attributes as a string on one line that will
/// be displayed when the element is folded.
///
///
/// Currently this puts all attributes from an element on the same
/// line of the start tag. It does not cater for elements where attributes
/// are not on the same line as the start tag.
///
string GetAttributeFoldText(XmlTextReader reader)
{
var text = new StringBuilder();
for (int i = 0; i < reader.AttributeCount; ++i)
{
reader.MoveToAttribute(i);
text.Append(reader.Name);
text.Append("=");
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
// Append a space if this is not the
// last attribute.
if (i < reader.AttributeCount - 1)
{
text.Append(" ");
}
}
return text.ToString();
}
#endregion Methods
}
}