-//\r
-// Commons.Xml.Relaxng.RelaxngValidatingReader\r
-//\r
-// Author:\r
-// Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>\r
-//\r
-// 2003 Atsushi Enomoto. "No rights reserved."\r
-//\r
-// Copyright (c) 2004 Novell Inc.\r
-// All rights reserved\r
-//\r
-\r
-//\r
-// Permission is hereby granted, free of charge, to any person obtaining\r
-// a copy of this software and associated documentation files (the\r
-// "Software"), to deal in the Software without restriction, including\r
-// without limitation the rights to use, copy, modify, merge, publish,\r
-// distribute, sublicense, and/or sell copies of the Software, and to\r
-// permit persons to whom the Software is furnished to do so, subject to\r
-// the following conditions:\r
-// \r
-// The above copyright notice and this permission notice shall be\r
-// included in all copies or substantial portions of the Software.\r
-// \r
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
-//\r
-using System;\r
-using System.Collections;\r
-using System.Text;\r
-using System.Xml;\r
-using Commons.Xml.Relaxng.Derivative;\r
-\r
-namespace Commons.Xml.Relaxng\r
-{\r
- public class RelaxngValidatingReader : XmlDefaultReader\r
- {\r
- public RelaxngValidatingReader (XmlReader reader)\r
- : this (reader, (RelaxngPattern) null)\r
- {\r
- }\r
-\r
- public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)\r
- : this (reader, grammarXml, null)\r
- {\r
- }\r
-\r
- public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)\r
- : this (reader, RelaxngGrammar.Read (grammarXml, provider))\r
- {\r
- }\r
-\r
- public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)\r
- : base (reader)\r
- {\r
- if (reader.NodeType == XmlNodeType.Attribute)\r
- throw new RelaxngException ("RELAX NG does not support standalone attribute validation (it is prohibited due to the specification section 7.1.5");\r
- this.reader = reader;\r
- this.pattern = pattern;\r
- }\r
-\r
- XmlReader reader;\r
- RelaxngPattern pattern;\r
- RdpPattern vState;\r
- RdpPattern prevState; // Mainly for debugging.\r
- ArrayList PredefinedAttributes = new ArrayList ();\r
- bool labelsComputed;\r
- Hashtable elementLabels = new Hashtable ();\r
- Hashtable attributeLabels = new Hashtable ();\r
- bool isEmptiable;\r
- bool roughLabelCheck;\r
- ArrayList strictCheckCache;\r
- bool reportDetails;\r
- string cachedValue;\r
-\r
- internal string CurrentStateXml {\r
- get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }\r
- }\r
-\r
- internal string PreviousStateXml {\r
- get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }\r
- }\r
-\r
- #region Validation State support\r
-\r
- public bool ReportDetails {\r
- get { return reportDetails; }\r
- set { reportDetails = value; }\r
- }\r
-\r
- public bool RoughLabelCheck {\r
- get { return roughLabelCheck; }\r
- set { roughLabelCheck = value; }\r
- }\r
-\r
- // It is used to disclose its validation feature to public\r
- class ValidationState\r
- {\r
- RdpPattern state;\r
-\r
- internal ValidationState (RdpPattern startState)\r
- {\r
- this.state = startState;\r
- }\r
-\r
- public RdpPattern Pattern {\r
- get { return state; }\r
- }\r
-\r
- public ValidationState AfterOpenStartTag (\r
- string localName, string ns)\r
- {\r
- RdpPattern p = state.StartTagOpenDeriv (\r
- localName, ns);\r
- return p is RdpNotAllowed ?\r
- null : new ValidationState (p);\r
- }\r
-\r
- public bool OpenStartTag (string localName, string ns)\r
- {\r
- RdpPattern p = state.StartTagOpenDeriv (\r
- localName, ns);\r
- if (p is RdpNotAllowed)\r
- return false;\r
- state = p;\r
- return true;\r
- }\r
-\r
- public ValidationState AfterCloseStartTag ()\r
- {\r
- RdpPattern p = state.StartTagCloseDeriv ();\r
- return p is RdpNotAllowed ?\r
- null : new ValidationState (p);\r
- }\r
-\r
- public bool CloseStartTag ()\r
- {\r
- RdpPattern p = state.StartTagCloseDeriv ();\r
- if (p is RdpNotAllowed)\r
- return false;\r
- state = p;\r
- return true;\r
- }\r
-\r
- public ValidationState AfterEndTag ()\r
- {\r
- RdpPattern p = state.EndTagDeriv ();\r
- if (p is RdpNotAllowed)\r
- return null;\r
- return new ValidationState (p);\r
- }\r
-\r
- public bool EndTag ()\r
- {\r
- RdpPattern p = state.EndTagDeriv ();\r
- if (p is RdpNotAllowed)\r
- return false;\r
- state = p;\r
- return true;\r
- }\r
-\r
- public ValidationState AfterAttribute (\r
- string localName, string ns, XmlReader reader)\r
- {\r
- RdpPattern p = state.AttDeriv (\r
- localName, ns, null, reader);\r
- if (p is RdpNotAllowed)\r
- return null;\r
- return new ValidationState (p);\r
- }\r
-\r
- public bool Attribute (\r
- string localName, string ns, XmlReader reader)\r
- {\r
- RdpPattern p = state.AttDeriv (\r
- localName, ns, null, reader);\r
- if (p is RdpNotAllowed)\r
- return false;\r
- state = p;\r
- return true;\r
- }\r
- }\r
-\r
- public object GetCurrentState ()\r
- {\r
- PrepareState ();\r
- return new ValidationState (vState);\r
- }\r
-\r
- private ValidationState ToState (object stateObject)\r
- {\r
- if (stateObject == null)\r
- throw new ArgumentNullException ("stateObject");\r
- ValidationState state = stateObject as ValidationState;\r
- if (state == null)\r
- throw new ArgumentException ("Argument stateObject is not of expected type.");\r
- return state;\r
- }\r
-\r
- public object AfterOpenStartTag (object stateObject,\r
- string localName, string ns)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.AfterOpenStartTag (localName, ns);\r
- }\r
-\r
- public bool OpenStartTag (object stateObject,\r
- string localName, string ns)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.OpenStartTag (localName, ns);\r
- }\r
-\r
- public object AfterAttribute (object stateObject,\r
- string localName, string ns)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.AfterAttribute (localName, ns, this);\r
- }\r
-\r
- public bool Attribute (object stateObject,\r
- string localName, string ns)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.Attribute (localName, ns, this);\r
- }\r
-\r
- public object AfterCloseStartTag (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.AfterCloseStartTag ();\r
- }\r
-\r
- public bool CloseStartTag (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.CloseStartTag ();\r
- }\r
-\r
- public object AfterEndTag (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.AfterEndTag ();\r
- }\r
-\r
- public bool EndTag (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- return state.EndTag ();\r
- }\r
-\r
- public ICollection GetElementLabels (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- RdpPattern p = state.Pattern;\r
- Hashtable elements = new Hashtable ();\r
- Hashtable attributes = new Hashtable ();\r
- p.GetLabels (elements, attributes);\r
-\r
- if (roughLabelCheck)\r
- return elements.Values;\r
-\r
- // Strict check that tries actual validation that will\r
- // cover rejection by notAllowed.\r
- if (strictCheckCache == null)\r
- strictCheckCache = new ArrayList ();\r
- else\r
- strictCheckCache.Clear ();\r
- foreach (XmlQualifiedName qname in elements.Values)\r
- if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)\r
- strictCheckCache.Add (qname);\r
- foreach (XmlQualifiedName qname in strictCheckCache)\r
- elements.Remove (qname);\r
- strictCheckCache.Clear ();\r
-\r
- return elements.Values;\r
- }\r
-\r
- public ICollection GetAttributeLabels (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- RdpPattern p = state.Pattern;\r
- Hashtable elements = new Hashtable ();\r
- Hashtable attributes = new Hashtable ();\r
- p.GetLabels (elements, attributes);\r
-\r
- if (roughLabelCheck)\r
- return attributes.Values;\r
-\r
- // Strict check that tries actual validation that will\r
- // cover rejection by notAllowed.\r
- if (strictCheckCache == null)\r
- strictCheckCache = new ArrayList ();\r
- else\r
- strictCheckCache.Clear ();\r
- foreach (XmlQualifiedName qname in attributes.Values)\r
- if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)\r
- strictCheckCache.Add (qname);\r
- foreach (XmlQualifiedName qname in strictCheckCache)\r
- attributes.Remove (qname);\r
- strictCheckCache.Clear ();\r
-\r
- return attributes.Values;\r
- }\r
-\r
- public bool Emptiable (object stateObject)\r
- {\r
- ValidationState state = ToState (stateObject);\r
- RdpPattern p = state.Pattern;\r
- return !(p.EndTagDeriv () is RdpNotAllowed);\r
- }\r
- #endregion\r
-\r
- private RelaxngException createValidationError (string message)\r
- {\r
- IXmlLineInfo li = reader as IXmlLineInfo;\r
- string lineInfo = reader.BaseURI;\r
- if (li != null)\r
- lineInfo += String.Format (" line {0}, column {1}",\r
- li.LineNumber, li.LinePosition);\r
- return new RelaxngException (message + lineInfo, prevState);\r
- }\r
-\r
- private void PrepareState ()\r
- {\r
- if (!pattern.IsCompiled) {\r
- pattern.Compile ();\r
- }\r
- if (vState == null)\r
- vState = pattern.StartPattern;\r
- }\r
-\r
- private string BuildLabels (bool elements)\r
- {\r
- StringBuilder sb = new StringBuilder ();\r
- ValidationState s = new ValidationState (prevState);\r
- ICollection col = elements ?\r
- GetElementLabels (s) : GetAttributeLabels (s);\r
- foreach (XmlQualifiedName qname in col) {\r
- sb.Append (qname.ToString ());\r
- sb.Append (' ');\r
- }\r
- return sb.ToString ();\r
- }\r
-\r
- public override bool Read ()\r
- {\r
- PrepareState ();\r
-\r
- labelsComputed = false;\r
- elementLabels.Clear ();\r
- attributeLabels.Clear ();\r
-\r
- bool ret = reader.Read ();\r
-\r
- // Process pending text node validation if required.\r
- if (cachedValue != null) {\r
- switch (reader.NodeType) {\r
- case XmlNodeType.Element:\r
- case XmlNodeType.EndElement:\r
- prevState = vState;\r
- vState = vState.TextDeriv (cachedValue, reader);\r
- if (vState.PatternType == RelaxngPatternType.NotAllowed)\r
- throw createValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue));\r
- cachedValue = null;\r
- break;\r
- default:\r
- if (!ret)\r
- goto case XmlNodeType.Element;\r
- break;\r
- }\r
- }\r
-\r
- switch (reader.NodeType) {\r
- case XmlNodeType.Element:\r
- // StartTagOpenDeriv\r
- prevState = vState;\r
- vState = vState.StartTagOpenDeriv (\r
- reader.LocalName, reader.NamespaceURI);\r
- if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
- string labels = String.Empty;\r
- if (reportDetails)\r
- labels = "Allowed elements are: " + BuildLabels (true);\r
- throw createValidationError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
- }\r
-\r
- // AttsDeriv equals to for each AttDeriv\r
- string elementNS = reader.NamespaceURI;\r
- if (reader.MoveToFirstAttribute ()) {\r
- do {\r
- if (reader.Name.IndexOf ("xmlns:") == 0 || reader.Name == "xmlns")\r
- continue;\r
-\r
- prevState = vState;\r
- string attrNS = reader.NamespaceURI;\r
- vState = vState.AttDeriv (reader.LocalName, attrNS, reader.GetAttribute (reader.LocalName, attrNS), this);\r
- if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
- string labels = String.Empty;\r
- if (reportDetails)\r
- labels = "Allowed attributes are: " + BuildLabels (false);\r
-\r
- throw createValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
- }\r
- } while (reader.MoveToNextAttribute ());\r
- MoveToElement ();\r
- }\r
-\r
- // StarTagCloseDeriv\r
- prevState = vState;\r
- vState = vState.StartTagCloseDeriv ();\r
- if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
- string labels = String.Empty;\r
- if (reportDetails)\r
- labels = "Expected attributes are: " + BuildLabels (false);\r
-\r
- throw createValidationError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
- }\r
-\r
- // if it is empty, then redirect to EndElement\r
- if (reader.IsEmptyElement)\r
- goto case XmlNodeType.EndElement;\r
- break;\r
- case XmlNodeType.EndElement:\r
- // EndTagDeriv\r
- prevState = vState;\r
- vState = vState.EndTagDeriv ();\r
- if (vState.PatternType == RelaxngPatternType.NotAllowed) {\r
- string labels = String.Empty;\r
- if (reportDetails)\r
- labels = "Expected elements are: " + BuildLabels (true);\r
- throw createValidationError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));\r
- }\r
- break;\r
- case XmlNodeType.CDATA:\r
- case XmlNodeType.Text:\r
- case XmlNodeType.SignificantWhitespace:\r
- case XmlNodeType.Whitespace:\r
- // Whitespace cannot be skipped because data and\r
- // value types are required to validate whitespaces.\r
- cachedValue += Value;\r
- break;\r
- }\r
- return ret;\r
- }\r
- }\r
-}\r
-\r
+//
+// Commons.Xml.Relaxng.RelaxngValidatingReader
+//
+// Author:
+// Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
+// Alexandre Alapetite <http://alexandre.alapetite.fr/cv/>
+//
+// 2003 Atsushi Enomoto. "No rights reserved."
+//
+// Copyright (c) 2004 Novell Inc.
+// All rights reserved
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections;
+using System.Text;
+using System.Xml;
+using Commons.Xml.Relaxng.Derivative;
+
+namespace Commons.Xml.Relaxng
+{
+ public class RelaxngValidatingReader : XmlDefaultReader
+ {
+ public RelaxngValidatingReader (XmlReader reader)
+ : this (reader, (RelaxngPattern) null)
+ {
+ }
+
+ public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)
+ : this (reader, grammarXml, null)
+ {
+ }
+
+ public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)
+ : this (reader, RelaxngGrammar.Read (grammarXml, provider))
+ {
+ }
+
+ public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)
+ : base (reader)
+ {
+ if (pattern == null)
+ throw new ArgumentNullException ("pattern");
+
+ if (reader.NodeType == XmlNodeType.Attribute)
+ throw new RelaxngException ("RELAX NG does not support standalone attribute validation (it is prohibited due to the specification section 7.1.5");
+ this.reader = reader;
+ this.pattern = pattern;
+ }
+
+ XmlReader reader;
+ RelaxngPattern pattern;
+ RdpPattern vState;
+ RdpPattern prevState; // Mainly for debugging.
+ bool roughLabelCheck;
+ ArrayList strictCheckCache;
+ bool reportDetails;
+ string cachedValue;
+ int startElementDepth = -1;
+ bool inContent;
+ bool firstRead = true;
+
+ public delegate bool RelaxngValidationEventHandler (XmlReader source, string message);
+
+ public static readonly RelaxngValidationEventHandler IgnoreError = delegate { return true; };
+
+ public event RelaxngValidationEventHandler InvalidNodeFound;
+
+ delegate RdpPattern RecoveryHandler (RdpPattern source);
+
+ RdpPattern HandleError (string error, bool elements, RdpPattern source, RecoveryHandler recover)
+ {
+ if (InvalidNodeFound != null && InvalidNodeFound (this, error))
+ return recover (source);
+ else
+ throw CreateValidationError (error, true);
+ }
+
+ internal string CurrentStateXml {
+ get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
+ }
+
+ internal string PreviousStateXml {
+ get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
+ }
+
+ #region Validation State support
+
+ public bool ReportDetails {
+ get { return reportDetails; }
+ set { reportDetails = value; }
+ }
+
+ public bool RoughLabelCheck {
+ get { return roughLabelCheck; }
+ set { roughLabelCheck = value; }
+ }
+
+ // It is used to disclose its validation feature to public
+ class ValidationState
+ {
+ RdpPattern state;
+
+ internal ValidationState (RdpPattern startState)
+ {
+ this.state = startState;
+ }
+
+ public RdpPattern Pattern {
+ get { return state; }
+ }
+
+ public ValidationState AfterOpenStartTag (
+ string localName, string ns)
+ {
+ RdpPattern p = state.StartTagOpenDeriv (
+ localName, ns);
+ return p is RdpNotAllowed ?
+ null : new ValidationState (p);
+ }
+
+ public bool OpenStartTag (string localName, string ns)
+ {
+ RdpPattern p = state.StartTagOpenDeriv (
+ localName, ns);
+ if (p is RdpNotAllowed)
+ return false;
+ state = p;
+ return true;
+ }
+
+ public ValidationState AfterCloseStartTag ()
+ {
+ RdpPattern p = state.StartTagCloseDeriv ();
+ return p is RdpNotAllowed ?
+ null : new ValidationState (p);
+ }
+
+ public bool CloseStartTag ()
+ {
+ RdpPattern p = state.StartTagCloseDeriv ();
+ if (p is RdpNotAllowed)
+ return false;
+ state = p;
+ return true;
+ }
+
+ public ValidationState AfterEndTag ()
+ {
+ RdpPattern p = state.EndTagDeriv ();
+ if (p is RdpNotAllowed)
+ return null;
+ return new ValidationState (p);
+ }
+
+ public bool EndTag ()
+ {
+ RdpPattern p = state.EndTagDeriv ();
+ if (p is RdpNotAllowed)
+ return false;
+ state = p;
+ return true;
+ }
+
+ public ValidationState AfterAttribute (
+ string localName, string ns, XmlReader reader)
+ {
+ RdpPattern p = state.AttDeriv (
+ localName, ns, null, reader);
+ if (p is RdpNotAllowed)
+ return null;
+ return new ValidationState (p);
+ }
+
+ public bool Attribute (
+ string localName, string ns, XmlReader reader)
+ {
+ RdpPattern p = state.AttDeriv (
+ localName, ns, null, reader);
+ if (p is RdpNotAllowed)
+ return false;
+ state = p;
+ return true;
+ }
+ }
+
+ public object GetCurrentState ()
+ {
+ PrepareState ();
+ return new ValidationState (vState);
+ }
+
+ private ValidationState ToState (object stateObject)
+ {
+ if (stateObject == null)
+ throw new ArgumentNullException ("stateObject");
+ ValidationState state = stateObject as ValidationState;
+ if (state == null)
+ throw new ArgumentException ("Argument stateObject is not of expected type.");
+ return state;
+ }
+
+ public object AfterOpenStartTag (object stateObject,
+ string localName, string ns)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.AfterOpenStartTag (localName, ns);
+ }
+
+ public bool OpenStartTag (object stateObject,
+ string localName, string ns)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.OpenStartTag (localName, ns);
+ }
+
+ public object AfterAttribute (object stateObject,
+ string localName, string ns)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.AfterAttribute (localName, ns, this);
+ }
+
+ public bool Attribute (object stateObject,
+ string localName, string ns)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.Attribute (localName, ns, this);
+ }
+
+ public object AfterCloseStartTag (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.AfterCloseStartTag ();
+ }
+
+ public bool CloseStartTag (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.CloseStartTag ();
+ }
+
+ public object AfterEndTag (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.AfterEndTag ();
+ }
+
+ public bool EndTag (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ return state.EndTag ();
+ }
+
+ public ICollection GetElementLabels (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ RdpPattern p = state.Pattern;
+ Hashtable elements = new Hashtable ();
+ Hashtable attributes = new Hashtable ();
+ p.GetLabels (elements, attributes);
+
+ if (roughLabelCheck)
+ return elements.Values;
+
+ // Strict check that tries actual validation that will
+ // cover rejection by notAllowed.
+ if (strictCheckCache == null)
+ strictCheckCache = new ArrayList ();
+ else
+ strictCheckCache.Clear ();
+ foreach (XmlQualifiedName qname in elements.Values)
+ if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)
+ strictCheckCache.Add (qname);
+ foreach (XmlQualifiedName qname in strictCheckCache)
+ elements.Remove (qname);
+ strictCheckCache.Clear ();
+
+ return elements.Values;
+ }
+
+ public ICollection GetAttributeLabels (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ RdpPattern p = state.Pattern;
+ Hashtable elements = new Hashtable ();
+ Hashtable attributes = new Hashtable ();
+ p.GetLabels (elements, attributes);
+
+ if (roughLabelCheck)
+ return attributes.Values;
+
+ // Strict check that tries actual validation that will
+ // cover rejection by notAllowed.
+ if (strictCheckCache == null)
+ strictCheckCache = new ArrayList ();
+ else
+ strictCheckCache.Clear ();
+ foreach (XmlQualifiedName qname in attributes.Values)
+ if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)
+ strictCheckCache.Add (qname);
+ foreach (XmlQualifiedName qname in strictCheckCache)
+ attributes.Remove (qname);
+ strictCheckCache.Clear ();
+
+ return attributes.Values;
+ }
+
+ public bool Emptiable (object stateObject)
+ {
+ ValidationState state = ToState (stateObject);
+ RdpPattern p = state.Pattern;
+ return !(p.EndTagDeriv () is RdpNotAllowed);
+ }
+ #endregion
+
+ private RelaxngException CreateValidationError (string message,
+ bool elements)
+ {
+ if (ReportDetails)
+ return CreateValidationError (String.Concat (message,
+ " Expected ",
+ elements ? "elements are: " : "attributes are: ",
+ BuildLabels (elements),
+ "."));
+ return CreateValidationError (message);
+ }
+
+ private RelaxngException CreateValidationError (string message)
+ {
+ IXmlLineInfo li = reader as IXmlLineInfo;
+ string lineInfo = reader.BaseURI;
+ if (li != null)
+ lineInfo += String.Format (" line {0}, column {1}",
+ li.LineNumber, li.LinePosition);
+ return new RelaxngException (message + lineInfo, prevState);
+ }
+
+ private void PrepareState ()
+ {
+ if (vState != null)
+ return;
+ if (!pattern.IsCompiled) {
+ pattern.Compile ();
+ }
+ if (vState == null)
+ vState = pattern.StartPattern;
+ }
+
+ private string BuildLabels (bool elements)
+ {
+ StringBuilder sb = new StringBuilder ();
+ ValidationState s = new ValidationState (prevState);
+ ICollection col = elements ?
+ GetElementLabels (s) : GetAttributeLabels (s);
+ foreach (XmlQualifiedName qname in col) {
+ sb.Append (qname.ToString ());
+ sb.Append (' ');
+ }
+ return sb.ToString ();
+ }
+
+ public override bool Read ()
+ {
+ PrepareState ();
+
+ // If the input XmlReader is already positioned on
+ // the first node to validate, skip Read() here
+ // (idea by Alex).
+ bool ret;
+ if (firstRead) {
+ firstRead = false;
+ if (reader.ReadState == ReadState.Initial)
+ ret = reader.Read ();
+ else
+ ret = !((reader.ReadState == ReadState.Closed) || (reader.ReadState == ReadState.EndOfFile));
+ }
+ else
+ ret = reader.Read ();
+
+ // Process pending text node validation if required.
+ if (cachedValue != null)
+ ValidateText (ret);
+ else if (cachedValue == null &&
+ reader.NodeType == XmlNodeType.EndElement &&
+ startElementDepth == reader.Depth)
+ ValidateWeakMatch3 ();
+
+ switch (reader.NodeType) {
+ case XmlNodeType.Element:
+ inContent = true;
+ // StartTagOpenDeriv
+ prevState = vState;
+ vState = memo.StartTagOpenDeriv (vState,
+ reader.LocalName, reader.NamespaceURI);
+ if (vState.PatternType == RelaxngPatternType.NotAllowed) {
+ if (InvalidNodeFound != null)
+ vState = HandleError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true, prevState, RecoverFromInvalidStartTag);
+ }
+
+ // AttsDeriv equals to for each AttDeriv
+ string elementNS = reader.NamespaceURI;
+ if (reader.MoveToFirstAttribute ()) {
+ do {
+ if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
+ continue;
+
+ RdpPattern savedState = vState;
+ prevState = vState;
+ string attrNS = reader.NamespaceURI;
+
+ vState = memo.StartAttDeriv (vState, reader.LocalName, attrNS);
+ if (vState == RdpNotAllowed.Instance) {
+ vState = HandleError (String.Format ("Invalid attribute occurence found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false, savedState, p => p);
+ continue; // the following steps are ignored.
+ }
+ prevState = vState;
+ vState = memo.TextOnlyDeriv (vState);
+ vState = TextDeriv (vState, reader.Value, reader);
+ if (Util.IsWhitespace (reader.Value))
+ vState = vState.Choice (prevState);
+ if (vState == RdpNotAllowed.Instance)
+ vState = HandleError (String.Format ("Invalid attribute value is found. Value = '{0}'", reader.Value), false, prevState, RecoverFromInvalidText);
+ prevState = vState;
+ vState = memo.EndAttDeriv (vState);
+ if (vState == RdpNotAllowed.Instance)
+ vState = HandleError (String.Format ("Invalid attribute value is found. Value = '{0}'", reader.Value), false, prevState, RecoverFromInvalidEnd);
+ } while (reader.MoveToNextAttribute ());
+ MoveToElement ();
+ }
+
+ // StarTagCloseDeriv
+ prevState = vState;
+ vState = memo.StartTagCloseDeriv (vState);
+ if (vState.PatternType == RelaxngPatternType.NotAllowed)
+ vState = HandleError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), false, prevState, RecoverFromInvalidStartTagClose);
+
+ // if it is empty, then redirect to EndElement
+ if (reader.IsEmptyElement) {
+ ValidateWeakMatch3 ();
+ goto case XmlNodeType.EndElement;
+ }
+ break;
+ case XmlNodeType.EndElement:
+ if (reader.Depth == 0)
+ inContent = false;
+ // EndTagDeriv
+ prevState = vState;
+ vState = memo.EndTagDeriv (vState);
+ if (vState.PatternType == RelaxngPatternType.NotAllowed)
+ vState = HandleError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}.", reader.LocalName, reader.NamespaceURI), true, prevState, RecoverFromInvalidEnd);
+ break;
+ case XmlNodeType.Whitespace:
+ if (inContent)
+ goto case XmlNodeType.Text;
+ break;
+ case XmlNodeType.CDATA:
+ case XmlNodeType.Text:
+ case XmlNodeType.SignificantWhitespace:
+ // Whitespace cannot be skipped because data and
+ // value types are required to validate whitespaces.
+ cachedValue += Value;
+ break;
+ }
+
+ if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
+ startElementDepth = reader.Depth;
+ else if (reader.NodeType == XmlNodeType.EndElement)
+ startElementDepth = -1;
+
+ return ret;
+ }
+
+ #region error recovery
+ // Error recovery feature can be enabled by using
+ // InvalidNodeFound event of type RelaxngValidationEventHandler.
+ //
+ // Other than startTagOpenDeriv, it is (again) based on
+ // James Clark's derivative algorithm.
+ // http://www.thaiopensource.com/relaxng/derivative.html
+ // For invalid start tag, we just recover from it by using
+ // xs:any-like pattern for unexpected node occurence.
+
+ RdpPattern MakeGroupHeadOptional (RdpPattern p)
+ {
+ if (p is RdpAbstractSingleContent)
+ return new RdpChoice (RdpEmpty.Instance, p);
+ RdpAbstractBinary ab = p as RdpAbstractBinary;
+ if (ab == null)
+ return p;
+ if (ab is RdpGroup)
+ return new RdpGroup (new RdpChoice (RdpEmpty.Instance, ab.LValue), ab.RValue);
+ else if (ab is RdpChoice)
+ return new RdpChoice (MakeGroupHeadOptional (ab.LValue), MakeGroupHeadOptional (ab.RValue));
+ else if (ab is RdpInterleave)
+ return new RdpInterleave (MakeGroupHeadOptional (ab.LValue), MakeGroupHeadOptional (ab.RValue));
+ else if (ab is RdpAfter) // FIXME: is it correct?
+ return new RdpAfter (MakeGroupHeadOptional (ab.LValue), MakeGroupHeadOptional (ab.RValue));
+ throw new SystemException ("INTERNAL ERROR: unexpected pattern: " + p.GetType ());
+ }
+
+ RdpPattern ReplaceAfterHeadWithEmpty (RdpPattern p)
+ {
+ if (p is RdpAbstractSingleContent)
+ return new RdpChoice (RdpEmpty.Instance, p);
+ RdpAbstractBinary ab = p as RdpAbstractBinary;
+ if (ab == null)
+ return p;
+ if (ab is RdpGroup)
+ return new RdpGroup (ReplaceAfterHeadWithEmpty (ab.LValue), ReplaceAfterHeadWithEmpty (ab.RValue));
+ else if (ab is RdpChoice)
+ return new RdpChoice (ReplaceAfterHeadWithEmpty (ab.LValue), ReplaceAfterHeadWithEmpty (ab.RValue));
+ else if (ab is RdpInterleave)
+ return new RdpInterleave (ReplaceAfterHeadWithEmpty (ab.LValue), ReplaceAfterHeadWithEmpty (ab.RValue));
+ else if (ab is RdpAfter)
+ return new RdpAfter (RdpEmpty.Instance, ab.RValue);
+ throw new SystemException ("INTERNAL ERROR: unexpected pattern: " + p.GetType ());
+ }
+
+ RdpPattern CollectAfterTailAsChoice (RdpPattern p)
+ {
+ RdpAbstractBinary ab = p as RdpAbstractBinary;
+ if (ab == null)
+ return RdpEmpty.Instance;
+ if (ab is RdpAfter)
+ return ab.RValue;
+ RdpPattern l = CollectAfterTailAsChoice (ab.LValue);
+ if (l == RdpEmpty.Instance)
+ return CollectAfterTailAsChoice (ab.RValue);
+ RdpPattern r = CollectAfterTailAsChoice (ab.RValue);
+ if (r == RdpEmpty.Instance)
+ return l;
+ return new RdpChoice (l, r);
+ }
+
+ RdpPattern ReplaceAttributesWithEmpty (RdpPattern p)
+ {
+ if (p is RdpAttribute)
+ return RdpEmpty.Instance;
+
+ RdpAbstractSingleContent asc = p as RdpAbstractSingleContent;
+ if (asc is RdpList)
+ return new RdpList (ReplaceAttributesWithEmpty (asc.Child));
+ if (asc is RdpOneOrMore)
+ return new RdpOneOrMore (ReplaceAttributesWithEmpty (asc.Child));
+ else if (asc is RdpElement)
+ return asc; // should not be expected to contain any attribute as RdpElement.
+
+ RdpAbstractBinary ab = p as RdpAbstractBinary;
+ if (ab == null)
+ return p;
+ if (ab is RdpGroup)
+ return new RdpGroup (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
+ else if (ab is RdpChoice)
+ return new RdpChoice (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
+ else if (ab is RdpInterleave)
+ return new RdpInterleave (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
+ else if (ab is RdpAfter) // FIXME: is it correct?
+ return new RdpAfter (ReplaceAttributesWithEmpty (ab.LValue), ReplaceAttributesWithEmpty (ab.RValue));
+ throw new SystemException ("INTERNAL ERROR: unexpected pattern: " + p.GetType ());
+ }
+
+ RdpPattern RecoverFromInvalidStartTag (RdpPattern p)
+ {
+ RdpPattern test1 = MakeGroupHeadOptional (p);
+ test1 = memo.StartTagOpenDeriv (test1, reader.LocalName, reader.NamespaceURI);
+ if (test1 != null)
+ return test1;
+ // FIXME: JJC derivative algorithm suggests more complicated recovery. We simply treats current "extra" node as "anything".
+ return new RdpChoice (RdpPattern.Anything, p);
+ }
+
+ RdpPattern RecoverFromInvalidText (RdpPattern p)
+ {
+ return ReplaceAfterHeadWithEmpty (p);
+ }
+
+ RdpPattern RecoverFromInvalidEnd (RdpPattern p)
+ {
+ return CollectAfterTailAsChoice (p);
+ }
+
+ RdpPattern RecoverFromInvalidStartTagClose (RdpPattern p)
+ {
+ return ReplaceAttributesWithEmpty (p);
+ }
+
+ #endregion
+
+ RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
+ {
+ if (value.Length > 0 && p.IsTextValueDependent)
+ return memo.TextDeriv (p, value, context);
+ else
+ return memo.EmptyTextDeriv (p);
+ }
+
+ void ValidateText (bool remain)
+ {
+ RdpPattern ts = vState;
+ switch (reader.NodeType) {
+ case XmlNodeType.EndElement:
+ if (startElementDepth != reader.Depth)
+ goto case XmlNodeType.Element;
+ ts = ValidateTextOnlyCore ();
+ break;
+ case XmlNodeType.Element:
+ startElementDepth = -1;
+ if (!Util.IsWhitespace (cachedValue)) {
+ // HandleError() is not really useful here since it involves else condition...
+ ts = memo.MixedTextDeriv (ts);
+ /*if (InvalidNodeFound != null) {
+ InvalidNodeFound (reader, "Not allowed text node was found.");
+ ts = vState;
+ cachedValue = null;
+ }
+ else*/
+ ts = TextDeriv (ts, cachedValue, reader);
+ }
+ break;
+ default:
+ if (!remain)
+ goto case XmlNodeType.Element;
+ return;
+ }
+
+ prevState = vState;
+ vState = ts;
+
+ if (vState.PatternType == RelaxngPatternType.NotAllowed)
+ vState = HandleError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true, prevState, RecoverFromInvalidText);
+ cachedValue = null;
+ return;
+ }
+
+ // section 6.2.7 weak match 3
+ // childrenDeriv cx p [] = childrenDeriv cx p [(TextNode "")]
+ void ValidateWeakMatch3 ()
+ {
+ cachedValue = String.Empty;
+ RdpPattern ts = ValidateTextOnlyCore ();
+
+ prevState = vState;
+ vState = ts;
+
+ if (vState.PatternType == RelaxngPatternType.NotAllowed)
+ vState = HandleError (String.Format ("Invalid text found. Text value = {0} ", cachedValue), true, prevState, RecoverFromInvalidText);
+ cachedValue = null;
+ startElementDepth = -1;
+ }
+
+ RdpPattern ValidateTextOnlyCore ()
+ {
+ RdpPattern ts = memo.TextOnlyDeriv (vState);
+ ts = TextDeriv (ts, cachedValue, reader);
+ if (Util.IsWhitespace (cachedValue))
+ ts = vState.Choice (ts);
+ return ts;
+ }
+
+ MemoizationStore memo = new MemoizationStore ();
+ }
+
+ #region Memoization support
+ internal class MemoizationStore
+ {
+ Hashtable startOpen = new Hashtable ();
+ Hashtable startClose = new Hashtable ();
+ Hashtable startAtt = new Hashtable ();
+ Hashtable endTag = new Hashtable ();
+ Hashtable endAtt = new Hashtable ();
+ Hashtable textOnly = new Hashtable ();
+ Hashtable mixedText = new Hashtable ();
+ Hashtable emptyText = new Hashtable ();
+ Hashtable text = new Hashtable ();
+ Hashtable text_value = new Hashtable ();
+ Hashtable qnames = new Hashtable ();
+
+ enum DerivativeType {
+ StartTagOpen,
+ StartAtt,
+ StartTagClose,
+ EndTag,
+ EndAtt,
+ Mixed,
+ TextOnly
+ }
+
+ XmlQualifiedName GetQName (string local, string ns)
+ {
+ Hashtable nst = qnames [ns] as Hashtable;
+ if (nst == null) {
+ nst = new Hashtable ();
+ qnames [ns] = nst;
+ }
+ XmlQualifiedName qn = nst [local] as XmlQualifiedName;
+ if (qn == null) {
+ qn = new XmlQualifiedName (local, ns);
+ nst [local] = qn;
+ }
+ return qn;
+ }
+
+ public RdpPattern StartTagOpenDeriv (RdpPattern p, string local, string ns)
+ {
+ Hashtable h = startOpen [p] as Hashtable;
+ if (h == null) {
+ h = new Hashtable ();
+ startOpen [p] = h;
+ }
+ XmlQualifiedName qn = GetQName (local, ns);
+ RdpPattern m = h [qn] as RdpPattern;
+ if (m == null) {
+ m = p.StartTagOpenDeriv (local, ns, this);
+ h [qn] = m;
+ }
+ return m;
+ }
+
+ public RdpPattern StartAttDeriv (RdpPattern p, string local, string ns)
+ {
+ Hashtable h = startAtt [p] as Hashtable;
+ if (h == null) {
+ h = new Hashtable ();
+ startAtt [p] = h;
+ }
+ XmlQualifiedName qn = GetQName (local, ns);
+ RdpPattern m = h [qn] as RdpPattern;
+ if (m == null) {
+ m = p.StartAttDeriv (local, ns, this);
+ h [qn] = m;
+ }
+ return m;
+ }
+
+ public RdpPattern StartTagCloseDeriv (RdpPattern p)
+ {
+ RdpPattern m = startClose [p] as RdpPattern;
+ if (m != null)
+ return m;
+
+ m = p.StartTagCloseDeriv (this);
+ startClose [p] = m;
+ return m;
+ }
+
+ public RdpPattern EndTagDeriv (RdpPattern p)
+ {
+ RdpPattern m = endTag [p] as RdpPattern;
+ if (m != null)
+ return m;
+
+ m = p.EndTagDeriv (this);
+ endTag [p] = m;
+ return m;
+ }
+
+ public RdpPattern EndAttDeriv (RdpPattern p)
+ {
+ RdpPattern m = endAtt [p] as RdpPattern;
+ if (m != null)
+ return m;
+
+ m = p.EndAttDeriv (this);
+ endAtt [p] = m;
+ return m;
+ }
+
+ public RdpPattern MixedTextDeriv (RdpPattern p)
+ {
+ RdpPattern m = mixedText [p] as RdpPattern;
+ if (m != null)
+ return m;
+
+ m = p.MixedTextDeriv (this);
+ mixedText [p] = m;
+ return m;
+ }
+
+ public RdpPattern TextOnlyDeriv (RdpPattern p)
+ {
+ RdpPattern m = textOnly [p] as RdpPattern;
+ if (m != null)
+ return m;
+
+ m = p.TextOnlyDeriv (this);
+ textOnly [p] = m;
+ return m;
+ }
+
+ public RdpPattern TextDeriv (RdpPattern p, string value, XmlReader context)
+ {
+ if (p.IsContextDependent)
+ return p.TextDeriv (value, context);
+
+ if (Object.ReferenceEquals (text_value [p], value))
+ return text [p] as RdpPattern;
+ RdpPattern m = p.TextDeriv (value, context, this);
+ text_value [p] = value;
+ text [p] = m;
+ return m;
+ }
+
+ public RdpPattern EmptyTextDeriv (RdpPattern p)
+ {
+ RdpPattern m = emptyText [p] as RdpPattern;
+ if (m != null)
+ return m;
+
+ m = p.EmptyTextDeriv (this);
+ emptyText [p] = m;
+ return m;
+ }
+ }
+ #endregion
+}
+