#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 using ICSharpCode.TextEditor; using ICSharpCode.TextEditor.Actions; using ICSharpCode.TextEditor.Document; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Xml; namespace Maestro.Editors.Generic.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(TextArea textArea, int lineNr, int caretOffset, char charTyped) // used for comment tag formater/inserter { textArea.Document.UndoStack.StartUndoGroup(); try { if (charTyped == '>') { StringBuilder stringBuilder = new StringBuilder(); int offset = Math.Min(caretOffset - 2, textArea.Document.TextLength - 1); while (true) { if (offset < 0) { break; } char ch = textArea.Document.GetCharAt(offset); if (ch == '<') { string reversedTag = stringBuilder.ToString().Trim(); if (!reversedTag.StartsWith("/") && !reversedTag.EndsWith("/")) { bool validXml = true; try { XmlDocument doc = new XmlDocument(); doc.LoadXml(textArea.Document.TextContent); } catch (Exception) { 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("!") && !tagString.StartsWith("?")) { textArea.Document.Insert(caretOffset, ""); } } } break; } stringBuilder.Append(ch); --offset; } } } catch (Exception e) { // Insanity check Debug.Assert(false, e.ToString()); } if (charTyped == '\n') { textArea.Caret.Column = IndentLine(textArea, lineNr); } textArea.Document.UndoStack.EndUndoGroup(); } /// /// Define XML specific smart indenting for a line :) /// protected override int SmartIndentLine(TextArea textArea, int lineNr) { if (lineNr <= 0) return AutoIndentLine(textArea, lineNr); try { TryIndent(textArea, lineNr, lineNr); return GetIndentation(textArea, lineNr).Length; } catch (XmlException) { return AutoIndentLine(textArea, lineNr); } } /// /// This function sets the indentlevel in a range of lines. /// public override void IndentLines(TextArea textArea, int begin, int end) { textArea.Document.UndoStack.StartUndoGroup(); try { TryIndent(textArea, begin, end); } catch (XmlException ex) { Debug.WriteLine(ex.ToString()); } finally { textArea.Document.UndoStack.EndUndoGroup(); } } #region Smart Indentation private void TryIndent(TextArea textArea, int begin, int end) { string currentIndentation = ""; Stack tagStack = new Stack(); IDocument document = textArea.Document; string tab = Tab.GetIndentationString(document); int nextLine = begin; // in #dev coordinates bool wasEmptyElement = false; XmlNodeType lastType = XmlNodeType.XmlDeclaration; // TextReader line number begin with 1, #dev line numbers with 0 using (StringReader stringReader = new StringReader(document.TextContent)) { 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 += 1; continue; } // set indentation of 'nextLine' LineSegment line = document.GetLineSegment(nextLine); string lineText = document.GetText(line); 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 += 1; } 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 = GetIndentation(textArea, 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) { // caution: here we compare 1-based and 0-based line numbers if (nextLine > end) break; // set indentation of 'nextLine' LineSegment line = document.GetLineSegment(nextLine); string lineText = document.GetText(line); string newText = attribIndent + lineText.Trim(); if (newText != lineText) { document.Replace(line.Offset, line.Length, newText); } nextLine += 1; } } } r.Close(); } } #endregion } }