Merge pull request #439 from mono-soc-2012/garyb/iconfix
[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 //      Alexandre Alapetite <http://alexandre.alapetite.fr/cv/>
7 //
8 // 2003 Atsushi Enomoto. "No rights reserved."
9 //
10 // Copyright (c) 2004 Novell Inc.
11 // All rights reserved
12 //
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34 using System;
35 using System.Collections;
36 using System.Text;
37 using System.Xml;
38 using Commons.Xml.Relaxng.Derivative;
39
40 namespace Commons.Xml.Relaxng
41 {
42         public class RelaxngValidatingReader : XmlDefaultReader
43         {
44                 public RelaxngValidatingReader (XmlReader reader)
45                         : this (reader, (RelaxngPattern) null)
46                 {
47                 }
48
49                 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)
50                         : this (reader, grammarXml, null)
51                 {
52                 }
53
54                 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)
55                         : this (reader, RelaxngGrammar.Read (grammarXml, provider))
56                 {
57                 }
58
59                 public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)
60                         : base (reader)
61                 {
62                         if (pattern == null)
63                                 throw new ArgumentNullException ("pattern");
64
65                         if (reader.NodeType == XmlNodeType.Attribute)
66                                 throw new RelaxngException ("RELAX NG does not support standalone attribute validation (it is prohibited due to the specification section 7.1.5");
67                         this.reader = reader;
68                         this.pattern = pattern;
69                 }
70
71                 XmlReader reader;
72                 RelaxngPattern pattern;
73                 RdpPattern vState;
74                 RdpPattern prevState;   // Mainly for debugging.
75                 bool roughLabelCheck;
76                 ArrayList strictCheckCache;
77                 bool reportDetails;
78                 string cachedValue;
79                 int startElementDepth = -1;
80                 bool inContent;
81                 bool firstRead = true;
82
83                 public delegate bool RelaxngValidationEventHandler (XmlReader source, string message);
84
85                 public static readonly RelaxngValidationEventHandler IgnoreError = delegate { return true; };
86
87                 public event RelaxngValidationEventHandler InvalidNodeFound;
88
89                 delegate RdpPattern RecoveryHandler (RdpPattern source);
90
91                 RdpPattern HandleError (string error, bool elements, RdpPattern source, RecoveryHandler recover)
92                 {
93                         if (InvalidNodeFound != null && InvalidNodeFound (this, error))
94                                 return recover (source);
95                         else
96                                 throw CreateValidationError (error, true);
97                 }
98
99                 internal string CurrentStateXml {
100                         get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
101                 }
102
103                 internal string PreviousStateXml {
104                         get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
105                 }
106
107                 #region Validation State support
108
109                 public bool ReportDetails {
110                         get { return reportDetails; }
111                         set { reportDetails = value; }
112                 }
113
114                 public bool RoughLabelCheck {
115                         get { return roughLabelCheck; }
116                         set { roughLabelCheck = value; }
117                 }
118
119                 // It is used to disclose its validation feature to public
120                 class ValidationState
121                 {
122                         RdpPattern state;
123
124                         internal ValidationState (RdpPattern startState)
125                         {
126                                 this.state = startState;
127                         }
128
129                         public RdpPattern Pattern {
130                                 get { return state; }
131                         }
132
133                         public ValidationState AfterOpenStartTag (
134                                 string localName, string ns)
135                         {
136                                 RdpPattern p = state.StartTagOpenDeriv (
137                                         localName, ns);
138                                 return p is RdpNotAllowed ?
139                                         null : new ValidationState (p);
140                         }
141
142                         public bool OpenStartTag (string localName, string ns)
143                         {
144                                 RdpPattern p = state.StartTagOpenDeriv (
145                                         localName, ns);
146                                 if (p is RdpNotAllowed)
147                                         return false;
148                                 state = p;
149                                 return true;
150                         }
151
152                         public ValidationState AfterCloseStartTag ()
153                         {
154                                 RdpPattern p = state.StartTagCloseDeriv ();
155                                 return p is RdpNotAllowed ?
156                                         null : new ValidationState (p);
157                         }
158
159                         public bool CloseStartTag ()
160                         {
161                                 RdpPattern p = state.StartTagCloseDeriv ();
162                                 if (p is RdpNotAllowed)
163                                         return false;
164                                 state = p;
165                                 return true;
166                         }
167
168                         public ValidationState AfterEndTag ()
169                         {
170                                 RdpPattern p = state.EndTagDeriv ();
171                                 if (p is RdpNotAllowed)
172                                         return null;
173                                 return new ValidationState (p);
174                         }
175
176                         public bool EndTag ()
177                         {
178                                 RdpPattern p = state.EndTagDeriv ();
179                                 if (p is RdpNotAllowed)
180                                         return false;
181                                 state = p;
182                                 return true;
183                         }
184
185                         public ValidationState AfterAttribute (
186                                 string localName, string ns, XmlReader reader)
187                         {
188                                 RdpPattern p = state.AttDeriv (
189                                         localName, ns, null, reader);
190                                 if (p is RdpNotAllowed)
191                                         return null;
192                                 return new ValidationState (p);
193                         }
194
195                         public bool Attribute (
196                                 string localName, string ns, XmlReader reader)
197                         {
198                                 RdpPattern p = state.AttDeriv (
199                                         localName, ns, null, reader);
200                                 if (p is RdpNotAllowed)
201                                         return false;
202                                 state = p;
203                                 return true;
204                         }
205                 }
206
207                 public object GetCurrentState ()
208                 {
209                         PrepareState ();
210                         return new ValidationState (vState);
211                 }
212
213                 private ValidationState ToState (object stateObject)
214                 {
215                         if (stateObject == null)
216                                 throw new ArgumentNullException ("stateObject");
217                         ValidationState state = stateObject as ValidationState;
218                         if (state == null)
219                                 throw new ArgumentException ("Argument stateObject is not of expected type.");
220                         return state;
221                 }
222
223                 public object AfterOpenStartTag (object stateObject,
224                         string localName, string ns)
225                 {
226                         ValidationState state = ToState (stateObject);
227                         return state.AfterOpenStartTag (localName, ns);
228                 }
229
230                 public bool OpenStartTag (object stateObject,
231                         string localName, string ns)
232                 {
233                         ValidationState state = ToState (stateObject);
234                         return state.OpenStartTag (localName, ns);
235                 }
236
237                 public object AfterAttribute (object stateObject,
238                         string localName, string ns)
239                 {
240                         ValidationState state = ToState (stateObject);
241                         return state.AfterAttribute (localName, ns, this);
242                 }
243
244                 public bool Attribute (object stateObject,
245                         string localName, string ns)
246                 {
247                         ValidationState state = ToState (stateObject);
248                         return state.Attribute (localName, ns, this);
249                 }
250
251                 public object AfterCloseStartTag (object stateObject)
252                 {
253                         ValidationState state = ToState (stateObject);
254                         return state.AfterCloseStartTag ();
255                 }
256
257                 public bool CloseStartTag (object stateObject)
258                 {
259                         ValidationState state = ToState (stateObject);
260                         return state.CloseStartTag ();
261                 }
262
263                 public object AfterEndTag (object stateObject)
264                 {
265                         ValidationState state = ToState (stateObject);
266                         return state.AfterEndTag ();
267                 }
268
269                 public bool EndTag (object stateObject)
270                 {
271                         ValidationState state = ToState (stateObject);
272                         return state.EndTag ();
273                 }
274
275                 public ICollection GetElementLabels (object stateObject)
276                 {
277                         ValidationState state = ToState (stateObject);
278                         RdpPattern p = state.Pattern;
279                         Hashtable elements = new Hashtable ();
280                         Hashtable attributes = new Hashtable ();
281                         p.GetLabels (elements, attributes);
282
283                         if (roughLabelCheck)
284                                 return elements.Values;
285
286                         // Strict check that tries actual validation that will
287                         // cover rejection by notAllowed.
288                         if (strictCheckCache == null)
289                                 strictCheckCache = new ArrayList ();
290                         else
291                                 strictCheckCache.Clear ();
292                         foreach (XmlQualifiedName qname in elements.Values)
293                                 if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)
294                                         strictCheckCache.Add (qname);
295                         foreach (XmlQualifiedName qname in strictCheckCache)
296                                 elements.Remove (qname);
297                         strictCheckCache.Clear ();
298
299                         return elements.Values;
300                 }
301
302                 public ICollection GetAttributeLabels (object stateObject)
303                 {
304                         ValidationState state = ToState (stateObject);
305                         RdpPattern p = state.Pattern;
306                         Hashtable elements = new Hashtable ();
307                         Hashtable attributes = new Hashtable ();
308                         p.GetLabels (elements, attributes);
309
310                         if (roughLabelCheck)
311                                 return attributes.Values;
312
313                         // Strict check that tries actual validation that will
314                         // cover rejection by notAllowed.
315                         if (strictCheckCache == null)
316                                 strictCheckCache = new ArrayList ();
317                         else
318                                 strictCheckCache.Clear ();
319                         foreach (XmlQualifiedName qname in attributes.Values)
320                                 if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)
321                                         strictCheckCache.Add (qname);
322                         foreach (XmlQualifiedName qname in strictCheckCache)
323                                 attributes.Remove (qname);
324                         strictCheckCache.Clear ();
325
326                         return attributes.Values;
327                 }
328
329                 public bool Emptiable (object stateObject)
330                 {
331                         ValidationState state = ToState (stateObject);
332                         RdpPattern p = state.Pattern;
333                         return !(p.EndTagDeriv () is RdpNotAllowed);
334                 }
335                 #endregion
336
337                 private RelaxngException CreateValidationError (string message,
338                         bool elements)
339                 {
340                         if (ReportDetails)
341                                 return CreateValidationError (String.Concat (message,
342                                         " Expected ",
343                                         elements ? "elements are: " : "attributes are: ",
344                                         BuildLabels (elements),
345                                         "."));
346                         return CreateValidationError (message);
347                 }
348
349                 private RelaxngException CreateValidationError (string message)
350                 {
351                         IXmlLineInfo li = reader as IXmlLineInfo;
352                         string lineInfo = reader.BaseURI;
353                         if (li != null)
354                                 lineInfo += String.Format (" line {0}, column {1}",
355                                         li.LineNumber, li.LinePosition);
356                         return new RelaxngException (message + lineInfo, prevState);
357                 }
358
359                 private void PrepareState ()
360                 {
361                         if (vState != null)
362                                 return;
363                         if (!pattern.IsCompiled) {
364                                 pattern.Compile ();
365                         }
366                         if (vState == null)
367                                 vState = pattern.StartPattern;
368                 }
369
370                 private string BuildLabels (bool elements)
371                 {
372                         StringBuilder sb = new StringBuilder ();
373                         ValidationState s = new ValidationState (prevState);
374                         ICollection col = elements ?
375                                 GetElementLabels (s) : GetAttributeLabels (s);
376                         foreach (XmlQualifiedName qname in col) {
377                                 sb.Append (qname.ToString ());
378                                 sb.Append (' ');
379                         }
380                         return sb.ToString ();
381                 }
382
383                 public override bool Read ()
384                 {
385                         PrepareState ();
386
387                         // If the input XmlReader is already positioned on
388                         // the first node to validate, skip Read() here
389                         // (idea by Alex).
390                         bool ret;
391                         if (firstRead) {
392                                 firstRead = false;
393                                 if (reader.ReadState == ReadState.Initial)
394                                         ret = reader.Read ();
395                                 else
396                                         ret = !((reader.ReadState == ReadState.Closed) || (reader.ReadState == ReadState.EndOfFile));
397                         }
398                         else
399                                 ret = reader.Read ();
400
401                         // Process pending text node validation if required.
402                         if (cachedValue != null)
403                                 ValidateText (ret);
404                         else if (cachedValue == null &&
405                                 reader.NodeType == XmlNodeType.EndElement && 
406                                 startElementDepth == reader.Depth)
407                                 ValidateWeakMatch3 ();
408
409                         switch (reader.NodeType) {
410                         case XmlNodeType.Element:
411                                 inContent = true;
412                                 // StartTagOpenDeriv
413                                 prevState = vState;
414                                 vState = memo.StartTagOpenDeriv (vState,
415                                         reader.LocalName, reader.NamespaceURI);
416                                 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
417                                         if (InvalidNodeFound != null)
418                                                 vState = HandleError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true, prevState, RecoverFromInvalidStartTag);
419                                 }
420
421                                 // AttsDeriv equals to for each AttDeriv
422                                 string elementNS = reader.NamespaceURI;
423                                 if (reader.MoveToFirstAttribute ()) {
424                                         do {
425                                                 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
426                                                         continue;
427
428                                                 RdpPattern savedState = vState;
429                                                 prevState = vState;
430                                                 string attrNS = reader.NamespaceURI;
431
432                                                 vState = memo.StartAttDeriv (vState, reader.LocalName, attrNS);
433                                                 if (vState == RdpNotAllowed.Instance) {
434                                                         vState = HandleError (String.Format ("Invalid attribute occurence found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false, savedState, p => p);
435                                                         continue; // the following steps are ignored.
436                                                 }
437                                                 prevState = vState;
438                                                 vState = memo.TextOnlyDeriv (vState);
439                                                 vState = TextDeriv (vState, reader.Value, reader);
440                                                 if (Util.IsWhitespace (reader.Value))
441                                                         vState = vState.Choice (prevState);
442                                                 if (vState == RdpNotAllowed.Instance)
443                                                         vState = HandleError (String.Format ("Invalid attribute value is found. Value = '{0}'", reader.Value), false, prevState, RecoverFromInvalidText);
444                                                 prevState = vState;
445                                                 vState = memo.EndAttDeriv (vState);
446                                                 if (vState == RdpNotAllowed.Instance)
447                                                         vState = HandleError (String.Format ("Invalid attribute value is found. Value = '{0}'", reader.Value), false, prevState, RecoverFromInvalidEnd);
448                                         } while (reader.MoveToNextAttribute ());
449                                         MoveToElement ();
450                                 }
451
452                                 // StarTagCloseDeriv
453                                 prevState = vState;
454                                 vState = memo.StartTagCloseDeriv (vState);
455                                 if (vState.PatternType == RelaxngPatternType.NotAllowed)
456                                         vState = HandleError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false, prevState, RecoverFromInvalidStartTagClose);
457
458                                 // if it is empty, then redirect to EndElement
459                                 if (reader.IsEmptyElement) {
460                                         ValidateWeakMatch3 ();
461                                         goto case XmlNodeType.EndElement;
462                                 }
463                                 break;
464                         case XmlNodeType.EndElement:
465                                 if (reader.Depth == 0)
466                                         inContent = false;
467                                 // EndTagDeriv
468                                 prevState = vState;
469                                 vState = memo.EndTagDeriv (vState);
470                                 if (vState.PatternType == RelaxngPatternType.NotAllowed)
471                                         vState = HandleError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true, prevState, RecoverFromInvalidEnd);
472                                 break;
473                         case XmlNodeType.Whitespace:
474                                 if (inContent)
475                                         goto case XmlNodeType.Text;
476                                 break;
477                         case XmlNodeType.CDATA:
478                         case XmlNodeType.Text:
479                         case XmlNodeType.SignificantWhitespace:
480                                 // Whitespace cannot be skipped because data and
481                                 // value types are required to validate whitespaces.
482                                 cachedValue += Value;
483                                 break;
484                         }
485
486                         if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
487                                 startElementDepth = reader.Depth;
488                         else if (reader.NodeType == XmlNodeType.EndElement)
489                                 startElementDepth = -1;
490
491                         return ret;
492                 }
493
494                 #region error recovery
495                 // Error recovery feature can be enabled by using
496                 // InvalidNodeFound event of type RelaxngValidationEventHandler.
497                 // 
498                 // Other than startTagOpenDeriv, it is (again) based on
499                 // James Clark's derivative algorithm.
500                 // http://www.thaiopensource.com/relaxng/derivative.html
501                 // For invalid start tag, we just recover from it by using
502                 // xs:any-like pattern for unexpected node occurence.
503
504                 RdpPattern MakeGroupHeadOptional (RdpPattern p)
505                 {
506                         if (p is RdpAbstractSingleContent)
507                                 return new RdpChoice (RdpEmpty.Instance, p);
508                         RdpAbstractBinary ab = p as RdpAbstractBinary;
509                         if (ab == null)
510                                 return p;
511                         if (ab is RdpGroup)
512                                 return new RdpGroup (new RdpChoice (RdpEmpty.Instance, ab.LValue), ab.RValue);
513                         else if (ab is RdpChoice)
514                                 return new RdpChoice (MakeGroupHeadOptional (ab.LValue), MakeGroupHeadOptional (ab.RValue));
515                         else if (ab is RdpInterleave)
516                                 return new RdpInterleave (MakeGroupHeadOptional (ab.LValue), MakeGroupHeadOptional (ab.RValue));
517                         else if (ab is RdpAfter) // FIXME: is it correct?
518                                 return new RdpAfter (MakeGroupHeadOptional (ab.LValue), MakeGroupHeadOptional (ab.RValue));
519                         throw new SystemException ("INTERNAL ERROR: unexpected pattern: " + p.GetType ());
520                 }
521
522                 RdpPattern ReplaceAfterHeadWithEmpty (RdpPattern p)
523                 {
524                         if (p is RdpAbstractSingleContent)
525                                 return new RdpChoice (RdpEmpty.Instance, p);
526                         RdpAbstractBinary ab = p as RdpAbstractBinary;
527                         if (ab == null)
528                                 return p;
529                         if (ab is RdpGroup)
530                                 return new RdpGroup (ReplaceAfterHeadWithEmpty (ab.LValue), ReplaceAfterHeadWithEmpty (ab.RValue));
531                         else if (ab is RdpChoice)
532                                 return new RdpChoice (ReplaceAfterHeadWithEmpty (ab.LValue), ReplaceAfterHeadWithEmpty (ab.RValue));
533                         else if (ab is RdpInterleave)
534                                 return new RdpInterleave (ReplaceAfterHeadWithEmpty (ab.LValue), ReplaceAfterHeadWithEmpty (ab.RValue));
535                         else if (ab is RdpAfter)
536                                 return new RdpAfter (RdpEmpty.Instance, ab.RValue);
537                         throw new SystemException ("INTERNAL ERROR: unexpected pattern: " + p.GetType ());
538                 }
539
540                 RdpPattern CollectAfterTailAsChoice (RdpPattern p)
541                 {
542                         RdpAbstractBinary ab = p as RdpAbstractBinary;
543                         if (ab == null)
544                                 return RdpEmpty.Instance;
545                         if (ab is RdpAfter)
546                                 return ab.RValue;
547                         RdpPattern l = CollectAfterTailAsChoice (ab.LValue);
548                         if (l == RdpEmpty.Instance)
549                                 return CollectAfterTailAsChoice (ab.RValue);
550                         RdpPattern r = CollectAfterTailAsChoice (ab.RValue);
551                         if (r == RdpEmpty.Instance)
552                                 return l;
553                         return new RdpChoice (l, r);
554                 }
555
556                 RdpPattern ReplaceAttributesWithEmpty (RdpPattern p)
557                 {
558                         if (p is RdpAttribute)
559                                 return RdpEmpty.Instance;
560
561                         RdpAbstractSingleContent asc = p as RdpAbstractSingleContent;
562                         if (asc is RdpList)
563                                 return new RdpList (ReplaceAttributesWithEmpty (asc.Child));
564                         if (asc is RdpOneOrMore)
565                                 return new RdpOneOrMore (ReplaceAttributesWithEmpty (asc.Child));
566                         else if (asc is RdpElement)
567                                 return asc; // should not be expected to contain any attribute as RdpElement.
568
569                         RdpAbstractBinary ab = p as RdpAbstractBinary;
570                         if (ab == null)
571                                 return p;
572                         if (ab is RdpGroup)
573                                 return new RdpGroup (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
574                         else if (ab is RdpChoice)
575                                 return new RdpChoice (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
576                         else if (ab is RdpInterleave)
577                                 return new RdpInterleave (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
578                         else if (ab is RdpAfter) // FIXME: is it correct?
579                                 return new RdpAfter (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
580                         throw new SystemException ("INTERNAL ERROR: unexpected pattern: " + p.GetType ());
581                 }
582
583                 RdpPattern RecoverFromInvalidStartTag (RdpPattern p)
584                 {
585                         RdpPattern test1 = MakeGroupHeadOptional (p);
586                         test1 = memo.StartTagOpenDeriv (test1, reader.LocalName, reader.NamespaceURI);
587                         if (test1 != null)
588                                 return test1;
589                         // FIXME: JJC derivative algorithm suggests more complicated recovery. We simply treats current "extra" node as "anything".
590                         return new RdpChoice (RdpPattern.Anything, p);
591                 }
592
593                 RdpPattern RecoverFromInvalidText (RdpPattern p)
594                 {
595                         return ReplaceAfterHeadWithEmpty (p);
596                 }
597
598                 RdpPattern RecoverFromInvalidEnd (RdpPattern p)
599                 {
600                         return CollectAfterTailAsChoice (p);
601                 }
602
603                 RdpPattern RecoverFromInvalidStartTagClose (RdpPattern p)
604                 {
605                         return ReplaceAttributesWithEmpty (p);
606                 }
607
608                 #endregion
609
610                 RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
611                 {
612                         if (value.Length > 0 && p.IsTextValueDependent)
613                                 return memo.TextDeriv (p, value, context);
614                         else
615                                 return memo.EmptyTextDeriv (p);
616                 }
617
618                 void ValidateText (bool remain)
619                 {
620                         RdpPattern ts = vState;
621                         switch (reader.NodeType) {
622                         case XmlNodeType.EndElement:
623                                 if (startElementDepth != reader.Depth)
624                                         goto case XmlNodeType.Element;
625                                 ts = ValidateTextOnlyCore ();
626                                 break;
627                         case XmlNodeType.Element:
628                                 startElementDepth = -1;
629                                 if (!Util.IsWhitespace (cachedValue)) {
630                                         // HandleError() is not really useful here since it involves else condition...
631                                         ts = memo.MixedTextDeriv (ts);
632                                         /*if (InvalidNodeFound != null) {
633                                                 InvalidNodeFound (reader, "Not allowed text node was found.");
634                                                 ts = vState;
635                                                 cachedValue = null;
636                                         }
637                                         else*/
638                                                 ts = TextDeriv (ts, cachedValue, reader);
639                                 }
640                                 break;
641                         default:
642                                 if (!remain)
643                                         goto case XmlNodeType.Element;
644                                 return;
645                         }
646
647                         prevState = vState;
648                         vState = ts;
649
650                         if (vState.PatternType == RelaxngPatternType.NotAllowed)
651                                 vState = HandleError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true, prevState, RecoverFromInvalidText);
652                         cachedValue = null;
653                         return;
654                 }
655
656                 // section 6.2.7 weak match 3
657                 // childrenDeriv cx p [] = childrenDeriv cx p [(TextNode "")]
658                 void ValidateWeakMatch3 ()
659                 {
660                         cachedValue = String.Empty;
661                         RdpPattern ts = ValidateTextOnlyCore ();
662
663                         prevState = vState;
664                         vState = ts;
665
666                         if (vState.PatternType == RelaxngPatternType.NotAllowed)
667                                 vState = HandleError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true, prevState, RecoverFromInvalidText);
668                         cachedValue = null;
669                         startElementDepth = -1;
670                 }
671
672                 RdpPattern ValidateTextOnlyCore ()
673                 {
674                         RdpPattern ts = memo.TextOnlyDeriv (vState);
675                         ts = TextDeriv (ts, cachedValue, reader);
676                         if (Util.IsWhitespace (cachedValue))
677                                 ts = vState.Choice (ts);
678                         return ts;
679                 }
680
681                 MemoizationStore memo = new MemoizationStore ();
682         }
683
684         #region Memoization support
685         internal class MemoizationStore
686         {
687                 Hashtable startOpen = new Hashtable ();
688                 Hashtable startClose = new Hashtable ();
689                 Hashtable startAtt = new Hashtable ();
690                 Hashtable endTag = new Hashtable ();
691                 Hashtable endAtt = new Hashtable ();
692                 Hashtable textOnly = new Hashtable ();
693                 Hashtable mixedText = new Hashtable ();
694                 Hashtable emptyText = new Hashtable ();
695                 Hashtable text = new Hashtable ();
696                 Hashtable text_value = new Hashtable ();
697                 Hashtable qnames = new Hashtable ();
698
699                 enum DerivativeType {
700                         StartTagOpen,
701                         StartAtt,
702                         StartTagClose,
703                         EndTag,
704                         EndAtt,
705                         Mixed,
706                         TextOnly
707                 }
708
709                 XmlQualifiedName GetQName (string local, string ns)
710                 {
711                         Hashtable nst = qnames [ns] as Hashtable;
712                         if (nst == null) {
713                                 nst = new Hashtable ();
714                                 qnames [ns] = nst;
715                         }
716                         XmlQualifiedName qn = nst [local] as XmlQualifiedName;
717                         if (qn == null) {
718                                 qn = new XmlQualifiedName (local, ns);
719                                 nst [local] = qn;
720                         }
721                         return qn;
722                 }
723
724                 public RdpPattern StartTagOpenDeriv (RdpPattern p, string local, string ns)
725                 {
726                         Hashtable h = startOpen [p] as Hashtable;
727                         if (h == null) {
728                                 h = new Hashtable ();
729                                 startOpen [p] = h;
730                         }
731                         XmlQualifiedName qn = GetQName (local, ns);
732                         RdpPattern m = h [qn] as RdpPattern;
733                         if (m == null) {
734                                 m = p.StartTagOpenDeriv (local, ns, this);
735                                 h [qn] = m;
736                         }
737                         return m;
738                 }
739
740                 public RdpPattern StartAttDeriv (RdpPattern p, string local, string ns)
741                 {
742                         Hashtable h = startAtt [p] as Hashtable;
743                         if (h == null) {
744                                 h = new Hashtable ();
745                                 startAtt [p] = h;
746                         }
747                         XmlQualifiedName qn = GetQName (local, ns);
748                         RdpPattern m = h [qn] as RdpPattern;
749                         if (m == null) {
750                                 m = p.StartAttDeriv (local, ns, this);
751                                 h [qn] = m;
752                         }
753                         return m;
754                 }
755
756                 public RdpPattern StartTagCloseDeriv (RdpPattern p)
757                 {
758                         RdpPattern m = startClose [p] as RdpPattern;
759                         if (m != null)
760                                 return m;
761
762                         m = p.StartTagCloseDeriv (this);
763                         startClose [p] = m;
764                         return m;
765                 }
766
767                 public RdpPattern EndTagDeriv (RdpPattern p)
768                 {
769                         RdpPattern m = endTag [p] as RdpPattern;
770                         if (m != null)
771                                 return m;
772
773                         m = p.EndTagDeriv (this);
774                         endTag [p] = m;
775                         return m;
776                 }
777
778                 public RdpPattern EndAttDeriv (RdpPattern p)
779                 {
780                         RdpPattern m = endAtt [p] as RdpPattern;
781                         if (m != null)
782                                 return m;
783
784                         m = p.EndAttDeriv (this);
785                         endAtt [p] = m;
786                         return m;
787                 }
788
789                 public RdpPattern MixedTextDeriv (RdpPattern p)
790                 {
791                         RdpPattern m = mixedText [p] as RdpPattern;
792                         if (m != null)
793                                 return m;
794
795                         m = p.MixedTextDeriv (this);
796                         mixedText [p] = m;
797                         return m;
798                 }
799
800                 public RdpPattern TextOnlyDeriv (RdpPattern p)
801                 {
802                         RdpPattern m = textOnly [p] as RdpPattern;
803                         if (m != null)
804                                 return m;
805
806                         m = p.TextOnlyDeriv (this);
807                         textOnly [p] = m;
808                         return m;
809                 }
810
811                 public RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
812                 {
813                         if (p.IsContextDependent)
814                                 return p.TextDeriv (value, context);
815
816                         if (Object.ReferenceEquals (text_value [p], value))
817                                 return text [p] as RdpPattern;
818                         RdpPattern m = p.TextDeriv (value, context, this);
819                         text_value [p] = value;
820                         text [p] = m;
821                         return m;
822                 }
823
824                 public RdpPattern EmptyTextDeriv (RdpPattern p)
825                 {
826                         RdpPattern m = emptyText [p] as RdpPattern;
827                         if (m != null)
828                                 return m;
829
830                         m = p.EmptyTextDeriv (this);
831                         emptyText [p] = m;
832                         return m;
833                 }
834         }
835         #endregion
836 }
837