Files
NativeScript/tns-core-modules/ui/styling/css-selector-parser.ts
Panayot Cankov c1aeeb51a7 Inital by-type split
Split type.class from CssTypeSelector to CssCompositeSelector, probably support type#id.class selectors

Apply review comments, refactor css-selectors internally

Applied refactoring, all tests pass, button does not notify changes

Add tests for the css selectors parser.

Added tests for css-selectors

Added basic implementation of mayMatch and changeMap for css match state

Implemented TKUnit.assertDeepEqual to check key and key/values in Map and Set

Watch for property and pseudoClass changes

Add one child group test

Add typings for animations

Added mechanism to enable/disable listeners for pseudo classes

Count listeners instead of checking handlers, reverse subscription and unsubscription
2016-07-18 17:24:09 +03:00

126 lines
4.5 KiB
TypeScript

/// <reference path="./css-selector-parser.d.ts" />
export interface SimpleSelector {
pos: number;
type: "" | "*" | "#" | "." | ":" | "[]";
comb?: "+" | "~" | ">" | " ";
}
export interface SimpleIdentifierSelector extends SimpleSelector {
ident: string;
}
export interface UniversalSelector extends SimpleSelector {
type: "*";
}
export interface TypeSelector extends SimpleIdentifierSelector {
type: "";
}
export interface ClassSelector extends SimpleIdentifierSelector {
type: ".";
}
export interface IdSelector extends SimpleIdentifierSelector {
type: "#";
}
export interface PseudoClassSelector extends SimpleIdentifierSelector {
type: ":";
}
export interface AttributeSelector extends SimpleSelector {
type: "[]";
prop: string;
test?: "=" | "^=" | "$=" | "*=" | "=" | "~=" | "|=";
value?: string;
}
export function isUniversal(sel: SimpleSelector): sel is UniversalSelector {
return sel.type === "*";
}
export function isType(sel: SimpleSelector): sel is TypeSelector {
return sel.type === "";
}
export function isClass(sel: SimpleSelector): sel is ClassSelector {
return sel.type === ".";
}
export function isId(sel: SimpleSelector): sel is IdSelector {
return sel.type === "#";
}
export function isPseudo(sel: SimpleSelector): sel is PseudoClassSelector {
return sel.type === ":";
}
export function isAttribute(sel: SimpleSelector): sel is AttributeSelector {
return sel.type === "[]";
}
var regex = /(\s*)(?:(\*)|(#|\.|:|\b)([_-\w][_-\w\d]*)|\[\s*([_-\w][_-\w\d]*)\s*(?:(=|\^=|\$=|\*=|\~=|\|=)\s*(?:([_-\w][_-\w\d]*)|"((?:[^\\"]|\\(?:"|n|r|f|\\|0-9a-f))*)"|'((?:[^\\']|\\(?:'|n|r|f|\\|0-9a-f))*)')\s*)?\])(?:\s*(\+|~|>|\s))?/g;
// no lead ws univ type pref and ident [ prop = ident -or- "string escapes \" \00aaff" -or- 'string escapes \' urf-8: \00aaff' ] combinator
export function parse(selector: string): SimpleSelector[] {
let selectors: any[] = [];
var result: RegExpExecArray;
var lastIndex = regex.lastIndex = 0;
while(result = regex.exec(selector)) {
let pos = result.index;
if (lastIndex !== pos) {
throw new Error(`Unexpected characters at index, near: ${lastIndex}: ${result.input.substr(lastIndex, 32)}`);
} else if (!result[0] || result[0].length === 0) {
throw new Error(`Last selector match got zero character result at index ${lastIndex}, near: ${result.input.substr(lastIndex, 32)}`);
}
pos += getLeadingWhiteSpace(result).length;
lastIndex = regex.lastIndex;
var type = getType(result);
let selector: SimpleSelector | SimpleIdentifierSelector | AttributeSelector;
switch(type) {
case "*":
selector = { pos, type };
break;
case "#":
case ".":
case ":":
case "":
let ident = getIdentifier(result);
selector = { pos, type, ident };
break;
case "[]":
let prop = getProperty(result);
let test = getPropertyTest(result);
// TODO: Unescape escape sequences. Unescape UTF-8 characters.
let value = getPropertyValue(result);
selector = test ? { pos, type, prop, test, value } : { pos, type, prop };
break;
default:
throw new Error("Unhandled type.");
}
let comb = getCombinator(result);
if (comb) {
selector.comb = comb;
}
selectors.push(selector);
}
if (selectors.length > 0) {
delete selectors[selectors.length - 1].comb;
}
return selectors;
}
function getLeadingWhiteSpace(result: RegExpExecArray): string {
return result[1] || "";
}
function getType(result: RegExpExecArray): "" | "*" | "." | "#" | ":" | "[]" {
return <"[]">(result[5] && "[]") || <"*">result[2] || <"" | "." | "#" | ":">result[3];
}
function getIdentifier(result: RegExpExecArray): string {
return result[4];
}
function getProperty(result: RegExpExecArray): string {
return result[5];
}
function getPropertyTest(result: RegExpExecArray): string {
return result[6] || undefined;
}
function getPropertyValue(result: RegExpExecArray): string {
return result[7] || result[8] || result[9];
}
function getCombinator(result: RegExpExecArray): "+" | "~" | ">" | " " {
return <("+" | "~" | ">" | " ")>result[result.length - 1] || undefined;
}