// 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 } }