parsel-js
Version:
A tiny, permissive CSS selector parser
2 lines (1 loc) • 5.21 kB
JavaScript
const e={attribute:/\[\s*(?:(?<namespace>\*|[-\w\P{ASCII}]*)\|)?(?<name>[-\w\P{ASCII}]+)\s*(?:(?<operator>\W?=)\s*(?<value>.+?)\s*(\s(?<caseSensitive>[iIsS]))?\s*)?\]/gu,id:/#(?<name>[-\w\P{ASCII}]+)/gu,class:/\.(?<name>[-\w\P{ASCII}]+)/gu,comma:/\s*,\s*/g,combinator:/\s*[\s>+~]\s*/g,"pseudo-element":/::(?<name>[-\w\P{ASCII}]+)(?:\((?<argument>¶*)\))?/gu,"pseudo-class":/:(?<name>[-\w\P{ASCII}]+)(?:\((?<argument>¶*)\))?/gu,universal:/(?:(?<namespace>\*|[-\w\P{ASCII}]*)\|)?\*/gu,type:/(?:(?<namespace>\*|[-\w\P{ASCII}]*)\|)?(?<name>[-\w\P{ASCII}]+)/gu},t=new Set(["combinator","comma"]),n=new Set(["not","is","where","has","matches","-moz-any","-webkit-any","nth-child","nth-last-child"]),s=/(?<index>[\dn+-]+)\s+of\s+(?<subtree>.+)/,r={"nth-child":s,"nth-last-child":s},o=t=>{switch(t){case"pseudo-element":case"pseudo-class":return new RegExp(e[t].source.replace("(?<argument>¶*)","(?<argument>.*)"),"gu");default:return e[t]}};function c(e,t){let n=0,s="";for(;t<e.length;t++){const r=e[t];switch(r){case"(":++n;break;case")":--n}if(s+=r,0===n)return s}return s}function i(n,s=e){if(!n)return[];const r=[n];for(const[e,t]of Object.entries(s))for(let n=0;n<r.length;n++){const s=r[n];if("string"!=typeof s)continue;t.lastIndex=0;const o=t.exec(s);if(!o)continue;const c=o.index-1,i=[],a=o[0],l=s.slice(0,c+1);l&&i.push(l),i.push({...o.groups,type:e,content:a});const u=s.slice(c+a.length+1);u&&i.push(u),r.splice(n,1,...i)}let o=0;for(const e of r)switch(typeof e){case"string":throw new Error(`Unexpected sequence ${e} found at index ${o}`);case"object":o+=e.content.length,e.pos=[o-e.content.length,o],t.has(e.type)&&(e.content=e.content.trim()||" ")}return r}const a=/(['"])([^\\\n]*?)\1/g,l=/\\./g;function u(t,n=e){if(""===(t=t.trim()))return[];const s=[];t=(t=t.replace(l,((e,t)=>(s.push({value:e,offset:t}),"".repeat(e.length))))).replace(a,((e,t,n,r)=>(s.push({value:e,offset:r}),`${t}${"".repeat(n.length)}${t}`)));{let e,n=0;for(;(e=t.indexOf("(",n))>-1;){const r=c(t,e);s.push({value:r,offset:e}),t=`${t.substring(0,e)}(${"¶".repeat(r.length-2)})${t.substring(e+r.length)}`,n=e+r.length}}const r=i(t,n),u=new Set;for(const e of s.reverse())for(const t of r){const{offset:n,value:s}=e;if(!(t.pos[0]<=n&&n+s.length<=t.pos[1]))continue;const{content:r}=t,o=n-t.pos[0];t.content=r.slice(0,o)+s+r.slice(o+s.length),t.content!==r&&u.add(t)}for(const e of u){const t=o(e.type);if(!t)throw new Error(`Unknown token type: ${e.type}`);t.lastIndex=0;const n=t.exec(e.content);if(!n)throw new Error(`Unable to parse content for ${e.type}: ${e.content}`);Object.assign(e,n.groups)}return r}function f(e,{list:t=!0}={}){if(t&&e.find((e=>"comma"===e.type))){const t=[],n=[];for(let s=0;s<e.length;s++)if("comma"===e[s].type){if(0===n.length)throw new Error("Incorrect comma at "+s);t.push(f(n,{list:!1})),n.length=0}else n.push(e[s]);if(0===n.length)throw new Error("Trailing comma");return t.push(f(n,{list:!1})),{type:"list",list:t}}for(let t=e.length-1;t>=0;t--){let n=e[t];if("combinator"===n.type){let s=e.slice(0,t),r=e.slice(t+1);return 0===s.length?{type:"relative",combinator:n.content,right:f(r)}:{type:"complex",combinator:n.content,left:f(s),right:f(r)}}}switch(e.length){case 0:throw new Error("Could not build AST.");case 1:return e[0];default:return{type:"compound",list:[...e]}}}function*p(e,t){switch(e.type){case"list":for(let t of e.list)yield*p(t,e);break;case"complex":yield*p(e.left,e),yield*p(e.right,e);break;case"relative":yield*p(e.right,e);break;case"compound":yield*e.list.map((t=>[t,e]));break;default:yield[e,t]}}function h(e,t,n){if(e)for(const[s,r]of p(e,n))t(s,r)}function m(e,{recursive:t=!0,list:s=!0}={}){const o=u(e);if(!o)return;const c=f(o,{list:s});if(!t)return c;for(const[e]of p(c)){if("pseudo-class"!==e.type||!e.argument)continue;if(!n.has(e.name))continue;let t=e.argument;const s=r[e.name];if(s){const n=s.exec(t);if(!n)continue;Object.assign(e,n.groups),t=n.groups.subtree}t&&Object.assign(e,{subtree:m(t,{recursive:!0,list:!0})})}return c}function g(e){if(Array.isArray(e))return e.map((e=>e.content)).join("");switch(e.type){case"list":return e.list.map(g).join(",");case"relative":return e.combinator+g(e.right);case"complex":return g(e.left)+e.combinator+g(e.right);case"compound":return e.list.map(g).join("");default:return e.content}}function d(e,t){return t=t||Math.max(...e)+1,e[0]*(t<<1)+e[1]*t+e[2]}function w(e){let t=e;if("string"==typeof t&&(t=m(t,{recursive:!0})),!t)return[];if("list"===t.type&&"list"in t){let e=10;const n=t.list.map((t=>{const n=w(t);return e=Math.max(e,...w(t)),n})),s=n.map((t=>d(t,e)));return n[s.indexOf(Math.max(...s))]}const s=[0,0,0];for(const[e]of p(t))switch(e.type){case"id":s[0]++;break;case"class":case"attribute":s[1]++;break;case"pseudo-element":case"type":s[2]++;break;case"pseudo-class":if("where"===e.name)break;if(!n.has(e.name)||!e.subtree){s[1]++;break}w(e.subtree).forEach(((e,t)=>s[t]+=e)),"nth-child"!==e.name&&"nth-last-child"!==e.name||s[1]++}return s}export{n as RECURSIVE_PSEUDO_CLASSES,r as RECURSIVE_PSEUDO_CLASSES_ARGS,e as TOKENS,t as TRIM_TOKENS,p as flatten,c as gobbleParens,m as parse,w as specificity,d as specificityToNumber,g as stringify,u as tokenize,i as tokenizeBy,h as walk};