//
//
//
//
// $Revision: -1 $
//
using ICSharpCode.SharpDevelop.Editor;
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using ICSharpCode.Core;
namespace ICSharpCode.XmlEditor
{
///
/// This class currently inserts the closing tags to typed openening tags
/// and does smart indentation for xml files.
///
public class XmlFormattingStrategy : DefaultFormattingStrategy
{
public override void FormatLine(ITextEditor editor, char charTyped)
{
editor.Document.StartUndoableAction();
try {
if (charTyped == '>') {
StringBuilder stringBuilder = new StringBuilder();
int offset = Math.Min(editor.Caret.Offset - 2, editor.Document.TextLength - 1);
while (true) {
if (offset < 0) {
break;
}
char ch = editor.Document.GetCharAt(offset);
if (ch == '<') {
string reversedTag = stringBuilder.ToString().Trim();
if (!reversedTag.StartsWith("/", StringComparison.Ordinal) && !reversedTag.EndsWith("/", StringComparison.Ordinal)) {
bool validXml = true;
try {
XmlDocument doc = new XmlDocument();
doc.LoadXml(editor.Document.Text);
} catch (XmlException) {
validXml = false;
}
// only insert the tag, if something is missing
if (!validXml) {
StringBuilder tag = new StringBuilder();
for (int i = reversedTag.Length - 1; i >= 0 && !Char.IsWhiteSpace(reversedTag[i]); --i) {
tag.Append(reversedTag[i]);
}
string tagString = tag.ToString();
if (tagString.Length > 0 && !tagString.StartsWith("!", StringComparison.Ordinal) && !tagString.StartsWith("?", StringComparison.Ordinal)) {
editor.Document.Insert(editor.Caret.Offset, "" + tagString + ">");
}
}
}
break;
}
stringBuilder.Append(ch);
--offset;
}
}
} catch (Exception e) { // Insanity check
Debug.Assert(false, e.ToString());
}
if (charTyped == '\n') {
IndentLine(editor, editor.Document.GetLineForOffset(editor.Caret.Offset));
}
editor.Document.EndUndoableAction();
}
public override void IndentLine(ITextEditor editor, IDocumentLine line)
{
editor.Document.StartUndoableAction();
try {
TryIndent(editor, line.LineNumber, line.LineNumber);
} catch (XmlException ex) {
LoggingService.Debug(ex.ToString());
} finally {
editor.Document.EndUndoableAction();
}
}
///
/// This function sets the indentlevel in a range of lines.
///
public override void IndentLines(ITextEditor editor, int begin, int end)
{
editor.Document.StartUndoableAction();
try {
TryIndent(editor, begin, end);
} catch (XmlException ex) {
LoggingService.Debug(ex.ToString());
} finally {
editor.Document.EndUndoableAction();
}
}
#region Smart Indentation
static void TryIndent(ITextEditor editor, int begin, int end)
{
string currentIndentation = "";
Stack tagStack = new Stack();
IDocument document = editor.Document;
string tab = editor.Options.IndentationString;
int nextLine = begin; // in #dev coordinates
bool wasEmptyElement = false;
XmlNodeType lastType = XmlNodeType.XmlDeclaration;
using (StringReader stringReader = new StringReader(document.Text)) {
XmlTextReader r = new XmlTextReader(stringReader);
r.XmlResolver = null; // prevent XmlTextReader from loading external DTDs
while (r.Read()) {
if (wasEmptyElement) {
wasEmptyElement = false;
if (tagStack.Count == 0)
currentIndentation = "";
else
currentIndentation = (string)tagStack.Pop();
}
if (r.NodeType == XmlNodeType.EndElement) {
if (tagStack.Count == 0)
currentIndentation = "";
else
currentIndentation = (string)tagStack.Pop();
}
while (r.LineNumber >= nextLine) { // caution: here we compare 1-based and 0-based line numbers
if (nextLine > end) break;
if (lastType == XmlNodeType.CDATA || lastType == XmlNodeType.Comment) {
nextLine++;
continue;
}
// set indentation of 'nextLine'
IDocumentLine line = document.GetLine(nextLine);
string lineText = line.Text;
string newText;
// special case: opening tag has closing bracket on extra line: remove one indentation level
if (lineText.Trim() == ">")
newText = (string)tagStack.Peek() + lineText.Trim();
else
newText = currentIndentation + lineText.Trim();
if (newText != lineText) {
document.Replace(line.Offset, line.Length, newText);
}
nextLine++;
}
if (r.LineNumber > end)
break;
wasEmptyElement = r.NodeType == XmlNodeType.Element && r.IsEmptyElement;
string attribIndent = null;
if (r.NodeType == XmlNodeType.Element) {
tagStack.Push(currentIndentation);
if (r.LineNumber < begin)
currentIndentation = DocumentUtilitites.GetIndentation(editor.Document, r.LineNumber - 1);
if (r.Name.Length < 16)
attribIndent = currentIndentation + new String(' ', 2 + r.Name.Length);
else
attribIndent = currentIndentation + tab;
currentIndentation += tab;
}
lastType = r.NodeType;
if (r.NodeType == XmlNodeType.Element && r.HasAttributes) {
int startLine = r.LineNumber;
r.MoveToAttribute(0); // move to first attribute
if (r.LineNumber != startLine)
attribIndent = currentIndentation; // change to tab-indentation
r.MoveToAttribute(r.AttributeCount - 1);
while (r.LineNumber >= nextLine) {
if (nextLine > end) break;
// set indentation of 'nextLine'
IDocumentLine line = document.GetLine(nextLine);
string lineText = line.Text;
string newText = attribIndent + lineText.Trim();
if (newText != lineText) {
document.Replace(line.Offset, line.Length, newText);
}
nextLine++;
}
}
}
r.Close();
}
}
#endregion
}
}