2005-03-25 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslKey.cs
1 //
2 // XslKey.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //      
8 // (C) 2003 Ben Maurer
9 // (C) 2003 Atsushi Enomoto
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.Xml;
37 using System.Xml.Schema;
38 using System.Xml.XPath;
39 using System.Xml.Xsl;
40 using Mono.Xml.XPath;
41
42 using QName = System.Xml.XmlQualifiedName;
43
44 namespace Mono.Xml.Xsl
45 {
46         internal class ExprKeyContainer : Expression
47         {
48                 Expression expr;
49                 public ExprKeyContainer (Expression expr)
50                 {
51                         this.expr = expr;
52                 }
53
54                 public Expression BodyExpression {
55                         get { return expr; }
56                 }
57
58                 public override object Evaluate (BaseIterator iter)
59                 {
60                         return expr.Evaluate (iter);
61                 }
62
63                 internal override XPathNodeType EvaluatedNodeType {
64                         get { return expr.EvaluatedNodeType; }
65                 }
66
67                 public override XPathResultType ReturnType {
68                         get { return expr.ReturnType; }
69                 }
70         }
71
72         internal class XslKey
73         {
74                 QName name;
75                 CompiledExpression useExpr;
76                 Pattern matchPattern;
77
78                 public XslKey (Compiler c)
79                 {
80                         this.name = c.ParseQNameAttribute ("name");
81
82                         c.KeyCompilationMode = true;
83                         useExpr = c.CompileExpression (c.GetAttribute ("use"));
84                         if (useExpr == null)
85                                 useExpr = c.CompileExpression (".");
86
87                         c.AssertAttribute ("match");
88                         string matchString = c.GetAttribute ("match");
89                         this.matchPattern = c.CompilePattern (matchString, c.Input);
90                         c.KeyCompilationMode = false;
91                 }
92
93                 public QName Name { get { return name; }}
94                 internal CompiledExpression Use { get { return useExpr; }}
95                 internal Pattern Match { get { return matchPattern; }}
96         }
97
98         // represents part of dynamic context that holds index table for a key
99         internal class KeyIndexTable
100         {
101                 XsltCompiledContext ctx;
102                 XslKey key;
103                 Hashtable map;
104                 Hashtable mappedDocuments;
105
106                 public KeyIndexTable (XsltCompiledContext ctx, XslKey key)
107                 {
108                         this.ctx = ctx;
109                         this.key = key;
110                 }
111
112                 public XslKey Key {
113                         get { return key; }
114                 }
115
116                 private void CollectTable (XPathNavigator doc, XsltContext ctx)
117                 {
118                         XPathNavigator nav = doc.Clone ();
119                         nav.MoveToRoot ();
120                         XPathNavigator tmp = doc.Clone ();
121
122                         do {
123                                 if (key.Match.Matches (nav, ctx)) {
124                                         tmp.MoveTo (nav);
125                                         CollectIndex (nav, tmp);
126                                 }
127                         } while (MoveNavigatorToNext (nav));
128                         if (map != null)
129                                 foreach (ArrayList list in map.Values)
130                                         list.Sort (XPathNavigatorComparer.Instance);
131                 }
132
133                 private bool MoveNavigatorToNext (XPathNavigator nav)
134                 {
135                         if (nav.MoveToFirstChild ())
136                                 return true;
137                         do {
138                                 if (nav.MoveToNext ())
139                                         return true;
140                         } while (nav.MoveToParent ());
141                         return false;
142                 }
143
144                 private void CollectIndex (XPathNavigator nav, XPathNavigator target)
145                 {
146                         XPathNodeIterator iter;
147                         switch (key.Use.ReturnType) {
148                         case XPathResultType.NodeSet:
149                                 iter = nav.Select (key.Use);
150                                 while (iter.MoveNext ())
151                                         AddIndex (iter.Current.Value, target);
152                                 break;
153                         case XPathResultType.Any:
154                                 object o = nav.Evaluate (key.Use);
155                                 iter = o as XPathNodeIterator;
156                                 if (iter != null) {
157                                         while (iter.MoveNext ())
158                                                 AddIndex (iter.Current.Value, target);
159                                 }
160                                 else
161                                         AddIndex (XPathFunctions.ToString (o), target);
162                                 break;
163                         default:
164                                 string keyValue = nav.EvaluateString (key.Use, null, null);
165                                 AddIndex (keyValue, target);
166                                 break;
167                         }
168                 }
169
170                 private void AddIndex (string key, XPathNavigator target)
171                 {
172                         ArrayList al = map [key] as ArrayList;
173                         if (al == null) {
174                                 al = new ArrayList ();
175                                 map [key] = al;
176                         }
177                         for (int i = 0; i < al.Count; i++)
178                                 if (((XPathNavigator) al [i]).IsSamePosition (target))
179                                         return;
180                         al.Add (target.Clone ());
181                 }
182
183                 private ArrayList GetNodesByValue (XPathNavigator nav, string value, XsltContext ctx)
184                 {
185                         if (map == null) {
186                                 mappedDocuments = new Hashtable ();
187                                 map = new Hashtable ();
188                         }
189                         if (!mappedDocuments.ContainsKey (nav.BaseURI)) {
190                                 mappedDocuments.Add (nav.BaseURI, nav.BaseURI);
191                                 CollectTable (nav, ctx);
192                         }
193                         
194                         return map [value] as ArrayList;
195                 }
196
197                 public bool Matches (XPathNavigator nav, string value, XsltContext ctx)
198                 {
199                         ArrayList al = GetNodesByValue (nav, value, ctx);
200                         if (al == null)
201                                 return false;
202                         for (int i = 0; i < al.Count; i++)
203                                 if (((XPathNavigator) al [i]).IsSamePosition (nav))
204                                         return true;
205                         return false;
206                 }
207
208                 // Invoked from XsltKey (XPathFunction)
209                 public BaseIterator Evaluate (BaseIterator iter,
210                         Expression valueExpr)
211                 {
212                         XPathNodeIterator i = iter;
213                         if (iter.CurrentPosition == 0) {
214                                 i = iter.Clone ();
215                                 i.MoveNext ();
216                         }
217                         XPathNavigator nav = i.Current;
218
219                         object o = valueExpr.Evaluate (iter);
220                         XPathNodeIterator it = o as XPathNodeIterator;
221                         XsltContext ctx = iter.NamespaceManager as XsltContext;
222
223                         BaseIterator result = null;
224
225                         if (it != null) {
226                                 while (it.MoveNext()) {
227                                         ArrayList nodes = GetNodesByValue (
228                                                 it.Current, it.Current.Value, ctx);
229                                         if (nodes == null)
230                                                 continue;
231                                         ListIterator tmp =
232                                                 new ListIterator (nodes, ctx);
233                                         if (result == null)
234                                                 result = tmp;
235                                         else
236                                                 result = new UnionIterator (
237                                                         iter, result, tmp);
238                                 }
239                         }
240                         else {
241                                 ArrayList nodes = GetNodesByValue (
242                                         nav, XPathFunctions.ToString (o), ctx);
243                                 if (nodes != null)
244                                         result = new ListIterator (nodes, ctx);
245                         }
246
247                         return result != null ? result : new NullIterator (iter);
248                 }
249         }
250 }