* support-test-*.cs: Rename from test-*-p2.cs.
[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                 Hashtable elementLabels = new Hashtable ();
72                 Hashtable attributeLabels = new Hashtable ();
73                 bool roughLabelCheck;
74                 ArrayList strictCheckCache;
75                 bool reportDetails;
76                 string cachedValue;
77                 int startElementDepth = -1;
78                 bool inContent;
79
80                 internal string CurrentStateXml {
81                         get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
82                 }
83
84                 internal string PreviousStateXml {
85                         get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
86                 }
87
88                 #region Validation State support
89
90                 public bool ReportDetails {
91                         get { return reportDetails; }
92                         set { reportDetails = value; }
93                 }
94
95                 public bool RoughLabelCheck {
96                         get { return roughLabelCheck; }
97                         set { roughLabelCheck = value; }
98                 }
99
100                 // It is used to disclose its validation feature to public
101                 class ValidationState
102                 {
103                         RdpPattern state;
104
105                         internal ValidationState (RdpPattern startState)
106                         {
107                                 this.state = startState;
108                         }
109
110                         public RdpPattern Pattern {
111                                 get { return state; }
112                         }
113
114                         public ValidationState AfterOpenStartTag (
115                                 string localName, string ns)
116                         {
117                                 RdpPattern p = state.StartTagOpenDeriv (
118                                         localName, ns);
119                                 return p is RdpNotAllowed ?
120                                         null : new ValidationState (p);
121                         }
122
123                         public bool OpenStartTag (string localName, string ns)
124                         {
125                                 RdpPattern p = state.StartTagOpenDeriv (
126                                         localName, ns);
127                                 if (p is RdpNotAllowed)
128                                         return false;
129                                 state = p;
130                                 return true;
131                         }
132
133                         public ValidationState AfterCloseStartTag ()
134                         {
135                                 RdpPattern p = state.StartTagCloseDeriv ();
136                                 return p is RdpNotAllowed ?
137                                         null : new ValidationState (p);
138                         }
139
140                         public bool CloseStartTag ()
141                         {
142                                 RdpPattern p = state.StartTagCloseDeriv ();
143                                 if (p is RdpNotAllowed)
144                                         return false;
145                                 state = p;
146                                 return true;
147                         }
148
149                         public ValidationState AfterEndTag ()
150                         {
151                                 RdpPattern p = state.EndTagDeriv ();
152                                 if (p is RdpNotAllowed)
153                                         return null;
154                                 return new ValidationState (p);
155                         }
156
157                         public bool EndTag ()
158                         {
159                                 RdpPattern p = state.EndTagDeriv ();
160                                 if (p is RdpNotAllowed)
161                                         return false;
162                                 state = p;
163                                 return true;
164                         }
165
166                         public ValidationState AfterAttribute (
167                                 string localName, string ns, XmlReader reader)
168                         {
169                                 RdpPattern p = state.AttDeriv (
170                                         localName, ns, null, reader);
171                                 if (p is RdpNotAllowed)
172                                         return null;
173                                 return new ValidationState (p);
174                         }
175
176                         public bool Attribute (
177                                 string localName, string ns, XmlReader reader)
178                         {
179                                 RdpPattern p = state.AttDeriv (
180                                         localName, ns, null, reader);
181                                 if (p is RdpNotAllowed)
182                                         return false;
183                                 state = p;
184                                 return true;
185                         }
186                 }
187
188                 public object GetCurrentState ()
189                 {
190                         PrepareState ();
191                         return new ValidationState (vState);
192                 }
193
194                 private ValidationState ToState (object stateObject)
195                 {
196                         if (stateObject == null)
197                                 throw new ArgumentNullException ("stateObject");
198                         ValidationState state = stateObject as ValidationState;
199                         if (state == null)
200                                 throw new ArgumentException ("Argument stateObject is not of expected type.");
201                         return state;
202                 }
203
204                 public object AfterOpenStartTag (object stateObject,
205                         string localName, string ns)
206                 {
207                         ValidationState state = ToState (stateObject);
208                         return state.AfterOpenStartTag (localName, ns);
209                 }
210
211                 public bool OpenStartTag (object stateObject,
212                         string localName, string ns)
213                 {
214                         ValidationState state = ToState (stateObject);
215                         return state.OpenStartTag (localName, ns);
216                 }
217
218                 public object AfterAttribute (object stateObject,
219                         string localName, string ns)
220                 {
221                         ValidationState state = ToState (stateObject);
222                         return state.AfterAttribute (localName, ns, this);
223                 }
224
225                 public bool Attribute (object stateObject,
226                         string localName, string ns)
227                 {
228                         ValidationState state = ToState (stateObject);
229                         return state.Attribute (localName, ns, this);
230                 }
231
232                 public object AfterCloseStartTag (object stateObject)
233                 {
234                         ValidationState state = ToState (stateObject);
235                         return state.AfterCloseStartTag ();
236                 }
237
238                 public bool CloseStartTag (object stateObject)
239                 {
240                         ValidationState state = ToState (stateObject);
241                         return state.CloseStartTag ();
242                 }
243
244                 public object AfterEndTag (object stateObject)
245                 {
246                         ValidationState state = ToState (stateObject);
247                         return state.AfterEndTag ();
248                 }
249
250                 public bool EndTag (object stateObject)
251                 {
252                         ValidationState state = ToState (stateObject);
253                         return state.EndTag ();
254                 }
255
256                 public ICollection GetElementLabels (object stateObject)
257                 {
258                         ValidationState state = ToState (stateObject);
259                         RdpPattern p = state.Pattern;
260                         Hashtable elements = new Hashtable ();
261                         Hashtable attributes = new Hashtable ();
262                         p.GetLabels (elements, attributes);
263
264                         if (roughLabelCheck)
265                                 return elements.Values;
266
267                         // Strict check that tries actual validation that will
268                         // cover rejection by notAllowed.
269                         if (strictCheckCache == null)
270                                 strictCheckCache = new ArrayList ();
271                         else
272                                 strictCheckCache.Clear ();
273                         foreach (XmlQualifiedName qname in elements.Values)
274                                 if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)
275                                         strictCheckCache.Add (qname);
276                         foreach (XmlQualifiedName qname in strictCheckCache)
277                                 elements.Remove (qname);
278                         strictCheckCache.Clear ();
279
280                         return elements.Values;
281                 }
282
283                 public ICollection GetAttributeLabels (object stateObject)
284                 {
285                         ValidationState state = ToState (stateObject);
286                         RdpPattern p = state.Pattern;
287                         Hashtable elements = new Hashtable ();
288                         Hashtable attributes = new Hashtable ();
289                         p.GetLabels (elements, attributes);
290
291                         if (roughLabelCheck)
292                                 return attributes.Values;
293
294                         // Strict check that tries actual validation that will
295                         // cover rejection by notAllowed.
296                         if (strictCheckCache == null)
297                                 strictCheckCache = new ArrayList ();
298                         else
299                                 strictCheckCache.Clear ();
300                         foreach (XmlQualifiedName qname in attributes.Values)
301                                 if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)
302                                         strictCheckCache.Add (qname);
303                         foreach (XmlQualifiedName qname in strictCheckCache)
304                                 attributes.Remove (qname);
305                         strictCheckCache.Clear ();
306
307                         return attributes.Values;
308                 }
309
310                 public bool Emptiable (object stateObject)
311                 {
312                         ValidationState state = ToState (stateObject);
313                         RdpPattern p = state.Pattern;
314                         return !(p.EndTagDeriv () is RdpNotAllowed);
315                 }
316                 #endregion
317
318                 private RelaxngException CreateValidationError (string message,
319                         bool elements)
320                 {
321                         if (ReportDetails)
322                                 return CreateValidationError (String.Concat (message,
323                                         " Expected ",
324                                         elements ? "elements are: " : "attributes are: ",
325                                         BuildLabels (elements),
326                                         "."));
327                         return CreateValidationError (message);
328                 }
329
330                 private RelaxngException CreateValidationError (string message)
331                 {
332                         IXmlLineInfo li = reader as IXmlLineInfo;
333                         string lineInfo = reader.BaseURI;
334                         if (li != null)
335                                 lineInfo += String.Format (" line {0}, column {1}",
336                                         li.LineNumber, li.LinePosition);
337                         return new RelaxngException (message + lineInfo, prevState);
338                 }
339
340                 private void PrepareState ()
341                 {
342                         if (vState != null)
343                                 return;
344                         if (!pattern.IsCompiled) {
345                                 pattern.Compile ();
346                         }
347                         if (vState == null)
348                                 vState = pattern.StartPattern;
349                 }
350
351                 private string BuildLabels (bool elements)
352                 {
353                         StringBuilder sb = new StringBuilder ();
354                         ValidationState s = new ValidationState (prevState);
355                         ICollection col = elements ?
356                                 GetElementLabels (s) : GetAttributeLabels (s);
357                         foreach (XmlQualifiedName qname in col) {
358                                 sb.Append (qname.ToString ());
359                                 sb.Append (' ');
360                         }
361                         return sb.ToString ();
362                 }
363
364                 public override bool Read ()
365                 {
366                         PrepareState ();
367
368                         elementLabels.Clear ();
369                         attributeLabels.Clear ();
370
371                         bool ret = reader.Read ();
372
373                         // Process pending text node validation if required.
374                         if (cachedValue != null)
375                                 ValidateText (ret);
376                         else if (cachedValue == null &&
377                                 reader.NodeType == XmlNodeType.EndElement && 
378                                 startElementDepth == reader.Depth)
379                                 ValidateWeakMatch3 ();
380
381                         switch (reader.NodeType) {
382                         case XmlNodeType.Element:
383                                 inContent = true;
384                                 // StartTagOpenDeriv
385                                 prevState = vState;
386                                 vState = memo.StartTagOpenDeriv (vState,
387                                         reader.LocalName, reader.NamespaceURI);
388                                 if (vState.PatternType == RelaxngPatternType.NotAllowed)
389                                         throw CreateValidationError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true);
390
391                                 // AttsDeriv equals to for each AttDeriv
392                                 string elementNS = reader.NamespaceURI;
393                                 if (reader.MoveToFirstAttribute ()) {
394                                         do {
395                                                 if (reader.Name.IndexOf ("xmlns:") == 0 || reader.Name == "xmlns")
396                                                         continue;
397
398                                                 prevState = vState;
399                                                 string attrNS = reader.NamespaceURI;
400
401 #if false // old code
402
403                                                 vState = vState.AttDeriv (reader.LocalName, attrNS, reader.GetAttribute (reader.LocalName, attrNS), this);
404                                                 if (vState == RdpNotAllowed.Instance)
405                                                         throw CreateValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false);
406
407 #else
408
409                                                 prevState = vState;
410                                                 vState = memo.StartAttDeriv (vState, reader.LocalName, attrNS);
411                                                 if (vState == RdpNotAllowed.Instance)
412                                                         throw CreateValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false);
413                                                 prevState = vState;
414                                                 vState = memo.TextOnlyDeriv (vState);
415                                                 vState = TextDeriv (vState, reader.Value, reader);
416                                                 if (Util.IsWhitespace (reader.Value))
417                                                         vState = vState.Choice (prevState);
418                                                 vState = memo.EndAttDeriv (vState);
419                                                 if (vState == RdpNotAllowed.Instance)
420                                                         throw CreateValidationError (String.Format ("Invalid attribute value is found. Value = '{0}'", reader.Value), false);
421
422 #endif
423                                         } while (reader.MoveToNextAttribute ());
424                                         MoveToElement ();
425                                 }
426
427                                 // StarTagCloseDeriv
428                                 prevState = vState;
429                                 vState = memo.StartTagCloseDeriv (vState);
430                                 if (vState.PatternType == RelaxngPatternType.NotAllowed)
431                                         throw CreateValidationError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false);
432
433                                 // if it is empty, then redirect to EndElement
434                                 if (reader.IsEmptyElement) {
435                                         ValidateWeakMatch3 ();
436                                         goto case XmlNodeType.EndElement;
437                                 }
438                                 break;
439                         case XmlNodeType.EndElement:
440                                 if (reader.Depth == 0)
441                                         inContent = false;
442                                 // EndTagDeriv
443                                 prevState = vState;
444                                 vState = memo.EndTagDeriv (vState);
445                                 if (vState.PatternType == RelaxngPatternType.NotAllowed)
446                                         throw CreateValidationError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true);
447                                 break;
448                         case XmlNodeType.Whitespace:
449                                 if (inContent)
450                                         goto case XmlNodeType.Text;
451                                 break;
452                         case XmlNodeType.CDATA:
453                         case XmlNodeType.Text:
454                         case XmlNodeType.SignificantWhitespace:
455                                 // Whitespace cannot be skipped because data and
456                                 // value types are required to validate whitespaces.
457                                 cachedValue += Value;
458                                 break;
459                         }
460
461                         if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
462                                 startElementDepth = reader.Depth;
463                         else if (reader.NodeType == XmlNodeType.EndElement)
464                                 startElementDepth = -1;
465
466                         return ret;
467                 }
468
469                 RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
470                 {
471                         if (value.Length > 0 && p.IsTextValueDependent)
472                                 return memo.TextDeriv (p, value, context);
473                         else
474                                 return memo.EmptyTextDeriv (p);
475                 }
476
477                 void ValidateText (bool remain)
478                 {
479                         RdpPattern ts = vState;
480                         switch (reader.NodeType) {
481                         case XmlNodeType.EndElement:
482                                 if (startElementDepth != reader.Depth)
483                                         goto case XmlNodeType.Element;
484                                 ts = ValidateTextOnlyCore ();
485                                 break;
486                         case XmlNodeType.Element:
487                                 startElementDepth = -1;
488                                 if (!Util.IsWhitespace (cachedValue)) {
489                                         ts = memo.MixedTextDeriv (ts);
490                                         ts = TextDeriv (ts, cachedValue, reader);
491                                 }
492                                 break;
493                         default:
494                                 if (!remain)
495                                         goto case XmlNodeType.Element;
496                                 return;
497                         }
498
499                         prevState = vState;
500                         vState = ts;
501
502                         if (vState.PatternType == RelaxngPatternType.NotAllowed)
503                                 throw CreateValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true);
504                         cachedValue = null;
505                         return;
506                 }
507
508                 // section 6.2.7 weak match 3
509                 // childrenDeriv cx p [] = childrenDeriv cx p [(TextNode "")]
510                 void ValidateWeakMatch3 ()
511                 {
512                         cachedValue = String.Empty;
513                         RdpPattern ts = ValidateTextOnlyCore ();
514
515                         prevState = vState;
516                         vState = ts;
517
518                         if (vState.PatternType == RelaxngPatternType.NotAllowed)
519                                 throw CreateValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true);
520                         cachedValue = null;
521                         startElementDepth = -1;
522                 }
523
524                 RdpPattern ValidateTextOnlyCore ()
525                 {
526                         RdpPattern ts = memo.TextOnlyDeriv (vState);
527                         ts = TextDeriv (ts, cachedValue, reader);
528                         if (Util.IsWhitespace (cachedValue))
529                                 ts = vState.Choice (ts);
530                         return ts;
531                 }
532
533                 MemoizationStore memo = new MemoizationStore ();
534         }
535
536         #region Memoization support
537         internal class MemoizationStore
538         {
539                 Hashtable startOpen = new Hashtable ();
540                 Hashtable startClose = new Hashtable ();
541                 Hashtable startAtt = new Hashtable ();
542                 Hashtable endTag = new Hashtable ();
543                 Hashtable endAtt = new Hashtable ();
544                 Hashtable textOnly = new Hashtable ();
545                 Hashtable mixedText = new Hashtable ();
546                 Hashtable emptyText = new Hashtable ();
547                 Hashtable text = new Hashtable ();
548                 Hashtable text_value = new Hashtable ();
549                 Hashtable qnames = new Hashtable ();
550
551                 enum DerivativeType {
552                         StartTagOpen,
553                         StartAtt,
554                         StartTagClose,
555                         EndTag,
556                         EndAtt,
557                         Mixed,
558                         TextOnly
559                 }
560
561                 XmlQualifiedName GetQName (string local, string ns)
562                 {
563                         Hashtable nst = qnames [ns] as Hashtable;
564                         if (nst == null) {
565                                 nst = new Hashtable ();
566                                 qnames [ns] = nst;
567                         }
568                         XmlQualifiedName qn = nst [local] as XmlQualifiedName;
569                         if (qn == null) {
570                                 qn = new XmlQualifiedName (local, ns);
571                                 nst [local] = qn;
572                         }
573                         return qn;
574                 }
575
576                 public RdpPattern StartTagOpenDeriv (RdpPattern p, string local, string ns)
577                 {
578                         Hashtable h = startOpen [p] as Hashtable;
579                         if (h == null) {
580                                 h = new Hashtable ();
581                                 startOpen [p] = h;
582                         }
583                         XmlQualifiedName qn = GetQName (local, ns);
584                         RdpPattern m = h [qn] as RdpPattern;
585                         if (m == null) {
586                                 m = p.StartTagOpenDeriv (local, ns, this);
587                                 h [qn] = m;
588                         }
589                         return m;
590                 }
591
592                 public RdpPattern StartAttDeriv (RdpPattern p, string local, string ns)
593                 {
594                         Hashtable h = startAtt [p] as Hashtable;
595                         if (h == null) {
596                                 h = new Hashtable ();
597                                 startAtt [p] = h;
598                         }
599                         XmlQualifiedName qn = GetQName (local, ns);
600                         RdpPattern m = h [qn] as RdpPattern;
601                         if (m == null) {
602                                 m = p.StartAttDeriv (local, ns, this);
603                                 h [qn] = m;
604                         }
605                         return m;
606                 }
607
608                 public RdpPattern StartTagCloseDeriv (RdpPattern p)
609                 {
610                         RdpPattern m = startClose [p] as RdpPattern;
611                         if (m != null)
612                                 return m;
613
614                         m = p.StartTagCloseDeriv (this);
615                         startClose [p] = m;
616                         return m;
617                 }
618
619                 public RdpPattern EndTagDeriv (RdpPattern p)
620                 {
621                         RdpPattern m = endTag [p] as RdpPattern;
622                         if (m != null)
623                                 return m;
624
625                         m = p.EndTagDeriv (this);
626                         endTag [p] = m;
627                         return m;
628                 }
629
630                 public RdpPattern EndAttDeriv (RdpPattern p)
631                 {
632                         RdpPattern m = endAtt [p] as RdpPattern;
633                         if (m != null)
634                                 return m;
635
636                         m = p.EndAttDeriv (this);
637                         endAtt [p] = m;
638                         return m;
639                 }
640
641                 public RdpPattern MixedTextDeriv (RdpPattern p)
642                 {
643                         RdpPattern m = mixedText [p] as RdpPattern;
644                         if (m != null)
645                                 return m;
646
647                         m = p.MixedTextDeriv (this);
648                         mixedText [p] = m;
649                         return m;
650                 }
651
652                 public RdpPattern TextOnlyDeriv (RdpPattern p)
653                 {
654                         RdpPattern m = textOnly [p] as RdpPattern;
655                         if (m != null)
656                                 return m;
657
658                         m = p.TextOnlyDeriv (this);
659                         textOnly [p] = m;
660                         return m;
661                 }
662
663                 public RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
664                 {
665                         if (p.IsContextDependent)
666                                 return p.TextDeriv (value, context);
667
668                         if (Object.ReferenceEquals (text_value [p], value))
669                                 return text [p] as RdpPattern;
670                         RdpPattern m = p.TextDeriv (value, context, this);
671                         text_value [p] = value;
672                         text [p] = m;
673                         return m;
674                 }
675
676                 public RdpPattern EmptyTextDeriv (RdpPattern p)
677                 {
678                         RdpPattern m = emptyText [p] as RdpPattern;
679                         if (m != null)
680                                 return m;
681
682                         m = p.EmptyTextDeriv (this);
683                         emptyText [p] = m;
684                         return m;
685                 }
686         }
687         #endregion
688 }
689