/* Extensible Markup Language (XML) 1.0 (Fifth Edition)
 * version http://www.w3.org/TR/2008/REC-xml-20081126/
 * extracted from http://www.w3.org/TR/xml/ on Thu Oct 11, 2018, 14:00 (UTC+08)
 */

document = S? prolog element:element Misc
           { return element }
prolog   = XMLDecl? Misc ( doctypedecl Misc )?
XMLDecl  = '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
VersionInfo
         = S 'version' Eq VersionNum
VersionNum
         = "'" '1.' [0-9]+ "'"
         / '"' '1.' [0-9]+ '"'
EncodingDecl
         = S 'encoding' Eq EncName
EncName  = '"' [A-Za-z][-.0-9A-Z_a-z]* '"'
         / "'" [A-Za-z][-.0-9A-Z_a-z]* "'"
SDDecl   = S 'standalone' Eq SDBool
SDBool   = "'" ( 'yes' / 'no' ) "'"
         / '"' ( 'yes' / 'no' ) '"'
Misc     = ( PI / Comment / S )*
doctypedecl
         = '<!DOCTYPE' S Name ( S ExternalID )? S? ( '[' intSubset ']' S? )? '>'
ExternalID
         = ( 'SYSTEM' / 'PUBLIC' S PubidLiteral ) S SystemLiteral
PubidLiteral
         = '"' ( PubidChar / "'" )* '"'
         / "'" ( PubidChar / '"' )* "'"
PubidChar
         = [-.0-9A-Z_a-z()*+,/:;=?!#@$%\r\n ]
SystemLiteral
         = '"' [^"]* '"'
         / "'" [^']* "'"
intSubset
         = ( markupdecl / DeclSep )*
markupdecl
         = elementdecl
         / AttlistDecl
         / EntityDecl
         / NotationDecl
         / PI
         / Comment
DeclSep  = PEReference
         / S
elementdecl
         = '<!ELEMENT' S Name S contentspec S? '>'
contentspec
         = 'EMPTY'
         / 'ANY'
         / Mixed
         / children
Mixed    = '(' S? '#PCDATA' S? ( ( '|' S? Name S? )* ')*' / ')' )
children = ( choice / seq ) [?*+]?
choice   = '(' S? cp S? ( '|' S? cp S? )+ ')'
seq      = '(' S? cp S? ( ',' S? cp S? )* ')'
cp       = ( Name / choice / seq ) [?*+]?
AttlistDecl
         = '<!ATTLIST' S Name AttDef* S? '>'
AttDef   = S Name S AttType S DefaultDecl
AttType  = 'CDATA'
         / TokenizedType
         / EnumeratedType
TokenizedType
         = 'ID'
         / 'IDREF'
         / 'IDREFS'
         / 'ENTITY'
         / 'ENTITIES'
         / 'NMTOKEN'
         / 'NMTOKENS'
EnumeratedType
         = NotationType
         / Enumeration
NotationType
         = 'NOTATION' S '(' S? Name S? ( '|' S? Name S? )* ')'
Enumeration
         = '(' S? Nmtoken S? ( '|' S? Nmtoken S? )* ')'
Nmtoken  = NameChar+
DefaultDecl
         = '#REQUIRED'
         / '#IMPLIED'
         / ( '#FIXED' S )? AttValue
EntityDecl
         = '<!ENTITY' S Name S EntityDef S? '>'
         / '<!ENTITY' S '%' S Name S PEDef S? '>'
EntityDef
         = EntityValue
         / ExternalID NDataDecl?
PEDef    = EntityValue
         / ExternalID
EntityValue
         = '"' ( [^%&"] / PEReference / Reference )* '"'
         / "'" ( [^%&'] / PEReference / Reference )* "'"
NDataDecl
         = S 'NDATA' S Name
NotationDecl
         = '<!NOTATION' S Name S ( ExternalID / PublicID ) S? '>'
PublicID = 'PUBLIC' S PubidLiteral
PEReference
         = '%' Name ';'

element  = '<' tag:$Name attrs:Attrs S? '/>'
           { return [tag, attrs] }
         / '<' stag:$Name attrs:Attrs S? '>' value:content '</' etag:$Name S? '>' &{ return stag===etag }
           { return [stag, attrs, value] }
Name     = NameStartChar NameChar*
NameStartChar
         = [:A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]
NameChar = NameStartChar
         / [-.0-9\xB7\u0300-\u036F\u203F-\u2040]
Attrs    = a:( S Attribute )*
           {
               let attrs = {};
               for(let i=0; i<a.length; i++) attrs[a[i][1][0]] = a[i][1][1];
               return attrs;
           }
Attribute
         = name:$Name Eq value:AttValue
           { return [name, value] }
AttValue = '"' v:$( [^<&"] / Reference )* '"'
           { return v }
         / "'" v:$( [^<&'] / Reference )* "'"
           { return v }
content  = e:( Misc element )+ Misc
           {
               let element = [];
               for(let i=0; i<e.length; i++) element.push(e[i][1]);
               return element;
           }
         / content:( [^<&] / Reference / CDSect / PI / Comment )*
           { return content.join('') }
CDSect   = '<![CDATA[' ( !']]>' Char )* ']]>'
           { return '' }
PI       = '<?' !( [Xx][Mm][Ll] ) Name ( S ( !'?>' Char )* )? '?>'
           { return '' }
Comment  = '<!--' ( '-'? ( !'-' Char ) )* '-->'
           { return '' }
Eq       = S? '=' S?
S        = [\t\r\n ]+
Char     = [\t\r\n -\uD7FF\uE000-\uFFFD]
Reference
         = '&' &( 'lt'/'gt'/'amp'/'apos'/'quot' ) s:$Name ';'
           { return { lt: '<', gt: '>', amp: '&', apos: "'", quot: '"' }[s] }
         / '&#' s:$[0-9]+ ';'
           { return String.fromCharCode(parseInt(s, 10)) }
         / '&#x' s:$[0-9a-fA-F]+ ';'
           { return String.fromCharCode(parseInt(s, 16)) }
