2 // Commons.Xml.Relaxng.RelaxngGrammar.cs
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;
38 using Commons.Xml.Relaxng.Derivative;
39 using Commons.Xml.Relaxng.Rnc;
41 namespace Commons.Xml.Relaxng
43 public class RelaxngGrammar : RelaxngPattern
46 public static string NamespaceURI =
47 "http://relaxng.org/ns/structure/1.0";
49 // object model fields
50 string defaultNamespace;
51 RelaxngGrammarContentList starts = new RelaxngGrammarContentList ();
52 RelaxngGrammarContentList defs = new RelaxngGrammarContentList ();
53 RelaxngGrammarContentList includes = new RelaxngGrammarContentList ();
54 RelaxngGrammarContentList divs = new RelaxngGrammarContentList ();
56 RelaxngDatatypeProvider provider;
59 RdpPattern startPattern;
61 // compile cache fields.
62 Hashtable assembledDefs = new Hashtable (); // [defName] = RelaxngDefine
63 RelaxngPattern assembledStart;
64 RdpPattern compiledStart;
65 Hashtable elementReplacedDefs = new Hashtable ();
67 Hashtable includedUris = new Hashtable ();
68 RelaxngGrammar parentGrammar;
69 Hashtable refPatterns = new Hashtable (); // key = RdpPattern of assembledDefs
71 // only for checkRecursion()
72 Hashtable checkedDefs = new Hashtable ();
74 // this should be checked after its compilation finished to complete
75 // missing-at-the-tracking patterns (especially of parent grammars).
76 // key = RdpPattern, value = ArrayList of unresolvedPatterns.
77 ArrayList unresolvedPatterns = new ArrayList ();
79 // contents key = RdpElement and value = name of the parent define.
80 private Hashtable ElementDefMap = new Hashtable ();
84 public RelaxngGrammar ()
88 private void ResetCompileState ()
91 assembledDefs.Clear ();
92 assembledStart = null;
94 elementReplacedDefs.Clear ();
95 includedUris.Clear ();
99 unresolvedPatterns.Clear ();
100 ElementDefMap.Clear ();
103 internal RelaxngGrammar ParentGrammar {
104 get { return parentGrammar; }
105 set { parentGrammar = value; }
108 internal RelaxngDatatypeProvider Provider {
109 get { return parentGrammar != null ? parentGrammar.Provider : provider; }
110 set { provider = value; }
113 public override RelaxngPatternType PatternType {
114 get { return RelaxngPatternType.Grammar; }
117 public string DefaultNamespace {
118 get { return defaultNamespace; }
119 set { defaultNamespace = value; }
122 public RelaxngGrammarContentList Starts {
123 get { return starts; }
126 public RelaxngGrammarContentList Defines {
130 public RelaxngGrammarContentList Includes {
131 get { return includes; }
134 public RelaxngGrammarContentList Divs {
138 public override void Write (XmlWriter writer)
140 writer.WriteStartElement ("", "grammar", RelaxngGrammar.NamespaceURI);
141 if (defaultNamespace != null)
142 writer.WriteAttributeString ("ns", defaultNamespace);
143 foreach (RelaxngStart start in Starts)
144 start.Write (writer);
145 foreach (RelaxngDefine define in Defines)
146 define.Write (writer);
147 foreach (RelaxngInclude include in Includes)
148 include.Write (writer);
149 foreach (RelaxngDiv div in Divs)
151 writer.WriteEndElement ();
154 internal override void WriteRnc (RncWriter writer)
156 writer.WriteGrammar (this);
159 internal Hashtable IncludedUris {
160 get { return includedUris; }
164 internal override void CheckConstraints ()
169 internal void CheckIncludeRecursion (string href)
171 if (this.includedUris [href] != null)
172 // FIXME: fill line info
173 throw new RelaxngException ("Include recursion found. href: " + href);
174 if (parentGrammar != null)
175 parentGrammar.CheckIncludeRecursion (href);
178 // Compile from this simplified syntax to derivatives.
179 internal override RdpPattern Compile (RelaxngGrammar grammar)
181 ResetCompileState ();
183 parentGrammar = grammar;
185 // First, process includes and divs. RELAX NG 4.1 - 4.15.
186 ArrayList compiledDivs = new ArrayList ();
187 foreach (RelaxngInclude inc in includes)
188 compiledDivs.Add (inc.Compile (this));
189 compiledDivs.AddRange (divs);
190 foreach (RelaxngDiv div in compiledDivs)
193 // Check constraints. RELAX NG 4.16
194 foreach (RelaxngStart start in starts)
195 start.Pattern.CheckConstraints ();
196 foreach (RelaxngDefine define in defs)
197 foreach (RelaxngPattern p in define.Patterns)
198 p.CheckConstraints ();
200 // Assemble combine into the same name defines/start.
201 // see RELAX NG 4.17.
204 // 4.18 : <grammar> must have at least one <start>.
205 if (assembledStart == null)
206 throw new RelaxngException ("A grammar elements must contain at least one start element.");
207 compiledStart = assembledStart.Compile (this);
209 // Assemble all define components into top grammar and
210 // return start patterns for descendant grammars.
211 // see RELAX NG 4.18.
213 if (parentGrammar != null)
214 return compiledStart;
215 assembledStart = null; // no use anymore
217 // 4.19 (a) remove non-reachable defines
219 compiledStart.MarkReachableDefs ();
220 ArrayList tmp = new ArrayList ();
221 foreach (DictionaryEntry entry in this.assembledDefs)
222 if (!reachableDefines.ContainsKey (entry.Key))
224 foreach (string key in tmp)
225 assembledDefs.Remove (key);
227 // 4.19 (b) check illegal recursion
228 CheckRecursion (compiledStart, 0);
229 // here we collected element-replaced definitions
230 foreach (DictionaryEntry entry in elementReplacedDefs)
231 assembledDefs.Add (entry.Key, entry.Value);
232 startPattern = compiledStart;
233 // 4.20,21 reduce notAllowed and empty.
237 startPattern = startPattern.ReduceEmptyAndNotAllowed (ref b, new Hashtable ());
240 Hashtable ht = new Hashtable ();
241 startPattern.setInternTable (ht);
242 RdpNotAllowed.Instance.setInternTable (ht);
243 RdpEmpty.Instance.setInternTable (ht);
244 RdpText.Instance.setInternTable (ht);
246 // Check Constraints: RELAX NG spec 7
248 startPattern.CheckConstraints (false, false, false, false, false, false);
250 CheckStartPatternContent (startPattern);
252 // 4.19 (c) expandRef - actual replacement
253 startPattern = compiledStart.ExpandRef (assembledDefs);
256 RdpContentType ct = startPattern.ContentType;
258 // return its start pattern.
263 private void CheckStartPatternContent (RdpPattern p)
265 switch (p.PatternType) {
266 case RelaxngPatternType.Ref:
267 CheckStartPatternContent (((RdpUnresolvedRef) p).RefPattern);
269 case RelaxngPatternType.Element:
271 case RelaxngPatternType.Choice:
272 RdpChoice c = p as RdpChoice;
273 CheckStartPatternContent (c.LValue);
274 CheckStartPatternContent (c.RValue);
276 case RelaxngPatternType.NotAllowed:
279 // FIXME: fill line info
280 throw new RelaxngException ("Start pattern contains an invalid content pattern.");
284 Hashtable reachableDefines = new Hashtable ();
287 internal void MarkReacheableDefine (string name)
289 if (reachableDefines.ContainsKey (name))
291 RdpPattern p = assembledDefs [name] as RdpPattern;
292 reachableDefines.Add (name, p);
293 p.MarkReachableDefs ();
297 private void CheckRecursion (RdpPattern p, int depth)
300 RdpAbstractBinary binary = p as RdpAbstractBinary;
301 if (binary != null) {
302 // choice, interleave, group
303 CheckRecursion (binary.LValue, depth);
304 CheckRecursion (binary.RValue, depth);
307 RdpAbstractSingleContent single = p as RdpAbstractSingleContent;
308 if (single != null) {
309 CheckRecursion (single.Child, depth);
313 switch (p.PatternType) {
314 case RelaxngPatternType.Ref:
315 // get checkRecursionDepth from table.
316 int checkRecursionDepth = -1;
317 object checkedDepth = checkedDefs [p];
318 if (checkedDepth != null)
319 checkRecursionDepth = (int) checkedDepth;
321 RdpUnresolvedRef pref = p as RdpUnresolvedRef;
322 RelaxngGrammar target = pref.TargetGrammar;
323 RdpPattern refPattern = pref.RefPattern;
324 if (refPattern == null)
325 // FIXME: fill line info
326 throw new RelaxngException ("No matching define found for " + pref.Name);
328 if (checkRecursionDepth == -1) {
329 checkedDefs [p] = depth;
330 /*test*/ if (refPattern.PatternType != RelaxngPatternType.Element)
331 CheckRecursion (refPattern, depth);
332 checkedDefs [p] = -2;
334 else if (depth == checkRecursionDepth)
335 // FIXME: fill line info
336 throw new RelaxngException (String.Format ("Detected illegal recursion. Ref name is {0}.", pref.Name));
340 case RelaxngPatternType.Attribute:
341 CheckRecursion (((RdpAttribute) p).Children, depth);
344 case RelaxngPatternType.DataExcept:
345 CheckRecursion (((RdpDataExcept) p).Except, depth);
348 case RelaxngPatternType.Element:
349 RdpElement el = p as RdpElement;
350 CheckRecursion (el.Children, depth + 1); // +1
352 case RelaxngPatternType.List:
353 CheckRecursion (((RdpList) p).Child, depth);
359 private void CollectGrammars ()
361 // collect ref and parentRef for each define.
363 // FIXME: This should be assembledStart.
364 CheckReferences (compiledStart);
367 foreach (string name in assembledDefs.Keys) {
368 RdpPattern p = (RdpPattern) assembledDefs [name];
373 // If it is child of any other pattern:
374 // * Remove all definitions under descendant grammars,
375 // replacing ref names, and
376 // * Then return its start pattern.
377 if (parentGrammar != null) {
378 // TODO: reachable check is incomplete.
379 foreach (string name in assembledDefs.Keys) {
381 refPatterns [assembledDefs [name] ] as ArrayList;
383 continue; // Not referenced.
385 // At this point, parent grammar doesn't
386 // collect assembledDefs as yet
387 string uname = GetUniqueName (name, parentGrammar);
388 parentGrammar.assembledDefs [uname] = assembledDefs [name];
393 private static string GetUniqueName (string name, RelaxngGrammar grammar)
395 foreach (RelaxngDefine def in grammar.Defines)
396 if (def.Name == name)
397 return GetUniqueName (name + '_', grammar);
401 private void FixupReference ()
403 foreach (RdpUnresolvedRef pref in this.unresolvedPatterns) {
404 RdpPattern defP = assembledDefs [pref.Name] as RdpPattern;
406 // FIXME: fill line info
407 throw new RelaxngException (String.Format ("Target definition was not found: {0}", pref.Name));
408 ArrayList al = refPatterns [defP] as ArrayList;
410 al = new ArrayList ();
411 refPatterns [defP] = al;
415 this.unresolvedPatterns.Clear ();
418 private void replaceDefines (string name, ArrayList al)
422 string newName = "define" + idx;
423 if (parentGrammar.assembledDefs [newName] == null) {
424 parentGrammar.assembledDefs [newName] =
425 assembledDefs [name];
426 foreach (RdpUnresolvedRef pref in al)
434 // remove ref and parentRef.
435 // add new defines for each elements.
436 private void CheckReferences (RdpPattern p)
438 RdpAbstractBinary binary = p as RdpAbstractBinary;
439 if (binary != null) {
440 // choice, interleave, group
441 CheckReferences (binary.LValue);
442 CheckReferences (binary.RValue);
445 RdpAbstractSingleContent single = p as RdpAbstractSingleContent;
446 if (single != null) {
447 CheckReferences (single.Child);
451 switch (p.PatternType) {
452 case RelaxngPatternType.Ref:
453 // FIXME: This should not re-expand ref
454 RdpUnresolvedRef pref = p as RdpUnresolvedRef;
455 if (pref.RefPattern != null)
458 RelaxngGrammar target = pref.TargetGrammar;
460 // FIXME: fill line info
461 throw new RelaxngException ("Referenced definition was not found.");
462 RdpPattern defP = target.assembledDefs [pref.Name] as RdpPattern;
464 target.unresolvedPatterns.Add (p);
466 ArrayList al = target.refPatterns [defP] as ArrayList;
468 al = new ArrayList ();
469 target.refPatterns [defP] = al;
472 pref.RefPattern = defP;
476 case RelaxngPatternType.Attribute:
477 CheckReferences (((RdpAttribute) p).Children);
480 case RelaxngPatternType.DataExcept:
481 CheckReferences (((RdpDataExcept) p).Except);
484 case RelaxngPatternType.Element:
485 RdpElement el = p as RdpElement;
486 CheckReferences (el.Children);
487 string name = ElementDefMap [el] as string;
491 string newName = "element0";
492 if (el.NameClass is RdpName)
493 newName = ((RdpName) el.NameClass).LocalName;
495 if (assembledDefs [newName] == null) {
496 elementReplacedDefs [newName] = el.Children;
499 newName = "element" + ++idx;
501 ElementDefMap [el] = newName;
503 // Even though the element is replaced with ref,
504 // derivative of ref is RdpElement in fact...
507 case RelaxngPatternType.List:
508 CheckReferences (((RdpList) p).Child);
511 case RelaxngPatternType.Empty:
512 case RelaxngPatternType.NotAllowed:
513 case RelaxngPatternType.Text:
514 case RelaxngPatternType.Value:
517 //case RelaxngPatternType.ExternalRef:
518 //case RelaxngPatternType.Include:
519 // Mixed, Optional, ZeroOrMore are already removed.
520 // Choice, Group, Interleave, OneOrMore are already proceeded.
524 #region 4.17 - Combine
525 private void AssembleCombine ()
527 // calculate combines.
528 bool haveHeadStart = false;
529 string combineStart = null;
530 Hashtable haveHeadDefs = new Hashtable ();
531 Hashtable combineDefs = new Hashtable ();
533 // 1.calculate combine for starts.
534 foreach (RelaxngStart start in starts)
535 CheckCombine (ref haveHeadStart,
536 ref combineStart, start.Combine, "start");
537 // 2.calculate combine for defines.
538 foreach (RelaxngDefine def in defs) {
540 haveHeadDefs.ContainsKey (def.Name) ?
541 haveHead = (bool) haveHeadDefs [def.Name]
543 string combine = combineDefs [def.Name] as string;
544 CheckCombine (ref haveHead, ref combine,
545 def.Combine, String.Format ("define name={0}", def.Name));
546 haveHeadDefs [def.Name] = haveHead;
547 combineDefs [def.Name] = combine;
551 // assemble starts and defines with "combine" attribute.
553 // 3.assemble starts.
554 if (starts.Count == 0) {
555 if (ParentGrammar == null)
556 throw new RelaxngException (this, "grammar must have at least one start component.");
558 assembledStart = ((RelaxngStart)starts [0]).Pattern;
559 for (int i=1; i<starts.Count; i++) {
560 RelaxngPattern p2 = ((RelaxngStart) starts [i]).Pattern;;
561 if (combineStart == "interleave") {
562 RelaxngInterleave intlv = new RelaxngInterleave ();
563 intlv.Patterns.Add (assembledStart);
564 intlv.Patterns.Add (p2);
565 assembledStart = intlv;
567 RelaxngChoice c = new RelaxngChoice ();
568 c.Patterns.Add (assembledStart);
575 // 4.assemble defines
576 foreach (RelaxngDefine def in defs) {
577 string combine = combineDefs [def.Name] as string;
579 assembledDefs [def.Name] as RdpPattern;
580 RdpPattern p2 = def.Compile (this);
582 if (combine == "interleave") {
583 assembledDefs [def.Name] =
584 new RdpInterleave (p1, p2);
586 assembledDefs [def.Name] =
587 new RdpChoice (p1, p2);
590 assembledDefs [def.Name] = p2;
596 // check combine attributes.
597 private void CheckCombine (ref bool haveHead, ref string combine, string newCombine, string targetSpec)
599 switch (newCombine) {
601 if (combine == "choice")
602 throw new RelaxngException (this, "\"combine\" was already specified \"choice\"");
604 combine = "interleave";
607 if (combine == "interleave")
608 throw new RelaxngException (this, "\"combine\" was already specified \"interleave\"");
614 throw new RelaxngException (this, String.Format ("There was already \"{0}\" element without \"combine\" attribute.", targetSpec));