diff options
author | bptato <nincsnevem662@gmail.com> | 2023-07-15 20:05:15 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-07-15 20:06:07 +0200 |
commit | 9aa9ecd2d2c5048ac3057aeee28b1e42ce4be0d8 (patch) | |
tree | 8f0ca8b8625f633b1e75f9f929f15b4b4322038f /src/html | |
parent | 96fceba76a218799d49652befab8b99289b07133 (diff) | |
download | chawan-9aa9ecd2d2c5048ac3057aeee28b1e42ce4be0d8.tar.gz |
htmlparser: add parse error handling
Diffstat (limited to 'src/html')
-rw-r--r-- | src/html/chadombuilder.nim | 1 | ||||
-rw-r--r-- | src/html/htmlparser.nim | 414 | ||||
-rw-r--r-- | src/html/htmltokenizer.nim | 233 | ||||
-rw-r--r-- | src/html/parseerror.nim | 70 |
4 files changed, 437 insertions, 281 deletions
diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim index 2ddd067b..eb83af23 100644 --- a/src/html/chadombuilder.nim +++ b/src/html/chadombuilder.nim @@ -152,7 +152,6 @@ proc newChaDOMBuilder(url: URL, window: Window): ChaDOMBuilder = return ChaDOMBuilder( document: document, finish: finish, - parseError: parseError, setQuirksMode: setQuirksMode, setCharacterSet: setCharacterset, elementPopped: elementPopped, diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim index 41bcf66b..e72f897e 100644 --- a/src/html/htmlparser.nim +++ b/src/html/htmlparser.nim @@ -8,11 +8,13 @@ import unicode import data/charset import encoding/decoderstream -import html/tags import html/htmltokenizer +import html/parseerror +import html/tags import utils/twtstr -export macros, unicode +# Generics break without exporting macros. Maybe a compiler bug? +export macros # Heavily inspired by html5ever's TreeSink design. type @@ -109,8 +111,11 @@ type ## Parsing has finished. DOMBuilderParseError*[Handle] = - proc(builder: DOMBuilder[Handle], message: string) {.nimcall.} - ## Parse error. + proc(builder: DOMBuilder[Handle], message: ParseError) {.nimcall.} + ## Parse error. `message` is an error code either specified by the + ## standard (in this case, message < LAST_SPECIFIED_ERROR) or named + ## arbitrarily. (At the time of writing, only tokenizer errors have + ## specified error codes.) DOMBuilderSetQuirksMode*[Handle] = proc(builder: DOMBuilder[Handle], quirksMode: QuirksMode) {.nimcall.} @@ -279,6 +284,10 @@ proc finish[Handle](parser: HTML5Parser[Handle]) = if parser.dombuilder.finish != nil: parser.dombuilder.finish(parser.dombuilder) +proc parseError(parser: HTML5Parser, e: ParseError) = + if parser.dombuilder.parseError != nil: + parser.dombuilder.parseError(parser.dombuilder, e) + proc setQuirksMode[Handle](parser: var HTML5Parser[Handle], mode: QuirksMode) = parser.quirksMode = mode if parser.dombuilder.setQuirksMode != nil: @@ -371,6 +380,9 @@ func isSVGIntegrationPoint[Handle](parser: HTML5Parser, return false # Parser +func hasParseError(parser: HTML5Parser): bool = + return parser.dombuilder.parseError != nil + func tagNameEquals[Handle](parser: HTML5Parser, handle: Handle, token: Token): bool = let tagType = parser.getTagType(handle) @@ -441,8 +453,6 @@ func adjustedCurrentNode[Handle](parser: HTML5Parser[Handle]): Handle = else: parser.currentNode -template parse_error() = discard - func lastElementOfTag[Handle](parser: HTML5Parser[Handle], tagType: TagType): tuple[element: Handle, pos: int] = for i in countdown(parser.openElements.high, 0): @@ -488,6 +498,12 @@ func hasElement[Handle](parser: HTML5Parser[Handle], tag: TagType): bool = return true return false +func hasElement[Handle](parser: HTML5Parser[Handle], tags: set[TagType]): bool = + for element in parser.openElements: + if parser.getTagType(element) in tags: + return true + return false + func hasElementInSpecificScope[Handle](parser: HTML5Parser[Handle], target: Handle, list: set[TagType]): bool = for i in countdown(parser.openElements.high, 0): @@ -980,8 +996,21 @@ proc changeEncoding(parser: var HTML5Parser, cs: Charset) = parser.charset = cs parser.needsreinterpret = true +proc parseErrorByTokenType(parser: var HTML5Parser, tokenType: TokenType) = + case tokenType + of START_TAG: + parser.parseError UNEXPECTED_START_TAG + of END_TAG: + parser.parseError UNEXPECTED_END_TAG + of EOF: + parser.parseError UNEXPECTED_EOF + else: + doAssert false + proc adoptionAgencyAlgorithm[Handle](parser: var HTML5Parser[Handle], token: Token): bool = + template parse_error(e: ParseError) = + parser.parseError(e) if parser.tagNameEquals(parser.currentNode, token): var fail = true for it in parser.activeFormatting: @@ -1009,13 +1038,14 @@ proc adoptionAgencyAlgorithm[Handle](parser: var HTML5Parser[Handle], return true let stackIndex = parser.openElements.find(formatting) if stackIndex < 0: - parse_error + parse_error ELEMENT_NOT_IN_OPEN_ELEMENTS parser.activeFormatting.delete(formattingIndex) return false if not parser.hasElementInScope(formatting): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE return false - if formatting != parser.currentNode: parse_error + if formatting != parser.currentNode: + parse_error ELEMENT_NOT_CURRENT_NODE var furthestBlock: Handle = nil var furthestBlockIndex: int for j in countdown(parser.openElements.high, 0): @@ -1087,7 +1117,7 @@ proc adoptionAgencyAlgorithm[Handle](parser: var HTML5Parser[Handle], proc closeP(parser: var HTML5Parser) = parser.generateImpliedEndTags(TAG_P) if parser.getTagType(parser.currentNode) != TAG_P: - parse_error + parser.parseError(MISMATCHED_TAGS) while parser.getTagType(parser.popElement()) != TAG_P: discard @@ -1275,22 +1305,43 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], token: Token, insertionMode: InsertionMode) = template pop_all_nodes = while parser.openElements.len > 1: pop_current_node + template anything_else = discard "anything_else" + macro `=>`(v: typed, body: untyped): untyped = quote do: discard (`v`, proc() = `body`) + template _ = discard + template reprocess(tok: Token) = parser.processInHTMLContent(tok, parser.insertionMode) + template parse_error(e: ParseError) = + parser.parseError(e) + + template parse_error_if_mismatch(tagtype: TagType) = + if parser.hasParseError(): + if parser.getTagType(parser.currentNode) != TAG_DD: + parse_error MISMATCHED_TAGS + + template parse_error_if_mismatch(tagtypes: set[TagType]) = + if parser.hasParseError(): + if parser.getTagType(parser.currentNode) notin tagtypes: + parse_error MISMATCHED_TAGS + case insertionMode of INITIAL: match token: AsciiWhitespace => (block: discard) - TokenType.COMMENT => (block: parser.insertComment(token, last_child_of(parser.document))) + TokenType.COMMENT => (block: + parser.insertComment(token, last_child_of(parser.document)) + ) TokenType.DOCTYPE => (block: - if token.name.isnone or token.name.get != "html" or token.pubid.issome or (token.sysid.issome and token.sysid.get != "about:legacy-compat"): - parse_error + if token.name.isNone or + token.name.get != "html" or token.pubid.isSome or + (token.sysid.isSome and token.sysid.get != "about:legacy-compat"): + parse_error INVALID_DOCTYPE let doctype = parser.createDocumentType(token.name.get(""), token.pubid.get(""), token.sysid.get("")) parser.append(parser.document, doctype) @@ -1303,7 +1354,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) _ => (block: if not parser.opts.isIframeSrcdoc: - parse_error + parse_error UNEXPECTED_INITIAL_TOKEN parser.setQuirksMode(QUIRKS) parser.insertionMode = BEFORE_HTML reprocess token @@ -1311,17 +1362,20 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], of BEFORE_HTML: match token: - TokenType.DOCTYPE => (block: parse_error) - TokenType.COMMENT => (block: parser.insertComment(token, last_child_of(parser.document))) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) + TokenType.COMMENT => (block: + parser.insertComment(token, last_child_of(parser.document)) + ) AsciiWhitespace => (block: discard) "<html>" => (block: - let element = parser.createElement(token, Namespace.HTML, parser.document) + let element = parser.createElement(token, Namespace.HTML, + parser.document) parser.append(parser.document, element) parser.pushElement(element) parser.insertionMode = BEFORE_HEAD ) ("</head>", "</body>", "</html>", "</br>") => (block: anything_else) - TokenType.END_TAG => (block: parse_error) + TokenType.END_TAG => (block: parse_error UNEXPECTED_END_TAG) _ => (block: let element = parser.createElement(TAG_HTML, Namespace.HTML) parser.append(parser.document, element) @@ -1334,14 +1388,14 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: AsciiWhitespace => (block: discard) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "<head>" => (block: parser.head = some(parser.insertHTMLElement(token)) parser.insertionMode = IN_HEAD ) ("</head>", "</body>", "</html>", "</br>") => (block: anything_else) - TokenType.END_TAG => (block: parse_error) + TokenType.END_TAG => (block: parse_error UNEXPECTED_END_TAG) _ => (block: let token = Token(t: START_TAG, tagtype: TAG_HEAD) parser.head = some(parser.insertHTMLElement(token)) @@ -1353,7 +1407,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: AsciiWhitespace => (block: discard) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) ("<base>", "<basefont>", "<bgsound>", "<link>") => (block: discard parser.insertHTMLElement(token) @@ -1406,17 +1460,17 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) "</template>" => (block: if not parser.hasElement(TAG_TEMPLATE): - parse_error + parse_error ELEMENT_NOT_IN_OPEN_ELEMENTS else: parser.generateImpliedEndTagsThoroughly() if parser.getTagType(parser.currentNode) != TAG_TEMPLATE: - parse_error + parse_error MISMATCHED_TAGS parser.popElementsIncl(TAG_TEMPLATE) parser.clearActiveFormattingTillMarker() discard parser.templateModes.pop() parser.resetInsertionMode() ) - ("<head>", TokenType.END_TAG) => (block: parse_error) + ("<head>", TokenType.END_TAG) => (block: parse_error UNEXPECTED_END_TAG) _ => (block: pop_current_node parser.insertionMode = AFTER_HEAD @@ -1425,19 +1479,20 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], of IN_HEAD_NOSCRIPT: match token: - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "</noscript>" => (block: pop_current_node parser.insertionMode = IN_HEAD ) (AsciiWhitespace, - TokenType.COMMENT, - "<basefont>", "<bgsound>", "<link>", "<meta>", "<noframes>", "<style>") => (block: + TokenType.COMMENT, + "<basefont>", "<bgsound>", "<link>", "<meta>", "<noframes>", + "<style>") => (block: parser.processInHTMLContent(token, IN_HEAD)) "</br>" => (block: anything_else) - ("<head>", "<noscript>") => (block: parse_error) - TokenType.END_TAG => (block: parse_error) + ("<head>", "<noscript>") => (block: parse_error UNEXPECTED_START_TAG) + TokenType.END_TAG => (block: parse_error UNEXPECTED_END_TAG) _ => (block: pop_current_node parser.insertionMode = IN_HEAD @@ -1448,7 +1503,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: AsciiWhitespace => (block: parser.insertCharacter(token.c)) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "<body>" => (block: discard parser.insertHTMLElement(token) @@ -1461,7 +1516,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) ("<base>", "<basefont>", "<bgsound>", "<link>", "<meta>", "<noframes>", "<script>", "<style>", "<template>", "<title>") => (block: - parse_error + parse_error UNEXPECTED_START_TAG parser.pushElement(parser.head.get) parser.processInHTMLContent(token, IN_HEAD) for i in countdown(parser.openElements.high, 0): @@ -1470,7 +1525,8 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) "</template>" => (block: parser.processInHTMLContent(token, IN_HEAD)) ("</body>", "</html>", "</br>") => (block: anything_else) - ("<head>", TokenType.END_TAG) => (block: parse_error) + ("<head>") => (block: parse_error UNEXPECTED_START_TAG) + (TokenType.END_TAG) => (block: parse_error UNEXPECTED_END_TAG) _ => (block: discard parser.insertHTMLElement(Token(t: START_TAG, tagtype: TAG_BODY)) parser.insertionMode = IN_BODY @@ -1487,15 +1543,27 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], let node = parser.openElements[i] if parser.tagNameEquals(node, token): parser.generateImpliedEndTags(token.tagtype) - if node != parser.currentNode: parse_error - while parser.popElement() != node: discard + if node != parser.currentNode: + parse_error ELEMENT_NOT_CURRENT_NODE + while parser.popElement() != node: + discard break elif parser.getTagType(node) in SpecialElements: - parse_error + parse_error UNEXPECTED_SPECIAL_ELEMENT return + template parse_error_if_body_has_disallowed_open_elements = + if parser.hasParseError(): + const Disallowed = AllTagTypes - { + TAG_DD, TAG_DT, TAG_LI, TAG_OPTGROUP, TAG_OPTION, TAG_P, TAG_RB, + TAG_RP, TAG_RT, TAG_RTC, TAG_TBODY, TAG_TD, TAG_TFOOT, TAG_TH, + TAG_THEAD, TAG_TR, TAG_BODY, TAG_HTML + } + if parser.hasElement(Disallowed): + parse_error MISMATCHED_TAGS + match token: - '\0' => (block: parse_error) + '\0' => (block: parse_error UNEXPECTED_NULL) AsciiWhitespace => (block: parser.reconstructActiveFormatting() parser.insertCharacter(token.c) @@ -1511,18 +1579,19 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.framesetOk = false ) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: - parse_error + parse_error UNEXPECTED_START_TAG if parser.hasElement(TAG_TEMPLATE): discard else: parser.addAttrsIfMissing(parser.openElements[0], token.attrs) ) - ("<base>", "<basefont>", "<bgsound>", "<link>", "<meta>", "<noframes>", "<script>", "<style>", "<template>", "<title>", - "</template>") => (block: parser.processInHTMLContent(token, IN_HEAD)) + ("<base>", "<basefont>", "<bgsound>", "<link>", "<meta>", "<noframes>", + "<script>", "<style>", "<template>", "<title>", + "</template>") => (block: parser.processInHTMLContent(token, IN_HEAD)) "<body>" => (block: - parse_error + parse_error UNEXPECTED_START_TAG if parser.openElements.len == 1 or parser.getTagType(parser.openElements[1]) != TAG_BODY or parser.hasElement(TAG_TEMPLATE): @@ -1532,7 +1601,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.addAttrsIfMissing(parser.openElements[1], token.attrs) ) "<frameset>" => (block: - parse_error + parse_error UNEXPECTED_START_TAG if parser.openElements.len == 1 or parser.getTagType(parser.openElements[1]) != TAG_BODY or not parser.framesetOk: @@ -1545,21 +1614,21 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], if parser.templateModes.len > 0: parser.processInHTMLContent(token, IN_TEMPLATE) else: - #NOTE parse error omitted - discard # stop + parse_error_if_body_has_disallowed_open_elements + # stop ) "</body>" => (block: if not parser.hasElementInScope(TAG_BODY): - parse_error + parse_error UNEXPECTED_END_TAG else: - #NOTE parse error omitted + parse_error_if_body_has_disallowed_open_elements parser.insertionMode = AFTER_BODY ) "</html>" => (block: if not parser.hasElementInScope(TAG_BODY): - parse_error + parse_error UNEXPECTED_END_TAG else: - #NOTE parse error omitted + parse_error_if_body_has_disallowed_open_elements parser.insertionMode = AFTER_BODY reprocess token ) @@ -1575,7 +1644,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], if parser.hasElementInButtonScope(TAG_P): parser.closeP() if parser.getTagType(parser.currentNode) in HTagTypes: - parse_error + parse_error NESTED_TAGS pop_current_node discard parser.insertHTMLElement(token) ) @@ -1589,7 +1658,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], "<form>" => (block: let hasTemplate = parser.hasElement(TAG_TEMPLATE) if parser.form.isSome and not hasTemplate: - parse_error + parse_error NESTED_TAGS else: if parser.hasElementInButtonScope(TAG_P): parser.closeP() @@ -1605,8 +1674,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], case tagType of TAG_LI: parser.generateImpliedEndTags(TAG_LI) - if parser.getTagType(parser.currentNode) != TAG_LI: - parse_error + parse_error_if_mismatch TAG_LI parser.popElementsIncl(TAG_LI) break of SpecialElements - {TAG_ADDRESS, TAG_DIV, TAG_P, TAG_LI}: @@ -1624,14 +1692,12 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], case tagType of TAG_DD: parser.generateImpliedEndTags(TAG_DD) - if parser.getTagType(parser.currentNode) != TAG_DD: - parse_error + parse_error_if_mismatch TAG_DD parser.popElementsIncl(TAG_DD) break of TAG_DT: parser.generateImpliedEndTags(TAG_DT) - if parser.getTagType(parser.currentNode) != TAG_DT: - parse_error + parse_error_if_mismatch TAG_DT parser.popElementsIncl(TAG_DT) break of SpecialElements - {TAG_ADDRESS, TAG_DIV, TAG_P, TAG_DD, TAG_DT}: @@ -1649,7 +1715,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) "<button>" => (block: if parser.hasElementInScope(TAG_BUTTON): - parse_error + parse_error NESTED_TAGS parser.generateImpliedEndTags() parser.popElementsIncl(TAG_BUTTON) parser.reconstructActiveFormatting() @@ -1662,11 +1728,10 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], "</hgroup>", "</listing>", "</main>", "</menu>", "</nav>", "</ol>", "</pre>", "</section>", "</summary>", "</ul>") => (block: if not parser.hasElementInScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != token.tagtype: - parse_error + parse_error_if_mismatch token.tagtype parser.popElementsIncl(token.tagtype) ) "</form>" => (block: @@ -1675,53 +1740,49 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.form = none(Handle) if form.isNone or not parser.hasElementInScope(parser.getTagType(form.get)): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE return let node = form.get parser.generateImpliedEndTags() if parser.currentNode != node: - parse_error + parse_error ELEMENT_NOT_CURRENT_NODE parser.openElements.delete(parser.openElements.find(node)) else: if not parser.hasElementInScope(TAG_FORM): - parse_error - return - parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != TAG_FORM: - parse_error - parser.popElementsIncl(TAG_FORM) + parse_error ELEMENT_NOT_IN_SCOPE + else: + parser.generateImpliedEndTags() + parse_error_if_mismatch TAG_FORM + parser.popElementsIncl(TAG_FORM) ) "</p>" => (block: if not parser.hasElementInButtonScope(TAG_P): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE discard parser.insertHTMLElement(Token(t: START_TAG, tagtype: TAG_P)) parser.closeP() ) "</li>" => (block: if not parser.hasElementInListItemScope(TAG_LI): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags(TAG_LI) - if parser.getTagType(parser.currentNode) != TAG_LI: - parse_error + parse_error_if_mismatch TAG_LI parser.popElementsIncl(TAG_LI) ) ("</dd>", "</dt>") => (block: if not parser.hasElementInScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags(token.tagtype) - if parser.getTagType(parser.currentNode) != token.tagtype: - parse_error + parse_error_if_mismatch token.tagtype parser.popElementsIncl(token.tagtype) ) ("</h1>", "</h2>", "</h3>", "</h4>", "</h5>", "</h6>") => (block: if not parser.hasElementInScope(HTagTypes): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != token.tagtype: - parse_error + parse_error_if_mismatch token.tagtype parser.popElementsIncl(HTagTypes) ) "</sarcasm>" => (block: @@ -1729,25 +1790,25 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], anything_else ) "<a>" => (block: - var anchor: Handle = nil + var anchor: Option[Handle] for i in countdown(parser.activeFormatting.high, 0): let format = parser.activeFormatting[i] if format[0] == nil: break if parser.getTagType(format[0]) == TAG_A: - anchor = format[0] + anchor = some(format[0]) break - if anchor != nil: - parse_error + if anchor.isSome: + parse_error NESTED_TAGS if parser.adoptionAgencyAlgorithm(token): any_other_end_tag return for i in 0..parser.activeFormatting.high: - if parser.activeFormatting[i][0] == anchor: + if parser.activeFormatting[i][0] == anchor.get: parser.activeFormatting.delete(i) break for i in 0..parser.openElements.high: - if parser.openElements[i] == anchor: + if parser.openElements[i] == anchor.get: parser.openElements.delete(i) break parser.reconstructActiveFormatting() @@ -1763,7 +1824,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], "<nobr>" => (block: parser.reconstructActiveFormatting() if parser.hasElementInScope(TAG_NOBR): - parse_error + parse_error NESTED_TAGS if parser.adoptionAgencyAlgorithm(token): any_other_end_tag return @@ -1786,11 +1847,10 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) ("</applet>", "</marquee>", "</object>") => (block: if not parser.hasElementInScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != token.tagtype: - parse_error + parse_error_if_mismatch token.tagtype while parser.getTagType(parser.popElement()) != token.tagtype: discard parser.clearActiveFormattingTillMarker() ) @@ -1803,7 +1863,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.insertionMode = IN_TABLE ) "</br>" => (block: - parse_error + parse_error UNEXPECTED_END_TAG reprocess Token(t: START_TAG, tagtype: TAG_BR) ) ("<area>", "<br>", "<embed>", "<img>", "<keygen>", "<wbr>") => (block: @@ -1881,19 +1941,21 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ("<rb>", "<rtc>") => (block: if parser.hasElementInScope(TAG_RUBY): parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != TAG_RUBY: parse_error + parse_error_if_mismatch TAG_RUBY discard parser.insertHTMLElement(token) ) ("<rp>", "<rt>") => (block: if parser.hasElementInScope(TAG_RUBY): parser.generateImpliedEndTags(TAG_RTC) - if parser.getTagType(parser.currentNode) notin {TAG_RUBY, TAG_RTC}: parse_error + parse_error_if_mismatch {TAG_RUBY, TAG_RTC} discard parser.insertHTMLElement(token) ) #NOTE <math> (not implemented) #TODO <svg> (SVG) ("<caption>", "<col>", "<colgroup>", "<frame>", "<head>", "<tbody>", - "<td>", "<tfoot>", "<th>", "<thead>", "<tr>") => (block: parse_error) + "<td>", "<tfoot>", "<th>", "<thead>", "<tr>") => (block: + parse_error UNEXPECTED_START_TAG + ) TokenType.START_TAG => (block: any_other_start_tag) TokenType.END_TAG => (block: any_other_end_tag) @@ -1907,7 +1969,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.insertCharacter(token.r) ) TokenType.EOF => (block: - parse_error + parse_error UNEXPECTED_EOF if parser.getTagType(parser.currentNode) == TAG_SCRIPT: parser.setScriptAlreadyStarted(parser.currentNode) pop_current_node @@ -1931,20 +1993,23 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: (TokenType.CHARACTER_ASCII, TokenType.CHARACTER) => (block: - if parser.getTagType(parser.currentNode) in {TAG_TABLE, TAG_TBODY, TAG_TFOOT, TAG_THEAD, TAG_TR}: + const CanHaveText = { + TAG_TABLE, TAG_TBODY, TAG_TFOOT, TAG_THEAD, TAG_TR + } + if parser.getTagType(parser.currentNode) in CanHaveText: parser.pendingTableChars = "" parser.pendingTableCharsWhitespace = true parser.oldInsertionMode = parser.insertionMode parser.insertionMode = IN_TABLE_TEXT reprocess token else: # anything else - parse_error + parse_error INVALID_TEXT_PARENT parser.fosterParenting = true parser.processInHTMLContent(token, IN_BODY) parser.fosterParenting = false ) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<caption>" => (block: clear_the_stack_back_to_a_table_context parser.activeFormatting.add((nil, nil)) @@ -1968,7 +2033,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], reprocess token ) "<table>" => (block: - parse_error + parse_error NESTED_TAGS if not parser.hasElementInScope(TAG_TABLE): discard else: @@ -1978,32 +2043,31 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) "</table>" => (block: if not parser.hasElementInScope(TAG_TABLE): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: while parser.getTagType(parser.popElement()) != TAG_TABLE: discard parser.resetInsertionMode() ) ("</body>", "</caption>", "</col>", "</colgroup>", "</html>", "</tbody>", "</td>", "</tfoot>", "</th>", "</thead>", "</tr>") => (block: - parse_error + parse_error UNEXPECTED_END_TAG ) ("<style>", "<script>", "<template>", "</template>") => (block: parser.processInHTMLContent(token, IN_HEAD) ) "<input>" => (block: + parse_error UNEXPECTED_START_TAG if not token.attrs.getOrDefault("type").equalsIgnoreCase("hidden"): # anything else - parse_error parser.fosterParenting = true parser.processInHTMLContent(token, IN_BODY) parser.fosterParenting = false else: - parse_error discard parser.insertHTMLElement(token) pop_current_node ) "<form>" => (block: - parse_error + parse_error UNEXPECTED_START_TAG if parser.form.isSome or parser.hasElement(TAG_TEMPLATE): discard else: @@ -2014,7 +2078,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.processInHTMLContent(token, IN_BODY) ) _ => (block: - parse_error + parse_error UNEXPECTED_START_TAG parser.fosterParenting = true parser.processInHTMLContent(token, IN_BODY) parser.fosterParenting = false @@ -2022,20 +2086,21 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], of IN_TABLE_TEXT: match token: - '\0' => (block: parse_error) + '\0' => (block: parse_error UNEXPECTED_NULL) TokenType.CHARACTER_ASCII => (block: if token.c notin AsciiWhitespace: parser.pendingTableCharsWhitespace = false parser.pendingTableChars &= token.c ) TokenType.CHARACTER => (block: - parser.pendingTableChars &= token.r + parser.pendingTableChars &= $token.r parser.pendingTableCharsWhitespace = false ) _ => (block: if not parser.pendingTableCharsWhitespace: - # I *think* this is effectively the same thing the specification wants... - parse_error + # I *think* this is effectively the same thing the specification + # wants... + parse_error NON_SPACE_TABLE_TEXT parser.fosterParenting = true parser.reconstructActiveFormatting() parser.insertCharacter(parser.pendingTableChars) @@ -2051,34 +2116,36 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: "</caption>" => (block: if not parser.hasElementInTableScope(TAG_CAPTION): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != TAG_CAPTION: parse_error - while parser.getTagType(parser.popElement()) != TAG_CAPTION: discard + parse_error_if_mismatch TAG_CAPTION + parser.popElementsIncl(TAG_CAPTION) parser.clearActiveFormattingTillMarker() parser.insertionMode = IN_TABLE ) ("<caption>", "<col>", "<colgroup>", "<tbody>", "<td>", "<tfoot>", "<th>", "<thead>", "<tr>", "</table>") => (block: if not parser.hasElementInTableScope(TAG_CAPTION): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != TAG_CAPTION: parse_error + parse_error_if_mismatch TAG_CAPTION parser.clearActiveFormattingTillMarker() parser.insertionMode = IN_TABLE reprocess token ) ("</body>", "</col>", "</colgroup>", "</html>", "</tbody>", "</td>", - "</tfoot>", "</th>", "</thead>", "</tr>") => (block: parse_error) + "</tfoot>", "</th>", "</thead>", "</tr>") => (block: + parse_error UNEXPECTED_END_TAG + ) _ => (block: parser.processInHTMLContent(token, IN_BODY)) of IN_COLUMN_GROUP: match token: AsciiWhitespace => (block: parser.insertCharacter(token.c)) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "<col>" => (block: discard parser.insertHTMLElement(token) @@ -2086,19 +2153,19 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) "</colgroup>" => (block: if parser.getTagType(parser.currentNode) != TAG_COLGROUP: - parse_error + parse_error MISMATCHED_TAGS else: pop_current_node parser.insertionMode = IN_TABLE ) - "</col>" => (block: parse_error) + "</col>" => (block: parse_error UNEXPECTED_END_TAG) ("<template>", "</template>") => (block: parser.processInHTMLContent(token, IN_HEAD) ) TokenType.EOF => (block: parser.processInHTMLContent(token, IN_BODY)) _ => (block: if parser.getTagType(parser.currentNode) != TAG_COLGROUP: - parse_error + parse_error MISMATCHED_TAGS else: pop_current_node parser.insertionMode = IN_TABLE @@ -2117,7 +2184,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.insertionMode = IN_ROW ) ("<th>", "<td>") => (block: - parse_error + parse_error UNEXPECTED_START_TAG clear_the_stack_back_to_a_table_body_context discard parser.insertHTMLElement(Token(t: START_TAG, tagtype: TAG_TR)) parser.insertionMode = IN_ROW @@ -2125,7 +2192,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) ("</tbody>", "</tfoot>", "</thead>") => (block: if not parser.hasElementInTableScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: clear_the_stack_back_to_a_table_body_context pop_current_node @@ -2134,7 +2201,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ("<caption>", "<col>", "<colgroup>", "<tbody>", "<tfoot>", "<thead>", "</table>") => (block: if not parser.hasElementInTableScope({TAG_TBODY, TAG_THEAD, TAG_TFOOT}): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: clear_the_stack_back_to_a_table_body_context pop_current_node @@ -2143,7 +2210,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) ("</body>", "</caption>", "</col>", "</colgroup>", "</html>", "</td>", "</th>", "</tr>") => (block: - parse_error + parse_error ELEMENT_NOT_IN_SCOPE ) _ => (block: parser.processInHTMLContent(token, IN_TABLE)) @@ -2161,7 +2228,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) "</tr>" => (block: if not parser.hasElementInTableScope(TAG_TR): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: clear_the_stack_back_to_a_table_row_context pop_current_node @@ -2170,7 +2237,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ("<caption>", "<col>", "<colgroup>", "<tbody>", "<tfoot>", "<thead>", "<tr>", "</table>") => (block: if not parser.hasElementInTableScope(TAG_TR): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: clear_the_stack_back_to_a_table_row_context pop_current_node @@ -2179,7 +2246,7 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) ("</tbody>", "</tfoot>", "</thead>") => (block: if not parser.hasElementInTableScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE elif not parser.hasElementInTableScope(TAG_TR): discard else: @@ -2189,42 +2256,42 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], reprocess token ) ("</body>", "</caption>", "</col>", "</colgroup>", "</html>", "</td>", - "</th>") => (block: parse_error) + "</th>") => (block: parse_error UNEXPECTED_END_TAG) _ => (block: parser.processInHTMLContent(token, IN_TABLE)) of IN_CELL: template close_cell() = parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) notin {TAG_TD, TAG_TH}: parse_error - while parser.getTagType(parser.popElement()) notin {TAG_TD, TAG_TH}: discard + parse_error_if_mismatch {TAG_TD, TAG_TH} + parser.popElementsIncl({TAG_TD, TAG_TH}) parser.clearActiveFormattingTillMarker() parser.insertionMode = IN_ROW match token: ("</td>", "</th>") => (block: if not parser.hasElementInTableScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: parser.generateImpliedEndTags() - if parser.getTagType(parser.currentNode) != token.tagtype: - parse_error - while parser.getTagType(parser.popElement()) != token.tagtype: discard + parse_error_if_mismatch token.tagtype + parser.popElementsIncl(token.tagtype) parser.clearActiveFormattingTillMarker() parser.insertionMode = IN_ROW ) ("<caption>", "<col>", "<colgroup>", "<tbody>", "<td>", "<tfoot>", "<thead>", "<tr>") => (block: if not parser.hasElementInTableScope({TAG_TD, TAG_TH}): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: close_cell reprocess token ) - ("</body>", "</caption>", "</col>", "</colgroup>", - "</html>") => (block: parse_error) + ("</body>", "</caption>", "</col>", "</colgroup>", "</html>") => (block: + parse_error UNEXPECTED_END_TAG + ) ("</table>", "</tbody>", "</tfoot>", "</thead>", "</tr>") => (block: if not parser.hasElementInTableScope(token.tagtype): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: close_cell reprocess token @@ -2233,10 +2300,11 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], of IN_SELECT: match token: - '\0' => (block: parse_error) + '\0' => (block: parse_error UNEXPECTED_NULL) TokenType.CHARACTER_ASCII => (block: parser.insertCharacter(token.c)) TokenType.CHARACTER => (block: parser.insertCharacter(token.r)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.COMMENT => (block: parser.insertComment(token)) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "<option>" => (block: if parser.getTagType(parser.currentNode) == TAG_OPTION: @@ -2257,29 +2325,29 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], if parser.getTagType(parser.currentNode) == TAG_OPTGROUP: pop_current_node else: - parse_error + parse_error MISMATCHED_TAGS ) "</option>" => (block: if parser.getTagType(parser.currentNode) == TAG_OPTION: pop_current_node else: - parse_error + parse_error MISMATCHED_TAGS ) "</select>" => (block: if not parser.hasElementInSelectScope(TAG_SELECT): - parse_error + parse_error ELEMENT_NOT_IN_SCOPE else: while parser.getTagType(parser.popElement()) != TAG_SELECT: discard parser.resetInsertionMode() ) "<select>" => (block: - parse_error + parse_error NESTED_TAGS if parser.hasElementInSelectScope(TAG_SELECT): while parser.getTagType(parser.popElement()) != TAG_SELECT: discard parser.resetInsertionMode() ) ("<input>", "<keygen>", "<textarea>") => (block: - parse_error + parse_error UNEXPECTED_START_TAG if not parser.hasElementInSelectScope(TAG_SELECT): discard else: @@ -2289,24 +2357,25 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) ("<script>", "<template>", "</template>") => (block: parser.processInHTMLContent(token, IN_HEAD)) TokenType.EOF => (block: parser.processInHTMLContent(token, IN_BODY)) - _ => (block: parse_error) + TokenType.START_TAG => (block: parse_error UNEXPECTED_START_TAG) + TokenType.END_TAG => (block: parse_error UNEXPECTED_END_TAG) of IN_SELECT_IN_TABLE: match token: ("<caption>", "<table>", "<tbody>", "<tfoot>", "<thead>", "<tr>", "<td>", "<th>") => (block: - parse_error + parse_error UNEXPECTED_START_TAG while parser.getTagType(parser.popElement()) != TAG_SELECT: discard parser.resetInsertionMode() reprocess token ) ("</caption>", "</table>", "</tbody>", "</tfoot>", "</thead>", "</tr>", "</td>", "</th>") => (block: - parse_error + parse_error UNEXPECTED_END_TAG if not parser.hasElementInTableScope(token.tagtype): discard else: - while parser.getTagType(parser.popElement()) != TAG_SELECT: discard + parser.popElementsIncl(TAG_SELECT) parser.resetInsertionMode() reprocess token ) @@ -2351,12 +2420,12 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], parser.insertionMode = IN_BODY reprocess token ) - TokenType.END_TAG => (block: parse_error) + TokenType.END_TAG => (block: parse_error UNEXPECTED_END_TAG) TokenType.EOF => (block: if not parser.hasElement(TAG_TEMPLATE): discard # stop else: - parse_error + parse_error UNEXPECTED_EOF parser.popElementsIncl(TAG_TEMPLATE) parser.clearActiveFormattingTillMarker() discard parser.templateModes.pop() @@ -2368,17 +2437,17 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: AsciiWhitespace => (block: parser.processInHTMLContent(token, IN_BODY)) TokenType.COMMENT => (block: parser.insertComment(token, last_child_of(parser.openElements[0]))) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "</html>" => (block: if parser.fragment: - parse_error + parse_error UNEXPECTED_END_TAG else: parser.insertionMode = AFTER_AFTER_BODY ) TokenType.EOF => (block: discard) # stop _ => (block: - parse_error + parse_error UNEXPECTED_AFTER_BODY_TOKEN parser.insertionMode = IN_BODY reprocess token ) @@ -2387,11 +2456,11 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], match token: AsciiWhitespace => (block: parser.insertCharacter(token.c)) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "<frameset>" => (block: if parser.getTagType(parser.currentNode) == TAG_HTML: - parse_error + parse_error UNEXPECTED_START_TAG else: pop_current_node if not parser.fragment and @@ -2405,21 +2474,21 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], "<noframes>" => (block: parser.processInHTMLContent(token, IN_HEAD)) TokenType.EOF => (block: if parser.getTagType(parser.currentNode) != TAG_HTML: - parse_error + parse_error UNEXPECTED_EOF # stop ) - _ => (block: parse_error) + _ => (block: parser.parseErrorByTokenType(token.t)) of AFTER_FRAMESET: match token: AsciiWhitespace => (block: parser.insertCharacter(token.c)) TokenType.COMMENT => (block: parser.insertComment(token)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) "<html>" => (block: parser.processInHTMLContent(token, IN_BODY)) "</html>" => (block: parser.insertionMode = AFTER_AFTER_FRAMESET) "<noframes>" => (block: parser.processInHTMLContent(token, IN_HEAD)) TokenType.EOF => (block: discard) # stop - _ => (block: parse_error) + _ => (block: parser.parseErrorByTokenType(token.t)) of AFTER_AFTER_BODY: match token: @@ -2431,18 +2500,22 @@ proc processInHTMLContent[Handle](parser: var HTML5Parser[Handle], ) TokenType.EOF => (block: discard) # stop _ => (block: - parse_error + parser.parseErrorByTokenType(token.t) parser.insertionMode = IN_BODY reprocess token ) of AFTER_AFTER_FRAMESET: match token: - TokenType.COMMENT => (block: parser.insertComment(token, last_child_of(parser.document))) - (TokenType.DOCTYPE, AsciiWhitespace, "<html>") => (block: parser.processInHTMLContent(token, IN_BODY)) + TokenType.COMMENT => (block: + parser.insertComment(token, last_child_of(parser.document)) + ) + (TokenType.DOCTYPE, AsciiWhitespace, "<html>") => (block: + parser.processInHTMLContent(token, IN_BODY) + ) TokenType.EOF => (block: discard) # stop "<noframes>" => (block: parser.processInHTMLContent(token, IN_HEAD)) - _ => (block: parse_error) + _ => (block: parser.parseErrorByTokenType(token.t)) const CaseTable = { "altglyph": "altGlyph", @@ -2488,14 +2561,18 @@ proc processInForeignContent(parser: var HTML5Parser, token: Token) = macro `=>`(v: typed, body: untyped): untyped = quote do: discard (`v`, proc() = `body`) + template script_end_tag() = pop_current_node #TODO document.write (?) #TODO SVG + template parse_error(e: ParseError) = + parser.parseError(e) + template any_other_end_tag() = if parser.getLocalName(parser.currentNode) != token.tagname: - parse_error + parse_error UNEXPECTED_END_TAG for i in countdown(parser.openElements.high, 1): let node = parser.openElements[i] if parser.getLocalName(parser.currentNode) == token.tagname: @@ -2508,20 +2585,20 @@ proc processInForeignContent(parser: var HTML5Parser, token: Token) = match token: '\0' => (block: - parse_error + parse_error UNEXPECTED_NULL parser.insertCharacter(Rune(0xFFFD)) ) AsciiWhitespace => (block: parser.insertCharacter(token.c)) TokenType.CHARACTER_ASCII => (block: parser.insertCharacter(token.c)) TokenType.CHARACTER => (block: parser.insertCharacter(token.r)) - TokenType.DOCTYPE => (block: parse_error) + TokenType.DOCTYPE => (block: parse_error UNEXPECTED_DOCTYPE) ("<b>", "<big>", "<blockquote>", "<body>", "<br>", "<center>", "<code>", "<dd>", "<div>", "<dl>", "<dt>", "<em>", "<embed>", "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>", "<head>", "<hr>", "<i>", "<img>", "<li>", "<listing>", "<menu>", "<meta>", "<nobr>", "<ol>", "<p>", "<pre>", "<ruby>", "<s>", "<small>", "<span>", "<strong>", "<strike>", "<sub>", "<sup>", "<table>", "<tt>", "<u>", "<ul>", "<var>") => (block: - parse_error + parse_error UNEXPECTED_START_TAG #NOTE MathML not implemented while not parser.isHTMLIntegrationPoint(parser.currentNode) and parser.getNamespace(parser.currentNode) != Namespace.HTML: @@ -2627,7 +2704,12 @@ proc parseHTML*[Handle](inputStream: Stream, dombuilder: DOMBuilder[Handle], else: DECODER_ERROR_MODE_FATAL let decoder = newDecoderStream(inputStream, parser.charset, errormode = em) - parser.tokenizer = newTokenizer(decoder) + let onParseError = if parser.hasParseError(): + proc(e: ParseError) = + parser.parseError(e) + else: + nil + parser.tokenizer = newTokenizer(decoder, onParseError) parser.constructTree() if parser.needsreinterpret and canReinterpret: inputStream.setPosition(0) diff --git a/src/html/htmltokenizer.nim b/src/html/htmltokenizer.nim index c95db720..d38d427c 100644 --- a/src/html/htmltokenizer.nim +++ b/src/html/htmltokenizer.nim @@ -5,9 +5,10 @@ import macros import tables import unicode +import encoding/decoderstream import html/entity +import html/parseerror import html/tags -import encoding/decoderstream import utils/opt import utils/radixtree import utils/twtstr @@ -25,6 +26,7 @@ type attrv: string attr: bool hasnonhtml*: bool + onParseError: proc(e: ParseError) decoder: DecoderStream sbuf: seq[Rune] @@ -108,12 +110,13 @@ proc readn(t: var Tokenizer) = if t.decoder.atEnd: t.eof_i = t.sbuf.len -proc newTokenizer*(s: DecoderStream): Tokenizer = +proc newTokenizer*(s: DecoderStream, onParseError: proc(e: ParseError)): Tokenizer = var t = Tokenizer( decoder: s, sbuf: newSeqOfCap[Rune](bufLen), eof_i: -1, - sbuf_i: 0 + sbuf_i: 0, + onParseError: onParseError ) t.readn() return t @@ -192,7 +195,9 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = template reconsume_in(s: TokenizerState) = tokenizer.reconsume() switch_state s - template parse_error(error: untyped) = discard # does nothing for now... TODO? + template parse_error(error: untyped) = + if tokenizer.onParseError != nil: + tokenizer.onParseError(error) template is_appropriate_end_tag_token(): bool = tokenizer.laststart != nil and tokenizer.laststart.tagname == tokenizer.tok.tagname template start_new_attribute = @@ -346,7 +351,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '&': switch_state_return CHARACTER_REFERENCE of '<': switch_state TAG_OPEN of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER emit_current of eof: emit_eof else: emit_current @@ -355,7 +360,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '&': switch_state_return CHARACTER_REFERENCE of '<': switch_state RCDATA_LESS_THAN_SIGN - of null: parse_error unexpected_null_character + of null: parse_error UNEXPECTED_NULL_CHARACTER of eof: emit_eof else: emit_current @@ -363,7 +368,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '<': switch_state RAWTEXT_LESS_THAN_SIGN of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER emit_replacement of eof: emit_eof else: emit_current @@ -372,7 +377,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '<': switch_state SCRIPT_DATA_LESS_THAN_SIGN of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER emit_replacement of eof: emit_eof else: emit_current @@ -380,7 +385,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of PLAINTEXT: case c of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER emit_replacement of eof: emit_eof else: emit_current @@ -393,15 +398,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = new_token Token(t: START_TAG) reconsume_in TAG_NAME of '?': - parse_error unexpected_question_mark_instead_of_tag_name + parse_error UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME new_token Token(t: COMMENT) reconsume_in BOGUS_COMMENT of eof: - parse_error eof_before_tag_name + parse_error EOF_BEFORE_TAG_NAME emit '<' emit_eof else: - parse_error invalid_first_character_of_tag_name + parse_error INVALID_FIRST_CHARACTER_OF_TAG_NAME emit '<' reconsume_in DATA @@ -411,15 +416,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = new_token Token(t: END_TAG) reconsume_in TAG_NAME of '>': - parse_error missing_end_tag_name + parse_error MISSING_END_TAG_NAME switch_state DATA of eof: - parse_error eof_before_tag_name + parse_error EOF_BEFORE_TAG_NAME emit '<' emit '/' emit_eof else: - parse_error invalid_first_character_of_tag_name + parse_error INVALID_FIRST_CHARACTER_OF_TAG_NAME new_token Token(t: COMMENT) reconsume_in BOGUS_COMMENT @@ -432,10 +437,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = emit_tok of AsciiUpperAlpha: tokenizer.tok.tagname &= c.tolower() of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.tagname &= Rune(0xFFFD) of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: tokenizer.tok.tagname &= r @@ -611,10 +616,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '<': switch_state SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER emit_replacement of eof: - parse_error eof_in_script_html_comment_like_text + parse_error EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT emit_eof else: emit_current @@ -627,10 +632,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '<': switch_state SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER switch_state SCRIPT_DATA_ESCAPED of eof: - parse_error eof_in_script_html_comment_like_text + parse_error EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT emit_eof else: switch_state SCRIPT_DATA_ESCAPED @@ -646,10 +651,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state SCRIPT_DATA emit '>' of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER switch_state SCRIPT_DATA_ESCAPED of eof: - parse_error eof_in_script_html_comment_like_text + parse_error EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT emit_eof else: switch_state SCRIPT_DATA_ESCAPED @@ -727,10 +732,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN emit '<' of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER emit_replacement of eof: - parse_error eof_in_script_html_comment_like_text + parse_error EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT emit_eof else: emit_current @@ -743,11 +748,11 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN emit '<' of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER switch_state SCRIPT_DATA_DOUBLE_ESCAPED emit_replacement of eof: - parse_error eof_in_script_html_comment_like_text + parse_error EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT emit_eof else: switch_state SCRIPT_DATA_DOUBLE_ESCAPED @@ -763,11 +768,11 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state SCRIPT_DATA emit '>' of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER switch_state SCRIPT_DATA_DOUBLE_ESCAPED emit_replacement of eof: - parse_error eof_in_script_html_comment_like_text + parse_error EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT emit_eof else: switch_state SCRIPT_DATA_DOUBLE_ESCAPED @@ -798,7 +803,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of AsciiWhitespace: discard of '/', '>', eof: reconsume_in AFTER_ATTRIBUTE_NAME of '=': - parse_error unexpected_equals_sign_before_attribute_name + parse_error UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME start_new_attribute switch_state ATTRIBUTE_NAME else: @@ -817,10 +822,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of AsciiUpperAlpha: tokenizer.attrn &= c.tolower() of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.attrn &= Rune(0xFFFD) of '"', '\'', '<': - parse_error unexpected_character_in_attribute_name + parse_error UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME anything_else else: tokenizer.attrn &= r @@ -834,7 +839,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: start_new_attribute @@ -846,7 +851,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '"': switch_state ATTRIBUTE_VALUE_DOUBLE_QUOTED of '\'': switch_state ATTRIBUTE_VALUE_SINGLE_QUOTED of '>': - parse_error missing_attribute_value + parse_error MISSING_ATTRIBUTE_VALUE switch_state DATA emit '>' else: reconsume_in ATTRIBUTE_VALUE_UNQUOTED @@ -856,10 +861,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '"': switch_state AFTER_ATTRIBUTE_VALUE_QUOTED of '&': switch_state_return CHARACTER_REFERENCE of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER append_to_current_attr_value Rune(0xFFFD) of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: append_to_current_attr_value r @@ -868,10 +873,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '\'': switch_state AFTER_ATTRIBUTE_VALUE_QUOTED of '&': switch_state_return CHARACTER_REFERENCE of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER append_to_current_attr_value Rune(0xFFFD) of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: append_to_current_attr_value r @@ -883,13 +888,13 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER append_to_current_attr_value Rune(0xFFFD) of '"', '\'', '<', '=', '`': - parse_error unexpected_character_in_unquoted_attribute_value + parse_error UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE append_to_current_attr_value c of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: append_to_current_attr_value r @@ -903,10 +908,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: - parse_error missing_whitespace_between_attributes + parse_error MISSING_WHITESPACE_BETWEEN_ATTRIBUTES reconsume_in BEFORE_ATTRIBUTE_NAME of SELF_CLOSING_START_TAG: @@ -916,10 +921,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of eof: - parse_error eof_in_tag + parse_error EOF_IN_TAG emit_eof else: - parse_error unexpected_solidus_in_tag + parse_error UNEXPECTED_SOLIDUS_IN_TAG reconsume_in BEFORE_ATTRIBUTE_NAME of BOGUS_COMMENT: @@ -931,7 +936,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of eof: emit_tok emit_eof - of null: parse_error unexpected_null_character + of null: parse_error UNEXPECTED_NULL_CHARACTER else: tokenizer.tok.data &= r of MARKUP_DECLARATION_OPEN: # note: rewritten to fit case model as we consume a char anyway @@ -954,12 +959,12 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = if tokenizer.hasnonhtml: switch_state CDATA_SECTION else: - parse_error cdata_in_html_content + parse_error CDATA_IN_HTML_CONTENT new_token Token(t: COMMENT, data: "[CDATA[") switch_state BOGUS_COMMENT else: anything_else else: - parse_error incorrectly_opened_comment + parse_error INCORRECTLY_OPENED_COMMENT new_token Token(t: COMMENT) reconsume_in BOGUS_COMMENT @@ -967,7 +972,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '-': switch_state COMMENT_START_DASH of '>': - parse_error abrupt_closing_of_empty_comment + parse_error ABRUPT_CLOSING_OF_EMPTY_COMMENT switch_state DATA emit_tok else: reconsume_in COMMENT @@ -976,11 +981,11 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '-': switch_state COMMENT_END of '>': - parse_error abrupt_closing_of_empty_comment + parse_error ABRUPT_CLOSING_OF_EMPTY_COMMENT switch_state DATA emit_tok of eof: - parse_error eof_in_comment + parse_error EOF_IN_COMMENT emit_tok emit_eof else: @@ -994,10 +999,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state COMMENT_LESS_THAN_SIGN of '-': switch_state COMMENT_END_DASH of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.data &= Rune(0xFFFD) of eof: - parse_error eof_in_comment + parse_error EOF_IN_COMMENT emit_tok emit_eof else: tokenizer.tok.data &= r @@ -1024,14 +1029,14 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '>', eof: reconsume_in COMMENT_END else: - parse_error nested_comment + parse_error NESTED_COMMENT reconsume_in COMMENT_END of COMMENT_END_DASH: case c of '-': switch_state COMMENT_END of eof: - parse_error eof_in_comment + parse_error EOF_IN_COMMENT emit_tok emit_eof else: @@ -1044,7 +1049,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '!': switch_state COMMENT_END_BANG of '-': tokenizer.tok.data &= '-' of eof: - parse_error eof_in_comment + parse_error EOF_IN_COMMENT emit_tok emit_eof else: @@ -1057,11 +1062,11 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = tokenizer.tok.data &= "--!" switch_state COMMENT_END_DASH of '>': - parse_error incorrectly_closed_comment + parse_error INCORRECTLY_CLOSED_COMMENT switch_state DATA emit_tok of eof: - parse_error eof_in_comment + parse_error EOF_IN_COMMENT emit_tok emit_eof else: @@ -1073,12 +1078,12 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of AsciiWhitespace: switch_state BEFORE_DOCTYPE_NAME of '>': reconsume_in BEFORE_DOCTYPE_NAME of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE new_token Token(t: DOCTYPE, quirks: true) emit_tok emit_eof else: - parse_error missing_whitespace_before_doctype_name + parse_error MISSING_WHITESPACE_BEFORE_DOCTYPE_NAME reconsume_in BEFORE_DOCTYPE_NAME of BEFORE_DOCTYPE_NAME: @@ -1088,15 +1093,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = new_token Token(t: DOCTYPE, name: some($c.tolower())) switch_state DOCTYPE_NAME of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER new_token Token(t: DOCTYPE, name: some($Rune(0xFFFD))) of '>': - parse_error missing_doctype_name + parse_error MISSING_DOCTYPE_NAME new_token Token(t: DOCTYPE, quirks: true) switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE new_token Token(t: DOCTYPE, quirks: true) emit_tok emit_eof @@ -1113,10 +1118,10 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of AsciiUpperAlpha: tokenizer.tok.name.get &= c.tolower() of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.name.get &= Rune(0xFFFD) of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof @@ -1131,7 +1136,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof @@ -1148,7 +1153,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = else: anything_else else: - parse_error invalid_character_sequence_after_doctype_name + parse_error INVALID_CHARACTER_SEQUENCE_AFTER_DOCTYPE_NAME tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1156,21 +1161,21 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of AsciiWhitespace: switch_state BEFORE_DOCTYPE_PUBLIC_IDENTIFIER of '"': - parse_error missing_whitespace_after_doctype_public_keyword + parse_error MISSING_WHITESPACE_AFTER_DOCTYPE_PUBLIC_KEYWORD tokenizer.tok.pubid = some("") switch_state DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED of '>': - parse_error missing_doctype_public_identifier + parse_error MISSING_DOCTYPE_PUBLIC_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error missing_quote_before_doctype_public_identifier + parse_error MISSING_QUOTE_BEFORE_DOCTYPE_PUBLIC_IDENTIFIER tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1184,17 +1189,17 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = tokenizer.tok.pubid = some("") switch_state DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED of '>': - parse_error missing_doctype_public_identifier + parse_error MISSING_DOCTYPE_PUBLIC_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error missing_quote_before_doctype_public_identifier + parse_error MISSING_QUOTE_BEFORE_DOCTYPE_PUBLIC_IDENTIFIER tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1202,15 +1207,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '"': switch_state AFTER_DOCTYPE_PUBLIC_IDENTIFIER of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.pubid.get &= Rune(0xFFFD) of '>': - parse_error abrupt_doctype_public_identifier + parse_error ABRUPT_DOCTYPE_PUBLIC_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof @@ -1221,15 +1226,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '\'': switch_state AFTER_DOCTYPE_PUBLIC_IDENTIFIER of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.pubid.get &= Rune(0xFFFD) of '>': - parse_error abrupt_doctype_public_identifier + parse_error ABRUPT_DOCTYPE_PUBLIC_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof @@ -1243,20 +1248,20 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of '"': - parse_error missing_whitespace_between_doctype_public_and_system_identifiers + parse_error MISSING_WHITESPACE_BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS tokenizer.tok.sysid = some("") switch_state DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED of '\'': - parse_error missing_whitespace_between_doctype_public_and_system_identifiers + parse_error MISSING_WHITESPACE_BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS tokenizer.tok.sysid = some("") switch_state DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error missing_quote_before_doctype_system_identifier + parse_error MISSING_QUOTE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1273,12 +1278,12 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = tokenizer.tok.sysid = some("") switch_state DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error missing_quote_before_doctype_system_identifier + parse_error MISSING_QUOTE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1286,25 +1291,25 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of AsciiWhitespace: switch_state BEFORE_DOCTYPE_SYSTEM_IDENTIFIER of '"': - parse_error missing_whitespace_after_doctype_system_keyword + parse_error MISSING_WHITESPACE_AFTER_DOCTYPE_SYSTEM_KEYWORD tokenizer.tok.sysid = some("") switch_state DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED of '\'': - parse_error missing_whitespace_after_doctype_system_keyword + parse_error MISSING_WHITESPACE_AFTER_DOCTYPE_SYSTEM_KEYWORD tokenizer.tok.sysid = some("") switch_state DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED of '>': - parse_error missing_doctype_system_identifier + parse_error MISSING_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error missing_quote_before_doctype_system_identifier + parse_error MISSING_QUOTE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1318,17 +1323,17 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = tokenizer.tok.pubid = some("") switch_state DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED of '>': - parse_error missing_doctype_system_identifier + parse_error MISSING_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error missing_quote_before_doctype_system_identifier + parse_error MISSING_QUOTE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true reconsume_in BOGUS_DOCTYPE @@ -1336,15 +1341,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '"': switch_state AFTER_DOCTYPE_SYSTEM_IDENTIFIER of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.sysid.get &= Rune(0xFFFD) of '>': - parse_error abrupt_doctype_system_identifier + parse_error ABRUPT_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof @@ -1355,15 +1360,15 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of '\'': switch_state AFTER_DOCTYPE_SYSTEM_IDENTIFIER of null: - parse_error unexpected_null_character + parse_error UNEXPECTED_NULL_CHARACTER tokenizer.tok.sysid.get &= Rune(0xFFFD) of '>': - parse_error abrupt_doctype_system_identifier + parse_error ABRUPT_DOCTYPE_SYSTEM_IDENTIFIER tokenizer.tok.quirks = true switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof @@ -1377,12 +1382,12 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state DATA emit_tok of eof: - parse_error eof_in_doctype + parse_error EOF_IN_DOCTYPE tokenizer.tok.quirks = true emit_tok emit_eof else: - parse_error unexpected_character_after_doctype_system_identifier + parse_error UNEXPECTED_CHARACTER_AFTER_DOCTYPE_SYSTEM_IDENTIFIER reconsume_in BOGUS_DOCTYPE of BOGUS_DOCTYPE: @@ -1390,7 +1395,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = of '>': switch_state DATA emit_tok - of null: parse_error unexpected_null_character + of null: parse_error UNEXPECTED_NULL_CHARACTER of eof: emit_tok emit_eof @@ -1400,7 +1405,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of ']': switch_state CDATA_SECTION_BRACKET of eof: - parse_error eof_in_cdata + parse_error EOF_IN_CDATA emit_eof else: emit_current @@ -1458,7 +1463,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = switch_state tokenizer.rstate else: if tokenizer.tmp[^1] != ';': - parse_error missing_semicolon_after_character_reference_parse_error + parse_error MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE tokenizer.tmp = value.get flush_code_points_consumed_as_a_character_reference switch_state tokenizer.rstate @@ -1474,7 +1479,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = else: emit_current of ';': - parse_error unknown_named_character_reference + parse_error UNKNOWN_NAMED_CHARACTER_REFERENCE reconsume_in tokenizer.rstate else: reconsume_in tokenizer.rstate @@ -1490,7 +1495,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of AsciiHexDigit: reconsume_in HEXADECIMAL_CHARACTER_REFERENCE else: - parse_error absence_of_digits_in_numeric_character_reference + parse_error ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE flush_code_points_consumed_as_a_character_reference reconsume_in tokenizer.rstate @@ -1498,7 +1503,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = case c of AsciiDigit: reconsume_in DECIMAL_CHARACTER_REFERENCE else: - parse_error absence_of_digits_in_numeric_character_reference + parse_error ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE flush_code_points_consumed_as_a_character_reference reconsume_in tokenizer.rstate @@ -1509,7 +1514,7 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = tokenizer.code += hexValue(c) of ';': switch_state NUMERIC_CHARACTER_REFERENCE_END else: - parse_error missing_semicolon_after_character_reference + parse_error MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE reconsume_in NUMERIC_CHARACTER_REFERENCE_END of DECIMAL_CHARACTER_REFERENCE: @@ -1519,23 +1524,23 @@ iterator tokenize*(tokenizer: var Tokenizer): Token = tokenizer.code += decValue(c) of ';': switch_state NUMERIC_CHARACTER_REFERENCE_END else: - parse_error missing_semicolon_after_character_reference + parse_error MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE reconsume_in NUMERIC_CHARACTER_REFERENCE_END of NUMERIC_CHARACTER_REFERENCE_END: ignore_eof # we reconsume anyway case tokenizer.code of 0x00: - parse_error null_character_reference + parse_error NULL_CHARACTER_REFERENCE tokenizer.code = 0xFFFD elif tokenizer.code > 0x10FFFF: - parse_error character_reference_outside_unicode_range + parse_error CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE tokenizer.code = 0xFFFD elif Rune(tokenizer.code).isSurrogate(): - parse_error surrogate_character_reference + parse_error SURROGATE_CHARACTER_REFERENCE tokenizer.code = 0xFFFD elif Rune(tokenizer.code).isNonCharacter(): - parse_error noncharacter_character_reference + parse_error NONCHARACTER_CHARACTER_REFERENCE # do nothing elif tokenizer.code in 0..255 and char(tokenizer.code) in ((Controls - AsciiWhitespace) + {chr(0x0D)}): const ControlMapTable = [ diff --git a/src/html/parseerror.nim b/src/html/parseerror.nim new file mode 100644 index 00000000..d99b2fed --- /dev/null +++ b/src/html/parseerror.nim @@ -0,0 +1,70 @@ +type ParseError* = enum + #TODO write a description for all error codes + ABRUPT_CLOSING_OF_EMPTY_COMMENT + ABRUPT_DOCTYPE_PUBLIC_IDENTIFIER + ABRUPT_DOCTYPE_SYSTEM_IDENTIFIER + ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE + CDATA_IN_HTML_CONTENT + CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE + CONTROL_CHARACTER_IN_INPUT_STREAM + CONTROL_CHARACTER_REFERENCE + END_TAG_WITH_ATTRIBUTES + DUPLICATE_ATTRIBUTE + END_TAG_WITH_TRAILING_SOLIDUS + EOF_BEFORE_TAG_NAME + EOF_IN_CDATA + EOF_IN_COMMENT + EOF_IN_DOCTYPE + EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT + EOF_IN_TAG + INCORRECTLY_CLOSED_COMMENT + INCORRECTLY_OPENED_COMMENT + INVALID_CHARACTER_SEQUENCE_AFTER_DOCTYPE_NAME + INVALID_FIRST_CHARACTER_OF_TAG_NAME + MISSING_ATTRIBUTE_VALUE + MISSING_DOCTYPE_NAME + MISSING_DOCTYPE_PUBLIC_IDENTIFIER + MISSING_DOCTYPE_SYSTEM_IDENTIFIER + MISSING_END_TAG_NAME + MISSING_QUOTE_BEFORE_DOCTYPE_PUBLIC_IDENTIFIER + MISSING_QUOTE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER + MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE + MISSING_WHITESPACE_AFTER_DOCTYPE_PUBLIC_KEYWORD + MISSING_WHITESPACE_AFTER_DOCTYPE_SYSTEM_KEYWORD + MISSING_WHITESPACE_BEFORE_DOCTYPE_NAME + MISSING_WHITESPACE_BETWEEN_ATTRIBUTES + MISSING_WHITESPACE_BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS + NESTED_COMMENT + NONCHARACTER_CHARACTER_REFERENCE + NONCHARACTER_IN_INPUT_STREAM + NON_VOID_HTML_ELEMENT_START_TAG_WITH_TRAILING_SOLIDUS + NULL_CHARACTER_REFERENCE + SURROGATE_CHARACTER_REFERENCE + SURROGATE_IN_INPUT_STREAM + UNEXPECTED_CHARACTER_AFTER_DOCTYPE_SYSTEM_IDENTIFIER + UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME + UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE + UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME + UNEXPECTED_NULL_CHARACTER + UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME + UNEXPECTED_SOLIDUS_IN_TAG + UNKNOWN_NAMED_CHARACTER_REFERENCE + LAST_SPECIFIED_ERROR # never returned + # From here on, error code names have not been specified by the standard. + MISMATCHED_TAGS = "Mismatched start and end tags" + INVALID_DOCTYPE = "Unrecognized document type" + UNEXPECTED_DOCTYPE = "Unexpected document type" + UNEXPECTED_INITIAL_TOKEN = "Unexpected token in initial state" + UNEXPECTED_START_TAG = "Unexpected start tag" + UNEXPECTED_END_TAG = "Unexpected end tag" + ELEMENT_NOT_IN_OPEN_ELEMENTS = "Element has not been added to open elements" + ELEMENT_NOT_IN_SCOPE = "Element not in appropriate scope" + ELEMENT_NOT_CURRENT_NODE = "Element is not current node" + #TODO merge with UNEXPECTED_NULL_CHARACTER? + UNEXPECTED_NULL = "Unexpected null character" + NESTED_TAGS = "Non-nestable nested tags" + UNEXPECTED_SPECIAL_ELEMENT = "Unexpected special element on open elements" + UNEXPECTED_EOF = "Unexpected end of file" + INVALID_TEXT_PARENT = "Invalid parent element for text node" + NON_SPACE_TABLE_TEXT = "Non-space table text" + UNEXPECTED_AFTER_BODY_TOKEN = "Unexpected token after body" |