import { Rule , StyleRules } from 'css' ; import { has , pipe } from 'ramda' ; import { ITagNode , NodeType } from 'svg-vdom' ; import { IDom } from '../../typings/node' ; import { createShortenID } from '../algorithm/create-shorten-id' ; import { regularAttr } from '../const/regular-attr' ; import { funcIRIToID , IRIFullMatch } from '../const/syntax' ; import { parseStyle } from '../style/parse' ; import { shortenStyle } from '../style/shorten' ; import { stringifyStyle } from '../style/stringify' ; import { hasProp } from '../utils/has-prop' ; import { traversalObj } from '../utils/traversal-obj' ; const idSelectorReg = /#([^,*#>+~:{\s[.]+)/gi ; const style2value = pipe ( stringifyStyle , shortenStyle ) ; interface IIDCache { [ propName ] : [ string , [ ITagNode , string | null ] [ ] ] ; } export const shortenID = async ( dom ) : Promise < void > => new Promise ( resolve => { let si = 0 ; const IDList = { } ; const shorten = ( node , attrname , key ) => { if ( hasProp ( IDList , key ) ) { IDList [ key ] [ 1 ] . push ( [ node , attrname ] ) ; return IDList [ key ] [ 0 ] ; } const sid = createShortenID ( si ++ ) ; IDList [ key ] = [ sid , [ [ node , attrname ] ] ] ; return sid ; } ; let cssRules ; if ( dom . stylesheet ) { cssRules = dom . stylesheet . stylesheet as StyleRules ; traversalObj ( has ( 'selectors' ) , ( ruleItem ) => { const selectors = ruleItem . selectors ; if ( selectors ) { selectors . forEach ( ( selector , selectorIndex ) => { selectors [ selectorIndex ] = selector . replace ( idSelectorReg , ( m , p ) => `#${ shorten ( dom . styletag as ITagNode , null , p ) }` ) ; } ) ; } } , cssRules . rules ) ; } const tags = dom . querySelectorAll ( NodeType . Tag ) as ITagNode [ ] ; tags . forEach ( node => { node . attributes . forEach ( attr => { if ( regularAttr [ attr . fullname ] . maybeFuncIRI ) { const firi = funcIRIToID . exec ( attr . value ) ; if ( firi ) { attr . value = `url(#${ shorten ( node , attr . fullname , firi [ 2 ] ) })` ; } } else if ( regularAttr [ attr . fullname ] . maybeIRI ) { const iri = IRIFullMatch . exec ( attr . value ) ; if ( iri ) { attr . value = `#${ shorten ( node , attr . fullname , iri [ 1 ] ) }` ; } } else if ( attr . fullname === 'style' ) { const styleList = parseStyle ( attr . value ) ; styleList . forEach ( styleItem => { if ( regularAttr [ styleItem . fullname ] . maybeFuncIRI ) { const firi = funcIRIToID . exec ( styleItem . value ) ; if ( firi ) { styleItem . value = `url(#${ shorten ( node , `style|${ styleItem . fullname }` , firi [ 2 ] ) })` ; } } } ) ; attr . value = style2value ( styleList ) ; } } ) ; } ) ; tags . forEach ( node => { const ID = node . getAttribute ( 'id' ) ; if ( ID !== null ) { if ( hasProp ( IDList , ID ) ) { const id = IDList [ ID ] [ 0 ] ; delete IDList [ ID ] ; node . setAttribute ( 'id' , id ) ; } else { node . removeAttribute ( 'id' ) ; } } } , dom ) ; Object . values ( IDList ) . forEach ( item => { for ( const [ node , attrName ] of item [ 1 ] ) { if ( typeof attrName === 'string' ) { if ( attrName . startsWith ( 'style|' ) ) { const styleList = parseStyle ( node . getAttribute ( 'style' ) as string ) . filter ( styleItem => styleItem . fullname !== attrName . slice ( 6 ) ) ; if ( styleList . length ) { node . setAttribute ( 'style' , style2value ( styleList ) ) ; } else { node . removeAttribute ( 'style' ) ; } } else { node . removeAttribute ( attrName ) ; } } else { const reg = new RegExp ( `#${ item [ 0 ] }(?=[,\\*#>+~:{\\s\\[\\.]|$)` ) ; traversalObj ( has ( 'selectors' ) , ( ruleItem , path ) => { const selectors = ruleItem . selectors ; if ( selectors ) { for ( let i = selectors . length ; i -- ; ) { if ( reg . test ( selectors [ i ] ) ) { selectors . splice ( i , 1 ) ; } } if ( ! selectors . length ) { const parent = path [ path . length - 1 ] as Rule [ ] ; parent . splice ( parent . indexOf ( ruleItem ) , 1 ) ; } } } , cssRules . rules ) ; } } } ) ; resolve ( ) ; } ) ;