|
@@ -46,6 +46,59 @@ extern private class S {
|
|
public static inline var ESCAPE = 18;
|
|
public static inline var ESCAPE = 18;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+class XmlParserException
|
|
|
|
+{
|
|
|
|
+ /**
|
|
|
|
+ * the XML parsing error message
|
|
|
|
+ */
|
|
|
|
+ public var message:String;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * the line number at which the XML parsing error occured
|
|
|
|
+ */
|
|
|
|
+ public var lineNumber:Int;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * the character position in the reported line at which the parsing error occured
|
|
|
|
+ */
|
|
|
|
+ public var positionAtLine:Int;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * the character position in the XML string at which the parsing error occured
|
|
|
|
+ */
|
|
|
|
+ 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
|
|
class Parser
|
|
{
|
|
{
|
|
static var escapes = {
|
|
static var escapes = {
|
|
@@ -59,8 +112,10 @@ class Parser
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- Parses the String into an XML Document. Set strict parsing to true in order to enable a strict check of XML attributes and entities.
|
|
|
|
- **/
|
|
|
|
|
|
+ * 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)
|
|
static public function parse(str:String, strict = false)
|
|
{
|
|
{
|
|
var doc = Xml.createDocument();
|
|
var doc = Xml.createDocument();
|
|
@@ -144,7 +199,7 @@ class Parser
|
|
{
|
|
{
|
|
p += 2;
|
|
p += 2;
|
|
if (str.substr(p, 6).toUpperCase() != "CDATA[")
|
|
if (str.substr(p, 6).toUpperCase() != "CDATA[")
|
|
- throw("Expected <![CDATA[");
|
|
|
|
|
|
+ throw new XmlParserException("Expected <![CDATA[", str, p);
|
|
p += 5;
|
|
p += 5;
|
|
state = S.CDATA;
|
|
state = S.CDATA;
|
|
start = p + 1;
|
|
start = p + 1;
|
|
@@ -152,13 +207,13 @@ class Parser
|
|
else if (str.fastCodeAt(p + 1) == 'D'.code || str.fastCodeAt(p + 1) == 'd'.code)
|
|
else if (str.fastCodeAt(p + 1) == 'D'.code || str.fastCodeAt(p + 1) == 'd'.code)
|
|
{
|
|
{
|
|
if(str.substr(p + 2, 6).toUpperCase() != "OCTYPE")
|
|
if(str.substr(p + 2, 6).toUpperCase() != "OCTYPE")
|
|
- throw("Expected <!DOCTYPE");
|
|
|
|
|
|
+ throw new XmlParserException("Expected <!DOCTYPE", str, p);
|
|
p += 8;
|
|
p += 8;
|
|
state = S.DOCTYPE;
|
|
state = S.DOCTYPE;
|
|
start = p + 1;
|
|
start = p + 1;
|
|
}
|
|
}
|
|
else if( str.fastCodeAt(p + 1) != '-'.code || str.fastCodeAt(p + 2) != '-'.code )
|
|
else if( str.fastCodeAt(p + 1) != '-'.code || str.fastCodeAt(p + 2) != '-'.code )
|
|
- throw("Expected <!--");
|
|
|
|
|
|
+ throw new XmlParserException("Expected <!--", str, p);
|
|
else
|
|
else
|
|
{
|
|
{
|
|
p += 2;
|
|
p += 2;
|
|
@@ -170,7 +225,7 @@ class Parser
|
|
start = p;
|
|
start = p;
|
|
case '/'.code:
|
|
case '/'.code:
|
|
if( parent == null )
|
|
if( parent == null )
|
|
- throw("Expected node name");
|
|
|
|
|
|
+ throw new XmlParserException("Expected node name", str, p);
|
|
start = p + 1;
|
|
start = p + 1;
|
|
state = S.IGNORE_SPACES;
|
|
state = S.IGNORE_SPACES;
|
|
next = S.CLOSE;
|
|
next = S.CLOSE;
|
|
@@ -183,7 +238,7 @@ class Parser
|
|
if (!isValidChar(c))
|
|
if (!isValidChar(c))
|
|
{
|
|
{
|
|
if( p == start )
|
|
if( p == start )
|
|
- throw("Expected node name");
|
|
|
|
|
|
+ throw new XmlParserException("Expected node name", str, p);
|
|
xml = Xml.createElement(str.substr(start, p - start));
|
|
xml = Xml.createElement(str.substr(start, p - start));
|
|
addChild(xml);
|
|
addChild(xml);
|
|
state = S.IGNORE_SPACES;
|
|
state = S.IGNORE_SPACES;
|
|
@@ -207,11 +262,11 @@ class Parser
|
|
{
|
|
{
|
|
var tmp;
|
|
var tmp;
|
|
if( start == p )
|
|
if( start == p )
|
|
- throw("Expected attribute name");
|
|
|
|
|
|
+ throw new XmlParserException("Expected attribute name", str, p);
|
|
tmp = str.substr(start,p-start);
|
|
tmp = str.substr(start,p-start);
|
|
aname = tmp;
|
|
aname = tmp;
|
|
if( xml.exists(aname) )
|
|
if( xml.exists(aname) )
|
|
- throw("Duplicate attribute");
|
|
|
|
|
|
+ throw new XmlParserException("Duplicate attribute [" + aname + "]", str, p);
|
|
state = S.IGNORE_SPACES;
|
|
state = S.IGNORE_SPACES;
|
|
next = S.EQUALS;
|
|
next = S.EQUALS;
|
|
continue;
|
|
continue;
|
|
@@ -223,7 +278,7 @@ class Parser
|
|
state = S.IGNORE_SPACES;
|
|
state = S.IGNORE_SPACES;
|
|
next = S.ATTVAL_BEGIN;
|
|
next = S.ATTVAL_BEGIN;
|
|
default:
|
|
default:
|
|
- throw("Expected =");
|
|
|
|
|
|
+ throw new XmlParserException("Expected =", str, p);
|
|
}
|
|
}
|
|
case S.ATTVAL_BEGIN:
|
|
case S.ATTVAL_BEGIN:
|
|
switch(c)
|
|
switch(c)
|
|
@@ -234,7 +289,7 @@ class Parser
|
|
start = p + 1;
|
|
start = p + 1;
|
|
attrValQuote = c;
|
|
attrValQuote = c;
|
|
default:
|
|
default:
|
|
- throw("Expected \"");
|
|
|
|
|
|
+ throw new XmlParserException("Expected \"", str, p);
|
|
}
|
|
}
|
|
case S.ATTRIB_VAL:
|
|
case S.ATTRIB_VAL:
|
|
switch (c) {
|
|
switch (c) {
|
|
@@ -245,7 +300,7 @@ class Parser
|
|
start = p + 1;
|
|
start = p + 1;
|
|
case '>'.code | '<'.code if( strict ):
|
|
case '>'.code | '<'.code if( strict ):
|
|
// HTML allows these in attributes values
|
|
// HTML allows these in attributes values
|
|
- throw "Invalid unescaped " + String.fromCharCode(c) + " in attribute value";
|
|
|
|
|
|
+ throw new XmlParserException("Invalid unescaped " + String.fromCharCode(c) + " in attribute value", str, p);
|
|
case _ if (c == attrValQuote):
|
|
case _ if (c == attrValQuote):
|
|
buf.addSub(str, start, p - start);
|
|
buf.addSub(str, start, p - start);
|
|
var val = buf.toString();
|
|
var val = buf.toString();
|
|
@@ -264,7 +319,7 @@ class Parser
|
|
case '>'.code:
|
|
case '>'.code:
|
|
state = S.BEGIN;
|
|
state = S.BEGIN;
|
|
default :
|
|
default :
|
|
- throw("Expected >");
|
|
|
|
|
|
+ throw new XmlParserException("Expected >", str, p);
|
|
}
|
|
}
|
|
case S.WAIT_END_RET:
|
|
case S.WAIT_END_RET:
|
|
switch(c)
|
|
switch(c)
|
|
@@ -274,17 +329,17 @@ class Parser
|
|
parent.addChild(Xml.createPCData(""));
|
|
parent.addChild(Xml.createPCData(""));
|
|
return p;
|
|
return p;
|
|
default :
|
|
default :
|
|
- throw("Expected >");
|
|
|
|
|
|
+ throw new XmlParserException("Expected >", str, p);
|
|
}
|
|
}
|
|
case S.CLOSE:
|
|
case S.CLOSE:
|
|
if (!isValidChar(c))
|
|
if (!isValidChar(c))
|
|
{
|
|
{
|
|
if( start == p )
|
|
if( start == p )
|
|
- throw("Expected node name");
|
|
|
|
|
|
+ throw new XmlParserException("Expected node name", str, p);
|
|
|
|
|
|
var v = str.substr(start,p - start);
|
|
var v = str.substr(start,p - start);
|
|
if (v != parent.nodeName)
|
|
if (v != parent.nodeName)
|
|
- throw "Expected </" +parent.nodeName + ">";
|
|
|
|
|
|
+ throw new XmlParserException("Expected </" +parent.nodeName + ">", str, p);
|
|
|
|
|
|
state = S.IGNORE_SPACES;
|
|
state = S.IGNORE_SPACES;
|
|
next = S.WAIT_END_RET;
|
|
next = S.WAIT_END_RET;
|
|
@@ -339,13 +394,13 @@ class Parser
|
|
buf.addChar(0x80 | ((c >> 6) & 63));
|
|
buf.addChar(0x80 | ((c >> 6) & 63));
|
|
buf.addChar(0x80 | (c & 63));
|
|
buf.addChar(0x80 | (c & 63));
|
|
} else
|
|
} else
|
|
- throw "Cannot encode UTF8-char " + c;
|
|
|
|
|
|
+ throw new XmlParserException("Cannot encode UTF8-char " + c, str, p);
|
|
} else
|
|
} else
|
|
#end
|
|
#end
|
|
buf.addChar(c);
|
|
buf.addChar(c);
|
|
} else if (!escapes.exists(s)) {
|
|
} else if (!escapes.exists(s)) {
|
|
if( strict )
|
|
if( strict )
|
|
- throw 'Undefined entity: $s';
|
|
|
|
|
|
+ throw new XmlParserException("Undefined entity: " + s, str, p);
|
|
buf.add('&$s;');
|
|
buf.add('&$s;');
|
|
} else {
|
|
} else {
|
|
buf.add(escapes.get(s));
|
|
buf.add(escapes.get(s));
|
|
@@ -354,7 +409,7 @@ class Parser
|
|
state = escapeNext;
|
|
state = escapeNext;
|
|
} else if (!isValidChar(c) && c != "#".code) {
|
|
} else if (!isValidChar(c) && c != "#".code) {
|
|
if( strict )
|
|
if( strict )
|
|
- throw 'Invalid character in entity: ' + String.fromCharCode(c);
|
|
|
|
|
|
+ throw new XmlParserException("Invalid character in entity: " + String.fromCharCode(c), str, p);
|
|
buf.addChar("&".code);
|
|
buf.addChar("&".code);
|
|
buf.addSub(str, start, p - start);
|
|
buf.addSub(str, start, p - start);
|
|
p--;
|
|
p--;
|
|
@@ -387,10 +442,10 @@ class Parser
|
|
return p;
|
|
return p;
|
|
}
|
|
}
|
|
|
|
|
|
- throw "Unexpected end";
|
|
|
|
|
|
+ throw new XmlParserException("Unexpected end", str, p);
|
|
}
|
|
}
|
|
|
|
|
|
static inline function isValidChar(c) {
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+}
|