1 // NAnt - A .NET build tool
\r
2 // Copyright (C) 2001 Gerry Shaw
\r
4 // This program is free software; you can redistribute it and/or modify
\r
5 // it under the terms of the GNU General Public License as published by
\r
6 // the Free Software Foundation; either version 2 of the License, or
\r
7 // (at your option) any later version.
\r
9 // This program is distributed in the hope that it will be useful,
\r
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
12 // GNU General Public License for more details.
\r
14 // You should have received a copy of the GNU General Public License
\r
15 // along with this program; if not, write to the Free Software
\r
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\r
18 // Gerry Shaw (gerry_shaw@yahoo.com)
\r
20 namespace SourceForge.NAnt {
\r
24 using System.Text.RegularExpressions;
\r
26 using System.Xml.XPath;
\r
27 using System.Collections;
\r
29 public struct TextPosition {
\r
30 public static readonly TextPosition InvalidPosition = new TextPosition(-1,-1);
\r
32 public TextPosition(int line, int column) {
\r
42 /// Maps XML nodes to the text positions from their original source.
\r
44 public class XPathTextPositionMap {
\r
46 Hashtable _map = new Hashtable();
\r
48 public XPathTextPositionMap(string url) {
\r
49 string parentXPath = "/"; // default to root
\r
50 string previousXPath = "";
\r
51 int previousDepth = 0;
\r
54 XmlTextReader reader = new XmlTextReader(url);
\r
55 ArrayList indexAtDepth = new ArrayList();
\r
57 // Explicitly load document XPath
\r
58 _map.Add((object) "/", (object) new TextPosition(1, 1));
\r
60 // loop thru all nodes in the document
\r
61 while (reader.Read()) {
\r
62 // reader to Node ...
\r
63 if ( (reader.NodeType.ToString() != "Whitespace") // Ignore those we aren't interested in
\r
64 && (reader.NodeType.ToString() != "EndElement")
\r
65 && (reader.NodeType.ToString() != "ProcessingInstruction")
\r
66 && (reader.NodeType.ToString() != "XmlDeclaration")
\r
68 int level = reader.Depth;
\r
69 string currentXPath = "";
\r
71 // If we arr higher than before
\r
72 if (reader.Depth < previousDepth) {
\r
73 // Clear vars for new depth
\r
74 string[] list = parentXPath.Split('/');
\r
75 string newXPath = ""; // once appended to / will be root node ...
\r
77 for (int j = 1; j < level+1; j++) {
\r
78 newXPath += "/" + list[j];
\r
81 // higher than before so trim xpath\
\r
82 parentXPath = newXPath; // one up from before
\r
84 // clear indexes for depth greater than ours
\r
85 indexAtDepth.RemoveRange(level+1, indexAtDepth.Count - (level+1));
\r
87 } else if (reader.Depth > previousDepth) {
\r
89 parentXPath = previousXPath;
\r
93 // Setup up index array
\r
94 // add any needed extra items ( usually only 1 )
\r
95 // would have uses array but not sure what maximum depth will be beforehand
\r
96 for (int index = indexAtDepth.Count; index < level+1; index++) {
\r
97 indexAtDepth.Add(0);
\r
100 if ((int) indexAtDepth[level] == 0) {
\r
102 indexAtDepth[level] = 1;
\r
104 indexAtDepth[level] = (int) indexAtDepth[level] + 1; // lower so append to xpath
\r
107 // Do actual XPath generation
\r
108 if (parentXPath.EndsWith("/")) {
\r
109 currentXPath = parentXPath;
\r
111 currentXPath = parentXPath + "/"; // add seperator
\r
114 // Set the final XPath
\r
115 currentXPath += "child::node()[" + indexAtDepth[level] + "]";
\r
117 // Add to our hash structures
\r
118 _map.Add((object) currentXPath, (object) new TextPosition(reader.LineNumber, reader.LinePosition));
\r
120 // setup up loop vars for next iteration
\r
121 previousXPath = currentXPath;
\r
122 previousDepth = reader.Depth;
\r
127 public TextPosition GetTextPosition(XmlNode node) {
\r
128 string xpath = GetXPathFromNode(node);
\r
129 return GetTextPosition(xpath);
\r
132 public TextPosition GetTextPosition(string xpath) {
\r
134 if (_map.ContainsKey(xpath)) {
\r
135 pos = (TextPosition) _map[xpath];
\r
137 pos = TextPosition.InvalidPosition;
\r
142 private string GetXPathFromNode(XmlNode node) {
\r
143 XPathNavigator nav = node.CreateNavigator();
\r
148 while (nav.NodeType.ToString() != "Root") {
\r
149 // loop thru children until we find ourselves
\r
150 XPathNavigator navParent = nav.Clone();
\r
151 navParent.MoveToParent();
\r
152 int parentIndex = 0;
\r
153 navParent.MoveToFirstChild();
\r
154 if (navParent.IsSamePosition(nav)) {
\r
155 index = parentIndex;
\r
157 while (navParent.MoveToNext()) {
\r
159 if (navParent.IsSamePosition(nav)) {
\r
160 index = parentIndex;
\r
164 nav.MoveToParent(); // do loop condiditon here
\r
166 // if we are at doc and index = 0 then there is no xml proc instruction
\r
167 if ((nav.NodeType.ToString()) != "Root" || (index == 0)) {
\r
168 index = index + 1; // special case at root to avoid processing instruction ..
\r
171 string thisNode = "child::node()[" + index + "]";
\r
176 // build xpath string
\r
177 xpath = thisNode + "/" + xpath;
\r
181 // prepend slash to ...
\r
182 xpath = "/" + xpath;
\r