2 // Commons.Xml.Relaxng.RelaxngValidatingReader
5 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
6 // Alexandre Alapetite <http://alexandre.alapetite.net/cv/>
8 // 2003 Atsushi Enomoto. "No rights reserved."
10 // Copyright (c) 2004 Novell Inc.
11 // All rights reserved
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:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
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.
35 using System.Collections;
38 using Commons.Xml.Relaxng.Derivative;
40 namespace Commons.Xml.Relaxng
42 public class RelaxngValidatingReader : XmlDefaultReader
44 public RelaxngValidatingReader (XmlReader reader)
45 : this (reader, (RelaxngPattern) null)
49 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)
50 : this (reader, grammarXml, null)
54 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)
55 : this (reader, RelaxngGrammar.Read (grammarXml, provider))
59 public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)
63 throw new ArgumentNullException ("pattern");
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");
68 this.pattern = pattern;
72 RelaxngPattern pattern;
74 RdpPattern prevState; // Mainly for debugging.
76 ArrayList strictCheckCache;
79 int startElementDepth = -1;
81 bool firstRead = true;
83 internal string CurrentStateXml {
84 get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
87 internal string PreviousStateXml {
88 get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
91 #region Validation State support
93 public bool ReportDetails {
94 get { return reportDetails; }
95 set { reportDetails = value; }
98 public bool RoughLabelCheck {
99 get { return roughLabelCheck; }
100 set { roughLabelCheck = value; }
103 // It is used to disclose its validation feature to public
104 class ValidationState
108 internal ValidationState (RdpPattern startState)
110 this.state = startState;
113 public RdpPattern Pattern {
114 get { return state; }
117 public ValidationState AfterOpenStartTag (
118 string localName, string ns)
120 RdpPattern p = state.StartTagOpenDeriv (
122 return p is RdpNotAllowed ?
123 null : new ValidationState (p);
126 public bool OpenStartTag (string localName, string ns)
128 RdpPattern p = state.StartTagOpenDeriv (
130 if (p is RdpNotAllowed)
136 public ValidationState AfterCloseStartTag ()
138 RdpPattern p = state.StartTagCloseDeriv ();
139 return p is RdpNotAllowed ?
140 null : new ValidationState (p);
143 public bool CloseStartTag ()
145 RdpPattern p = state.StartTagCloseDeriv ();
146 if (p is RdpNotAllowed)
152 public ValidationState AfterEndTag ()
154 RdpPattern p = state.EndTagDeriv ();
155 if (p is RdpNotAllowed)
157 return new ValidationState (p);
160 public bool EndTag ()
162 RdpPattern p = state.EndTagDeriv ();
163 if (p is RdpNotAllowed)
169 public ValidationState AfterAttribute (
170 string localName, string ns, XmlReader reader)
172 RdpPattern p = state.AttDeriv (
173 localName, ns, null, reader);
174 if (p is RdpNotAllowed)
176 return new ValidationState (p);
179 public bool Attribute (
180 string localName, string ns, XmlReader reader)
182 RdpPattern p = state.AttDeriv (
183 localName, ns, null, reader);
184 if (p is RdpNotAllowed)
191 public object GetCurrentState ()
194 return new ValidationState (vState);
197 private ValidationState ToState (object stateObject)
199 if (stateObject == null)
200 throw new ArgumentNullException ("stateObject");
201 ValidationState state = stateObject as ValidationState;
203 throw new ArgumentException ("Argument stateObject is not of expected type.");
207 public object AfterOpenStartTag (object stateObject,
208 string localName, string ns)
210 ValidationState state = ToState (stateObject);
211 return state.AfterOpenStartTag (localName, ns);
214 public bool OpenStartTag (object stateObject,
215 string localName, string ns)
217 ValidationState state = ToState (stateObject);
218 return state.OpenStartTag (localName, ns);
221 public object AfterAttribute (object stateObject,
222 string localName, string ns)
224 ValidationState state = ToState (stateObject);
225 return state.AfterAttribute (localName, ns, this);
228 public bool Attribute (object stateObject,
229 string localName, string ns)
231 ValidationState state = ToState (stateObject);
232 return state.Attribute (localName, ns, this);
235 public object AfterCloseStartTag (object stateObject)
237 ValidationState state = ToState (stateObject);
238 return state.AfterCloseStartTag ();
241 public bool CloseStartTag (object stateObject)
243 ValidationState state = ToState (stateObject);
244 return state.CloseStartTag ();
247 public object AfterEndTag (object stateObject)
249 ValidationState state = ToState (stateObject);
250 return state.AfterEndTag ();
253 public bool EndTag (object stateObject)
255 ValidationState state = ToState (stateObject);
256 return state.EndTag ();
259 public ICollection GetElementLabels (object stateObject)
261 ValidationState state = ToState (stateObject);
262 RdpPattern p = state.Pattern;
263 Hashtable elements = new Hashtable ();
264 Hashtable attributes = new Hashtable ();
265 p.GetLabels (elements, attributes);
268 return elements.Values;
270 // Strict check that tries actual validation that will
271 // cover rejection by notAllowed.
272 if (strictCheckCache == null)
273 strictCheckCache = new ArrayList ();
275 strictCheckCache.Clear ();
276 foreach (XmlQualifiedName qname in elements.Values)
277 if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)
278 strictCheckCache.Add (qname);
279 foreach (XmlQualifiedName qname in strictCheckCache)
280 elements.Remove (qname);
281 strictCheckCache.Clear ();
283 return elements.Values;
286 public ICollection GetAttributeLabels (object stateObject)
288 ValidationState state = ToState (stateObject);
289 RdpPattern p = state.Pattern;
290 Hashtable elements = new Hashtable ();
291 Hashtable attributes = new Hashtable ();
292 p.GetLabels (elements, attributes);
295 return attributes.Values;
297 // Strict check that tries actual validation that will
298 // cover rejection by notAllowed.
299 if (strictCheckCache == null)
300 strictCheckCache = new ArrayList ();
302 strictCheckCache.Clear ();
303 foreach (XmlQualifiedName qname in attributes.Values)
304 if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)
305 strictCheckCache.Add (qname);
306 foreach (XmlQualifiedName qname in strictCheckCache)
307 attributes.Remove (qname);
308 strictCheckCache.Clear ();
310 return attributes.Values;
313 public bool Emptiable (object stateObject)
315 ValidationState state = ToState (stateObject);
316 RdpPattern p = state.Pattern;
317 return !(p.EndTagDeriv () is RdpNotAllowed);
321 private RelaxngException CreateValidationError (string message,
325 return CreateValidationError (String.Concat (message,
327 elements ? "elements are: " : "attributes are: ",
328 BuildLabels (elements),
330 return CreateValidationError (message);
333 private RelaxngException CreateValidationError (string message)
335 IXmlLineInfo li = reader as IXmlLineInfo;
336 string lineInfo = reader.BaseURI;
338 lineInfo += String.Format (" line {0}, column {1}",
339 li.LineNumber, li.LinePosition);
340 return new RelaxngException (message + lineInfo, prevState);
343 private void PrepareState ()
347 if (!pattern.IsCompiled) {
351 vState = pattern.StartPattern;
354 private string BuildLabels (bool elements)
356 StringBuilder sb = new StringBuilder ();
357 ValidationState s = new ValidationState (prevState);
358 ICollection col = elements ?
359 GetElementLabels (s) : GetAttributeLabels (s);
360 foreach (XmlQualifiedName qname in col) {
361 sb.Append (qname.ToString ());
364 return sb.ToString ();
367 public override bool Read ()
371 // If the input XmlReader is already positioned on
372 // the first node to validate, skip Read() here
377 if (reader.ReadState == ReadState.Initial)
378 ret = reader.Read ();
380 ret = !((reader.ReadState == ReadState.Closed) || (reader.ReadState == ReadState.EndOfFile));
383 ret = reader.Read ();
385 // Process pending text node validation if required.
386 if (cachedValue != null)
388 else if (cachedValue == null &&
389 reader.NodeType == XmlNodeType.EndElement &&
390 startElementDepth == reader.Depth)
391 ValidateWeakMatch3 ();
393 switch (reader.NodeType) {
394 case XmlNodeType.Element:
398 vState = memo.StartTagOpenDeriv (vState,
399 reader.LocalName, reader.NamespaceURI);
400 if (vState.PatternType == RelaxngPatternType.NotAllowed)
401 throw CreateValidationError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true);
403 // AttsDeriv equals to for each AttDeriv
404 string elementNS = reader.NamespaceURI;
405 if (reader.MoveToFirstAttribute ()) {
407 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
411 string attrNS = reader.NamespaceURI;
413 #if false // old code
415 vState = vState.AttDeriv (reader.LocalName, attrNS, reader.GetAttribute (reader.LocalName, attrNS), this);
416 if (vState == RdpNotAllowed.Instance)
417 throw CreateValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false);
422 vState = memo.StartAttDeriv (vState, reader.LocalName, attrNS);
423 if (vState == RdpNotAllowed.Instance)
424 throw CreateValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false);
426 vState = memo.TextOnlyDeriv (vState);
427 vState = TextDeriv (vState, reader.Value, reader);
428 if (Util.IsWhitespace (reader.Value))
429 vState = vState.Choice (prevState);
430 vState = memo.EndAttDeriv (vState);
431 if (vState == RdpNotAllowed.Instance)
432 throw CreateValidationError (String.Format ("Invalid attribute value is found. Value = '{0}'", reader.Value), false);
435 } while (reader.MoveToNextAttribute ());
441 vState = memo.StartTagCloseDeriv (vState);
442 if (vState.PatternType == RelaxngPatternType.NotAllowed)
443 throw CreateValidationError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false);
445 // if it is empty, then redirect to EndElement
446 if (reader.IsEmptyElement) {
447 ValidateWeakMatch3 ();
448 goto case XmlNodeType.EndElement;
451 case XmlNodeType.EndElement:
452 if (reader.Depth == 0)
456 vState = memo.EndTagDeriv (vState);
457 if (vState.PatternType == RelaxngPatternType.NotAllowed)
458 throw CreateValidationError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true);
460 case XmlNodeType.Whitespace:
462 goto case XmlNodeType.Text;
464 case XmlNodeType.CDATA:
465 case XmlNodeType.Text:
466 case XmlNodeType.SignificantWhitespace:
467 // Whitespace cannot be skipped because data and
468 // value types are required to validate whitespaces.
469 cachedValue += Value;
473 if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
474 startElementDepth = reader.Depth;
475 else if (reader.NodeType == XmlNodeType.EndElement)
476 startElementDepth = -1;
481 RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
483 if (value.Length > 0 && p.IsTextValueDependent)
484 return memo.TextDeriv (p, value, context);
486 return memo.EmptyTextDeriv (p);
489 void ValidateText (bool remain)
491 RdpPattern ts = vState;
492 switch (reader.NodeType) {
493 case XmlNodeType.EndElement:
494 if (startElementDepth != reader.Depth)
495 goto case XmlNodeType.Element;
496 ts = ValidateTextOnlyCore ();
498 case XmlNodeType.Element:
499 startElementDepth = -1;
500 if (!Util.IsWhitespace (cachedValue)) {
501 ts = memo.MixedTextDeriv (ts);
502 ts = TextDeriv (ts, cachedValue, reader);
507 goto case XmlNodeType.Element;
514 if (vState.PatternType == RelaxngPatternType.NotAllowed)
515 throw CreateValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true);
520 // section 6.2.7 weak match 3
521 // childrenDeriv cx p [] = childrenDeriv cx p [(TextNode "")]
522 void ValidateWeakMatch3 ()
524 cachedValue = String.Empty;
525 RdpPattern ts = ValidateTextOnlyCore ();
530 if (vState.PatternType == RelaxngPatternType.NotAllowed)
531 throw CreateValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true);
533 startElementDepth = -1;
536 RdpPattern ValidateTextOnlyCore ()
538 RdpPattern ts = memo.TextOnlyDeriv (vState);
539 ts = TextDeriv (ts, cachedValue, reader);
540 if (Util.IsWhitespace (cachedValue))
541 ts = vState.Choice (ts);
545 MemoizationStore memo = new MemoizationStore ();
548 #region Memoization support
549 internal class MemoizationStore
551 Hashtable startOpen = new Hashtable ();
552 Hashtable startClose = new Hashtable ();
553 Hashtable startAtt = new Hashtable ();
554 Hashtable endTag = new Hashtable ();
555 Hashtable endAtt = new Hashtable ();
556 Hashtable textOnly = new Hashtable ();
557 Hashtable mixedText = new Hashtable ();
558 Hashtable emptyText = new Hashtable ();
559 Hashtable text = new Hashtable ();
560 Hashtable text_value = new Hashtable ();
561 Hashtable qnames = new Hashtable ();
563 enum DerivativeType {
573 XmlQualifiedName GetQName (string local, string ns)
575 Hashtable nst = qnames [ns] as Hashtable;
577 nst = new Hashtable ();
580 XmlQualifiedName qn = nst [local] as XmlQualifiedName;
582 qn = new XmlQualifiedName (local, ns);
588 public RdpPattern StartTagOpenDeriv (RdpPattern p, string local, string ns)
590 Hashtable h = startOpen [p] as Hashtable;
592 h = new Hashtable ();
595 XmlQualifiedName qn = GetQName (local, ns);
596 RdpPattern m = h [qn] as RdpPattern;
598 m = p.StartTagOpenDeriv (local, ns, this);
604 public RdpPattern StartAttDeriv (RdpPattern p, string local, string ns)
606 Hashtable h = startAtt [p] as Hashtable;
608 h = new Hashtable ();
611 XmlQualifiedName qn = GetQName (local, ns);
612 RdpPattern m = h [qn] as RdpPattern;
614 m = p.StartAttDeriv (local, ns, this);
620 public RdpPattern StartTagCloseDeriv (RdpPattern p)
622 RdpPattern m = startClose [p] as RdpPattern;
626 m = p.StartTagCloseDeriv (this);
631 public RdpPattern EndTagDeriv (RdpPattern p)
633 RdpPattern m = endTag [p] as RdpPattern;
637 m = p.EndTagDeriv (this);
642 public RdpPattern EndAttDeriv (RdpPattern p)
644 RdpPattern m = endAtt [p] as RdpPattern;
648 m = p.EndAttDeriv (this);
653 public RdpPattern MixedTextDeriv (RdpPattern p)
655 RdpPattern m = mixedText [p] as RdpPattern;
659 m = p.MixedTextDeriv (this);
664 public RdpPattern TextOnlyDeriv (RdpPattern p)
666 RdpPattern m = textOnly [p] as RdpPattern;
670 m = p.TextOnlyDeriv (this);
675 public RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
677 if (p.IsContextDependent)
678 return p.TextDeriv (value, context);
680 if (Object.ReferenceEquals (text_value [p], value))
681 return text [p] as RdpPattern;
682 RdpPattern m = p.TextDeriv (value, context, this);
683 text_value [p] = value;
688 public RdpPattern EmptyTextDeriv (RdpPattern p)
690 RdpPattern m = emptyText [p] as RdpPattern;
694 m = p.EmptyTextDeriv (this);