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