/**
 * Based on css parser/compiler by NxChg
 * https://github.com/NxtChg/pieces/tree/master/js/css_parser
 */ import balanced from 'balanced-match' ; import { CssTree } from './types' ; param { string } css param options param { boolean } options . preserveStatic param { boolean } options . removeComments returns { object } function parseCss ( css , options = { } ) { const defaults = { preserveStatic : true , removeComments : false , } ; const settings = Object . assign ( { } , defaults , options ) ; const errors = [ ] ; function error ( msg ) { throw new Error ( `CSS parse error: ${ msg }` ) ; } function match ( re ) { const m = re . exec ( css ) ; if ( m ) { css = css . slice ( m [ 0 ] . length ) ; return m ; } } function open ( ) { return match ( /^{\s*/ ) ; } function close ( ) { return match ( /^}/ ) ; } function whitespace ( ) { match ( /^\s*/ ) ; } function comment ( ) { whitespace ( ) ; if ( css [ 0 ] !== '/' || css [ 1 ] !== '*' ) { return ; } let i = 2 ; while ( css [ i ] && ( css [ i ] !== '*' || css [ i + 1 ] !== '/' ) ) { i ++ ; } if ( ! css [ i ] ) { return error ( 'end of comment is missing' ) ; } const str = css . slice ( 2 , i ) ; css = css . slice ( i + 2 ) ; return { type : 'comment' , comment : str , } ; } function comments ( ) { const cmnts = [ ] ; let c ; while ( ( c = comment ( ) ) ) { cmnts . push ( c ) ; } return settings . removeComments ? [ ] : cmnts ; } function selector ( ) { whitespace ( ) ; while ( css [ 0 ] === '}' ) { error ( 'extra closing bracket' ) ; } const m = match ( /^(("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|[^{])+)/ ) ; if ( m ) { return m [ 0 ] . trim ( ) . replace ( /\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g , '' ) . replace ( /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g , function ( m ) { return m . replace ( /,/g , '\u200C' ) ; } ) . split ( /\s*(?![^(]*\)),\s*/ ) . map ( function ( s ) { return s . replace ( /\u200C/g , ',' ) ; } ) ; } } function declaration ( ) { if ( css [ 0 ] === '@' ) { return at_rule ( ) ; } match ( /^([;\s]*)+/ ) ; const comment_regexp = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g ; let prop = match ( /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/ ) ; if ( ! prop ) { return ; } prop = prop [ 0 ] . trim ( ) ; if ( ! match ( /^:\s*/ ) ) { return error ( "property missing ':'" ) ; } const val = match ( /^((?:\/\*.*?\*\/|'(?:\\'|.)*?'|"(?:\\"|.)*?"|\((\s*'(?:\\'|.)*?'|"(?:\\"|.)*?"|[^)]*?)\s*\)|[^};])+)/ ) ; const ret = { type : 'declaration' , property : prop . replace ( comment_regexp , '' ) , value : val ? val [ 0 ] . replace ( comment_regexp , '' ) . trim ( ) : '' , } ; match ( /^[;\s]*/ ) ; return ret ; } function declarations ( ) { if ( ! open ( ) ) { return error ( "missing '{'" ) ; } let d ; let decls = comments ( ) ; while ( ( d = declaration ( ) ) ) { decls . push ( d ) ; decls = decls . concat ( comments ( ) ) ; } if ( ! close ( ) ) { return error ( "missing '}'" ) ; } return decls ; } function keyframe ( ) { whitespace ( ) ; const vals = [ ] ; let m ; while ( ( m = match ( /^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/ ) ) ) { vals . push ( m [ 1 ] ) ; match ( /^,\s*/ ) ; } if ( vals . length ) { return { type : 'keyframe' , values : vals , declarations : declarations ( ) , } ; } } function at_keyframes ( ) { let m = match ( /^@([-\w]+)?keyframes\s*/ ) ; if ( ! m ) { return ; } const vendor = m [ 1 ] ; m = match ( /^([-\w]+)\s*/ ) ; if ( ! m ) { return error ( '@keyframes missing name' ) ; } const name = m [ 1 ] ; if ( ! open ( ) ) { return error ( "@keyframes missing '{'" ) ; } let frame ; let frames = comments ( ) ; while ( ( frame = keyframe ( ) ) ) { frames . push ( frame ) ; frames = frames . concat ( comments ( ) ) ; } if ( ! close ( ) ) { return error ( "@keyframes missing '}'" ) ; } return { type : 'keyframes' , name : name , vendor : vendor , keyframes : frames , } ; } function at_page ( ) { const m = match ( /^@page */ ) ; if ( m ) { const sel = selector ( ) || [ ] ; return { type : 'page' , selectors : sel , declarations : declarations ( ) } ; } } function at_page_margin_box ( ) { const m = match ( /@(top|bottom|left|right)-(left|center|right|top|middle|bottom)-?(corner)?\s*/ ) ; if ( m ) { const name = `${ m [ 1 ] }-${ m [ 2 ] }` + ( m [ 3 ] ? `-${ m [ 3 ] }` : '' ) ; return { type : 'page-margin-box' , name , declarations : declarations ( ) } ; } } function at_fontface ( ) { const m = match ( /^@font-face\s*/ ) ; if ( m ) { return { type : 'font-face' , declarations : declarations ( ) } ; } } function at_supports ( ) { const m = match ( /^@supports *([^{]+)/ ) ; if ( m ) { return { type : 'supports' , supports : m [ 1 ] . trim ( ) , rules : rules ( ) } ; } } function at_host ( ) { const m = match ( /^@host\s*/ ) ; if ( m ) { return { type : 'host' , rules : rules ( ) } ; } } function at_media ( ) { const m = match ( /^@media([^{]+)*/ ) ; if ( m ) { return { type : 'media' , media : ( m [ 1 ] || '' ) . trim ( ) , rules : rules ( ) } ; } } function at_custom_m ( ) { const m = match ( /^@custom-media\s+(--[^\s]+)\s*([^{;]+);/ ) ; if ( m ) { return { type : 'custom-media' , name : m [ 1 ] . trim ( ) , media : m [ 2 ] . trim ( ) } ; } } function at_document ( ) { const m = match ( /^@([-\w]+)?document *([^{]+)/ ) ; if ( m ) { return { type : 'document' , document : m [ 2 ] . trim ( ) , vendor : m [ 1 ] ? m [ 1 ] . trim ( ) : null , rules : rules ( ) , } ; } } function at_x ( ) { const m = match ( /^@(import|charset|namespace)\s*([^;]+);/ ) ; if ( m ) { return { type : m [ 1 ] , name : m [ 2 ] . trim ( ) } ; } } function at_rule ( ) { whitespace ( ) ; if ( css [ 0 ] === '@' ) { const ret = at_x ( ) || at_fontface ( ) || at_media ( ) || at_keyframes ( ) || at_supports ( ) || at_document ( ) || at_custom_m ( ) || at_host ( ) || at_page ( ) || at_page_margin_box ( ) ; if ( ret && ! settings . preserveStatic ) { let hasVarFunc = false ; if ( ret . declarations ) { hasVarFunc = ret . declarations . some ( ( decl ) => /var\(/ . test ( decl . value ) ) ; } else { const arr = ret . keyframes || ret . rules || [ ] ; hasVarFunc = arr . some ( ( obj ) => ( obj . declarations || [ ] ) . some ( ( decl ) => /var\(/ . test ( decl . value ) ) ) ; } return hasVarFunc ? ret : { } ; } return ret ; } } function rule ( ) { if ( ! settings . preserveStatic ) { const balancedMatch = balanced ( '{' , '}' , css ) ; if ( balancedMatch ) { const hasVarDecl = /:(?:root|host)(?![.:#(])/ . test ( balancedMatch . pre ) && /--\S*\s*:/ . test ( balancedMatch . body ) ; const hasVarFunc = /var\(/ . test ( balancedMatch . body ) ; if ( ! hasVarDecl && ! hasVarFunc ) { css = css . slice ( balancedMatch . end + 1 ) ; return { } ; } } } const sel = selector ( ) || [ ] ; const decls = settings . preserveStatic ? declarations ( ) : declarations ( ) . filter ( ( decl ) => { const hasVarDecl = sel . some ( ( s ) => /:(?:root|host)(?![.:#(])/ . test ( s ) ) && /^--\S/ . test ( decl . property ) ; const hasVarFunc = /var\(/ . test ( decl . value ) ; return hasVarDecl || hasVarFunc ; } ) ; if ( ! sel . length ) { error ( 'selector missing' ) ; } return { type : 'rule' , selectors : sel , declarations : decls , } ; } function rules ( core ) { if ( ! core && ! open ( ) ) { return error ( "missing '{'" ) ; } let node ; let rules = comments ( ) ; while ( css . length && ( core || css [ 0 ] !== '}' ) && ( node = at_rule ( ) || rule ( ) ) ) { if ( node . type ) { rules . push ( node ) ; } rules = rules . concat ( comments ( ) ) ; } if ( ! core && ! close ( ) ) { return error ( "missing '}'" ) ; } return rules ; } return { type : 'stylesheet' , stylesheet : { rules : rules ( true ) , errors : errors , } , } ; } export default parseCss ;