You've already forked linux-packaging-mono
625 lines
18 KiB
C#
625 lines
18 KiB
C#
//
|
|
// 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
|
|
}
|
|
}
|