2005-09-20 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / Commons.Xml.Relaxng / Commons.Xml.Relaxng / RelaxngValidatingReader.cs
1 //\r
2 // Commons.Xml.Relaxng.RelaxngValidatingReader\r
3 //\r
4 // Author:\r
5 //      Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>\r
6 //\r
7 // 2003 Atsushi Enomoto. "No rights reserved."\r
8 //\r
9 // Copyright (c) 2004 Novell Inc.\r
10 // All rights reserved\r
11 //\r
12
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 using System;\r
34 using System.Collections;\r
35 using System.Text;\r
36 using System.Xml;\r
37 using Commons.Xml.Relaxng.Derivative;\r
38 \r
39 namespace Commons.Xml.Relaxng\r
40 {\r
41         public class RelaxngValidatingReader : XmlDefaultReader\r
42         {\r
43                 public RelaxngValidatingReader (XmlReader reader)\r
44                         : this (reader, (RelaxngPattern) null)\r
45                 {\r
46                 }\r
47 \r
48                 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)\r
49                         : this (reader, grammarXml, null)\r
50                 {\r
51                 }\r
52 \r
53                 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)\r
54                         : this (reader, RelaxngGrammar.Read (grammarXml, provider))\r
55                 {\r
56                 }\r
57 \r
58                 public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)\r
59                         : base (reader)\r
60                 {\r
61                         if (reader.NodeType == XmlNodeType.Attribute)\r
62                                 throw new RelaxngException ("RELAX NG does not support standalone attribute validation (it is prohibited due to the specification section 7.1.5");\r
63                         this.reader = reader;\r
64                         this.pattern = pattern;\r
65                 }\r
66 \r
67                 XmlReader reader;\r
68                 RelaxngPattern pattern;\r
69                 RdpPattern vState;\r
70                 RdpPattern prevState;   // Mainly for debugging.\r
71                 ArrayList PredefinedAttributes = new ArrayList ();\r
72                 bool labelsComputed;\r
73                 Hashtable elementLabels = new Hashtable ();\r
74                 Hashtable attributeLabels = new Hashtable ();\r
75                 bool isEmptiable;\r
76                 bool roughLabelCheck;\r
77                 ArrayList strictCheckCache;\r
78                 bool reportDetails;\r
79 \r
80                 internal string CurrentStateXml {\r
81                         get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }\r
82                 }\r
83 \r
84                 internal string PreviousStateXml {\r
85                         get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }\r
86                 }\r
87 \r
88                 #region Validation State support\r
89 \r
90                 public bool ReportDetails {\r
91                         get { return reportDetails; }\r
92                         set { reportDetails = value; }\r
93                 }\r
94 \r
95                 public bool RoughLabelCheck {\r
96                         get { return roughLabelCheck; }\r
97                         set { roughLabelCheck = value; }\r
98                 }\r
99 \r
100                 // It is used to disclose its validation feature to public\r
101                 class ValidationState\r
102                 {\r
103                         RdpPattern state;\r
104 \r
105                         internal ValidationState (RdpPattern startState)\r
106                         {\r
107                                 this.state = startState;\r
108                         }\r
109 \r
110                         public RdpPattern Pattern {\r
111                                 get { return state; }\r
112                         }\r
113 \r
114                         public ValidationState AfterOpenStartTag (\r
115                                 string localName, string ns)\r
116                         {\r
117                                 RdpPattern p = state.StartTagOpenDeriv (\r
118                                         localName, ns);\r
119                                 return p is RdpNotAllowed ?\r
120                                         null : new ValidationState (p);\r
121                         }\r
122 \r
123                         public bool OpenStartTag (string localName, string ns)\r
124                         {\r
125                                 RdpPattern p = state.StartTagOpenDeriv (\r
126                                         localName, ns);\r
127                                 if (p is RdpNotAllowed)\r
128                                         return false;\r
129                                 state = p;\r
130                                 return true;\r
131                         }\r
132 \r
133                         public ValidationState AfterCloseStartTag ()\r
134                         {\r
135                                 RdpPattern p = state.StartTagCloseDeriv ();\r
136                                 return p is RdpNotAllowed ?\r
137                                         null : new ValidationState (p);\r
138                         }\r
139 \r
140                         public bool CloseStartTag ()\r
141                         {\r
142                                 RdpPattern p = state.StartTagCloseDeriv ();\r
143                                 if (p is RdpNotAllowed)\r
144                                         return false;\r
145                                 state = p;\r
146                                 return true;\r
147                         }\r
148 \r
149                         public ValidationState AfterEndTag ()\r
150                         {\r
151                                 RdpPattern p = state.EndTagDeriv ();\r
152                                 if (p is RdpNotAllowed)\r
153                                         return null;\r
154                                 return new ValidationState (p);\r
155                         }\r
156 \r
157                         public bool EndTag ()\r
158                         {\r
159                                 RdpPattern p = state.EndTagDeriv ();\r
160                                 if (p is RdpNotAllowed)\r
161                                         return false;\r
162                                 state = p;\r
163                                 return true;\r
164                         }\r
165 \r
166                         public ValidationState AfterAttribute (\r
167                                 string localName, string ns, XmlReader reader)\r
168                         {\r
169                                 RdpPattern p = state.AttDeriv (\r
170                                         localName, ns, null, reader);\r
171                                 if (p is RdpNotAllowed)\r
172                                         return null;\r
173                                 return new ValidationState (p);\r
174                         }\r
175 \r
176                         public bool Attribute (\r
177                                 string localName, string ns, XmlReader reader)\r
178                         {\r
179                                 RdpPattern p = state.AttDeriv (\r
180                                         localName, ns, null, reader);\r
181                                 if (p is RdpNotAllowed)\r
182                                         return false;\r
183                                 state = p;\r
184                                 return true;\r
185                         }\r
186                 }\r
187 \r
188                 public object GetCurrentState ()\r
189                 {\r
190                         PrepareState ();\r
191                         return new ValidationState (vState);\r
192                 }\r
193 \r
194                 private ValidationState ToState (object stateObject)\r
195                 {\r
196                         if (stateObject == null)\r
197                                 throw new ArgumentNullException ("stateObject");\r
198                         ValidationState state = stateObject as ValidationState;\r
199                         if (state == null)\r
200                                 throw new ArgumentException ("Argument stateObject is not of expected type.");\r
201                         return state;\r
202                 }\r
203 \r
204                 public object AfterOpenStartTag (object stateObject,\r
205                         string localName, string ns)\r
206                 {\r
207                         ValidationState state = ToState (stateObject);\r
208                         return state.AfterOpenStartTag (localName, ns);\r
209                 }\r
210 \r
211                 public bool OpenStartTag (object stateObject,\r
212                         string localName, string ns)\r
213                 {\r
214                         ValidationState state = ToState (stateObject);\r
215                         return state.OpenStartTag (localName, ns);\r
216                 }\r
217 \r
218                 public object AfterAttribute (object stateObject,\r
219                         string localName, string ns)\r
220                 {\r
221                         ValidationState state = ToState (stateObject);\r
222                         return state.AfterAttribute (localName, ns, this);\r
223                 }\r
224 \r
225                 public bool Attribute (object stateObject,\r
226                         string localName, string ns)\r
227                 {\r
228                         ValidationState state = ToState (stateObject);\r
229                         return state.Attribute (localName, ns, this);\r
230                 }\r
231 \r
232                 public object AfterCloseStartTag (object stateObject)\r
233                 {\r
234                         ValidationState state = ToState (stateObject);\r
235                         return state.AfterCloseStartTag ();\r
236                 }\r
237 \r
238                 public bool CloseStartTag (object stateObject)\r
239                 {\r
240                         ValidationState state = ToState (stateObject);\r
241                         return state.CloseStartTag ();\r
242                 }\r
243 \r
244                 public object AfterEndTag (object stateObject)\r
245                 {\r
246                         ValidationState state = ToState (stateObject);\r
247                         return state.AfterEndTag ();\r
248                 }\r
249 \r
250                 public bool EndTag (object stateObject)\r
251                 {\r
252                         ValidationState state = ToState (stateObject);\r
253                         return state.EndTag ();\r
254                 }\r
255 \r
256                 public ICollection GetElementLabels (object stateObject)\r
257                 {\r
258                         ValidationState state = ToState (stateObject);\r
259                         RdpPattern p = state.Pattern;\r
260                         Hashtable elements = new Hashtable ();\r
261                         Hashtable attributes = new Hashtable ();\r
262                         p.GetLabels (elements, attributes);\r
263 \r
264                         if (roughLabelCheck)\r
265                                 return elements.Values;\r
266 \r
267                         // Strict check that tries actual validation that will\r
268                         // cover rejection by notAllowed.\r
269                         if (strictCheckCache == null)\r
270                                 strictCheckCache = new ArrayList ();\r
271                         else\r
272                                 strictCheckCache.Clear ();\r
273                         foreach (XmlQualifiedName qname in elements.Values)\r
274                                 if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)\r
275                                         strictCheckCache.Add (qname);\r
276                         foreach (XmlQualifiedName qname in strictCheckCache)\r
277                                 elements.Remove (qname);\r
278                         strictCheckCache.Clear ();\r
279 \r
280                         return elements.Values;\r
281                 }\r
282 \r
283                 public ICollection GetAttributeLabels (object stateObject)\r
284                 {\r
285                         ValidationState state = ToState (stateObject);\r
286                         RdpPattern p = state.Pattern;\r
287                         Hashtable elements = new Hashtable ();\r
288                         Hashtable attributes = new Hashtable ();\r
289                         p.GetLabels (elements, attributes);\r
290 \r
291                         if (roughLabelCheck)\r
292                                 return attributes.Values;\r
293 \r
294                         // Strict check that tries actual validation that will\r
295                         // cover rejection by notAllowed.\r
296                         if (strictCheckCache == null)\r
297                                 strictCheckCache = new ArrayList ();\r
298                         else\r
299                                 strictCheckCache.Clear ();\r
300                         foreach (XmlQualifiedName qname in attributes.Values)\r
301                                 if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)\r
302                                         strictCheckCache.Add (qname);\r
303                         foreach (XmlQualifiedName qname in strictCheckCache)\r
304                                 attributes.Remove (qname);\r
305                         strictCheckCache.Clear ();\r
306 \r
307                         return attributes.Values;\r
308                 }\r
309 \r
310                 public bool Emptiable (object stateObject)\r
311                 {\r
312                         ValidationState state = ToState (stateObject);\r
313                         RdpPattern p = state.Pattern;\r
314                         return !(p.EndTagDeriv () is RdpNotAllowed);\r
315                 }\r
316                 #endregion\r
317 \r
318                 private RelaxngException createValidationError (string message)\r
319                 {\r
320                         IXmlLineInfo li = reader as IXmlLineInfo;\r
321                         string lineInfo = reader.BaseURI;\r
322                         if (li != null)\r
323                                 lineInfo += String.Format (" line {0}, column {1}",\r
324                                         li.LineNumber, li.LinePosition);\r
325                         return new RelaxngException (message + lineInfo, prevState);\r
326                 }\r
327 \r
328                 private void PrepareState ()\r
329                 {\r
330                         if (!pattern.IsCompiled) {\r
331                                 pattern.Compile ();\r
332                         }\r
333                         if (vState == null)\r
334                                 vState = pattern.StartPattern;\r
335                 }\r
336 \r
337                 private string BuildLabels (bool elements)\r
338                 {\r
339                         StringBuilder sb = new StringBuilder ();\r
340                         ValidationState s = new ValidationState (prevState);\r
341                         ICollection col = elements ?\r
342                                 GetElementLabels (s) : GetAttributeLabels (s);\r
343                         foreach (XmlQualifiedName qname in col) {\r
344                                 sb.Append (qname.ToString ());\r
345                                 sb.Append (' ');\r
346                         }\r
347                         return sb.ToString ();\r
348                 }\r
349 \r
350                 public override bool Read ()\r
351                 {\r
352                         PrepareState ();\r
353 \r
354                         labelsComputed = false;\r
355                         elementLabels.Clear ();\r
356                         attributeLabels.Clear ();\r
357 \r
358                         bool ret = reader.Read ();\r
359 \r
360                         switch (reader.NodeType) {\r
361                         case XmlNodeType.Element:\r
362                                 // StartTagOpenDeriv\r
363                                 prevState = vState;\r
364                                 vState = vState.StartTagOpenDeriv (\r
365                                         reader.LocalName, reader.NamespaceURI);\r
366                                 if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
367                                         string labels = String.Empty;\r
368                                         if (reportDetails)\r
369                                                 labels = "Allowed elements are: " + BuildLabels (true);\r
370                                         throw createValidationError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
371                                 }\r
372 \r
373                                 // AttsDeriv equals to for each AttDeriv\r
374                                 string elementNS = reader.NamespaceURI;\r
375                                 if (reader.MoveToFirstAttribute ()) {\r
376                                         do {\r
377                                                 if (reader.Name.IndexOf ("xmlns:") == 0 || reader.Name == "xmlns")\r
378                                                         continue;\r
379 \r
380                                                 prevState = vState;\r
381                                                 string attrNS = reader.NamespaceURI;\r
382                                                 vState = vState.AttDeriv (reader.LocalName, attrNS, reader.GetAttribute (reader.Name), this);\r
383                                                 if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
384                                                         string labels = String.Empty;\r
385                                                         if (reportDetails)\r
386                                                                 labels = "Allowed attributes are: " + BuildLabels (false);\r
387 \r
388                                                         throw createValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
389                                                 }\r
390                                         } while (reader.MoveToNextAttribute ());\r
391                                         MoveToElement ();\r
392                                 }\r
393 \r
394                                 // StarTagCloseDeriv\r
395                                 prevState = vState;\r
396                                 vState = vState.StartTagCloseDeriv ();\r
397                                 if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
398                                         string labels = String.Empty;\r
399                                         if (reportDetails)\r
400                                                 labels = "Expected attributes are: " + BuildLabels (false);\r
401 \r
402                                         throw createValidationError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
403                                 }\r
404 \r
405                                 // if it is empty, then redirect to EndElement\r
406                                 if (reader.IsEmptyElement)\r
407                                         goto case XmlNodeType.EndElement;\r
408                                 break;\r
409                         case XmlNodeType.EndElement:\r
410                                 // EndTagDeriv\r
411                                 prevState = vState;\r
412                                 vState = vState.EndTagDeriv ();\r
413                                 if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
414                                         string labels = String.Empty;\r
415                                         if (reportDetails)\r
416                                                 labels = "Expected elements are: " + BuildLabels (true);\r
417                                         throw createValidationError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
418                                 }\r
419                                 break;\r
420                         case XmlNodeType.CDATA:\r
421                         case XmlNodeType.Text:\r
422                         case XmlNodeType.SignificantWhitespace:\r
423                                 // Whitespace cannot be skipped because data and\r
424                                 // value types are required to validate whitespaces.\r
425                                 prevState = vState;\r
426                                 vState = vState.TextDeriv (this.Value, reader);\r
427                                 if (vState.PatternType == RelaxngPatternType.NotAllowed)\r
428                                         throw createValidationError (String.Format ("Invalid text found. Text value = {0} ", reader.Value));\r
429                                 break;\r
430                                 \r
431                         }\r
432                         return ret;\r
433                 }\r
434         }\r
435 }\r
436 \r