Files
linux-packaging-mono/mcs/class/Commons.Xml.Relaxng/Commons.Xml.Relaxng/RelaxngGrammar.cs
Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

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
}
}