// NAnt - A .NET build tool // Copyright (C) 2001 Gerry Shaw // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Gerry Shaw (gerry_shaw@yahoo.com) namespace SourceForge.NAnt { using System; using System.IO; using System.Text.RegularExpressions; using System.Xml; using System.Xml.XPath; using System.Collections; public struct TextPosition { public static readonly TextPosition InvalidPosition = new TextPosition(-1,-1); public TextPosition(int line, int column) { Line = line; Column = column; } public int Line; public int Column; } /// /// Maps XML nodes to the text positions from their original source. /// public class XPathTextPositionMap { Hashtable _map = new Hashtable(); public XPathTextPositionMap(string url) { string parentXPath = "/"; // default to root string previousXPath = ""; int previousDepth = 0; // Load text reader XmlTextReader reader = new XmlTextReader(url); ArrayList indexAtDepth = new ArrayList(); // Explicitly load document XPath _map.Add((object) "/", (object) new TextPosition(1, 1)); // loop thru all nodes in the document while (reader.Read()) { // reader to Node ... if ( (reader.NodeType.ToString() != "Whitespace") // Ignore those we aren't interested in && (reader.NodeType.ToString() != "EndElement") && (reader.NodeType.ToString() != "ProcessingInstruction") && (reader.NodeType.ToString() != "XmlDeclaration") ) { int level = reader.Depth; string currentXPath = ""; // If we arr higher than before if (reader.Depth < previousDepth) { // Clear vars for new depth string[] list = parentXPath.Split('/'); string newXPath = ""; // once appended to / will be root node ... for (int j = 1; j < level+1; j++) { newXPath += "/" + list[j]; } // higher than before so trim xpath\ parentXPath = newXPath; // one up from before // clear indexes for depth greater than ours indexAtDepth.RemoveRange(level+1, indexAtDepth.Count - (level+1)); } else if (reader.Depth > previousDepth) { // we are lower parentXPath = previousXPath; } // End depth setup // Setup up index array // add any needed extra items ( usually only 1 ) // would have uses array but not sure what maximum depth will be beforehand for (int index = indexAtDepth.Count; index < level+1; index++) { indexAtDepth.Add(0); } // Set child index if ((int) indexAtDepth[level] == 0) { // first time thru indexAtDepth[level] = 1; } else { indexAtDepth[level] = (int) indexAtDepth[level] + 1; // lower so append to xpath } // Do actual XPath generation if (parentXPath.EndsWith("/")) { currentXPath = parentXPath; } else { currentXPath = parentXPath + "/"; // add seperator } // Set the final XPath currentXPath += "child::node()[" + indexAtDepth[level] + "]"; // Add to our hash structures _map.Add((object) currentXPath, (object) new TextPosition(reader.LineNumber, reader.LinePosition)); // setup up loop vars for next iteration previousXPath = currentXPath; previousDepth = reader.Depth; } } } public TextPosition GetTextPosition(XmlNode node) { string xpath = GetXPathFromNode(node); return GetTextPosition(xpath); } public TextPosition GetTextPosition(string xpath) { TextPosition pos; if (_map.ContainsKey(xpath)) { pos = (TextPosition) _map[xpath]; } else { pos = TextPosition.InvalidPosition; } return pos; } private string GetXPathFromNode(XmlNode node) { XPathNavigator nav = node.CreateNavigator(); string xpath = ""; int index = 0; while (nav.NodeType.ToString() != "Root") { // loop thru children until we find ourselves XPathNavigator navParent = nav.Clone(); navParent.MoveToParent(); int parentIndex = 0; navParent.MoveToFirstChild(); if (navParent.IsSamePosition(nav)) { index = parentIndex; } while (navParent.MoveToNext()) { parentIndex++; if (navParent.IsSamePosition(nav)) { index = parentIndex; } } nav.MoveToParent(); // do loop condiditon here // if we are at doc and index = 0 then there is no xml proc instruction if ((nav.NodeType.ToString()) != "Root" || (index == 0)) { index = index + 1; // special case at root to avoid processing instruction .. } string thisNode = "child::node()[" + index + "]"; if (xpath == "") { xpath = thisNode; } else { // build xpath string xpath = thisNode + "/" + xpath; } } // prepend slash to ... xpath = "/" + xpath; return xpath; } } }