330 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			330 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C)2005-2019 Haxe Foundation
 | |
|  *
 | |
|  * 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.
 | |
|  */
 | |
| 
 | |
| 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]*$/;
 | |
| 
 | |
| 	static function isBlank(x:Xml) {
 | |
| 		return (x.nodeType == Xml.PCData && blanks.match(x.nodeValue)) || x.nodeType == Xml.Comment;
 | |
| 	}
 | |
| 
 | |
| 	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, _):
 | |
| 								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 = checkList([x].iterator(), r);
 | |
| 						if (m == CMatch) {
 | |
| 							found = true;
 | |
| 							switch (r) {
 | |
| 								case RMulti(rsub, one):
 | |
| 									if (one) {
 | |
| 										var i;
 | |
| 										for (i in 0...rules.length)
 | |
| 											if (rules[i] == r)
 | |
| 												rules[i] = RMulti(rsub);
 | |
| 									}
 | |
| 								default:
 | |
| 									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):
 | |
| 				var found = false;
 | |
| 				for (x in it) {
 | |
| 					if (isBlank(x))
 | |
| 						continue;
 | |
| 					var m = checkList([x].iterator(), r);
 | |
| 					if (m != CMatch)
 | |
| 						return m;
 | |
| 					found = true;
 | |
| 				}
 | |
| 				if (one && !found)
 | |
| 					return CMissing(r);
 | |
| 				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, _):
 | |
| 				path.push(x);
 | |
| 				return makeWhere(path) + "invalid attribute value for " + att;
 | |
| 			case CInvalidData(x, _):
 | |
| 				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 = checkList([x].iterator(), r);
 | |
| 		if (m == CMatch)
 | |
| 			return;
 | |
| 		throw makeError(m);
 | |
| 	}
 | |
| 
 | |
| 	public static function checkDocument(x:Xml, r:Rule) {
 | |
| 		if (x.nodeType != Xml.Document)
 | |
| 			throw "Document expected";
 | |
| 		var m = checkList(x.iterator(), r);
 | |
| 		if (m == CMatch)
 | |
| 			return;
 | |
| 		throw makeError(m);
 | |
| 	}
 | |
| }
 |