import { Declaration , KeyFrames , Media , Node , Rule , parse as cssParse } from 'css' ; import { has } from 'ramda' ; import { ITagNode , ITextNode , NodeType } from 'svg-vdom' ; import { IDom } from '../../typings/node' ; import { regularAttr } from '../const/regular-attr' ; import { traversalObj } from '../utils/traversal-obj' ; import { knownCSS } from '../validate/known-css' ; import { legalValue } from '../validate/legal-value' ; import { combineText } from '../xml/combine-text' ; const rmCSSNode = ( cssNode , plist ) => { const index = plist . indexOf ( cssNode ) ; if ( index !== - 1 ) { plist . splice ( index , 1 ) ; } } ; export const combineStyle = async ( dom ) : Promise < void > => new Promise ( resolve => { let firstStyle ; const styles = dom . querySelectorAll ( 'style' ) as ITagNode [ ] ; styles . forEach ( node => { const type = node . getAttribute ( 'type' ) ; if ( type && type !== 'text/css' ) { node . remove ( ) ; return ; } if ( firstStyle ) { firstStyle . appendChild ( node . childNodes ) ; node . remove ( ) ; } else { firstStyle = node ; } } ) ; const ruleParents = [ ] ; if ( firstStyle ) { combineText ( firstStyle ) ; const childNodes = firstStyle . childNodes as ITextNode [ ] ; if ( childNodes . length !== 1 || ! childNodes [ 0 ] . textContent ) { firstStyle . remove ( ) ; } else { if ( ! childNodes [ 0 ] . textContent . includes ( '<' ) ) { childNodes [ 0 ] . nodeType = NodeType . Text ; } try { const parsedCss = cssParse ( childNodes [ 0 ] . textContent ) ; if ( parsedCss . stylesheet ) { dom . stylesheet = parsedCss ; dom . styletag = firstStyle ; traversalObj < Node > ( has ( 'type' ) , ( cssNode , parents ) => { switch ( cssNode . type ) { case 'rule' : case 'keyframe' : case 'font-face' : case 'page' : { const cssRule = cssNode as Rule ; if ( ! cssRule . declarations ) { rmCSSNode ( cssRule , parents [ parents . length - 1 ] as Node [ ] ) ; return ; } const declared = new Set < string > ( ) ; for ( let i = cssRule . declarations . length ; i -- ; ) { const ruleItem = cssRule . declarations [ i ] as Declaration ; if ( ! ruleItem . property || ! ruleItem . value || declared . has ( ruleItem . property ) ) { cssRule . declarations . splice ( i , 1 ) ; } else { declared . add ( ruleItem . property ) ; } } if ( ! cssRule . declarations . length ) { rmCSSNode ( cssRule , parents [ parents . length - 1 ] as Node [ ] ) ; } else { ruleParents . push ( [ cssRule as Required < Rule > , parents [ parents . length - 1 ] as Node [ ] ] ) ; } break ; } case 'keyframes' : { const keyframes = cssNode as KeyFrames ; if ( ! keyframes . keyframes || ! keyframes . keyframes . length ) { rmCSSNode ( cssNode , parents [ parents . length - 1 ] as Node [ ] ) ; } break ; } case 'media' : case 'host' : case 'supports' : case 'document' : { const ruleParent = cssNode as Media ; if ( ! ruleParent . rules || ! ruleParent . rules . length ) { rmCSSNode ( cssNode , parents [ parents . length - 1 ] as Node [ ] ) ; } break ; } case 'comment' : rmCSSNode ( cssNode , parents [ parents . length - 1 ] as Node [ ] ) ; break ; default : break ; } } , parsedCss . stylesheet . rules , true ) ; } else { firstStyle . remove ( ) ; } } catch { firstStyle . remove ( ) ; } } } if ( ruleParents . length ) { for ( const [ rule , parent ] of ruleParents ) { rule . declarations . forEach ( styleItem => { const styleDefine = regularAttr [ styleItem . property as string ] ; if ( ! legalValue ( styleDefine , { fullname : styleItem . property as string , value : styleItem . value as string , name : '' , } ) && ! knownCSS ( styleItem . property as string ) ) { styleItem . value = '' ; } } ) ; rule . declarations = rule . declarations . filter ( item => ! ! item . value ) ; if ( ! rule . declarations . length ) { rmCSSNode ( rule as Node , parent ) ; } } } resolve ( ) ; } ) ;