import { parse as cssParse , stringify } from 'css' ; import { NodeType , TagNode , TextNode } from 'svg-vdom' ; import { IStyleObj } from '../../typings/style' ; import { TDynamicObj } from '../../typings' ; import { IDom , ITag } from '../../typings/node' ; import { needUnitInStyle } from '../const/definitions' ; import { numberFullMatch } from '../const/syntax' ; import { parseStyle } from '../style/parse' ; import { stringifyStyle } from '../style/stringify' ; import { parseStyleTree } from '../xml/parse-style-tree' ; const STYLE_THRESHOLD = 3 ; const TRANSLATE_THRESHOLD = 10 ; const createKey = ( name , value , important ? ) => { let val = value ; if ( needUnitInStyle . includes ( name ) && numberFullMatch . test ( val ) && + val !== 0 ) { val += 'px' ; } return `${ name }:${ val }${ important ? '!important' : '' }` ; } ; export const styleToClass = async ( dom ) : Promise < void > => new Promise ( resolve => { parseStyleTree ( dom ) ; let index = 0 ; const tags = dom . querySelectorAll ( NodeType . Tag ) as ITag [ ] ; const keyCache = { } ; for ( const tag of tags ) { const keys = [ ] ; const tagStyle = tag . styles as IStyleObj ; Object . keys ( tagStyle ) . sort ( ) . forEach ( k => { if ( tagStyle [ k ] . from === 'inline' && ! tagStyle [ k ] . override ) { keys . push ( createKey ( k , tagStyle [ k ] . value , tagStyle [ k ] . important ) ) ; } } ) ; if ( keys . length >= STYLE_THRESHOLD ) { const key = keys . join ( ';' ) ; if ( ! keyCache [ key ] ) { keyCache [ key ] = { className : `c${ ( + new Date + index ) . toString ( 36 ) }` , tags : [ tag ] , keys , } ; index ++ ; } else { keyCache [ key ] . tags . push ( tag ) ; } } } const caches = Object . entries ( keyCache ) . filter ( ( [ , cache ] ) => cache . tags . length * cache . keys . length >= TRANSLATE_THRESHOLD ) ; if ( caches . length ) { let styleContent = '' ; if ( dom . stylesheet ) { styleContent = stringify ( dom . stylesheet ) ; } else { const styleTag = new TagNode ( { nodeName : 'style' , nodeType : NodeType . Tag , } ) ; const styleText = new TextNode ( { nodeName : '#text' , nodeType : NodeType . CDATA , textContent : '' , } ) ; styleTag . appendChild ( styleText ) ; const rootSVG = dom . querySelector ( 'svg' ) as ITag ; rootSVG . insertBefore ( styleTag , rootSVG . childNodes [ 0 ] ) ; dom . styletag = styleTag ; } for ( const [ key , val ] of caches ) { styleContent += `.${ val . className }{${ key }}` ; val . tags . forEach ( tag => { const styleAttr = tag . getAttribute ( 'style' ) as string ; const styleList = parseStyle ( styleAttr ) ; for ( let i = styleList . length ; i -- ; ) { if ( val . keys . includes ( createKey ( styleList [ i ] . fullname , styleList [ i ] . value , styleList [ i ] . important ) ) ) { styleList . splice ( i , 1 ) ; } } if ( styleList . length ) { tag . setAttribute ( 'style' , stringifyStyle ( styleList ) ) ; } else { tag . removeAttribute ( 'style' ) ; } if ( tag . hasAttribute ( 'class' ) ) { tag . setAttribute ( 'class' , `${ tag . getAttribute ( 'class' ) as string } ${ val . className }` ) ; } else { tag . setAttribute ( 'class' , val . className ) ; } } ) ; } try { dom . stylesheet = cssParse ( styleContent ) ; } catch { console . log ( `${ styleContent } convert failed!` ) ; } } resolve ( ) ; } ) ;