-//\r
-// Commons.Xml.Relaxng.RelaxngGrammar.cs\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.IO;\r
-using System.Net;\r
-using System.Xml;\r
-using Commons.Xml.Relaxng.Derivative;\r
-using Commons.Xml.Relaxng.Rnc;\r
-\r
-namespace Commons.Xml.Relaxng\r
-{\r
- public class RelaxngGrammar : RelaxngPattern\r
- {\r
- // field\r
- public static string NamespaceURI =\r
- "http://relaxng.org/ns/structure/1.0";\r
-\r
- // object model fields\r
- string defaultNamespace;\r
- RelaxngGrammarContentList starts = new RelaxngGrammarContentList ();\r
- RelaxngGrammarContentList defs = new RelaxngGrammarContentList ();\r
- RelaxngGrammarContentList includes = new RelaxngGrammarContentList ();\r
- RelaxngGrammarContentList divs = new RelaxngGrammarContentList ();\r
-\r
- RelaxngDatatypeProvider provider;\r
-\r
- // compiled fields.\r
- RdpPattern startPattern;\r
-\r
- // compile cache fields.\r
- Hashtable assembledDefs = new Hashtable (); // [defName] = RelaxngDefine\r
- RelaxngPattern assembledStart;\r
- RdpPattern compiledStart;\r
- Hashtable elementReplacedDefs = new Hashtable ();\r
-\r
- Hashtable includedUris = new Hashtable ();\r
- RelaxngGrammar parentGrammar;\r
- Hashtable refPatterns = new Hashtable (); // key = RdpPattern of assembledDefs\r
-\r
- // only for checkRecursion()\r
- Hashtable checkedDefs = new Hashtable ();\r
-\r
- // this should be checked after its compilation finished to complete\r
- // missing-at-the-tracking patterns (especially of parent grammars).\r
- // key = RdpPattern, value = ArrayList of unresolvedPatterns.\r
- ArrayList unresolvedPatterns = new ArrayList ();\r
-\r
- // contents key = RdpElement and value = name of the parent define.\r
- private Hashtable ElementDefMap = new Hashtable ();\r
-\r
- // Public\r
-\r
- public RelaxngGrammar ()\r
- {\r
- }\r
-\r
- private void ResetCompileState ()\r
- {\r
- startPattern = null;\r
- assembledDefs.Clear ();\r
- assembledStart = null;\r
- compiledStart = null;\r
- elementReplacedDefs.Clear ();\r
- includedUris.Clear ();\r
- parentGrammar = null;\r
- refPatterns.Clear ();\r
- checkedDefs.Clear ();\r
- unresolvedPatterns.Clear ();\r
- ElementDefMap.Clear ();\r
- }\r
-\r
- internal RelaxngGrammar ParentGrammar {\r
- get { return parentGrammar; }\r
- set { parentGrammar = value; }\r
- }\r
-\r
- internal RelaxngDatatypeProvider Provider {\r
- get { return parentGrammar != null ? parentGrammar.Provider : provider; }\r
- set { provider = value; }\r
- }\r
-\r
- public override RelaxngPatternType PatternType {\r
- get { return RelaxngPatternType.Grammar; }\r
- }\r
-\r
- public string DefaultNamespace {\r
- get { return defaultNamespace; }\r
- set { defaultNamespace = value; }\r
- }\r
-\r
- public RelaxngGrammarContentList Starts {\r
- get { return starts; }\r
- }\r
-\r
- public RelaxngGrammarContentList Defines {\r
- get { return defs; }\r
- }\r
-\r
- public RelaxngGrammarContentList Includes {\r
- get { return includes; }\r
- }\r
-\r
- public RelaxngGrammarContentList Divs {\r
- get { return divs; }\r
- }\r
-\r
- public override void Write (XmlWriter writer)\r
- {\r
- writer.WriteStartElement ("", "grammar", RelaxngGrammar.NamespaceURI);\r
- if (defaultNamespace != null)\r
- writer.WriteAttributeString ("ns", defaultNamespace);\r
- foreach (RelaxngStart start in Starts)\r
- start.Write (writer);\r
- foreach (RelaxngDefine define in Defines)\r
- define.Write (writer);\r
- foreach (RelaxngInclude include in Includes)\r
- include.Write (writer);\r
- foreach (RelaxngDiv div in Divs)\r
- div.Write (writer);\r
- writer.WriteEndElement ();\r
- }\r
-\r
- internal override void WriteRnc (RncWriter writer)\r
- {\r
- writer.WriteGrammar (this);\r
- }\r
-\r
- internal Hashtable IncludedUris {\r
- get { return includedUris; }\r
- }\r
-\r
- // Internal\r
- internal override void CheckConstraints ()\r
- {\r
- // do nothing here.\r
- }\r
-\r
- internal void CheckIncludeRecursion (string href)\r
- {\r
- if (this.includedUris [href] != null)\r
- // FIXME: fill line info\r
- throw new RelaxngException ("Include recursion found. href: " + href);\r
- if (parentGrammar != null)\r
- parentGrammar.CheckIncludeRecursion (href);\r
- }\r
-\r
- // Compile from this simplified syntax to derivatives.\r
- internal override RdpPattern Compile (RelaxngGrammar grammar)\r
- {\r
- ResetCompileState ();\r
-\r
- parentGrammar = grammar;\r
-\r
- // First, process includes and divs. RELAX NG 4.1 - 4.15.\r
- ArrayList compiledDivs = new ArrayList ();\r
- foreach (RelaxngInclude inc in includes)\r
- compiledDivs.Add (inc.Compile (this));\r
- compiledDivs.AddRange (divs);\r
- foreach (RelaxngDiv div in compiledDivs)\r
- div.Compile (this);\r
-\r
- // Check constraints. RELAX NG 4.16\r
- foreach (RelaxngStart start in starts)\r
- start.Pattern.CheckConstraints ();\r
- foreach (RelaxngDefine define in defs)\r
- foreach (RelaxngPattern p in define.Patterns)\r
- p.CheckConstraints ();\r
-\r
- // Assemble combine into the same name defines/start.\r
- // see RELAX NG 4.17.\r
- AssembleCombine ();\r
-\r
- // 4.18 : <grammar> must have at least one <start>.\r
- if (assembledStart == null)\r
- throw new RelaxngException ("A grammar elements must contain at least one start element.");\r
- compiledStart = assembledStart.Compile (this);\r
-\r
- // Assemble all define components into top grammar and\r
- // return start patterns for descendant grammars.\r
- // see RELAX NG 4.18.\r
- CollectGrammars ();\r
- if (parentGrammar != null)\r
- return compiledStart;\r
- assembledStart = null; // no use anymore\r
-\r
- // 4.19 (a) remove non-reachable defines\r
-/*\r
- compiledStart.MarkReachableDefs ();\r
- ArrayList tmp = new ArrayList ();\r
- foreach (DictionaryEntry entry in this.assembledDefs)\r
- if (!reachableDefines.ContainsKey (entry.Key))\r
- tmp.Add (entry.Key);\r
- foreach (string key in tmp)\r
- assembledDefs.Remove (key);\r
-*/\r
- // 4.19 (b) check illegal recursion\r
- CheckRecursion (compiledStart, 0);\r
- // here we collected element-replaced definitions\r
- foreach (DictionaryEntry entry in elementReplacedDefs)\r
- assembledDefs.Add (entry.Key, entry.Value);\r
- startPattern = compiledStart;\r
- // 4.20,21 reduce notAllowed and empty.\r
- bool b;\r
- do {\r
- b = false;\r
- startPattern = startPattern.ReduceEmptyAndNotAllowed (ref b, new Hashtable ());\r
- } while (b);\r
-\r
- Hashtable ht = new Hashtable ();\r
- startPattern.setInternTable (ht);\r
- RdpNotAllowed.Instance.setInternTable (ht);\r
- RdpEmpty.Instance.setInternTable (ht);\r
- RdpText.Instance.setInternTable (ht);\r
-\r
- // Check Constraints: RELAX NG spec 7\r
- // 7.1.1-4, 7.3, 7.4\r
- startPattern.CheckConstraints (false, false, false, false, false, false);\r
- // 7.1.5\r
- CheckStartPatternContent (startPattern);\r
-\r
- // 4.19 (c) expandRef - actual replacement\r
- startPattern = compiledStart.ExpandRef (assembledDefs);\r
-\r
- // 7.2\r
- RdpContentType ct = startPattern.ContentType;\r
-\r
- // return its start pattern.\r
- IsCompiled = true;\r
- return startPattern;\r
- }\r
-\r
- private void CheckStartPatternContent (RdpPattern p)\r
- {\r
- switch (p.PatternType) {\r
- case RelaxngPatternType.Ref:\r
- CheckStartPatternContent (((RdpUnresolvedRef) p).RefPattern);\r
- break;\r
- case RelaxngPatternType.Element:\r
- break;\r
- case RelaxngPatternType.Choice:\r
- RdpChoice c = p as RdpChoice;\r
- CheckStartPatternContent (c.LValue);\r
- CheckStartPatternContent (c.RValue);\r
- break;\r
- case RelaxngPatternType.NotAllowed:\r
- break;\r
- default:\r
- // FIXME: fill line info\r
- throw new RelaxngException ("Start pattern contains an invalid content pattern.");\r
- }\r
- }\r
-\r
- Hashtable reachableDefines = new Hashtable ();\r
-\r
- // for step 4.19\r
- internal void MarkReacheableDefine (string name)\r
- {\r
- if (reachableDefines.ContainsKey (name))\r
- return;\r
- RdpPattern p = assembledDefs [name] as RdpPattern;\r
- reachableDefines.Add (name, p);\r
- p.MarkReachableDefs ();\r
- }\r
-\r
- // 4.19 (b)\r
- private void CheckRecursion (RdpPattern p, int depth)\r
- {\r
-\r
- RdpAbstractBinary binary = p as RdpAbstractBinary;\r
- if (binary != null) {\r
- // choice, interleave, group\r
- CheckRecursion (binary.LValue, depth);\r
- CheckRecursion (binary.RValue, depth);\r
- return;\r
- }\r
- RdpAbstractSingleContent single = p as RdpAbstractSingleContent;\r
- if (single != null) {\r
- CheckRecursion (single.Child, depth);\r
- return;\r
- }\r
-\r
- switch (p.PatternType) {\r
- case RelaxngPatternType.Ref:\r
- // get checkRecursionDepth from table.\r
- int checkRecursionDepth = -1;\r
- object checkedDepth = checkedDefs [p];\r
- if (checkedDepth != null)\r
- checkRecursionDepth = (int) checkedDepth;\r
- // get refPattern\r
- RdpUnresolvedRef pref = p as RdpUnresolvedRef;\r
- RelaxngGrammar target = pref.TargetGrammar;\r
- RdpPattern refPattern = pref.RefPattern;\r
- if (refPattern == null)\r
- // FIXME: fill line info\r
- throw new RelaxngException ("No matching define found for " + pref.Name);\r
-\r
- if (checkRecursionDepth == -1) {\r
- checkedDefs [p] = depth;\r
-/*test*/ if (refPattern.PatternType != RelaxngPatternType.Element)\r
- CheckRecursion (refPattern, depth);\r
- checkedDefs [p] = -2;\r
- }\r
- else if (depth == checkRecursionDepth)\r
- // FIXME: fill line info\r
- throw new RelaxngException (String.Format ("Detected illegal recursion. Ref name is {0}.", pref.Name));\r
-\r
- break;\r
-\r
- case RelaxngPatternType.Attribute:\r
- CheckRecursion (((RdpAttribute) p).Children, depth);\r
- break;\r
-\r
- case RelaxngPatternType.DataExcept:\r
- CheckRecursion (((RdpDataExcept) p).Except, depth);\r
- break;\r
-\r
- case RelaxngPatternType.Element:\r
- RdpElement el = p as RdpElement;\r
- CheckRecursion (el.Children, depth + 1); // +1\r
- break;\r
- case RelaxngPatternType.List:\r
- CheckRecursion (((RdpList) p).Child, depth);\r
- break;\r
- }\r
- }\r
-\r
- // 4.18\r
- private void CollectGrammars ()\r
- {\r
- // collect ref and parentRef for each define.\r
-\r
- // FIXME: This should be assembledStart.\r
- CheckReferences (compiledStart);\r
- FixupReference ();\r
-\r
- foreach (string name in assembledDefs.Keys) {\r
- RdpPattern p = (RdpPattern) assembledDefs [name];\r
- CheckReferences (p);\r
- FixupReference ();\r
- }\r
-\r
- // If it is child of any other pattern:\r
- // * Remove all definitions under descendant grammars,\r
- // replacing ref names, and\r
- // * Then return its start pattern.\r
- if (parentGrammar != null) {\r
- // TODO: reachable check is incomplete.\r
- foreach (string name in assembledDefs.Keys) {\r
- ArrayList al = \r
- refPatterns [assembledDefs [name] ] as ArrayList;\r
- if (al == null)\r
- continue; // Not referenced.\r
-\r
- // At this point, parent grammar doesn't \r
- // collect assembledDefs as yet\r
- string uname = GetUniqueName (name, parentGrammar);\r
- parentGrammar.assembledDefs [uname] = assembledDefs [name];\r
- }\r
- }\r
- }\r
-\r
- private static string GetUniqueName (string name, RelaxngGrammar grammar)\r
- {\r
- foreach (RelaxngDefine def in grammar.Defines)\r
- if (def.Name == name)\r
- return GetUniqueName (name + '_', grammar);\r
- return name;\r
- }\r
-\r
- private void FixupReference ()\r
- {\r
- foreach (RdpUnresolvedRef pref in this.unresolvedPatterns) {\r
- RdpPattern defP = assembledDefs [pref.Name] as RdpPattern;\r
- if (defP == null)\r
- // FIXME: fill line info\r
- throw new RelaxngException (String.Format ("Target definition was not found: {0}", pref.Name));\r
- ArrayList al = refPatterns [defP] as ArrayList;\r
- if (al == null) {\r
- al = new ArrayList ();\r
- refPatterns [defP] = al;\r
- }\r
- al.Add (pref);\r
- }\r
- this.unresolvedPatterns.Clear ();\r
- }\r
-\r
- private void replaceDefines (string name, ArrayList al)\r
- {\r
- int idx = 0;\r
- while (true) {\r
- string newName = "define" + idx;\r
- if (parentGrammar.assembledDefs [newName] == null) {\r
- parentGrammar.assembledDefs [newName] = \r
- assembledDefs [name];\r
- foreach (RdpUnresolvedRef pref in al)\r
- pref.Name = newName;\r
- break;\r
- }\r
- idx++;\r
- }\r
- }\r
-\r
- // remove ref and parentRef.\r
- // add new defines for each elements.\r
- private void CheckReferences (RdpPattern p)\r
- {\r
- RdpAbstractBinary binary = p as RdpAbstractBinary;\r
- if (binary != null) {\r
- // choice, interleave, group\r
- CheckReferences (binary.LValue);\r
- CheckReferences (binary.RValue);\r
- return;\r
- }\r
- RdpAbstractSingleContent single = p as RdpAbstractSingleContent;\r
- if (single != null) {\r
- CheckReferences (single.Child);\r
- return;\r
- }\r
-\r
- switch (p.PatternType) {\r
- case RelaxngPatternType.Ref:\r
- // FIXME: This should not re-expand ref\r
- RdpUnresolvedRef pref = p as RdpUnresolvedRef;\r
- if (pref.RefPattern != null)\r
- break;\r
-\r
- RelaxngGrammar target = pref.TargetGrammar;\r
- if (target == null)\r
- // FIXME: fill line info\r
- throw new RelaxngException ("Referenced definition was not found.");\r
- RdpPattern defP = target.assembledDefs [pref.Name] as RdpPattern;\r
- if (defP == null)\r
- target.unresolvedPatterns.Add (p);\r
- else {\r
- ArrayList al = target.refPatterns [defP] as ArrayList;\r
- if (al == null) {\r
- al = new ArrayList ();\r
- target.refPatterns [defP] = al;\r
- }\r
- al.Add (p);\r
- pref.RefPattern = defP;\r
- }\r
- break;\r
-\r
- case RelaxngPatternType.Attribute:\r
- CheckReferences (((RdpAttribute) p).Children);\r
- break;\r
-\r
- case RelaxngPatternType.DataExcept:\r
- CheckReferences (((RdpDataExcept) p).Except);\r
- break;\r
-\r
- case RelaxngPatternType.Element:\r
- RdpElement el = p as RdpElement;\r
- CheckReferences (el.Children);\r
- string name = ElementDefMap [el] as string;\r
- if (name == null) {\r
- // add new define\r
- int idx = 0;\r
- string newName = "element0";\r
- if (el.NameClass is RdpName)\r
- newName = ((RdpName) el.NameClass).LocalName;\r
- while (true) {\r
- if (assembledDefs [newName] == null) {\r
- elementReplacedDefs [newName] = el.Children;\r
- break;\r
- }\r
- newName = "element" + ++idx;\r
- }\r
- ElementDefMap [el] = newName;\r
- }\r
- // Even though the element is replaced with ref,\r
- // derivative of ref is RdpElement in fact...\r
- break;\r
-\r
- case RelaxngPatternType.List:\r
- CheckReferences (((RdpList) p).Child);\r
- break;\r
-\r
- case RelaxngPatternType.Empty:\r
- case RelaxngPatternType.NotAllowed:\r
- case RelaxngPatternType.Text:\r
- case RelaxngPatternType.Value:\r
- break;\r
-\r
- //case RelaxngPatternType.ExternalRef:\r
- //case RelaxngPatternType.Include:\r
- // Mixed, Optional, ZeroOrMore are already removed.\r
- // Choice, Group, Interleave, OneOrMore are already proceeded.\r
- }\r
- }\r
-\r
- #region 4.17 - Combine\r
- private void AssembleCombine ()\r
- {\r
- // calculate combines.\r
- bool haveHeadStart = false;\r
- string combineStart = null;\r
- Hashtable haveHeadDefs = new Hashtable ();\r
- Hashtable combineDefs = new Hashtable ();\r
-\r
- // 1.calculate combine for starts.\r
- foreach (RelaxngStart start in starts)\r
- CheckCombine (ref haveHeadStart, \r
- ref combineStart, start.Combine, "start");\r
- // 2.calculate combine for defines.\r
- foreach (RelaxngDefine def in defs) {\r
- bool haveHead = \r
- haveHeadDefs.ContainsKey (def.Name) ?\r
- haveHead = (bool) haveHeadDefs [def.Name]\r
- : false;\r
- string combine = combineDefs [def.Name] as string;\r
- CheckCombine (ref haveHead, ref combine,\r
- def.Combine, String.Format ("define name={0}", def.Name));\r
- haveHeadDefs [def.Name] = haveHead;\r
- combineDefs [def.Name] = combine;\r
- continue;\r
- }\r
-\r
- // assemble starts and defines with "combine" attribute.\r
-\r
- // 3.assemble starts.\r
- if (starts.Count == 0) {\r
- if (ParentGrammar == null)\r
- throw new RelaxngException (this, "grammar must have at least one start component.");\r
- } else {\r
- assembledStart = ((RelaxngStart)starts [0]).Pattern;\r
- for (int i=1; i<starts.Count; i++) {\r
- RelaxngPattern p2 = ((RelaxngStart) starts [i]).Pattern;;\r
- if (combineStart == "interleave") {\r
- RelaxngInterleave intlv = new RelaxngInterleave ();\r
- intlv.Patterns.Add (assembledStart);\r
- intlv.Patterns.Add (p2);\r
- assembledStart = intlv;\r
- } else {\r
- RelaxngChoice c = new RelaxngChoice ();\r
- c.Patterns.Add (assembledStart);\r
- c.Patterns.Add (p2);\r
- assembledStart = c;\r
- }\r
- }\r
- }\r
-\r
- // 4.assemble defines\r
- foreach (RelaxngDefine def in defs) {\r
- string combine = combineDefs [def.Name] as string;\r
- RdpPattern p1 = \r
- assembledDefs [def.Name] as RdpPattern;\r
- RdpPattern p2 = def.Compile (this);\r
- if (p1 != null) {\r
- if (combine == "interleave") {\r
- assembledDefs [def.Name] =\r
- new RdpInterleave (p1, p2);\r
- } else {\r
- assembledDefs [def.Name] =\r
- new RdpChoice (p1, p2);\r
- }\r
- } else {\r
- assembledDefs [def.Name] = p2;\r
- }\r
- }\r
-\r
- }\r
-\r
- // check combine attributes.\r
- private void CheckCombine (ref bool haveHead, ref string combine, string newCombine, string targetSpec)\r
- {\r
- switch (newCombine) {\r
- case "interleave":\r
- if (combine == "choice")\r
- throw new RelaxngException (this, "\"combine\" was already specified \"choice\"");\r
- else\r
- combine = "interleave";\r
- break;\r
- case "choice":\r
- if (combine == "interleave")\r
- throw new RelaxngException (this, "\"combine\" was already specified \"interleave\"");\r
- else\r
- combine = "choice";\r
- break;\r
- case null:\r
- if (haveHead)\r
- throw new RelaxngException (this, String.Format ("There was already \"{0}\" element without \"combine\" attribute.", targetSpec));\r
- haveHead = true;\r
- break;\r
- }\r
- }\r
- #endregion\r
- }\r
-}\r
+//
+// Commons.Xml.Relaxng.RelaxngGrammar.cs
+//
+// Author:
+// Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
+//
+// 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.IO;
+using System.Net;
+using System.Xml;
+using Commons.Xml.Relaxng.Derivative;
+using Commons.Xml.Relaxng.Rnc;
+
+namespace Commons.Xml.Relaxng
+{
+ public class RelaxngGrammar : RelaxngPattern
+ {
+ // field
+ public static string NamespaceURI =
+ "http://relaxng.org/ns/structure/1.0";
+
+ // Parser condition: it is used to resolve "included" source
+ bool isSourceRnc;
+
+ // object model fields
+ string defaultNamespace;
+ RelaxngGrammarContentList starts = new RelaxngGrammarContentList ();
+ RelaxngGrammarContentList defs = new RelaxngGrammarContentList ();
+ RelaxngGrammarContentList includes = new RelaxngGrammarContentList ();
+ RelaxngGrammarContentList divs = new RelaxngGrammarContentList ();
+
+ // compiled fields.
+ RdpPattern startPattern;
+
+ // compile cache fields.
+ Hashtable assembledDefs = new Hashtable (); // [defName] = RelaxngDefine
+ RelaxngPattern assembledStart;
+ RdpPattern compiledStart;
+ Hashtable elementReplacedDefs = new Hashtable ();
+
+ Hashtable includedUris = new Hashtable ();
+ RelaxngGrammar parentGrammar;
+ Hashtable refPatterns = new Hashtable (); // key = RdpPattern of assembledDefs
+
+ // only for checkRecursion()
+ Hashtable checkedDefs = new Hashtable ();
+
+ // this should be checked after its compilation finished to complete
+ // missing-at-the-tracking patterns (especially of parent grammars).
+ // key = RdpPattern, value = ArrayList of unresolvedPatterns.
+ ArrayList unresolvedPatterns = new ArrayList ();
+
+ // contents key = RdpElement and value = name of the parent define.
+ private Hashtable ElementDefMap = new Hashtable ();
+
+ // Public
+
+ public RelaxngGrammar ()
+ {
+ }
+
+ private void ResetCompileState ()
+ {
+ startPattern = null;
+ assembledDefs.Clear ();
+ assembledStart = null;
+ compiledStart = null;
+ elementReplacedDefs.Clear ();
+ includedUris.Clear ();
+ parentGrammar = null;
+ refPatterns.Clear ();
+ checkedDefs.Clear ();
+ unresolvedPatterns.Clear ();
+ ElementDefMap.Clear ();
+ }
+
+ internal bool IsSourceCompactSyntax {
+ get { return isSourceRnc; }
+ set { isSourceRnc = value; }
+ }
+
+ internal RelaxngGrammar ParentGrammar {
+ get { return parentGrammar; }
+ set { parentGrammar = value; }
+ }
+
+ internal RelaxngDatatypeProvider Provider {
+ get { return (base.DataProvider == null) ? (parentGrammar != null ? parentGrammar.Provider : base.DataProvider) : base.DataProvider; }
+ set { base.DataProvider = value; }
+ }
+
+ public override RelaxngPatternType PatternType {
+ get { return RelaxngPatternType.Grammar; }
+ }
+
+ public string DefaultNamespace {
+ get { return defaultNamespace; }
+ set { defaultNamespace = value; }
+ }
+
+ public RelaxngGrammarContentList Starts {
+ get { return starts; }
+ }
+
+ public RelaxngGrammarContentList Defines {
+ get { return defs; }
+ }
+
+ public RelaxngGrammarContentList Includes {
+ get { return includes; }
+ }
+
+ public RelaxngGrammarContentList Divs {
+ get { return divs; }
+ }
+
+ public override void Write (XmlWriter writer)
+ {
+ writer.WriteStartElement ("", "grammar", RelaxngGrammar.NamespaceURI);
+ if (defaultNamespace != null)
+ writer.WriteAttributeString ("ns", defaultNamespace);
+ foreach (RelaxngStart start in Starts)
+ start.Write (writer);
+ foreach (RelaxngDefine define in Defines)
+ define.Write (writer);
+ foreach (RelaxngInclude include in Includes)
+ include.Write (writer);
+ foreach (RelaxngDiv div in Divs)
+ div.Write (writer);
+ writer.WriteEndElement ();
+ }
+
+ internal override void WriteRnc (RncWriter writer)
+ {
+ writer.WriteGrammar (this);
+ }
+
+ internal Hashtable IncludedUris {
+ get { return includedUris; }
+ }
+
+ // Internal
+ internal override void CheckConstraints ()
+ {
+ // do nothing here.
+ }
+
+ internal void CheckIncludeRecursion (string href)
+ {
+ if (this.includedUris [href] != null)
+ // FIXME: fill line info
+ throw new RelaxngException ("Include recursion found. href: " + href);
+ if (parentGrammar != null)
+ parentGrammar.CheckIncludeRecursion (href);
+ }
+
+ // Compile from this simplified syntax to derivatives.
+ internal override RdpPattern Compile (RelaxngGrammar grammar)
+ {
+ ResetCompileState ();
+
+ parentGrammar = grammar;
+
+ // First, process includes and divs. RELAX NG 4.1 - 4.15.
+ ArrayList compiledDivs = new ArrayList ();
+ foreach (RelaxngInclude inc in includes)
+ compiledDivs.Add (inc.Compile (this));
+ compiledDivs.AddRange (divs);
+ foreach (RelaxngDiv div in compiledDivs)
+ div.Compile (this);
+
+ // Check constraints. RELAX NG 4.16
+ foreach (RelaxngStart start in starts)
+ start.Pattern.CheckConstraints ();
+ foreach (RelaxngDefine define in defs)
+ foreach (RelaxngPattern p in define.Patterns)
+ p.CheckConstraints ();
+
+ // Assemble combine into the same name defines/start.
+ // see RELAX NG 4.17.
+ AssembleCombine ();
+
+ // 4.18 : <grammar> must have at least one <start>.
+ if (assembledStart == null)
+ throw new RelaxngException ("A grammar elements must contain at least one start element.");
+ compiledStart = assembledStart.Compile (this);
+
+ // Assemble all define components into top grammar and
+ // return start patterns for descendant grammars.
+ // see RELAX NG 4.18.
+ CollectGrammars ();
+ if (parentGrammar != null)
+ return compiledStart;
+ assembledStart = null; // no use anymore
+
+ // 4.19 (a) remove non-reachable defines
+/*
+ compiledStart.MarkReachableDefs ();
+ ArrayList tmp = new ArrayList ();
+ foreach (DictionaryEntry entry in this.assembledDefs)
+ if (!reachableDefines.ContainsKey (entry.Key))
+ tmp.Add (entry.Key);
+ foreach (string key in tmp)
+ assembledDefs.Remove (key);
+*/
+ // 4.19 (b) check illegal recursion
+ CheckRecursion (compiledStart, 0);
+ // here we collected element-replaced definitions
+ foreach (DictionaryEntry entry in elementReplacedDefs)
+ assembledDefs.Add (entry.Key, entry.Value);
+ startPattern = compiledStart;
+ // 4.20,21 reduce notAllowed and empty.
+ bool b;
+ do {
+ b = false;
+ startPattern = startPattern.ReduceEmptyAndNotAllowed (ref b, new Hashtable ());
+ } while (b);
+
+ Hashtable ht = new Hashtable ();
+ startPattern.setInternTable (ht);
+
+ // Check Constraints: RELAX NG spec 7
+ // 7.1.1-4, 7.3, 7.4
+ startPattern.CheckConstraints (false, false, false, false, false, false);
+ // 7.1.5
+ CheckStartPatternContent (startPattern);
+
+ // 4.19 (c) expandRef - actual replacement
+ startPattern = compiledStart.ExpandRef (assembledDefs);
+
+ // 7.2
+ RdpContentType ct = startPattern.ContentType;
+
+ // return its start pattern.
+ IsCompiled = true;
+ return startPattern;
+ }
+
+ private void CheckStartPatternContent (RdpPattern p)
+ {
+ switch (p.PatternType) {
+ case RelaxngPatternType.Ref:
+ CheckStartPatternContent (((RdpUnresolvedRef) p).RefPattern);
+ break;
+ case RelaxngPatternType.Element:
+ break;
+ case RelaxngPatternType.Choice:
+ RdpChoice c = p as RdpChoice;
+ CheckStartPatternContent (c.LValue);
+ CheckStartPatternContent (c.RValue);
+ break;
+ case RelaxngPatternType.NotAllowed:
+ break;
+ default:
+ // FIXME: fill line info
+ throw new RelaxngException ("Start pattern contains an invalid content pattern.");
+ }
+ }
+
+ Hashtable reachableDefines = new Hashtable ();
+
+ // for step 4.19
+ internal void MarkReacheableDefine (string name)
+ {
+ if (reachableDefines.ContainsKey (name))
+ return;
+ RdpPattern p = assembledDefs [name] as RdpPattern;
+ reachableDefines.Add (name, p);
+ p.MarkReachableDefs ();
+ }
+
+ // 4.19 (b)
+ private void CheckRecursion (RdpPattern p, int depth)
+ {
+
+ RdpAbstractBinary binary = p as RdpAbstractBinary;
+ if (binary != null) {
+ // choice, interleave, group
+ CheckRecursion (binary.LValue, depth);
+ CheckRecursion (binary.RValue, depth);
+ return;
+ }
+ RdpAbstractSingleContent single = p as RdpAbstractSingleContent;
+ if (single != null) {
+ CheckRecursion (single.Child, depth);
+ return;
+ }
+
+ switch (p.PatternType) {
+ case RelaxngPatternType.Ref:
+ // get checkRecursionDepth from table.
+ int checkRecursionDepth = -1;
+ object checkedDepth = checkedDefs [p];
+ if (checkedDepth != null)
+ checkRecursionDepth = (int) checkedDepth;
+ // get refPattern
+ RdpUnresolvedRef pref = p as RdpUnresolvedRef;
+ RelaxngGrammar target = pref.TargetGrammar;
+ RdpPattern refPattern = pref.RefPattern;
+ if (refPattern == null)
+ // FIXME: fill line info
+ throw new RelaxngException ("No matching define found for " + pref.Name);
+
+ if (checkRecursionDepth == -1) {
+ checkedDefs [p] = depth;
+/*test*/ if (refPattern.PatternType != RelaxngPatternType.Element)
+ CheckRecursion (refPattern, depth);
+ checkedDefs [p] = -2;
+ }
+ else if (depth == checkRecursionDepth)
+ // FIXME: fill line info
+ throw new RelaxngException (String.Format ("Detected illegal recursion. Ref name is {0}.", pref.Name));
+
+ break;
+
+ case RelaxngPatternType.Attribute:
+ CheckRecursion (((RdpAttribute) p).Children, depth);
+ break;
+
+ case RelaxngPatternType.DataExcept:
+ CheckRecursion (((RdpDataExcept) p).Except, depth);
+ break;
+
+ case RelaxngPatternType.Element:
+ RdpElement el = p as RdpElement;
+ CheckRecursion (el.Children, depth + 1); // +1
+ break;
+ case RelaxngPatternType.List:
+ CheckRecursion (((RdpList) p).Child, depth);
+ break;
+ }
+ }
+
+ // 4.18
+ private void CollectGrammars ()
+ {
+ // collect ref and parentRef for each define.
+
+ // FIXME: This should be assembledStart.
+ CheckReferences (compiledStart);
+ FixupReference ();
+
+ foreach (string name in assembledDefs.Keys) {
+ RdpPattern p = (RdpPattern) assembledDefs [name];
+ CheckReferences (p);
+ FixupReference ();
+ }
+
+ // If it is child of any other pattern:
+ // * Remove all definitions under descendant grammars,
+ // replacing ref names, and
+ // * Then return its start pattern.
+ if (parentGrammar != null) {
+ // TODO: reachable check is incomplete.
+ foreach (string name in assembledDefs.Keys) {
+ ArrayList al =
+ refPatterns [assembledDefs [name] ] as ArrayList;
+ if (al == null)
+ continue; // Not referenced.
+
+ // At this point, parent grammar doesn't
+ // collect assembledDefs as yet
+ string uname = GetUniqueName (name, parentGrammar);
+ parentGrammar.assembledDefs [uname] = assembledDefs [name];
+ }
+ }
+ }
+
+ private static string GetUniqueName (string name, RelaxngGrammar grammar)
+ {
+ foreach (RelaxngDefine def in grammar.Defines)
+ if (def.Name == name)
+ return GetUniqueName (name + '_', grammar);
+ return name;
+ }
+
+ private void FixupReference ()
+ {
+ foreach (RdpUnresolvedRef pref in this.unresolvedPatterns) {
+ RdpPattern defP = assembledDefs [pref.Name] as RdpPattern;
+ if (defP == null)
+ // FIXME: fill line info
+ throw new RelaxngException (String.Format ("Target definition was not found: {0}", pref.Name));
+ ArrayList al = refPatterns [defP] as ArrayList;
+ if (al == null) {
+ al = new ArrayList ();
+ refPatterns [defP] = al;
+ }
+ al.Add (pref);
+ }
+ this.unresolvedPatterns.Clear ();
+ }
+
+ private void replaceDefines (string name, ArrayList al)
+ {
+ int idx = 0;
+ while (true) {
+ string newName = "define" + idx;
+ if (parentGrammar.assembledDefs [newName] == null) {
+ parentGrammar.assembledDefs [newName] =
+ assembledDefs [name];
+ foreach (RdpUnresolvedRef pref in al)
+ pref.Name = newName;
+ break;
+ }
+ idx++;
+ }
+ }
+
+ // remove ref and parentRef.
+ // add new defines for each elements.
+ private void CheckReferences (RdpPattern p)
+ {
+ RdpAbstractBinary binary = p as RdpAbstractBinary;
+ if (binary != null) {
+ // choice, interleave, group
+ CheckReferences (binary.LValue);
+ CheckReferences (binary.RValue);
+ return;
+ }
+ RdpAbstractSingleContent single = p as RdpAbstractSingleContent;
+ if (single != null) {
+ CheckReferences (single.Child);
+ return;
+ }
+
+ switch (p.PatternType) {
+ case RelaxngPatternType.Ref:
+ // FIXME: This should not re-expand ref
+ RdpUnresolvedRef pref = p as RdpUnresolvedRef;
+ if (pref.RefPattern != null)
+ break;
+
+ RelaxngGrammar target = pref.TargetGrammar;
+ if (target == null)
+ // FIXME: fill line info
+ throw new RelaxngException ("Referenced definition was not found.");
+ RdpPattern defP = target.assembledDefs [pref.Name] as RdpPattern;
+ if (defP == null)
+ target.unresolvedPatterns.Add (p);
+ else {
+ ArrayList al = target.refPatterns [defP] as ArrayList;
+ if (al == null) {
+ al = new ArrayList ();
+ target.refPatterns [defP] = al;
+ }
+ al.Add (p);
+ pref.RefPattern = defP;
+ }
+ break;
+
+ case RelaxngPatternType.Attribute:
+ CheckReferences (((RdpAttribute) p).Children);
+ break;
+
+ case RelaxngPatternType.DataExcept:
+ CheckReferences (((RdpDataExcept) p).Except);
+ break;
+
+ case RelaxngPatternType.Element:
+ RdpElement el = p as RdpElement;
+ CheckReferences (el.Children);
+ string name = ElementDefMap [el] as string;
+ if (name == null) {
+ // add new define
+ int idx = 0;
+ string newName = "element0";
+ if (el.NameClass is RdpName)
+ newName = ((RdpName) el.NameClass).LocalName;
+ while (true) {
+ if (assembledDefs [newName] == null) {
+ elementReplacedDefs [newName] = el.Children;
+ break;
+ }
+ newName = "element" + ++idx;
+ }
+ ElementDefMap [el] = newName;
+ }
+ // Even though the element is replaced with ref,
+ // derivative of ref is RdpElement in fact...
+ break;
+
+ case RelaxngPatternType.List:
+ CheckReferences (((RdpList) p).Child);
+ break;
+
+ case RelaxngPatternType.Empty:
+ case RelaxngPatternType.NotAllowed:
+ case RelaxngPatternType.Text:
+ case RelaxngPatternType.Value:
+ break;
+
+ //case RelaxngPatternType.ExternalRef:
+ //case RelaxngPatternType.Include:
+ // Mixed, Optional, ZeroOrMore are already removed.
+ // Choice, Group, Interleave, OneOrMore are already proceeded.
+ }
+ }
+
+ #region 4.17 - Combine
+ private void AssembleCombine ()
+ {
+ // calculate combines.
+ bool haveHeadStart = false;
+ string combineStart = null;
+ Hashtable haveHeadDefs = new Hashtable ();
+ Hashtable combineDefs = new Hashtable ();
+
+ // 1.calculate combine for starts.
+ foreach (RelaxngStart start in starts)
+ CheckCombine (ref haveHeadStart,
+ ref combineStart, start.Combine, "start");
+ // 2.calculate combine for defines.
+ foreach (RelaxngDefine def in defs) {
+ bool haveHead =
+ haveHeadDefs.ContainsKey (def.Name) ?
+ haveHead = (bool) haveHeadDefs [def.Name]
+ : false;
+ string combine = combineDefs [def.Name] as string;
+ CheckCombine (ref haveHead, ref combine,
+ def.Combine, String.Format ("define name={0}", def.Name));
+ haveHeadDefs [def.Name] = haveHead;
+ combineDefs [def.Name] = combine;
+ continue;
+ }
+
+ // assemble starts and defines with "combine" attribute.
+
+ // 3.assemble starts.
+ if (starts.Count == 0) {
+ if (ParentGrammar == null)
+ throw new RelaxngException (this, "grammar must have at least one start component.");
+ } else {
+ assembledStart = ((RelaxngStart)starts [0]).Pattern;
+ for (int i=1; i<starts.Count; i++) {
+ RelaxngPattern p2 = ((RelaxngStart) starts [i]).Pattern;;
+ if (combineStart == "interleave") {
+ RelaxngInterleave intlv = new RelaxngInterleave ();
+ intlv.Patterns.Add (assembledStart);
+ intlv.Patterns.Add (p2);
+ assembledStart = intlv;
+ } else {
+ RelaxngChoice c = new RelaxngChoice ();
+ c.Patterns.Add (assembledStart);
+ c.Patterns.Add (p2);
+ assembledStart = c;
+ }
+ }
+ }
+
+ // 4.assemble defines
+ foreach (RelaxngDefine def in defs) {
+ string combine = combineDefs [def.Name] as string;
+ RdpPattern p1 =
+ assembledDefs [def.Name] as RdpPattern;
+ RdpPattern p2 = def.Compile (this);
+ if (p1 != null) {
+ if (combine == "interleave") {
+ assembledDefs [def.Name] =
+ new RdpInterleave (p1, p2);
+ } else {
+ assembledDefs [def.Name] =
+ new RdpChoice (p1, p2);
+ }
+ } else {
+ assembledDefs [def.Name] = p2;
+ }
+ }
+
+ }
+
+ // check combine attributes.
+ private void CheckCombine (ref bool haveHead, ref string combine, string newCombine, string targetSpec)
+ {
+ switch (newCombine) {
+ case "interleave":
+ if (combine == "choice")
+ throw new RelaxngException (this, "\"combine\" was already specified \"choice\"");
+ else
+ combine = "interleave";
+ break;
+ case "choice":
+ if (combine == "interleave")
+ throw new RelaxngException (this, "\"combine\" was already specified \"interleave\"");
+ else
+ combine = "choice";
+ break;
+ case null:
+ if (haveHead)
+ throw new RelaxngException (this, String.Format ("There was already \"{0}\" element without \"combine\" attribute.", targetSpec));
+ haveHead = true;
+ break;
+ }
+ }
+ #endregion
+ }
+}