Nicolas Cannasse 19 years ago
parent
commit
6410d87fbc
2 changed files with 378 additions and 0 deletions
  1. 286 0
      std/haxe/xml/Check.hx
  2. 92 0
      std/haxe/xml/Fast.hx

+ 286 - 0
std/haxe/xml/Check.hx

@@ -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);
+	}
+
+}

+ 92 - 0
std/haxe/xml/Fast.hx

@@ -0,0 +1,92 @@
+package haxe.xml;
+
+private class NodeAccess implements Dynamic<Fast> {
+
+	var __x : Xml;
+
+	public function new( x : Xml ) {
+		__x = x;
+	}
+
+	function __resolve( name : String ) : Fast {
+		var x = __x.elementsNamed(name).next();
+		if( x == null ) {
+			var xname = if( __x.nodeType == Xml.Document ) "Document" else __x.nodeName;
+			throw xname+" is missing element "+name;
+		}
+		return new Fast(x);
+	}
+
+}
+
+private class AttribAccess implements Dynamic<String> {
+
+	var __x : Xml;
+
+	public function new( x : Xml ) {
+		__x = x;
+	}
+
+	function __resolve( name : String ) : String {
+		if( __x.nodeType == Xml.Document )
+			throw "Cannot access document attribute "+name;
+		var v = __x.get(name);
+		if( v == null )
+			throw __x.nodeName+" is missing attribute "+name;
+		return v;
+	}
+
+}
+
+private class NodeListAccess implements Dynamic<List<Fast>> {
+
+	var __x : Xml;
+
+	public function new( x : Xml ) {
+		__x = x;
+	}
+
+	function __resolve( name : String ) : List<Fast> {
+		var l = new List();
+		for( x in __x.elementsNamed(name) )
+			l.add(new Fast(x));
+		return l;
+	}
+
+}
+
+class Fast {
+
+	var __x : Xml;
+
+	public var data(getData,null) : String;
+	public var node(default,null) : NodeAccess;
+	public var nodes(default,null) : NodeListAccess;
+	public var att(default,null) : AttribAccess;
+
+	public function new( x : Xml ) {
+		if( x.nodeType != Xml.Document && x.nodeType != Xml.Element )
+			throw "Invalid nodeType "+x.nodeType;
+		__x = x;
+		node = new NodeAccess(x);
+		nodes = new NodeListAccess(x);
+		att = new AttribAccess(x);
+	}
+
+	function name() {
+		return if( __x.nodeType == Xml.Document ) "Document" else __x.nodeName;
+	}
+
+	function getData() {
+		var it = __x.iterator();
+		if( !it.hasNext() )
+			throw name()+" does not have data";
+		var v = it.next();
+		if( it.hasNext() )
+			throw name()+" does not only have data";
+		if( v.nodeType != Xml.PCData && v.nodeType != Xml.CData )
+			throw name()+" does not have data";
+		return v.nodeValue;
+	}
+
+}