import { parse } from 'postcss' ; import replaceRegex from './replace/regex' ; import excludeList from './helpers/excludeList' ; import { NameGenerator } from './nameGenerator' ; import { BaseLibrary , BaseLibraryOptions } from './baseLibrary' ; export interface AttributeSelector { type : string ; originalString : string ; regexType : string ; nameGeneratorCounter : NameGenerator ; } export interface AttributeLibraryOptions extends BaseLibraryOptions { regex ? : boolean ; getRenamedValues ? : boolean ; } export interface GetAllOptions { getRenamedValues ? : boolean ; regex ? : boolean ; addSelectorType ? : boolean ; } export class AttributeLibrary extends BaseLibrary { attributeSelectors : { [ s ] : AttributeSelector ; } = { } ; static isSelector ( selector ) { return selector . charAt ( 0 ) === '.' || selector . charAt ( 0 ) === '#' ; } static removePseudoElements ( value ) { const splits = value . split ( /\\/g ) ; const splittedEscapes = splits . map ( ( split , i ) => { if ( splits . length - 1 > i ) { return split ; } const firstChar = split . charAt ( 0 ) || '' ; const modifiedString = split . slice ( 1 ) ; const removedPseudoElements = modifiedString . replace ( /:.+/g , '' ) ; return firstChar + removedPseudoElements ; } ) ; return splittedEscapes . join ( '' ) ; } static replaceAnAttributeSelector ( result , attributeSelector , value , slicedSelector ) { if ( result !== false ) return result ; const attributeString = attributeSelector . slice ( 1 , attributeSelector . length ) ; if ( attributeSelector . charAt ( 0 ) === '|' ) { const match = slicedSelector . match ( `^${ attributeString }-` ) ; if ( match ) { const newMatch = match [ 0 ] . replace ( /-$/ , '' ) ; return `${ newMatch }-${ value . nameGeneratorCounter . generate ( slicedSelector ) }` ; } } if ( attributeSelector . charAt ( 0 ) . match ( /^[=~|]/ ) ) { const match = slicedSelector . match ( attributeString ) ; if ( match && slicedSelector === match [ 0 ] ) { return match [ 0 ] ; } } switch ( attributeSelector . charAt ( 0 ) ) { case '*' : { const match = slicedSelector . match ( attributeString ) ; if ( match ) { return value . nameGeneratorCounter . generate ( slicedSelector ) + match [ 0 ] + value . nameGeneratorCounter . generate ( slicedSelector ) ; } break ; } case '^' : { const match = slicedSelector . match ( `^${ attributeString }` ) ; if ( match ) { return match [ 0 ] + value . nameGeneratorCounter . generate ( slicedSelector ) ; } break ; } case '$' : { const match = slicedSelector . match ( `${ attributeString }$` ) ; if ( match ) { return value . nameGeneratorCounter . generate ( slicedSelector ) + match [ 0 ] ; } break ; } default : break ; } return false ; } constructor ( name ? ) { super ( name ) ; this . setExclude ( excludeList ) ; } selectorFirstChar = ( ) : string => '#' ; prefetchValue = ( selector ) : string => selector . replace ( /(\.|#)/ , '' ) . replace ( /\\/g , '' ) ; getAttributeSelectorRegex = ( ) : RegExp => replaceRegex . idAttributeSelectors ; reset ( ) { super . reset ( ) ; this . attributeSelectors = { } ; this . setExclude ( excludeList ) ; } fillLibrary ( data , options = { } ) { const code = data . toString ( ) ; const result = parse ( code ) ; result . walk ( ( root ) => { const parentName = ( root . parent as any ) . name || '' ; if ( root . selector && ! parentName . match ( /keyframes/ ) ) { const matchedSelectors = root . selector . match ( replaceRegex . selectors ) || [ ] ; this . set ( matchedSelectors , options ) ; } } ) ; } isValidSelector ( selector ) { return selector . charAt ( 0 ) === this . selectorFirstChar ( ) ; } prepareValue ( repObj , options = { } ) { const replacedObject = { ... repObj } ; if ( ! this . isValidSelector ( replacedObject . value ) ) { return null ; } const escapeFreeSelector = AttributeLibrary . removePseudoElements ( replacedObject . value ) ; const realSelector = escapeFreeSelector . slice ( 1 , escapeFreeSelector . length ) ; if ( ! options . ignoreAttributeSelectors ) { const newName = this . replaceAttributeSelector ( escapeFreeSelector ) ; if ( newName ) { replacedObject . renamedValue = newName ; } } if ( replacedObject . renamedValue !== undefined && AttributeLibrary . isSelector ( replacedObject . renamedValue ) ) { replacedObject . renamedValue = ( replacedObject . renamedValue . slice ( 1 , replacedObject . renamedValue . length ) ) ; } replacedObject . value = realSelector ; return replacedObject ; } postfetchValue ( result , opts ) { const options = { addSelectorType : false , ... opts , } ; return ( ( options . addSelectorType ? this . selectorFirstChar ( ) : '' ) + this . prefix + result + this . suffix ) ; } getAll ( opts ? ) ; getAll ( opts ? ) ; getAll ( opts = { } ) { let originalSelector ; let compressedSelector ; let resultArray = [ ] ; let result = { } ; const selectors = this . values ; const options = { getRenamedValues : false , regex : false , addSelectorType : false , ... opts , } ; Object . keys ( selectors ) . forEach ( ( selector ) => { compressedSelector = selectors [ selector ] ; originalSelector = selector ; if ( options . getRenamedValues ) { result [ compressedSelector ] = originalSelector ; resultArray . push ( compressedSelector ) ; } else { result [ selector ] = compressedSelector ; resultArray . push ( originalSelector ) ; } } ) ; if ( options . regex ) { resultArray = resultArray . sort ( ( a , b ) => b . length - a . length ) ; } if ( options . addSelectorType ) { resultArray = resultArray . map ( ( value ) => `${ this . selectorFirstChar ( ) }${ value }` ) ; } if ( options . regex ) { const regex = options . addSelectorType ? new RegExp ( resultArray . map ( ( v ) => `\\${ v . replace ( /\[/ , '\\[' ) }` ) . join ( '|' ) , 'g' ) : new RegExp ( `(\\s|\\${ this . selectorFirstChar ( ) })(${ resultArray . map ( ( v ) => v . replace ( /\[/ , '\\[' ) ) . join ( '|' ) })[\\s)]` , 'g' ) ; const regexResult = resultArray . length === 0 ? undefined : regex ; return regexResult ; } if ( options . addSelectorType ) { const tempResult = { } ; resultArray . forEach ( ( value ) => { const modValue = value . slice ( 1 , value . length ) ; tempResult [ value ] = result [ modValue ] ; } ) ; result = tempResult ; } return result ; } setAttributeSelector ( attributeSelector ) { if ( ! attributeSelector ) { return ; } if ( Array . isArray ( attributeSelector ) ) { attributeSelector . forEach ( ( value ) => this . setAttributeSelector ( value ) ) ; return ; } const re = new RegExp ( this . getAttributeSelectorRegex ( ) ) ; const exec = re . exec ( attributeSelector ) ; if ( ! exec ) { return ; } const term = exec [ 3 ] ; let attributeSelectorType = exec [ 2 ] ; let selector = term ; if ( attributeSelectorType === '' ) { attributeSelectorType = '=' ; } if ( term . charAt ( 0 ) . match ( /"|'/ ) ) { selector = term . slice ( 1 , exec [ 3 ] . length - 1 ) ; } const attributeSelectorKey = attributeSelectorType + selector ; this . attributeSelectors [ attributeSelectorKey ] = { type : exec [ 1 ] , originalString : attributeSelector , regexType : attributeSelectorType , nameGeneratorCounter : new NameGenerator ( 'attribute' ) , } ; Object . keys ( this . values ) . forEach ( ( key ) => { const r = AttributeLibrary . replaceAnAttributeSelector ( false , attributeSelectorKey , this . attributeSelectors [ attributeSelectorKey ] , key ) ; if ( r !== false ) { const prev = this . values [ key ] ; this . values [ key ] = r ; delete this . compressedValues [ prev ] ; this . compressedValues [ r ] = key ; } } ) ; } replaceAttributeSelector ( selector ) { if ( ! this . isValidSelector ( selector ) ) { return false ; } const slicedSelector = selector . replace ( /^[.#]/ , '' ) ; return ( Object . entries ( this . attributeSelectors ) . reduce < string | false > ( ( a , entry ) => ( AttributeLibrary . replaceAnAttributeSelector ( a , entry [ 0 ] , entry [ 1 ] , slicedSelector ) ) , false ) ) ; } } export default new AttributeLibrary ( ) ;