AttributeSelector.cjs 3.6 KB

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