import { makeMap , no } from './utils' ; const isNonPhrasingTag = makeMap ( 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + 'title,tr,track' ) ; const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ ; const ncname = '[a-zA-Z_][\\w\\-\\.]*' ; const qnameCapture = `((?:${ ncname }\\:)?${ ncname })` ; const startTagOpen = new RegExp ( `^<${ qnameCapture }` ) ; const startTagClose = /^\s*(\/?)>/ ; const endTag = new RegExp ( `^<\\/${ qnameCapture }[^>]*>` ) ; const doctype = /^<!DOCTYPE [^>]+>/i ; const comment = /^<!\--/ ; const conditionalComment = /^<!\[/ ; let IS_REGEX_CAPTURING_BROKEN = false ; 'x' . replace ( /x(.)?/g , function ( m , g ) { IS_REGEX_CAPTURING_BROKEN = g === '' ; } ) ; export const isPlainTextElement = makeMap ( 'script,style,textarea' , true ) ; const reCache = { } ; const decodingMap = { '&lt;' : '<' , '&gt;' : '>' , '&quot;' : '"' , '&amp;' : '&' , '&#10;' : '\n' , '&#9;' : '\t' } ; const encodedAttr = /&(?:lt|gt|quot|amp);/g ; const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g ; const isIgnoreNewlineTag = makeMap ( 'pre,textarea' , true ) ; const shouldIgnoreFirstNewline = ( tag , html ) => tag && isIgnoreNewlineTag ( tag ) && html [ 0 ] === '\n' ; function decodeAttr ( value , shouldDecodeNewlines ) { const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr ; return value . replace ( re , match => decodingMap [ match ] ) ; } export function parseHTML ( html , options ) { const stack = [ ] ; const expectHTML = options . expectHTML ; const isUnaryTag = options . isUnaryTag || no ; const canBeLeftOpenTag = options . canBeLeftOpenTag || no ; let index = 0 ; let last , lastTag ; while ( html ) { last = html ; if ( ! lastTag || ! isPlainTextElement ( lastTag ) ) { let textEnd = html . indexOf ( '<' ) ; if ( textEnd === 0 ) { if ( comment . test ( html ) ) { const commentEnd = html . indexOf ( '-->' ) ; if ( commentEnd >= 0 ) { if ( options . shouldKeepComment ) { options . comment ( html . substring ( 4 , commentEnd ) ) ; } advance ( commentEnd + 3 ) ; continue ; } } if ( conditionalComment . test ( html ) ) { const conditionalEnd = html . indexOf ( ']>' ) ; if ( conditionalEnd >= 0 ) { advance ( conditionalEnd + 2 ) ; continue ; } } const doctypeMatch = html . match ( doctype ) ; if ( doctypeMatch ) { advance ( doctypeMatch [ 0 ] . length ) ; continue ; } const endTagMatch = html . match ( endTag ) ; if ( endTagMatch ) { const curIndex = index ; advance ( endTagMatch [ 0 ] . length ) ; parseEndTag ( endTagMatch [ 1 ] , curIndex , index ) ; continue ; } const startTagMatch = parseStartTag ( ) ; if ( startTagMatch ) { handleStartTag ( startTagMatch ) ; if ( shouldIgnoreFirstNewline ( lastTag , html ) ) { advance ( 1 ) ; } continue ; } } let text , rest , next ; if ( textEnd >= 0 ) { rest = html . slice ( textEnd ) ; while ( ! endTag . test ( rest ) && ! startTagOpen . test ( rest ) && ! comment . test ( rest ) && ! conditionalComment . test ( rest ) ) { next = rest . indexOf ( '<' , 1 ) ; if ( next < 0 ) break ; textEnd += next ; rest = html . slice ( textEnd ) ; } text = html . substring ( 0 , textEnd ) ; advance ( textEnd ) ; } if ( textEnd < 0 ) { text = html ; html = '' ; } if ( options . chars && text ) { options . chars ( text ) ; } } else { let endTagLength = 0 ; const stackedTag = lastTag . toLowerCase ( ) ; const reStackedTag = reCache [ stackedTag ] || ( reCache [ stackedTag ] = new RegExp ( '([\\s\\S]*?)(</' + stackedTag + '[^>]*>)' , 'i' ) ) ; const rest = html . replace ( reStackedTag , function ( all , text , endTag ) { endTagLength = endTag . length ; if ( ! isPlainTextElement ( stackedTag ) && stackedTag !== 'noscript' ) { text = text . replace ( /<!\--([\s\S]*?)-->/g , '$1' ) . replace ( /<!\[CDATA\[([\s\S]*?)]]>/g , '$1' ) ; } if ( shouldIgnoreFirstNewline ( stackedTag , text ) ) { text = text . slice ( 1 ) ; } if ( options . chars ) { options . chars ( text ) ; } return '' ; } ) ; index += html . length - rest . length ; html = rest ; parseEndTag ( stackedTag , index - endTagLength , index ) ; } if ( html === last ) { options . chars && options . chars ( html ) ; if ( process . env . NODE_ENV !== 'production' && ! stack . length && options . warn ) { options . warn ( `Mal-formatted tag at end of template: "${ html }"` ) ; } break ; } } parseEndTag ( ) ; function advance ( n ) { index += n ; html = html . substring ( n ) ; } function parseStartTag ( ) { const start = html . match ( startTagOpen ) ; if ( start ) { const match = { tagName : start [ 1 ] , attrs : [ ] , start : index } ; advance ( start [ 0 ] . length ) ; let end , attr ; while ( ! ( end = html . match ( startTagClose ) ) && ( attr = html . match ( attribute ) ) ) { advance ( attr [ 0 ] . length ) ; match . attrs . push ( attr ) ; } if ( end ) { match . unarySlash = end [ 1 ] ; advance ( end [ 0 ] . length ) ; match . end = index ; return match ; } } } function handleStartTag ( match ) { const tagName = match . tagName ; const unarySlash = match . unarySlash ; if ( expectHTML ) { if ( lastTag === 'p' && isNonPhrasingTag ( tagName ) ) { parseEndTag ( lastTag ) ; } if ( canBeLeftOpenTag ( tagName ) && lastTag === tagName ) { parseEndTag ( tagName ) ; } } const unary = isUnaryTag ( tagName ) || ! ! unarySlash ; const l = match . attrs . length ; const attrs = new Array ( l ) ; for ( let i = 0 ; i < l ; i ++ ) { const args = match . attrs [ i ] ; if ( IS_REGEX_CAPTURING_BROKEN && args [ 0 ] . indexOf ( '""' ) === - 1 ) { if ( args [ 3 ] === '' ) { delete args [ 3 ] ; } if ( args [ 4 ] === '' ) { delete args [ 4 ] ; } if ( args [ 5 ] === '' ) { delete args [ 5 ] ; } } const value = args [ 3 ] || args [ 4 ] || args [ 5 ] || '' ; const shouldDecodeNewlines = tagName === 'a' && args [ 1 ] === 'href' ? options . shouldDecodeNewlinesForHref : options . shouldDecodeNewlines ; attrs [ i ] = { name : args [ 1 ] , value : decodeAttr ( value , shouldDecodeNewlines ) } ; } if ( ! unary ) { stack . push ( { tag : tagName , lowerCasedTag : tagName . toLowerCase ( ) , attrs : attrs } ) ; lastTag = tagName ; } if ( options . start ) { options . start ( tagName , attrs , unary , match . start , match . end ) ; } } function parseEndTag ( tagName , start , end ) { let pos , lowerCasedTagName ; if ( start == null ) start = index ; if ( end == null ) end = index ; if ( tagName ) { lowerCasedTagName = tagName . toLowerCase ( ) ; } if ( tagName ) { for ( pos = stack . length - 1 ; pos >= 0 ; pos -- ) { if ( stack [ pos ] . lowerCasedTag === lowerCasedTagName ) { break ; } } } else { pos = 0 ; } if ( pos >= 0 ) { for ( let i = stack . length - 1 ; i >= pos ; i -- ) { if ( process . env . NODE_ENV !== 'production' && ( i > pos || ! tagName ) && options . warn ) { options . warn ( `tag <${ stack [ i ] . tag }> has no matching end tag.` ) ; } if ( options . end ) { options . end ( stack [ i ] . tag , start , end ) ; } } stack . length = pos ; lastTag = pos && stack [ pos - 1 ] . tag ; } else if ( lowerCasedTagName === 'br' ) { if ( options . start ) { options . start ( tagName , [ ] , true , start , end ) ; } } else if ( lowerCasedTagName === 'p' ) { if ( options . start ) { options . start ( tagName , [ ] , false , start , end ) ; } if ( options . end ) { options . end ( tagName , start , end ) ; } } } }