| import { |
| Ident, |
| String as StringToken, |
| Delim, |
| LeftSquareBracket, |
| RightSquareBracket |
| } from '../../tokenizer/index.js'; |
| |
| const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($) |
| const ASTERISK = 0x002A; // U+002A ASTERISK (*) |
| const EQUALSSIGN = 0x003D; // U+003D EQUALS SIGN (=) |
| const CIRCUMFLEXACCENT = 0x005E; // U+005E (^) |
| const VERTICALLINE = 0x007C; // U+007C VERTICAL LINE (|) |
| const TILDE = 0x007E; // U+007E TILDE (~) |
| |
| function getAttributeName() { |
| if (this.eof) { |
| this.error('Unexpected end of input'); |
| } |
| |
| const start = this.tokenStart; |
| let expectIdent = false; |
| |
| if (this.isDelim(ASTERISK)) { |
| expectIdent = true; |
| this.next(); |
| } else if (!this.isDelim(VERTICALLINE)) { |
| this.eat(Ident); |
| } |
| |
| if (this.isDelim(VERTICALLINE)) { |
| if (this.charCodeAt(this.tokenStart + 1) !== EQUALSSIGN) { |
| this.next(); |
| this.eat(Ident); |
| } else if (expectIdent) { |
| this.error('Identifier is expected', this.tokenEnd); |
| } |
| } else if (expectIdent) { |
| this.error('Vertical line is expected'); |
| } |
| |
| return { |
| type: 'Identifier', |
| loc: this.getLocation(start, this.tokenStart), |
| name: this.substrToCursor(start) |
| }; |
| } |
| |
| function getOperator() { |
| const start = this.tokenStart; |
| const code = this.charCodeAt(start); |
| |
| if (code !== EQUALSSIGN && // = |
| code !== TILDE && // ~= |
| code !== CIRCUMFLEXACCENT && // ^= |
| code !== DOLLARSIGN && // $= |
| code !== ASTERISK && // *= |
| code !== VERTICALLINE // |= |
| ) { |
| this.error('Attribute selector (=, ~=, ^=, $=, *=, |=) is expected'); |
| } |
| |
| this.next(); |
| |
| if (code !== EQUALSSIGN) { |
| if (!this.isDelim(EQUALSSIGN)) { |
| this.error('Equal sign is expected'); |
| } |
| |
| this.next(); |
| } |
| |
| return this.substrToCursor(start); |
| } |
| |
| // '[' <wq-name> ']' |
| // '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']' |
| export const name = 'AttributeSelector'; |
| export const structure = { |
| name: 'Identifier', |
| matcher: [String, null], |
| value: ['String', 'Identifier', null], |
| flags: [String, null] |
| }; |
| |
| export function parse() { |
| const start = this.tokenStart; |
| let name; |
| let matcher = null; |
| let value = null; |
| let flags = null; |
| |
| this.eat(LeftSquareBracket); |
| this.skipSC(); |
| |
| name = getAttributeName.call(this); |
| this.skipSC(); |
| |
| if (this.tokenType !== RightSquareBracket) { |
| // avoid case `[name i]` |
| if (this.tokenType !== Ident) { |
| matcher = getOperator.call(this); |
| |
| this.skipSC(); |
| |
| value = this.tokenType === StringToken |
| ? this.String() |
| : this.Identifier(); |
| |
| this.skipSC(); |
| } |
| |
| // attribute flags |
| if (this.tokenType === Ident) { |
| flags = this.consume(Ident); |
| |
| this.skipSC(); |
| } |
| } |
| |
| this.eat(RightSquareBracket); |
| |
| return { |
| type: 'AttributeSelector', |
| loc: this.getLocation(start, this.tokenStart), |
| name, |
| matcher, |
| value, |
| flags |
| }; |
| } |
| |
| export function generate(node) { |
| this.token(Delim, '['); |
| this.node(node.name); |
| |
| if (node.matcher !== null) { |
| this.tokenize(node.matcher); |
| this.node(node.value); |
| } |
| |
| if (node.flags !== null) { |
| this.token(Ident, node.flags); |
| } |
| |
| this.token(Delim, ']'); |
| } |