WICG / css-parser-api

This is the repo where the CSS Houdini parser API will be worked on
Other
71 stars 14 forks source link

Overview

The goal of this specification is to allow authors access to the engine's parser. There are two over-arching use cases:

  1. Pass the parser a string receive and receive an object to work with instead of building custom parsing in JS.
  2. Extend what the parser understands for fully polyfilling

Desires for these APIs:

Example: Parsing out a ruleset

   var background = window.cssParse.rule("background: green");
   console.log(background.styleMap.get("background").value) // "green"

   var styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
   console.log(styles.length) // 5
   console.log(styles[0].styleMap.get("margin-top").value) // 5
   console.log(styles[0].styleMap.get("margin-top").type) // "px"

Example: Parsing out a stylesheet

    const style = fetch("style.css")
        .then(response=>CSS.parseStylesheet(response.body));
    style.then(console.log);

    /* example of the object once we have it more refined */
    );

Use Cases

  1. Extending CSS in a more intrusive way than our hooks allow - most of the stylesheet is valid CSS, but you want to intercede on the unrecognized things and fix it up into valid CSS. (Probably don't want to do this - it collides with the custom property/function/at-rule case, but without the friendliness to future language extensions.)
  2. Custom properties that are more complex than we allow you to specify - something that takes "<length> || <color>", for example. Right now your only choice is to give it the grammar "*" which just gives you a string that you have to parse yourself. Similar with custom functions (--conic-gradient(...)) and custom at-rules (@--svg) - these will rarely fit in our limited set of allowed grammars.
  3. CSS-like languages that want to rely on CSS's generic syntax, like CAS or that one mapping language I forget the name of. These have nothing to do with CSS and will likely result in something other than a stylesheet: CAS turns into a series of querySelector() and setAttribute() calls; the mapping thing turns into some data structures specific to that application.
  4. Extending HTML/SVG attributes that use a CSS-like syntax, like <img sizes>. If you wanted to add support for the h descriptor to sizes, you currently have to write your own full-feature CSS parser. (sizes is pretty complex; you shouldn't skimp.) Better to let the engine parse it as "generic CSS", then you can recognize the parts you need and do image selection yourself.

Example of the problem

Here is an example of some JS code that is wanting to parse out various CSS types and also seperate out the values from their units.


        function parseValues(value,propertyName) {
            // Trim value on the edges
            value = value.trim();

            // Normalize letter-casing
            value = value.toLowerCase();

            // Map colors to a standard value (eg: white, blue, yellow)
            if (isKeywordColor(value)) { return "<color-keyword>"; }

            value = value.replace(/[#][0-9a-fA-F]+/g, '#xxyyzz');

            // Escapce identifiers containing numbers
            var numbers = ['ZERO','ONE','TWO','THREE','FOUR','FIVE','SIX','SEVEN','EIGHT','NINE'];

            value = value.replace(
                /([_a-z][-_a-z]|[_a-df-z])[0-9]+[-_a-z0-9]*/g,
                s=>numbers.reduce(
                    (m,nstr,nint)=>m.replace(RegExp(nint,'g'),nstr),
                    s
                )
            );

            // Remove any digits eg: 55px -> px, 1.5 -> 0.0, 1 -> 0
            value = value.replace(/(?:[+]|[-]|)(?:(?:[0-9]+)(?:[.][0-9]+|)|(?:[.][0-9]+))(?:[e](?:[+]|[-]|)(?:[0-9]+))?(%|e[a-z]+|[a-df-z][a-z]*)/g, "$1");
            value = value.replace(/(?:[+]|[-]|)(?:[0-9]+)(?:[.][0-9]+)(?:[e](?:[+]|[-]|)(?:[0-9]+))?/g, " <float> ");
            value = value.replace(/(?:[+]|[-]|)(?:[.][0-9]+)(?:[e](?:[+]|[-]|)(?:[0-9]+))?/g, " <float> ");
            value = value.replace(/(?:[+]|[-]|)(?:[0-9]+)(?:[e](?:[+]|[-]|)(?:[0-9]+))/g, " <float> ");
            value = value.replace(/(?:[+]|[-]|)(?:[0-9]+)/g, " <int> ");

            // Unescapce identifiers containing numbers
            value = numbers.reduce(
                (m,nstr,nint)=>m.replace(RegExp(nstr,'g'),nint),
                value
            )

            // Remove quotes
            value = value.replace(/('|‘|’|")/g, "");

            //
            switch(propertyName) {
                case 'counter-increment':
                case 'counter-reset':
                    // Anonymize the user identifier
                    value = value.replace(/[-_a-zA-Z0-9]+/g,' <custom-ident> ');
                    break;
                case 'grid':
                case 'grid-template':
                case 'grid-template-rows':
                case 'grid-template-columns':
                case 'grid-template-areas':
                    // Anonymize line names
                    value = value.replace(/\[[-_a-zA-Z0-9 ]+\]/g,' <line-names> ');
                    break;
                case '--var':
                    // Replace (...), {...} and [...]
                    value = value.replace(/[(](?:[^()]+|[(](?:[^()]+|[(](?:[^()]+|[(](?:[^()]+|[(](?:[^()]*)[)])*[)])*[)])*[)])*[)]/g, " <parentheses-block> ");
                    value = value.replace(/[(](?:[^()]+|[(](?:[^()]+|[(](?:[^()]+|[(](?:[^()]+|[(](?:[^()]*)[)])*[)])*[)])*[)])*[)]/g, " <parentheses-block> ");
                    value = value.replace(/\[(?:[^()]+|\[(?:[^()]+|\[(?:[^()]+|\[(?:[^()]+|\[(?:[^()]*)\])*\])*\])*\])*\]/g, " <curly-brackets-block> ");
                    value = value.replace(/\[(?:[^()]+|\[(?:[^()]+|\[(?:[^()]+|\[(?:[^()]+|\[(?:[^()]*)\])*\])*\])*\])*\]/g, " <curly-brackets-block> ");
                    value = value.replace(/\{(?:[^()]+|\{(?:[^()]+|\{(?:[^()]+|\{(?:[^()]+|\{(?:[^()]*)\})*\})*\})*\})*\}/g, " <square-brackets-block> ");
                    value = value.replace(/\{(?:[^()]+|\{(?:[^()]+|\{(?:[^()]+|\{(?:[^()]+|\{(?:[^()]*)\})*\})*\})*\})*\}/g, " <square-brackets-block> ");
                    break;
            }

            return value.trim();
        }