|
@@ -0,0 +1,286 @@
|
|
|
+package haxe.xml;
|
|
|
+
|
|
|
+enum Filter {
|
|
|
+ FInt;
|
|
|
+ FBool;
|
|
|
+ FEnum( values : Array<String> );
|
|
|
+ FReg( matcher : EReg );
|
|
|
+}
|
|
|
+
|
|
|
+enum Attrib {
|
|
|
+ Att( name : String, ?filter : Filter, ?defvalue : String );
|
|
|
+}
|
|
|
+
|
|
|
+enum Rule {
|
|
|
+ RNode( name : String, ?attribs : Array<Attrib>, ?childs : Rule );
|
|
|
+ RData( ?filter : Filter );
|
|
|
+ RMulti( rule : Rule, ?atLeastOne : Bool );
|
|
|
+ RList( rules : Array<Rule>, ?ordered : Bool );
|
|
|
+ RChoice( choices : Array<Rule> );
|
|
|
+ ROptional( rule : Rule );
|
|
|
+}
|
|
|
+
|
|
|
+private enum CheckResult {
|
|
|
+ CMatch;
|
|
|
+ CMissing( r : Rule );
|
|
|
+ CExtra( x : Xml );
|
|
|
+ CElementExpected( name : String, x : Xml );
|
|
|
+ CDataExpected( x : Xml );
|
|
|
+ CExtraAttrib( att : String, x : Xml );
|
|
|
+ CMissingAttrib( att : String, x : Xml );
|
|
|
+ CInvalidAttrib( att : String, x : Xml, f : Filter );
|
|
|
+ CInvalidData( x : Xml, f : Filter );
|
|
|
+ CInElement( x : Xml, r : CheckResult );
|
|
|
+}
|
|
|
+
|
|
|
+class Check {
|
|
|
+
|
|
|
+ static var blanks = ~/^[ \r\n\t]*$/m;
|
|
|
+
|
|
|
+ static function isBlank( x : Xml ) {
|
|
|
+ return( x.nodeType == Xml.PCData && blanks.match(x.nodeValue) );
|
|
|
+ }
|
|
|
+
|
|
|
+ static function filterMatch( s : String, f : Filter ) {
|
|
|
+ switch( f ) {
|
|
|
+ case FInt: return filterMatch(s,FReg(~/[0-9]+/));
|
|
|
+ case FBool: return filterMatch(s,FEnum(["true","false","0","1"]));
|
|
|
+ case FEnum(values):
|
|
|
+ for( v in values )
|
|
|
+ if( s == v )
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ case FReg(r):
|
|
|
+ return r.match(s);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static function isNullable( r : Rule ) {
|
|
|
+ switch( r ) {
|
|
|
+ case RMulti(r,one):
|
|
|
+ return( one != true || isNullable(r) );
|
|
|
+ case RList(rl,_):
|
|
|
+ for( r in rl )
|
|
|
+ if( !isNullable(r) )
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
+ case RChoice(rl):
|
|
|
+ for( r in rl )
|
|
|
+ if( isNullable(r) )
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ case RData(_):
|
|
|
+ return false;
|
|
|
+ case RNode(_,_,_):
|
|
|
+ return false;
|
|
|
+ case ROptional(_):
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static function check( x : Xml, r : Rule ) {
|
|
|
+ switch( r ) {
|
|
|
+ // check the node validity
|
|
|
+ case RNode(name,attribs,childs):
|
|
|
+ if( x.nodeType != Xml.Element || x.nodeName != name )
|
|
|
+ return CElementExpected(name,x);
|
|
|
+ var attribs = if( attribs == null ) new Array() else attribs.copy();
|
|
|
+ // check defined attributes
|
|
|
+ for( xatt in x.attributes() ) {
|
|
|
+ var found = false;
|
|
|
+ for( att in attribs )
|
|
|
+ switch( att ) {
|
|
|
+ case Att(name,filter,defvalue):
|
|
|
+ if( xatt != name )
|
|
|
+ continue;
|
|
|
+ if( filter != null && !filterMatch(x.get(xatt),filter) )
|
|
|
+ return CInvalidAttrib(name,x,filter);
|
|
|
+ attribs.remove(att);
|
|
|
+ found = true;
|
|
|
+ }
|
|
|
+ if( !found )
|
|
|
+ return CExtraAttrib(xatt,x);
|
|
|
+ }
|
|
|
+ // check remaining unchecked attributes
|
|
|
+ for( att in attribs )
|
|
|
+ switch( att ) {
|
|
|
+ case Att(name,_,defvalue):
|
|
|
+ if( defvalue == null )
|
|
|
+ return CMissingAttrib(name,x);
|
|
|
+ }
|
|
|
+ // check childs
|
|
|
+ if( childs == null )
|
|
|
+ childs = RList([]);
|
|
|
+ var m = checkList(x.iterator(),childs);
|
|
|
+ if( m != CMatch )
|
|
|
+ return CInElement(x,m);
|
|
|
+ // set default attribs values
|
|
|
+ for( att in attribs )
|
|
|
+ switch( att ) {
|
|
|
+ case Att(name,_,defvalue):
|
|
|
+ x.set(name,defvalue);
|
|
|
+ }
|
|
|
+ return CMatch;
|
|
|
+ // check the data validity
|
|
|
+ case RData(filter):
|
|
|
+ if( x.nodeType != Xml.PCData && x.nodeType != Xml.CData )
|
|
|
+ return CDataExpected(x);
|
|
|
+ if( filter != null && !filterMatch(x.nodeValue,filter) )
|
|
|
+ return CInvalidData(x,filter);
|
|
|
+ return CMatch;
|
|
|
+ // several choices
|
|
|
+ case RChoice(choices):
|
|
|
+ if( choices.length == 0 )
|
|
|
+ throw "No choice possible";
|
|
|
+ for( c in choices )
|
|
|
+ if( check(x,c) == CMatch )
|
|
|
+ return CMatch;
|
|
|
+ return check(x,choices[0]);
|
|
|
+ case ROptional(r):
|
|
|
+ return check(x,r);
|
|
|
+ default:
|
|
|
+ throw "Unexpected "+Std.string(r);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static function checkList( it : Iterator<Xml>, r : Rule ) {
|
|
|
+ switch( r ) {
|
|
|
+ case RList(rules,ordered):
|
|
|
+ var rules = rules.copy();
|
|
|
+ for( x in it ) {
|
|
|
+ if( isBlank(x) )
|
|
|
+ continue;
|
|
|
+ var found = false;
|
|
|
+ for( r in rules ) {
|
|
|
+ var m = check(x,r);
|
|
|
+ if( m == CMatch ) {
|
|
|
+ found = true;
|
|
|
+ rules.remove(r);
|
|
|
+ break;
|
|
|
+ } else if( ordered && !isNullable(r) )
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+ if( !found )
|
|
|
+ return CExtra(x);
|
|
|
+ }
|
|
|
+ for( r in rules )
|
|
|
+ if( !isNullable(r) )
|
|
|
+ return CMissing(r);
|
|
|
+ return CMatch;
|
|
|
+ case RMulti(r,one):
|
|
|
+ if( one && !it.hasNext() )
|
|
|
+ return CMissing(r);
|
|
|
+ for( x in it ) {
|
|
|
+ var m = check(x,r);
|
|
|
+ if( m != CMatch )
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+ return CMatch;
|
|
|
+ default:
|
|
|
+ var found = false;
|
|
|
+ for( x in it ) {
|
|
|
+ if( isBlank(x) )
|
|
|
+ continue;
|
|
|
+ var m = check(x,r);
|
|
|
+ if( m != CMatch )
|
|
|
+ return m;
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if( !found ) {
|
|
|
+ switch(r) {
|
|
|
+ case ROptional(_):
|
|
|
+ default: return CMissing(r);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for( x in it ) {
|
|
|
+ if( isBlank(x) )
|
|
|
+ continue;
|
|
|
+ return CExtra(x);
|
|
|
+ }
|
|
|
+ return CMatch;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static function makeWhere( path : Array<Xml> ) {
|
|
|
+ if( path.length == 0 )
|
|
|
+ return "";
|
|
|
+ var s = "In ";
|
|
|
+ var first = true;
|
|
|
+ for( x in path ) {
|
|
|
+ if( first )
|
|
|
+ first = false;
|
|
|
+ else
|
|
|
+ s += ".";
|
|
|
+ s += x.nodeName;
|
|
|
+ }
|
|
|
+ return s+": ";
|
|
|
+ }
|
|
|
+
|
|
|
+ static function makeString( x : Xml ) {
|
|
|
+ if( x.nodeType == Xml.Element )
|
|
|
+ return "element "+x.nodeName;
|
|
|
+ var s = x.nodeValue.split("\r").join("\\r").split("\n").join("\\n").split("\t").join("\\t");
|
|
|
+ if( s.length > 20 )
|
|
|
+ return s.substr(0,17)+"...";
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+
|
|
|
+ static function makeRule( r : Rule ) {
|
|
|
+ switch( r ) {
|
|
|
+ case RNode(name,_,_): return "element "+name;
|
|
|
+ case RData(_): return "data";
|
|
|
+ case RMulti(r,_): return makeRule(r);
|
|
|
+ case RList(rules,_): return makeRule(rules[0]);
|
|
|
+ case RChoice(choices): return makeRule(choices[0]);
|
|
|
+ case ROptional(r): return makeRule(r);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static function makeError(m,?path) {
|
|
|
+ if( path == null )
|
|
|
+ path = new Array();
|
|
|
+ switch( m ) {
|
|
|
+ case CMatch: throw "assert";
|
|
|
+ case CMissing(r):
|
|
|
+ return makeWhere(path)+"Missing "+makeRule(r);
|
|
|
+ case CExtra(x):
|
|
|
+ return makeWhere(path)+"Unexpected "+makeString(x);
|
|
|
+ case CElementExpected(name,x):
|
|
|
+ return makeWhere(path)+makeString(x)+" while expected element "+name;
|
|
|
+ case CDataExpected(x):
|
|
|
+ return makeWhere(path)+makeString(x)+" while data expected";
|
|
|
+ case CExtraAttrib(att,x):
|
|
|
+ path.push(x);
|
|
|
+ return makeWhere(path)+"unexpected attribute "+att;
|
|
|
+ case CMissingAttrib(att,x):
|
|
|
+ path.push(x);
|
|
|
+ return makeWhere(path)+"missing required attribute "+att;
|
|
|
+ case CInvalidAttrib(att,x,f):
|
|
|
+ path.push(x);
|
|
|
+ return makeWhere(path)+"invalid attribute value for "+att;
|
|
|
+ case CInvalidData(x,f):
|
|
|
+ return makeWhere(path)+"invalid data format for "+makeString(x);
|
|
|
+ case CInElement(x,m):
|
|
|
+ path.push(x);
|
|
|
+ return makeError(m,path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function checkNode( x : Xml, r : Rule ) {
|
|
|
+ var m = check(x,r);
|
|
|
+ if( m == CMatch )
|
|
|
+ return;
|
|
|
+ throw makeError(m);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function checkDocument( x : Xml, r : Rule ) {
|
|
|
+ if( x.nodeType != Xml.Document )
|
|
|
+ throw "Document excepted";
|
|
|
+ var m = checkList(x.iterator(),r);
|
|
|
+ if( m == CMatch )
|
|
|
+ return;
|
|
|
+ throw makeError(m);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|