AttributeSelector.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import {
  2. Ident,
  3. String as StringToken,
  4. Delim,
  5. LeftSquareBracket,
  6. RightSquareBracket
  7. } from '../../tokenizer/index.js';
  8. const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($)
  9. const ASTERISK = 0x002A; // U+002A ASTERISK (*)
  10. const EQUALSSIGN = 0x003D; // U+003D EQUALS SIGN (=)
  11. const CIRCUMFLEXACCENT = 0x005E; // U+005E (^)
  12. const VERTICALLINE = 0x007C; // U+007C VERTICAL LINE (|)
  13. const TILDE = 0x007E; // U+007E TILDE (~)
  14. function getAttributeName() {
  15. if (this.eof) {
  16. this.error('Unexpected end of input');
  17. }
  18. const start = this.tokenStart;
  19. let expectIdent = false;
  20. if (this.isDelim(ASTERISK)) {
  21. expectIdent = true;
  22. this.next();
  23. } else if (!this.isDelim(VERTICALLINE)) {
  24. this.eat(Ident);
  25. }
  26. if (this.isDelim(VERTICALLINE)) {
  27. if (this.charCodeAt(this.tokenStart + 1) !== EQUALSSIGN) {
  28. this.next();
  29. this.eat(Ident);
  30. } else if (expectIdent) {
  31. this.error('Identifier is expected', this.tokenEnd);
  32. }
  33. } else if (expectIdent) {
  34. this.error('Vertical line is expected');
  35. }
  36. return {
  37. type: 'Identifier',
  38. loc: this.getLocation(start, this.tokenStart),
  39. name: this.substrToCursor(start)
  40. };
  41. }
  42. function getOperator() {
  43. const start = this.tokenStart;
  44. const code = this.charCodeAt(start);
  45. if (code !== EQUALSSIGN && // =
  46. code !== TILDE && // ~=
  47. code !== CIRCUMFLEXACCENT && // ^=
  48. code !== DOLLARSIGN && // $=
  49. code !== ASTERISK && // *=
  50. code !== VERTICALLINE // |=
  51. ) {
  52. this.error('Attribute selector (=, ~=, ^=, $=, *=, |=) is expected');
  53. }
  54. this.next();
  55. if (code !== EQUALSSIGN) {
  56. if (!this.isDelim(EQUALSSIGN)) {
  57. this.error('Equal sign is expected');
  58. }
  59. this.next();
  60. }
  61. return this.substrToCursor(start);
  62. }
  63. // '[' <wq-name> ']'
  64. // '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']'
  65. export const name = 'AttributeSelector';
  66. export const structure = {
  67. name: 'Identifier',
  68. matcher: [String, null],
  69. value: ['String', 'Identifier', null],
  70. flags: [String, null]
  71. };
  72. export function parse() {
  73. const start = this.tokenStart;
  74. let name;
  75. let matcher = null;
  76. let value = null;
  77. let flags = null;
  78. this.eat(LeftSquareBracket);
  79. this.skipSC();
  80. name = getAttributeName.call(this);
  81. this.skipSC();
  82. if (this.tokenType !== RightSquareBracket) {
  83. // avoid case `[name i]`
  84. if (this.tokenType !== Ident) {
  85. matcher = getOperator.call(this);
  86. this.skipSC();
  87. value = this.tokenType === StringToken
  88. ? this.String()
  89. : this.Identifier();
  90. this.skipSC();
  91. }
  92. // attribute flags
  93. if (this.tokenType === Ident) {
  94. flags = this.consume(Ident);
  95. this.skipSC();
  96. }
  97. }
  98. this.eat(RightSquareBracket);
  99. return {
  100. type: 'AttributeSelector',
  101. loc: this.getLocation(start, this.tokenStart),
  102. name,
  103. matcher,
  104. value,
  105. flags
  106. };
  107. }
  108. export function generate(node) {
  109. this.token(Delim, '[');
  110. this.node(node.name);
  111. if (node.matcher !== null) {
  112. this.tokenize(node.matcher);
  113. this.node(node.value);
  114. }
  115. if (node.flags !== null) {
  116. this.token(Ident, node.flags);
  117. }
  118. this.token(Delim, ']');
  119. }