import { IAttr , IDocument , ITagNode , NodeType } from 'svg-vdom' ; import { TAttrObj } from '../../typings' ; import { ITag } from '../../typings/node' ; import { IStyleObj } from '../../typings/style' ; import { checkAnimateMotion } from '../animate/check-animate-motion' ; import { shapeElements } from '../const/definitions' ; import { regularAttr } from '../const/regular-attr' ; import { funcIRIToID , IRIFullMatch } from '../const/syntax' ; import { parseStyle } from '../style/parse' ; import { stringifyStyle } from '../style/stringify' ; import { isTag } from '../xml/is-tag' ; import { parseStyleTree } from '../xml/parse-style-tree' ; interface IIDCacheITem { tag ? : ITag ; iri : Array < [ ITag , string ] > ; } interface IIDCache { [ propName ] : IIDCacheITem | undefined ; } const checkSub = ( node , IDList , isDefs = false ) => { let hasId = false ; if ( ! isDefs ) { const id = node . getAttribute ( 'id' ) ; if ( id ) { if ( IDList [ id ] ) { hasId = true ; ( IDList [ id ] as IIDCacheITem ) . tag = node ; } } } if ( ! hasId ) { for ( let ci = node . childNodes . length ; ci -- ; ) { const childNode = node . childNodes [ ci ] ; if ( isTag ( childNode ) ) { checkSub ( childNode , IDList ) ; } else { childNode . remove ( ) ; } } if ( ! node . childNodes . length ) { node . remove ( ) ; } else if ( ! isDefs ) { ( node . parentNode as ITagNode ) . replaceChild ( node . childNodes , node ) ; } } } ; const checkDefsApply = ( item , dom ) => { const [ node , attrName ] = item . iri [ 0 ] ; if ( attrName !== 'href' && attrName !== 'xlink:href' ) { return ; } switch ( node . nodeName ) { case 'use' : { if ( node . hasAttribute ( 'x' ) || node . hasAttribute ( 'y' ) ) { return ; } if ( [ 'svg' , 'symbol' ] . includes ( ( item . tag as ITagNode ) . nodeName ) && ( node . hasAttribute ( 'width' ) || node . hasAttribute ( 'height' ) ) ) { return ; } const originStyle = { } ; const originAttr = { } ; for ( const [ key , val ] of Object . entries ( node . styles as IStyleObj ) ) { if ( val . from === 'styletag' ) { return ; } if ( val . from === 'attr' ) { originAttr [ key ] = val . value ; } if ( val . from === 'inline' ) { originStyle [ key ] = val . value ; } } const useTag = item . tag as ITag ; ( node . parentNode as ITag ) . replaceChild ( useTag , node ) ; const styleList = useTag . hasAttribute ( 'style' ) ? parseStyle ( useTag . getAttribute ( 'style' ) as string ) : [ ] ; for ( const [ key , val ] of Object . entries ( originAttr ) ) { if ( ! useTag . hasAttribute ( key ) && ! styleList . some ( sItem => sItem . fullname === key ) ) { useTag . setAttribute ( key , val ) ; } } for ( const [ key , val ] of Object . entries ( originStyle ) ) { if ( ! useTag . hasAttribute ( key ) && ! styleList . some ( sItem => sItem . fullname === key ) ) { styleList . push ( { name : key , fullname : key , value : val , } ) ; } } if ( styleList . length ) { useTag . setAttribute ( 'style' , stringifyStyle ( styleList ) ) ; } return ; } case 'mpath' : { const pathTag = item . tag as ITagNode ; const mpathParent = node . parentNode as ITagNode ; if ( ! shapeElements . includes ( pathTag . nodeName ) ) { node . remove ( ) ; pathTag . remove ( ) ; if ( ! checkAnimateMotion ( mpathParent , dom ) ) { mpathParent . remove ( ) ; } return ; } if ( pathTag . nodeName === 'path' ) { const d = pathTag . getAttribute ( 'd' ) ; if ( d ) { mpathParent . setAttribute ( 'path' , d ) ; node . remove ( ) ; pathTag . remove ( ) ; } } return ; } default : break ; } } ; export const shortenDefs = async ( dom ) : Promise < void > => new Promise ( resolve => { let firstDefs ; ( dom . querySelectorAll ( 'defs' ) as ITagNode [ ] ) . forEach ( node => { if ( firstDefs ) { firstDefs . appendChild ( node . childNodes . filter ( isTag ) ) ; node . remove ( ) ; } else { firstDefs = node ; for ( let ci = node . childNodes . length ; ci -- ; ) { const childNode = node . childNodes [ ci ] ; if ( ! isTag ( childNode ) ) { childNode . remove ( ) ; } } } } ) ; if ( firstDefs ) { const IDList = { } ; ( dom . querySelectorAll ( isTag ) as ITagNode [ ] ) . forEach ( node => { node . attributes . forEach ( attr => { if ( regularAttr [ attr . fullname ] . maybeFuncIRI ) { const firi = funcIRIToID . exec ( attr . value ) ; if ( firi ) { if ( ! IDList [ firi [ 2 ] ] ) { IDList [ firi [ 2 ] ] = { iri : [ ] , } ; } ( IDList [ firi [ 2 ] ] as IIDCacheITem ) . iri . push ( [ node , attr . fullname ] ) ; } } else if ( regularAttr [ attr . fullname ] . maybeIRI ) { const iri = IRIFullMatch . exec ( attr . value ) ; if ( iri ) { if ( ! IDList [ iri [ 1 ] ] ) { IDList [ iri [ 1 ] ] = { iri : [ ] , } ; } ( IDList [ iri [ 1 ] ] as IIDCacheITem ) . iri . push ( [ node , attr . fullname ] ) ; } } } ) ; } ) ; checkSub ( firstDefs , IDList , true ) ; parseStyleTree ( dom ) ; ( Object . values ( IDList ) as IIDCacheITem [ ] ) . forEach ( item => { if ( item . tag ) { for ( let i = item . iri . length ; i -- ; ) { const [ tag ] = item . iri [ i ] ; if ( ! tag . closest ( NodeType . Document ) ) { item . iri . splice ( i , 1 ) ; } } if ( ! item . iri . length ) { item . tag . remove ( ) ; } if ( item . iri . length === 1 ) { checkDefsApply ( item , dom ) ; } } } ) ; if ( ! firstDefs . childNodes . length ) { firstDefs . remove ( ) ; } } resolve ( ) ; } ) ;