11import { createParser } from 'css-selector-parser' ;
2- import type { AstRule } from 'css-selector-parser' ;
2+ import type { AstRule , AstSelector } from 'css-selector-parser' ;
33import { Descriptor } from './Descriptor.js' ;
4- import { createCSSOM , elementsAreComparable , mergeElements } from './Utility.js' ;
4+ import { createCSSOM , elementsAreComparable , mergeElements , sanitizeElement } from './Utility.js' ;
55
66export class Options {
77 duplicates ?: 'preserve' | 'remove' ;
88 fill ?: 'fill' | 'no-fill' ;
99 imports ?: 'include' | 'style-only' ;
1010 mergeNth ?: 'merge' | 'no-merge' ;
11+ sanitize ?: 'all' | 'imports' | 'off' ;
1112}
1213
1314const parse = createParser ( { syntax : 'progressive' } ) ;
1415
1516class Rule {
16- rule ;
17- selectorAst ;
17+ rule : CSSStyleRule ;
18+ sanitize : boolean ;
19+ selectorAst : AstSelector ;
1820
19- constructor ( rule : CSSStyleRule ) {
21+ constructor ( rule : CSSStyleRule , sanitize = false ) {
2022 this . rule = rule ;
23+ this . sanitize = sanitize ;
2124 this . selectorAst = parse ( rule . selectorText ) ;
2225 }
2326}
@@ -47,12 +50,12 @@ export async function cssToHtml (css: CSSRuleList | string, options: Options = {
4750 const rules = new Array < Rule > ( ) ;
4851 const importSet = new Set < string > ( ) ;
4952
50- async function parseRules ( source : CSSRuleList , urlBase : string ) : Promise < void > {
53+ async function parseRules ( source : CSSRuleList , urlBase : string , sanitize ?: boolean ) : Promise < void > {
5154 let seenStyleRule = false ;
5255 for ( const rule of Object . values ( source ! ) ) {
5356 if ( rule instanceof CSSStyleRule ) {
5457 seenStyleRule = true ;
55- rules . push ( new Rule ( rule ) ) ;
58+ rules . push ( new Rule ( rule , sanitize ) ) ;
5659 }
5760 // Fetch the content of imported stylesheets.
5861 else if ( rule instanceof CSSImportRule && ! seenStyleRule && options . imports === 'include' ) {
@@ -63,23 +66,23 @@ export async function cssToHtml (css: CSSRuleList | string, options: Options = {
6366 if ( resource . status !== 200 ) throw new Error ( `Response status for stylesheet "${ url . href } " was ${ resource . status } .` ) ;
6467 const text = await resource . text ( ) ;
6568 const importedRule = createCSSOM ( text ) ;
66- if ( importedRule ) await parseRules ( importedRule , url . href ) ;
69+ if ( importedRule ) await parseRules ( importedRule , url . href , options . sanitize === 'imports' ) ;
6770 }
6871 }
6972 }
7073 }
7174 await parseRules ( styleRules , window . location . href ) ;
7275
7376 // Populate the DOM.
74- for ( const { rule, selectorAst } of rules ) {
77+ for ( const { rule, sanitize , selectorAst } of rules ) {
7578 // Traverse each rule nest of the selector AST.
7679 for ( const r of selectorAst . rules ) {
7780 const nest = new Array < Descriptor > ( ) ;
7881 let invalidNest = false ;
7982 // Create a descriptor for each of the nested selectors.
8083 let next : AstRule | undefined = r ;
8184 do {
82- const descriptor = new Descriptor ( next ) ;
85+ const descriptor = new Descriptor ( next , undefined , sanitize ) ;
8386 if ( descriptor . invalid ) {
8487 invalidNest = true ;
8588 next = undefined ;
@@ -180,5 +183,13 @@ export async function cssToHtml (css: CSSRuleList | string, options: Options = {
180183 }
181184 }
182185
186+ if ( options . sanitize !== 'off' && options . sanitize !== 'imports' ) {
187+ const cleanHtml = sanitizeElement ( output ) ;
188+ if ( cleanHtml instanceof HTMLBodyElement ) return cleanHtml ;
189+ const body = document . createElement ( 'body' ) ;
190+ body . append ( cleanHtml ) ;
191+ return body ;
192+ }
193+
183194 return output ;
184195}
0 commit comments