@@ -64,7 +64,7 @@ class ParseHtmlContext {
64
64
public readonly callbacks : ParseHtmlCallbacks ;
65
65
public state : State = State . Data ; // begin in the Data state
66
66
public currentDataIdx = 0 ; // where the current data start index is
67
- public currentTag : CurrentTag = new CurrentTag ( ) ; // describes the current tag that is being read
67
+ public currentTag : CurrentTag | null = null ; // describes the current tag that is being read
68
68
// public currentEntity: CurrentEntity = new CurrentEntity(); // describes the current HTML entity (ex: '&') that is being read
69
69
70
70
constructor ( html : string , callbacks : ParseHtmlCallbacks ) {
@@ -268,18 +268,18 @@ function stateTagOpen(context: ParseHtmlContext, char: string, charCode: number)
268
268
context . state = State . MarkupDeclarationOpenState ;
269
269
} else if ( char === '/' ) {
270
270
context . state = State . EndTagOpen ;
271
- context . currentTag . isClosing = true ;
271
+ context . currentTag ! . isClosing = true ;
272
272
} else if ( char === '<' ) {
273
273
// start of another tag (ignore the previous, incomplete one)
274
274
startNewTag ( context ) ;
275
275
} else if ( isAsciiLetterChar ( charCode ) ) {
276
276
// tag name start (and no '/' read)
277
277
context . state = State . TagName ;
278
- context . currentTag . isOpening = true ;
278
+ context . currentTag ! . isOpening = true ;
279
279
} else {
280
280
// Any other
281
281
context . state = State . Data ;
282
- context . currentTag = new CurrentTag ( ) ;
282
+ context . currentTag = null ;
283
283
}
284
284
}
285
285
@@ -288,16 +288,16 @@ function stateTagOpen(context: ParseHtmlContext, char: string, charCode: number)
288
288
// https://www.w3.org/TR/html51/syntax.html#tag-name-state
289
289
function stateTagName ( context : ParseHtmlContext , char : string , charCode : number ) {
290
290
if ( isWhitespaceChar ( charCode ) ) {
291
- context . currentTag . name = captureTagName ( context ) ;
291
+ context . currentTag ! . name = captureTagName ( context ) ;
292
292
context . state = State . BeforeAttributeName ;
293
293
} else if ( char === '<' ) {
294
294
// start of another tag (ignore the previous, incomplete one)
295
295
startNewTag ( context ) ;
296
296
} else if ( char === '/' ) {
297
- context . currentTag . name = captureTagName ( context ) ;
297
+ context . currentTag ! . name = captureTagName ( context ) ;
298
298
context . state = State . SelfClosingStartTag ;
299
299
} else if ( char === '>' ) {
300
- context . currentTag . name = captureTagName ( context ) ;
300
+ context . currentTag ! . name = captureTagName ( context ) ;
301
301
emitTagAndPreviousTextNode ( context ) ; // resets to Data state as well
302
302
} else if ( ! isAsciiLetterChar ( charCode ) && ! isDigitChar ( charCode ) && char !== ':' ) {
303
303
// Anything else that does not form an html tag. Note: the colon
@@ -473,7 +473,7 @@ function stateAfterAttributeValueQuoted(context: ParseHtmlContext, char: string,
473
473
// https://www.w3.org/TR/html51/syntax.html#self-closing-start-tag-state
474
474
function stateSelfClosingStartTag ( context : ParseHtmlContext , char : string ) {
475
475
if ( char === '>' ) {
476
- context . currentTag . isClosing = true ;
476
+ context . currentTag ! . isClosing = true ;
477
477
emitTagAndPreviousTextNode ( context ) ; // resets to Data state as well
478
478
} else {
479
479
// Note: the spec calls for a character after a '/' within a start
@@ -495,11 +495,11 @@ function stateMarkupDeclarationOpen(context: ParseHtmlContext) {
495
495
if ( html . slice ( charIdx , charIdx + 2 ) === '--' ) {
496
496
// html comment
497
497
context . charIdx ++ ; // "consume" the second '-' character. Next loop iteration will consume the character after the '<!--' sequence
498
- context . currentTag . type = CurrentTagType . Comment ;
498
+ context . currentTag ! . type = CurrentTagType . Comment ;
499
499
context . state = State . CommentStart ;
500
500
} else if ( html . slice ( charIdx , charIdx + 7 ) . toUpperCase ( ) === 'DOCTYPE' ) {
501
501
context . charIdx += 6 ; // "consume" the characters "OCTYPE" (the current loop iteraction consumed the 'D'). Next loop iteration will consume the character after the '<!DOCTYPE' sequence
502
- context . currentTag . type = CurrentTagType . Doctype ;
502
+ context . currentTag ! . type = CurrentTagType . Doctype ;
503
503
context . state = State . Doctype ;
504
504
} else {
505
505
// At this point, the spec specifies that the state machine should
@@ -631,7 +631,7 @@ function stateDoctype(context: ParseHtmlContext, char: string) {
631
631
*/
632
632
function resetToDataState ( context : ParseHtmlContext ) {
633
633
context . state = State . Data ;
634
- context . currentTag = new CurrentTag ( ) ;
634
+ context . currentTag = null ;
635
635
}
636
636
637
637
/**
@@ -652,27 +652,42 @@ function startNewTag(context: ParseHtmlContext) {
652
652
* text node before it.
653
653
*/
654
654
function emitTagAndPreviousTextNode ( context : ParseHtmlContext ) {
655
- const textBeforeTag = context . html . slice ( context . currentDataIdx , context . currentTag . idx ) ;
655
+ const currentTag = context . currentTag ! ;
656
+ const { idx : currentTagIdx , type : currentTagType , name : currentTagName } = currentTag ;
657
+
658
+ const textBeforeTag = context . html . slice ( context . currentDataIdx , currentTagIdx ) ;
656
659
if ( textBeforeTag ) {
657
660
// the html tag was the first element in the html string, or two
658
661
// tags next to each other, in which case we should not emit a text
659
662
// node
660
663
context . callbacks . onText ( textBeforeTag , context . currentDataIdx ) ;
661
664
}
662
665
663
- const currentTag = context . currentTag ;
664
- if ( currentTag . type === CurrentTagType . Comment ) {
665
- context . callbacks . onComment ( currentTag . idx ) ;
666
- } else if ( currentTag . type === CurrentTagType . Doctype ) {
667
- context . callbacks . onDoctype ( currentTag . idx ) ;
668
- } else {
669
- if ( currentTag . isOpening ) {
670
- context . callbacks . onOpenTag ( currentTag . name , currentTag . idx ) ;
671
- }
672
- if ( currentTag . isClosing ) {
673
- // note: self-closing tags will emit both opening and closing
674
- context . callbacks . onCloseTag ( currentTag . name , currentTag . idx ) ;
666
+ switch ( currentTagType ) {
667
+ case CurrentTagType . Comment :
668
+ context . callbacks . onComment ( currentTagIdx ) ;
669
+ break ;
670
+
671
+ case CurrentTagType . Doctype :
672
+ context . callbacks . onDoctype ( currentTagIdx ) ;
673
+ break ;
674
+
675
+ case CurrentTagType . Tag : {
676
+ const { isOpening, isClosing } = currentTag ;
677
+
678
+ if ( isOpening ) {
679
+ context . callbacks . onOpenTag ( currentTagName , currentTagIdx ) ;
680
+ }
681
+ if ( isClosing ) {
682
+ // note: self-closing tags will emit both opening and closing
683
+ context . callbacks . onCloseTag ( currentTagName , currentTagIdx ) ;
684
+ }
685
+ break ;
675
686
}
687
+
688
+ /* istanbul ignore next */
689
+ default :
690
+ assertNever ( currentTagType ) ;
676
691
}
677
692
678
693
// Since we just emitted a tag, reset to the data state for the next char
@@ -692,7 +707,7 @@ function emitText(context: ParseHtmlContext) {
692
707
* index, and converts it to lower case
693
708
*/
694
709
function captureTagName ( context : ParseHtmlContext ) {
695
- const startIdx = context . currentTag . idx + ( context . currentTag . isClosing ? 2 : 1 ) ;
710
+ const startIdx = context . currentTag ! . idx + ( context . currentTag ! . isClosing ? 2 : 1 ) ;
696
711
return context . html . slice ( startIdx , context . charIdx ) . toLowerCase ( ) ;
697
712
}
698
713
0 commit comments