forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			420 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			420 lines
		
	
	
		
			12 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;
 | |
| 
 | |
| using StringTools;
 | |
| 
 | |
| private enum abstract S(Int) {
 | |
| 	var IGNORE_SPACES;
 | |
| 	var BEGIN;
 | |
| 	var BEGIN_NODE;
 | |
| 	var TAG_NAME;
 | |
| 	var BODY;
 | |
| 	var ATTRIB_NAME;
 | |
| 	var EQUALS;
 | |
| 	var ATTVAL_BEGIN;
 | |
| 	var ATTRIB_VAL;
 | |
| 	var CHILDS;
 | |
| 	var CLOSE;
 | |
| 	var WAIT_END;
 | |
| 	var WAIT_END_RET;
 | |
| 	var PCDATA;
 | |
| 	var HEADER;
 | |
| 	var COMMENT;
 | |
| 	var DOCTYPE;
 | |
| 	var CDATA;
 | |
| 	var ESCAPE;
 | |
| }
 | |
| 
 | |
| class XmlParserException {
 | |
| 	/**
 | |
| 		the XML parsing error message
 | |
| 	**/
 | |
| 	public var message:String;
 | |
| 
 | |
| 	/**
 | |
| 		the line number at which the XML parsing error occurred
 | |
| 	**/
 | |
| 	public var lineNumber:Int;
 | |
| 
 | |
| 	/**
 | |
| 		the character position in the reported line at which the parsing error occurred
 | |
| 	**/
 | |
| 	public var positionAtLine:Int;
 | |
| 
 | |
| 	/**
 | |
| 		the character position in the XML string at which the parsing error occurred
 | |
| 	**/
 | |
| 	public var position:Int;
 | |
| 
 | |
| 	/**
 | |
| 		the invalid XML string
 | |
| 	**/
 | |
| 	public var xml:String;
 | |
| 
 | |
| 	public function new(message:String, xml:String, position:Int) {
 | |
| 		this.xml = xml;
 | |
| 		this.message = message;
 | |
| 		this.position = position;
 | |
| 		lineNumber = 1;
 | |
| 		positionAtLine = 0;
 | |
| 
 | |
| 		for (i in 0...position) {
 | |
| 			var c = xml.fastCodeAt(i);
 | |
| 			if (c == '\n'.code) {
 | |
| 				lineNumber++;
 | |
| 				positionAtLine = 0;
 | |
| 			} else {
 | |
| 				if (c != '\r'.code)
 | |
| 					positionAtLine++;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public function toString():String {
 | |
| 		return Type.getClassName(Type.getClass(this)) + ": " + message + " at line " + lineNumber + " char " + positionAtLine;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| class Parser {
 | |
| 	static var escapes = {
 | |
| 		var h = new haxe.ds.StringMap();
 | |
| 		h.set("lt", "<");
 | |
| 		h.set("gt", ">");
 | |
| 		h.set("amp", "&");
 | |
| 		h.set("quot", '"');
 | |
| 		h.set("apos", "'");
 | |
| 		h;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 		Parses the String into an XML Document. Set strict parsing to true in order to enable a strict check of XML attributes and entities.
 | |
| 
 | |
| 		@throws haxe.xml.XmlParserException
 | |
| 	**/
 | |
| 	static public function parse(str:String, strict = false) {
 | |
| 		var doc = Xml.createDocument();
 | |
| 		doParse(str, strict, 0, doc);
 | |
| 		return doc;
 | |
| 	}
 | |
| 
 | |
| 	static function doParse(str:String, strict:Bool, p:Int = 0, ?parent:Xml):Int {
 | |
| 		var xml:Xml = null;
 | |
| 		var state = S.BEGIN;
 | |
| 		var next = S.BEGIN;
 | |
| 		var aname = null;
 | |
| 		var start = 0;
 | |
| 		var nsubs = 0;
 | |
| 		var nbrackets = 0;
 | |
| 		var buf = new StringBuf();
 | |
| 		// need extra state because next is in use
 | |
| 		var escapeNext = S.BEGIN;
 | |
| 		var attrValQuote = -1;
 | |
| 		inline function addChild(xml:Xml) {
 | |
| 			parent.addChild(xml);
 | |
| 			nsubs++;
 | |
| 		}
 | |
| 		while (p < str.length) {
 | |
| 			var c = str.unsafeCodeAt(p);
 | |
| 			switch (state) {
 | |
| 				case S.IGNORE_SPACES:
 | |
| 					switch (c) {
 | |
| 						case '\n'.code, '\r'.code, '\t'.code, ' '.code:
 | |
| 						default:
 | |
| 							state = next;
 | |
| 							continue;
 | |
| 					}
 | |
| 				case S.BEGIN:
 | |
| 					switch (c) {
 | |
| 						case '<'.code:
 | |
| 							state = S.IGNORE_SPACES;
 | |
| 							next = S.BEGIN_NODE;
 | |
| 						default:
 | |
| 							start = p;
 | |
| 							state = S.PCDATA;
 | |
| 							continue;
 | |
| 					}
 | |
| 				case S.PCDATA:
 | |
| 					if (c == '<'.code) {
 | |
| 						buf.addSub(str, start, p - start);
 | |
| 						var child = Xml.createPCData(buf.toString());
 | |
| 						buf = new StringBuf();
 | |
| 						addChild(child);
 | |
| 						state = S.IGNORE_SPACES;
 | |
| 						next = S.BEGIN_NODE;
 | |
| 					} else if (c == '&'.code) {
 | |
| 						buf.addSub(str, start, p - start);
 | |
| 						state = S.ESCAPE;
 | |
| 						escapeNext = S.PCDATA;
 | |
| 						start = p + 1;
 | |
| 					}
 | |
| 				case S.CDATA:
 | |
| 					if (c == ']'.code && str.fastCodeAt(p + 1) == ']'.code && str.fastCodeAt(p + 2) == '>'.code) {
 | |
| 						var child = Xml.createCData(str.substr(start, p - start));
 | |
| 						addChild(child);
 | |
| 						p += 2;
 | |
| 						state = S.BEGIN;
 | |
| 					}
 | |
| 				case S.BEGIN_NODE:
 | |
| 					switch (c) {
 | |
| 						case '!'.code:
 | |
| 							if (str.fastCodeAt(p + 1) == '['.code) {
 | |
| 								p += 2;
 | |
| 								if (str.substr(p, 6).toUpperCase() != "CDATA[")
 | |
| 									throw new XmlParserException("Expected <![CDATA[", str, p);
 | |
| 								p += 5;
 | |
| 								state = S.CDATA;
 | |
| 								start = p + 1;
 | |
| 							} else if (str.fastCodeAt(p + 1) == 'D'.code || str.fastCodeAt(p + 1) == 'd'.code) {
 | |
| 								if (str.substr(p + 2, 6).toUpperCase() != "OCTYPE")
 | |
| 									throw new XmlParserException("Expected <!DOCTYPE", str, p);
 | |
| 								p += 8;
 | |
| 								state = S.DOCTYPE;
 | |
| 								start = p + 1;
 | |
| 							} else if (str.fastCodeAt(p + 1) != '-'.code || str.fastCodeAt(p + 2) != '-'.code) throw new XmlParserException("Expected <!--",
 | |
| 								str, p); else {
 | |
| 								p += 2;
 | |
| 								state = S.COMMENT;
 | |
| 								start = p + 1;
 | |
| 							}
 | |
| 						case '?'.code:
 | |
| 							state = S.HEADER;
 | |
| 							start = p;
 | |
| 						case '/'.code:
 | |
| 							if (parent == null)
 | |
| 								throw new XmlParserException("Expected node name", str, p);
 | |
| 							start = p + 1;
 | |
| 							state = S.IGNORE_SPACES;
 | |
| 							next = S.CLOSE;
 | |
| 						default:
 | |
| 							state = S.TAG_NAME;
 | |
| 							start = p;
 | |
| 							continue;
 | |
| 					}
 | |
| 				case S.TAG_NAME:
 | |
| 					if (!isValidChar(c)) {
 | |
| 						if (p == start)
 | |
| 							throw new XmlParserException("Expected node name", str, p);
 | |
| 						xml = Xml.createElement(str.substr(start, p - start));
 | |
| 						addChild(xml);
 | |
| 						state = S.IGNORE_SPACES;
 | |
| 						next = S.BODY;
 | |
| 						continue;
 | |
| 					}
 | |
| 				case S.BODY:
 | |
| 					switch (c) {
 | |
| 						case '/'.code:
 | |
| 							state = S.WAIT_END;
 | |
| 						case '>'.code:
 | |
| 							state = S.CHILDS;
 | |
| 						default:
 | |
| 							state = S.ATTRIB_NAME;
 | |
| 							start = p;
 | |
| 							continue;
 | |
| 					}
 | |
| 				case S.ATTRIB_NAME:
 | |
| 					if (!isValidChar(c)) {
 | |
| 						var tmp;
 | |
| 						if (start == p)
 | |
| 							throw new XmlParserException("Expected attribute name", str, p);
 | |
| 						tmp = str.substr(start, p - start);
 | |
| 						aname = tmp;
 | |
| 						if (xml.exists(aname))
 | |
| 							throw new XmlParserException("Duplicate attribute [" + aname + "]", str, p);
 | |
| 						state = S.IGNORE_SPACES;
 | |
| 						next = S.EQUALS;
 | |
| 						continue;
 | |
| 					}
 | |
| 				case S.EQUALS:
 | |
| 					switch (c) {
 | |
| 						case '='.code:
 | |
| 							state = S.IGNORE_SPACES;
 | |
| 							next = S.ATTVAL_BEGIN;
 | |
| 						default:
 | |
| 							throw new XmlParserException("Expected =", str, p);
 | |
| 					}
 | |
| 				case S.ATTVAL_BEGIN:
 | |
| 					switch (c) {
 | |
| 						case '"'.code | '\''.code:
 | |
| 							buf = new StringBuf();
 | |
| 							state = S.ATTRIB_VAL;
 | |
| 							start = p + 1;
 | |
| 							attrValQuote = c;
 | |
| 						default:
 | |
| 							throw new XmlParserException("Expected \"", str, p);
 | |
| 					}
 | |
| 				case S.ATTRIB_VAL:
 | |
| 					switch (c) {
 | |
| 						case '&'.code:
 | |
| 							buf.addSub(str, start, p - start);
 | |
| 							state = S.ESCAPE;
 | |
| 							escapeNext = S.ATTRIB_VAL;
 | |
| 							start = p + 1;
 | |
| 						case '>'.code | '<'.code if (strict):
 | |
| 							// HTML allows these in attributes values
 | |
| 							throw new XmlParserException("Invalid unescaped " + String.fromCharCode(c) + " in attribute value", str, p);
 | |
| 						case _ if (c == attrValQuote):
 | |
| 							buf.addSub(str, start, p - start);
 | |
| 							var val = buf.toString();
 | |
| 							buf = new StringBuf();
 | |
| 							xml.set(aname, val);
 | |
| 							state = S.IGNORE_SPACES;
 | |
| 							next = S.BODY;
 | |
| 					}
 | |
| 				case S.CHILDS:
 | |
| 					p = doParse(str, strict, p, xml);
 | |
| 					start = p;
 | |
| 					state = S.BEGIN;
 | |
| 				case S.WAIT_END:
 | |
| 					switch (c) {
 | |
| 						case '>'.code:
 | |
| 							state = S.BEGIN;
 | |
| 						default:
 | |
| 							throw new XmlParserException("Expected >", str, p);
 | |
| 					}
 | |
| 				case S.WAIT_END_RET:
 | |
| 					switch (c) {
 | |
| 						case '>'.code:
 | |
| 							if (nsubs == 0)
 | |
| 								parent.addChild(Xml.createPCData(""));
 | |
| 							return p;
 | |
| 						default:
 | |
| 							throw new XmlParserException("Expected >", str, p);
 | |
| 					}
 | |
| 				case S.CLOSE:
 | |
| 					if (!isValidChar(c)) {
 | |
| 						if (start == p)
 | |
| 							throw new XmlParserException("Expected node name", str, p);
 | |
| 
 | |
| 						var v = str.substr(start, p - start);
 | |
| 						if (parent == null || parent.nodeType != Element) {
 | |
| 							throw new XmlParserException('Unexpected </$v>, tag is not open', str, p);
 | |
| 						}
 | |
| 						if (v != parent.nodeName)
 | |
| 							throw new XmlParserException("Expected </" + parent.nodeName + ">", str, p);
 | |
| 
 | |
| 						state = S.IGNORE_SPACES;
 | |
| 						next = S.WAIT_END_RET;
 | |
| 						continue;
 | |
| 					}
 | |
| 				case S.COMMENT:
 | |
| 					if (c == '-'.code && str.fastCodeAt(p + 1) == '-'.code && str.fastCodeAt(p + 2) == '>'.code) {
 | |
| 						addChild(Xml.createComment(str.substr(start, p - start)));
 | |
| 						p += 2;
 | |
| 						state = S.BEGIN;
 | |
| 					}
 | |
| 				case S.DOCTYPE:
 | |
| 					if (c == '['.code)
 | |
| 						nbrackets++;
 | |
| 					else if (c == ']'.code)
 | |
| 						nbrackets--;
 | |
| 					else if (c == '>'.code && nbrackets == 0) {
 | |
| 						addChild(Xml.createDocType(str.substr(start, p - start)));
 | |
| 						state = S.BEGIN;
 | |
| 					}
 | |
| 				case S.HEADER:
 | |
| 					if (c == '?'.code && str.fastCodeAt(p + 1) == '>'.code) {
 | |
| 						p++;
 | |
| 						var str = str.substr(start + 1, p - start - 2);
 | |
| 						addChild(Xml.createProcessingInstruction(str));
 | |
| 						state = S.BEGIN;
 | |
| 					}
 | |
| 				case S.ESCAPE:
 | |
| 					if (c == ';'.code) {
 | |
| 						var s = str.substr(start, p - start);
 | |
| 						if (s.fastCodeAt(0) == '#'.code) {
 | |
| 							var c = s.fastCodeAt(1) == 'x'.code ? Std.parseInt("0" + s.substr(1, s.length - 1)) : Std.parseInt(s.substr(1, s.length - 1));
 | |
| 							#if !(target.unicode)
 | |
| 							if (c >= 128) {
 | |
| 								// UTF8-encode it
 | |
| 								if (c <= 0x7FF) {
 | |
| 									buf.addChar(0xC0 | (c >> 6));
 | |
| 									buf.addChar(0x80 | (c & 63));
 | |
| 								} else if (c <= 0xFFFF) {
 | |
| 									buf.addChar(0xE0 | (c >> 12));
 | |
| 									buf.addChar(0x80 | ((c >> 6) & 63));
 | |
| 									buf.addChar(0x80 | (c & 63));
 | |
| 								} else if (c <= 0x10FFFF) {
 | |
| 									buf.addChar(0xF0 | (c >> 18));
 | |
| 									buf.addChar(0x80 | ((c >> 12) & 63));
 | |
| 									buf.addChar(0x80 | ((c >> 6) & 63));
 | |
| 									buf.addChar(0x80 | (c & 63));
 | |
| 								} else
 | |
| 									throw new XmlParserException("Cannot encode UTF8-char " + c, str, p);
 | |
| 							} else
 | |
| 							#end
 | |
| 							buf.addChar(c);
 | |
| 						} else if (!escapes.exists(s)) {
 | |
| 							if (strict)
 | |
| 								throw new XmlParserException("Undefined entity: " + s, str, p);
 | |
| 							buf.add('&$s;');
 | |
| 						} else {
 | |
| 							buf.add(escapes.get(s));
 | |
| 						}
 | |
| 						start = p + 1;
 | |
| 						state = escapeNext;
 | |
| 					} else if (!isValidChar(c) && c != "#".code) {
 | |
| 						if (strict)
 | |
| 							throw new XmlParserException("Invalid character in entity: " + String.fromCharCode(c), str, p);
 | |
| 						buf.addChar("&".code);
 | |
| 						buf.addSub(str, start, p - start);
 | |
| 						p--;
 | |
| 						start = p + 1;
 | |
| 						state = escapeNext;
 | |
| 					}
 | |
| 			}
 | |
| 			++p;
 | |
| 		}
 | |
| 
 | |
| 		if (state == S.BEGIN) {
 | |
| 			start = p;
 | |
| 			state = S.PCDATA;
 | |
| 		}
 | |
| 
 | |
| 		if (state == S.PCDATA) {
 | |
| 			if (parent.nodeType == Element) {
 | |
| 				throw new XmlParserException("Unclosed node <" + parent.nodeName + ">", str, p);
 | |
| 			}
 | |
| 			if (p != start || nsubs == 0) {
 | |
| 				buf.addSub(str, start, p - start);
 | |
| 				addChild(Xml.createPCData(buf.toString()));
 | |
| 			}
 | |
| 			return p;
 | |
| 		}
 | |
| 
 | |
| 		if (!strict && state == S.ESCAPE && escapeNext == S.PCDATA) {
 | |
| 			buf.addChar("&".code);
 | |
| 			buf.addSub(str, start, p - start);
 | |
| 			addChild(Xml.createPCData(buf.toString()));
 | |
| 			return p;
 | |
| 		}
 | |
| 
 | |
| 		throw new XmlParserException("Unexpected end", str, p);
 | |
| 	}
 | |
| 
 | |
| 	static inline function isValidChar(c) {
 | |
| 		return (c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || (c >= '0'.code && c <= '9'.code) || c == ':'.code || c == '.'.code
 | |
| 			|| c == '_'.code || c == '-'.code;
 | |
| 	}
 | |
| }
 |