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