@@ -25,6 +25,11 @@ export enum HtmlTokenType {
2525 ATTR_NAME ,
2626 ATTR_VALUE ,
2727 DOC_TYPE ,
28+ EXPANSION_FORM_START ,
29+ EXPANSION_CASE_VALUE ,
30+ EXPANSION_CASE_EXP_START ,
31+ EXPANSION_CASE_EXP_END ,
32+ EXPANSION_FORM_END ,
2833 EOF
2934}
3035
@@ -43,8 +48,10 @@ export class HtmlTokenizeResult {
4348 constructor ( public tokens : HtmlToken [ ] , public errors : HtmlTokenError [ ] ) { }
4449}
4550
46- export function tokenizeHtml ( sourceContent : string , sourceUrl : string ) : HtmlTokenizeResult {
47- return new _HtmlTokenizer ( new ParseSourceFile ( sourceContent , sourceUrl ) ) . tokenize ( ) ;
51+ export function tokenizeHtml ( sourceContent : string , sourceUrl : string ,
52+ tokenizeExpansionForms : boolean = false ) : HtmlTokenizeResult {
53+ return new _HtmlTokenizer ( new ParseSourceFile ( sourceContent , sourceUrl ) , tokenizeExpansionForms )
54+ . tokenize ( ) ;
4855}
4956
5057const $EOF = 0 ;
@@ -75,6 +82,9 @@ const $GT = 62;
7582const $QUESTION = 63 ;
7683const $LBRACKET = 91 ;
7784const $RBRACKET = 93 ;
85+ const $LBRACE = 123 ;
86+ const $RBRACE = 125 ;
87+ const $COMMA = 44 ;
7888const $A = 65 ;
7989const $F = 70 ;
8090const $X = 88 ;
@@ -108,16 +118,20 @@ class _HtmlTokenizer {
108118 private length : number ;
109119 // Note: this is always lowercase!
110120 private peek : number = - 1 ;
121+ private nextPeek : number = - 1 ;
111122 private index : number = - 1 ;
112123 private line : number = 0 ;
113124 private column : number = - 1 ;
114125 private currentTokenStart : ParseLocation ;
115126 private currentTokenType : HtmlTokenType ;
116127
128+ private inExpansionCase : boolean = false ;
129+ private inExpansionForm : boolean = false ;
130+
117131 tokens : HtmlToken [ ] = [ ] ;
118132 errors : HtmlTokenError [ ] = [ ] ;
119133
120- constructor ( private file : ParseSourceFile ) {
134+ constructor ( private file : ParseSourceFile , private tokenizeExpansionForms : boolean ) {
121135 this . input = file . content ;
122136 this . length = file . content . length ;
123137 this . _advance ( ) ;
@@ -149,6 +163,18 @@ class _HtmlTokenizer {
149163 } else {
150164 this . _consumeTagOpen ( start ) ;
151165 }
166+ } else if ( isSpecialFormStart ( this . peek , this . nextPeek ) && this . tokenizeExpansionForms ) {
167+ this . _consumeExpansionFormStart ( ) ;
168+
169+ } else if ( this . peek === $EQ && this . tokenizeExpansionForms ) {
170+ this . _consumeExpansionCaseStart ( ) ;
171+
172+ } else if ( this . peek === $RBRACE && this . inExpansionCase && this . tokenizeExpansionForms ) {
173+ this . _consumeExpansionCaseEnd ( ) ;
174+
175+ } else if ( this . peek === $RBRACE && ! this . inExpansionCase && this . tokenizeExpansionForms ) {
176+ this . _consumeExpansionFormEnd ( ) ;
177+
152178 } else {
153179 this . _consumeText ( ) ;
154180 }
@@ -218,6 +244,8 @@ class _HtmlTokenizer {
218244 }
219245 this . index ++ ;
220246 this . peek = this . index >= this . length ? $EOF : StringWrapper . charCodeAt ( this . input , this . index ) ;
247+ this . nextPeek =
248+ this . index + 1 >= this . length ? $EOF : StringWrapper . charCodeAt ( this . input , this . index + 1 ) ;
221249 }
222250
223251 private _attemptCharCode ( charCode : number ) : boolean {
@@ -506,20 +534,109 @@ class _HtmlTokenizer {
506534 this . _endToken ( prefixAndName ) ;
507535 }
508536
537+ private _consumeExpansionFormStart ( ) {
538+ this . _beginToken ( HtmlTokenType . EXPANSION_FORM_START , this . _getLocation ( ) ) ;
539+ this . _requireCharCode ( $LBRACE ) ;
540+ this . _endToken ( [ ] ) ;
541+
542+ this . _beginToken ( HtmlTokenType . RAW_TEXT , this . _getLocation ( ) ) ;
543+ let condition = this . _readUntil ( $COMMA ) ;
544+ this . _endToken ( [ condition ] , this . _getLocation ( ) ) ;
545+ this . _requireCharCode ( $COMMA ) ;
546+ this . _attemptCharCodeUntilFn ( isNotWhitespace ) ;
547+
548+ this . _beginToken ( HtmlTokenType . RAW_TEXT , this . _getLocation ( ) ) ;
549+ let type = this . _readUntil ( $COMMA ) ;
550+ this . _endToken ( [ type ] , this . _getLocation ( ) ) ;
551+ this . _requireCharCode ( $COMMA ) ;
552+ this . _attemptCharCodeUntilFn ( isNotWhitespace ) ;
553+
554+ this . inExpansionForm = true ;
555+ }
556+
557+ private _consumeExpansionCaseStart ( ) {
558+ this . _requireCharCode ( $EQ ) ;
559+
560+ this . _beginToken ( HtmlTokenType . EXPANSION_CASE_VALUE , this . _getLocation ( ) ) ;
561+ let value = this . _readUntil ( $LBRACE ) . trim ( ) ;
562+ this . _endToken ( [ value ] , this . _getLocation ( ) ) ;
563+ this . _attemptCharCodeUntilFn ( isNotWhitespace ) ;
564+
565+ this . _beginToken ( HtmlTokenType . EXPANSION_CASE_EXP_START , this . _getLocation ( ) ) ;
566+ this . _requireCharCode ( $LBRACE ) ;
567+ this . _endToken ( [ ] , this . _getLocation ( ) ) ;
568+ this . _attemptCharCodeUntilFn ( isNotWhitespace ) ;
569+
570+ this . inExpansionCase = true ;
571+ }
572+
573+ private _consumeExpansionCaseEnd ( ) {
574+ this . _beginToken ( HtmlTokenType . EXPANSION_CASE_EXP_END , this . _getLocation ( ) ) ;
575+ this . _requireCharCode ( $RBRACE ) ;
576+ this . _endToken ( [ ] , this . _getLocation ( ) ) ;
577+ this . _attemptCharCodeUntilFn ( isNotWhitespace ) ;
578+
579+ this . inExpansionCase = false ;
580+ }
581+
582+ private _consumeExpansionFormEnd ( ) {
583+ this . _beginToken ( HtmlTokenType . EXPANSION_FORM_END , this . _getLocation ( ) ) ;
584+ this . _requireCharCode ( $RBRACE ) ;
585+ this . _endToken ( [ ] ) ;
586+
587+ this . inExpansionForm = false ;
588+ }
589+
509590 private _consumeText ( ) {
510591 var start = this . _getLocation ( ) ;
511592 this . _beginToken ( HtmlTokenType . TEXT , start ) ;
512- var parts = [ this . _readChar ( true ) ] ;
513- while ( ! isTextEnd ( this . peek ) ) {
593+
594+ var parts = [ ] ;
595+ let interpolation = false ;
596+
597+ if ( this . peek === $LBRACE && this . nextPeek === $LBRACE ) {
598+ parts . push ( this . _readChar ( true ) ) ;
599+ parts . push ( this . _readChar ( true ) ) ;
600+ interpolation = true ;
601+ } else {
514602 parts . push ( this . _readChar ( true ) ) ;
515603 }
604+
605+ while ( ! this . isTextEnd ( interpolation ) ) {
606+ if ( this . peek === $LBRACE && this . nextPeek === $LBRACE ) {
607+ parts . push ( this . _readChar ( true ) ) ;
608+ parts . push ( this . _readChar ( true ) ) ;
609+ interpolation = true ;
610+ } else if ( this . peek === $RBRACE && this . nextPeek === $RBRACE && interpolation ) {
611+ parts . push ( this . _readChar ( true ) ) ;
612+ parts . push ( this . _readChar ( true ) ) ;
613+ interpolation = false ;
614+ } else {
615+ parts . push ( this . _readChar ( true ) ) ;
616+ }
617+ }
516618 this . _endToken ( [ this . _processCarriageReturns ( parts . join ( '' ) ) ] ) ;
517619 }
518620
621+ private isTextEnd ( interpolation : boolean ) : boolean {
622+ if ( this . peek === $LT || this . peek === $EOF ) return true ;
623+ if ( this . tokenizeExpansionForms ) {
624+ if ( isSpecialFormStart ( this . peek , this . nextPeek ) ) return true ;
625+ if ( this . peek === $RBRACE && ! interpolation && this . inExpansionForm ) return true ;
626+ }
627+ return false ;
628+ }
629+
519630 private _savePosition ( ) : number [ ] {
520631 return [ this . peek , this . index , this . column , this . line , this . tokens . length ] ;
521632 }
522633
634+ private _readUntil ( char : number ) : string {
635+ let start = this . index ;
636+ this . _attemptUntilChar ( char ) ;
637+ return this . input . substring ( start , this . index ) ;
638+ }
639+
523640 private _restorePosition ( position : number [ ] ) : void {
524641 this . peek = position [ 0 ] ;
525642 this . index = position [ 1 ] ;
@@ -558,8 +675,8 @@ function isNamedEntityEnd(code: number): boolean {
558675 return code == $SEMICOLON || code == $EOF || ! isAsciiLetter ( code ) ;
559676}
560677
561- function isTextEnd ( code : number ) : boolean {
562- return code === $LT || code === $EOF ;
678+ function isSpecialFormStart ( peek : number , nextPeek : number ) : boolean {
679+ return peek === $LBRACE && nextPeek != $LBRACE ;
563680}
564681
565682function isAsciiLetter ( code : number ) : boolean {
0 commit comments