2 // Commons.Xml.Relaxng.RelaxngValidatingReader
5 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
7 // 2003 Atsushi Enomoto. "No rights reserved."
9 // Copyright (c) 2004 Novell Inc.
10 // All rights reserved
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:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
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.
34 using System.Collections;
37 using Commons.Xml.Relaxng.Derivative;
39 namespace Commons.Xml.Relaxng
41 public class RelaxngValidatingReader : XmlDefaultReader
43 public RelaxngValidatingReader (XmlReader reader)
44 : this (reader, (RelaxngPattern) null)
48 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)
49 : this (reader, grammarXml, null)
53 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)
54 : this (reader, RelaxngGrammar.Read (grammarXml, provider))
58 public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)
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");
64 this.pattern = pattern;
68 RelaxngPattern pattern;
70 RdpPattern prevState; // Mainly for debugging.
71 Hashtable elementLabels = new Hashtable ();
72 Hashtable attributeLabels = new Hashtable ();
74 ArrayList strictCheckCache;
77 int startElementDepth = -1;
80 internal string CurrentStateXml {
81 get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
84 internal string PreviousStateXml {
85 get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
88 #region Validation State support
90 public bool ReportDetails {
91 get { return reportDetails; }
92 set { reportDetails = value; }
95 public bool RoughLabelCheck {
96 get { return roughLabelCheck; }
97 set { roughLabelCheck = value; }
100 // It is used to disclose its validation feature to public
101 class ValidationState
105 internal ValidationState (RdpPattern startState)
107 this.state = startState;
110 public RdpPattern Pattern {
111 get { return state; }
114 public ValidationState AfterOpenStartTag (
115 string localName, string ns)
117 RdpPattern p = state.StartTagOpenDeriv (
119 return p is RdpNotAllowed ?
120 null : new ValidationState (p);
123 public bool OpenStartTag (string localName, string ns)
125 RdpPattern p = state.StartTagOpenDeriv (
127 if (p is RdpNotAllowed)
133 public ValidationState AfterCloseStartTag ()
135 RdpPattern p = state.StartTagCloseDeriv ();
136 return p is RdpNotAllowed ?
137 null : new ValidationState (p);
140 public bool CloseStartTag ()
142 RdpPattern p = state.StartTagCloseDeriv ();
143 if (p is RdpNotAllowed)
149 public ValidationState AfterEndTag ()
151 RdpPattern p = state.EndTagDeriv ();
152 if (p is RdpNotAllowed)
154 return new ValidationState (p);
157 public bool EndTag ()
159 RdpPattern p = state.EndTagDeriv ();
160 if (p is RdpNotAllowed)
166 public ValidationState AfterAttribute (
167 string localName, string ns, XmlReader reader)
169 RdpPattern p = state.AttDeriv (
170 localName, ns, null, reader);
171 if (p is RdpNotAllowed)
173 return new ValidationState (p);
176 public bool Attribute (
177 string localName, string ns, XmlReader reader)
179 RdpPattern p = state.AttDeriv (
180 localName, ns, null, reader);
181 if (p is RdpNotAllowed)
188 public object GetCurrentState ()
191 return new ValidationState (vState);
194 private ValidationState ToState (object stateObject)
196 if (stateObject == null)
197 throw new ArgumentNullException ("stateObject");
198 ValidationState state = stateObject as ValidationState;
200 throw new ArgumentException ("Argument stateObject is not of expected type.");
204 public object AfterOpenStartTag (object stateObject,
205 string localName, string ns)
207 ValidationState state = ToState (stateObject);
208 return state.AfterOpenStartTag (localName, ns);
211 public bool OpenStartTag (object stateObject,
212 string localName, string ns)
214 ValidationState state = ToState (stateObject);
215 return state.OpenStartTag (localName, ns);
218 public object AfterAttribute (object stateObject,
219 string localName, string ns)
221 ValidationState state = ToState (stateObject);
222 return state.AfterAttribute (localName, ns, this);
225 public bool Attribute (object stateObject,
226 string localName, string ns)
228 ValidationState state = ToState (stateObject);
229 return state.Attribute (localName, ns, this);
232 public object AfterCloseStartTag (object stateObject)
234 ValidationState state = ToState (stateObject);
235 return state.AfterCloseStartTag ();
238 public bool CloseStartTag (object stateObject)
240 ValidationState state = ToState (stateObject);
241 return state.CloseStartTag ();
244 public object AfterEndTag (object stateObject)
246 ValidationState state = ToState (stateObject);
247 return state.AfterEndTag ();
250 public bool EndTag (object stateObject)
252 ValidationState state = ToState (stateObject);
253 return state.EndTag ();
256 public ICollection GetElementLabels (object stateObject)
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);
265 return elements.Values;
267 // Strict check that tries actual validation that will
268 // cover rejection by notAllowed.
269 if (strictCheckCache == null)
270 strictCheckCache = new ArrayList ();
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 ();
280 return elements.Values;
283 public ICollection GetAttributeLabels (object stateObject)
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);
292 return attributes.Values;
294 // Strict check that tries actual validation that will
295 // cover rejection by notAllowed.
296 if (strictCheckCache == null)
297 strictCheckCache = new ArrayList ();
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 ();
307 return attributes.Values;
310 public bool Emptiable (object stateObject)
312 ValidationState state = ToState (stateObject);
313 RdpPattern p = state.Pattern;
314 return !(p.EndTagDeriv () is RdpNotAllowed);
318 private RelaxngException CreateValidationError (string message,
322 return CreateValidationError (String.Concat (message,
324 elements ? "elements are: " : "attributes are: ",
325 BuildLabels (elements),
327 return CreateValidationError (message);
330 private RelaxngException CreateValidationError (string message)
332 IXmlLineInfo li = reader as IXmlLineInfo;
333 string lineInfo = reader.BaseURI;
335 lineInfo += String.Format (" line {0}, column {1}",
336 li.LineNumber, li.LinePosition);
337 return new RelaxngException (message + lineInfo, prevState);
340 private void PrepareState ()
344 if (!pattern.IsCompiled) {
348 vState = pattern.StartPattern;
351 private string BuildLabels (bool elements)
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 ());
361 return sb.ToString ();
364 public override bool Read ()
368 elementLabels.Clear ();
369 attributeLabels.Clear ();
371 bool ret = reader.Read ();
373 // Process pending text node validation if required.
374 if (cachedValue != null)
376 else if (cachedValue == null &&
377 reader.NodeType == XmlNodeType.EndElement &&
378 startElementDepth == reader.Depth)
379 ValidateWeakMatch3 ();
381 switch (reader.NodeType) {
382 case XmlNodeType.Element:
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);
391 // AttsDeriv equals to for each AttDeriv
392 string elementNS = reader.NamespaceURI;
393 if (reader.MoveToFirstAttribute ()) {
395 if (reader.Name.IndexOf ("xmlns:") == 0 || reader.Name == "xmlns")
399 string attrNS = reader.NamespaceURI;
401 #if false // old code
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);
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);
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);
423 } while (reader.MoveToNextAttribute ());
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);
433 // if it is empty, then redirect to EndElement
434 if (reader.IsEmptyElement) {
435 ValidateWeakMatch3 ();
436 goto case XmlNodeType.EndElement;
439 case XmlNodeType.EndElement:
440 if (reader.Depth == 0)
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);
448 case XmlNodeType.Whitespace:
450 goto case XmlNodeType.Text;
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;
461 if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
462 startElementDepth = reader.Depth;
463 else if (reader.NodeType == XmlNodeType.EndElement)
464 startElementDepth = -1;
469 RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
471 if (value.Length > 0 && p.IsTextValueDependent)
472 return memo.TextDeriv (p, value, context);
474 return memo.EmptyTextDeriv (p);
477 void ValidateText (bool remain)
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 ();
486 case XmlNodeType.Element:
487 startElementDepth = -1;
488 if (!Util.IsWhitespace (cachedValue)) {
489 ts = memo.MixedTextDeriv (ts);
490 ts = TextDeriv (ts, cachedValue, reader);
495 goto case XmlNodeType.Element;
502 if (vState.PatternType == RelaxngPatternType.NotAllowed)
503 throw CreateValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true);
508 // section 6.2.7 weak match 3
509 // childrenDeriv cx p [] = childrenDeriv cx p [(TextNode "")]
510 void ValidateWeakMatch3 ()
512 cachedValue = String.Empty;
513 RdpPattern ts = ValidateTextOnlyCore ();
518 if (vState.PatternType == RelaxngPatternType.NotAllowed)
519 throw CreateValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true);
521 startElementDepth = -1;
524 RdpPattern ValidateTextOnlyCore ()
526 RdpPattern ts = memo.TextOnlyDeriv (vState);
527 ts = TextDeriv (ts, cachedValue, reader);
528 if (Util.IsWhitespace (cachedValue))
529 ts = vState.Choice (ts);
533 MemoizationStore memo = new MemoizationStore ();
536 #region Memoization support
537 internal class MemoizationStore
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 ();
551 enum DerivativeType {
561 XmlQualifiedName GetQName (string local, string ns)
563 Hashtable nst = qnames [ns] as Hashtable;
565 nst = new Hashtable ();
568 XmlQualifiedName qn = nst [local] as XmlQualifiedName;
570 qn = new XmlQualifiedName (local, ns);
576 public RdpPattern StartTagOpenDeriv (RdpPattern p, string local, string ns)
578 Hashtable h = startOpen [p] as Hashtable;
580 h = new Hashtable ();
583 XmlQualifiedName qn = GetQName (local, ns);
584 RdpPattern m = h [qn] as RdpPattern;
586 m = p.StartTagOpenDeriv (local, ns, this);
592 public RdpPattern StartAttDeriv (RdpPattern p, string local, string ns)
594 Hashtable h = startAtt [p] as Hashtable;
596 h = new Hashtable ();
599 XmlQualifiedName qn = GetQName (local, ns);
600 RdpPattern m = h [qn] as RdpPattern;
602 m = p.StartAttDeriv (local, ns, this);
608 public RdpPattern StartTagCloseDeriv (RdpPattern p)
610 RdpPattern m = startClose [p] as RdpPattern;
614 m = p.StartTagCloseDeriv (this);
619 public RdpPattern EndTagDeriv (RdpPattern p)
621 RdpPattern m = endTag [p] as RdpPattern;
625 m = p.EndTagDeriv (this);
630 public RdpPattern EndAttDeriv (RdpPattern p)
632 RdpPattern m = endAtt [p] as RdpPattern;
636 m = p.EndAttDeriv (this);
641 public RdpPattern MixedTextDeriv (RdpPattern p)
643 RdpPattern m = mixedText [p] as RdpPattern;
647 m = p.MixedTextDeriv (this);
652 public RdpPattern TextOnlyDeriv (RdpPattern p)
654 RdpPattern m = textOnly [p] as RdpPattern;
658 m = p.TextOnlyDeriv (this);
663 public RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
665 if (p.IsContextDependent)
666 return p.TextDeriv (value, context);
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;
676 public RdpPattern EmptyTextDeriv (RdpPattern p)
678 RdpPattern m = emptyText [p] as RdpPattern;
682 m = p.EmptyTextDeriv (this);