2005-06-05 Peter Bartok <pbartok@novell.com>
[mono.git] / mcs / nant / src / Util / XmlNodeTextPositionMap.cs
1 // NAnt - A .NET build tool\r
2 // Copyright (C) 2001 Gerry Shaw\r
3 //\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
8 //\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
13 //\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
17 //\r
18 // Gerry Shaw (gerry_shaw@yahoo.com)\r
19 \r
20 namespace SourceForge.NAnt {\r
21 \r
22     using System;\r
23     using System.IO;\r
24     using System.Text.RegularExpressions;\r
25     using System.Xml;\r
26     using System.Xml.XPath;\r
27     using System.Collections;\r
28 \r
29     public struct TextPosition {\r
30         public static readonly TextPosition InvalidPosition = new TextPosition(-1,-1);\r
31 \r
32         public TextPosition(int line, int column) {\r
33             Line = line;\r
34             Column = column;\r
35         }\r
36 \r
37         public int Line;\r
38         public int Column;\r
39     }\r
40 \r
41     /// <summary>\r
42     /// Maps XML nodes to the text positions from their original source.\r
43     /// </summary>\r
44     public class XPathTextPositionMap {\r
45 \r
46         Hashtable _map = new Hashtable();\r
47 \r
48         public XPathTextPositionMap(string url) {\r
49             string parentXPath = "/"; // default to root\r
50             string previousXPath = "";\r
51             int previousDepth = 0;\r
52 \r
53             // Load text reader\r
54             XmlTextReader reader = new XmlTextReader(url);\r
55             ArrayList indexAtDepth = new ArrayList();\r
56 \r
57             // Explicitly load document XPath\r
58             _map.Add((object) "/", (object) new TextPosition(1, 1));\r
59 \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
67                     ) {\r
68                     int level = reader.Depth;\r
69                     string currentXPath = "";\r
70 \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
76 \r
77                         for (int j = 1; j < level+1; j++) {\r
78                             newXPath += "/" + list[j];\r
79                         }\r
80 \r
81                         // higher than before so trim xpath\\r
82                         parentXPath = newXPath; // one up from before\r
83 \r
84                         // clear indexes for depth greater than ours\r
85                         indexAtDepth.RemoveRange(level+1, indexAtDepth.Count - (level+1));\r
86 \r
87                     } else if (reader.Depth > previousDepth) {\r
88                         // we are lower\r
89                         parentXPath = previousXPath;\r
90                     }\r
91 \r
92                     // End depth setup\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
98                     }\r
99                     // Set child index\r
100                     if ((int) indexAtDepth[level] == 0) {\r
101                         // first time thru\r
102                         indexAtDepth[level] = 1;\r
103                     } else {\r
104                         indexAtDepth[level] = (int) indexAtDepth[level] + 1; // lower so append to xpath\r
105                     }\r
106 \r
107                     // Do actual XPath generation\r
108                     if (parentXPath.EndsWith("/")) {\r
109                         currentXPath = parentXPath;\r
110                     } else {\r
111                         currentXPath = parentXPath + "/"; // add seperator\r
112                     }\r
113 \r
114                     // Set the final XPath\r
115                     currentXPath += "child::node()[" + indexAtDepth[level] + "]";\r
116 \r
117                     // Add to our hash structures\r
118                     _map.Add((object) currentXPath, (object) new TextPosition(reader.LineNumber, reader.LinePosition));\r
119 \r
120                     // setup up loop vars for next iteration\r
121                     previousXPath = currentXPath;\r
122                     previousDepth = reader.Depth;\r
123                 }\r
124             }\r
125         }\r
126 \r
127         public TextPosition GetTextPosition(XmlNode node) {\r
128             string xpath = GetXPathFromNode(node);\r
129             return GetTextPosition(xpath);\r
130         }\r
131 \r
132         public TextPosition GetTextPosition(string xpath) {\r
133             TextPosition pos;\r
134             if (_map.ContainsKey(xpath)) {\r
135                 pos = (TextPosition) _map[xpath];\r
136             } else {\r
137                 pos = TextPosition.InvalidPosition;\r
138             }\r
139             return pos;\r
140         }\r
141 \r
142         private string GetXPathFromNode(XmlNode node) {\r
143             XPathNavigator nav = node.CreateNavigator();\r
144 \r
145             string xpath = "";\r
146             int index = 0;\r
147 \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
156                 }\r
157                 while (navParent.MoveToNext()) {\r
158                     parentIndex++;\r
159                     if (navParent.IsSamePosition(nav)) {\r
160                         index = parentIndex;\r
161                     }\r
162                 }\r
163 \r
164                 nav.MoveToParent(); // do loop condiditon here\r
165 \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
169                 }\r
170 \r
171                 string thisNode = "child::node()[" + index  + "]";\r
172 \r
173                 if (xpath == "") {\r
174                     xpath = thisNode;\r
175                 } else {\r
176                     // build xpath string\r
177                     xpath = thisNode + "/" + xpath;\r
178                 }\r
179             }\r
180 \r
181             // prepend slash to ...\r
182             xpath = "/" + xpath;\r
183 \r
184             return xpath;\r
185         }\r
186     }\r
187 }\r