#region Disclaimer / License
// Copyright (C) 2012, Jackie Ng
// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com
//
// Original code from SharpDevelop 3.2.1 licensed under the same terms (LGPL 2.1)
// Copyright 2002-2010 by
//
// AlphaSierraPapa, Christoph Wille
// Vordernberger Strasse 27/8
// A-8700 Leoben
// Austria
//
// email: office@alphasierrapapa.com
// court of jurisdiction: Landesgericht Leoben
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion Disclaimer / License
using ICSharpCode.TextEditor.Document;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
namespace Maestro.Editors.Generic.XmlEditor
{
///
/// Holds information about the start of a fold in an xml string.
///
public class XmlFoldStart
{
private int line = 0;
private int col = 0;
private string prefix = String.Empty;
private string name = String.Empty;
private string foldText = String.Empty;
///
/// Initializes a new instance of the XmlFoldStart class
///
///
///
///
///
public XmlFoldStart(string prefix, string name, int line, int col)
{
this.line = line;
this.col = col;
this.prefix = prefix;
this.name = name;
}
///
/// The line where the fold should start. Lines start from 0.
///
public int Line
{
get
{
return line;
}
}
///
/// The column where the fold should start. Columns start from 0.
///
public int Column
{
get
{
return col;
}
}
///
/// The name of the xml item with its prefix if it has one.
///
public string Name
{
get
{
if (prefix.Length > 0)
{
return String.Concat(prefix, ":", name);
}
else
{
return name;
}
}
}
///
/// The text to be displayed when the item is folded.
///
public string FoldText
{
get
{
return foldText;
}
set
{
foldText = value;
}
}
}
///
/// Determines folds for an xml string in the editor.
///
public class XmlFoldingStrategy : IFoldingStrategy
{
///
/// Flag indicating whether attributes should be displayed on folded
/// elements.
///
private bool showAttributesWhenFolded = false;
///
/// Initializes a new instance of the XmlFoldingStrategy class
///
public XmlFoldingStrategy()
{
}
#region IFoldingStrategy
///
/// 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)
{
showAttributesWhenFolded = XmlEditorOptions.ShowAttributesWhenFolded;
List foldMarkers = new List();
Stack stack = new Stack();
try
{
string xml = document.TextContent;
XmlTextReader 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:
XmlFoldStart foldStart = (XmlFoldStart)stack.Pop();
CreateElementFold(document, foldMarkers, reader, foldStart);
break;
case XmlNodeType.Comment:
CreateCommentFold(document, foldMarkers, reader);
break;
}
}
}
catch (Exception)
{
// If the xml is not well formed keep the foldings
// that already exist in the document.
return new List(document.FoldingManager.FoldMarker);
}
return foldMarkers;
}
#endregion IFoldingStrategy
///
/// 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.
private 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("");
FoldMarker foldMarker = new FoldMarker(document, startLine, startCol, endLine, endCol, FoldType.TypeBody, foldText);
foldMarkers.Add(foldMarker);
}
}
}
///
/// Creates an XmlFoldStart for the start tag of an element.
///
private 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.
XmlFoldStart 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;
}
///
/// Create an element fold if the start and end tag are on
/// different lines.
///
private 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;
FoldMarker foldMarker = new FoldMarker(document, foldStart.Line, foldStart.Column, endLine, endCol, FoldType.TypeBody, foldStart.FoldText);
foldMarkers.Add(foldMarker);
}
}
///
/// 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.
///
private string GetAttributeFoldText(XmlTextReader reader)
{
StringBuilder 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());
text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
text.Append(reader.QuoteChar.ToString());
// Append a space if this is not the
// last attribute.
if (i < reader.AttributeCount - 1)
{
text.Append(" ");
}
}
return text.ToString();
}
///
/// 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.
///
private static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
{
StringBuilder encodedValue = new StringBuilder(attributeValue);
encodedValue.Replace("&", "&");
encodedValue.Replace("<", "<");
encodedValue.Replace(">", ">");
if (quoteChar == '"')
{
encodedValue.Replace("\"", """);
}
else
{
encodedValue.Replace("'", "'");
}
return encodedValue.ToString();
}
}
}