AttributeSelector.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. var TYPE = require('../../tokenizer').TYPE;
  2. var IDENTIFIER = TYPE.Identifier;
  3. var STRING = TYPE.String;
  4. var DOLLARSIGN = TYPE.DollarSign;
  5. var ASTERISK = TYPE.Asterisk;
  6. var COLON = TYPE.Colon;
  7. var EQUALSSIGN = TYPE.EqualsSign;
  8. var LEFTSQUAREBRACKET = TYPE.LeftSquareBracket;
  9. var RIGHTSQUAREBRACKET = TYPE.RightSquareBracket;
  10. var CIRCUMFLEXACCENT = TYPE.CircumflexAccent;
  11. var VERTICALLINE = TYPE.VerticalLine;
  12. var TILDE = TYPE.Tilde;
  13. function getAttributeName() {
  14. if (this.scanner.eof) {
  15. this.scanner.error('Unexpected end of input');
  16. }
  17. var start = this.scanner.tokenStart;
  18. var expectIdentifier = false;
  19. var checkColon = true;
  20. if (this.scanner.tokenType === ASTERISK) {
  21. expectIdentifier = true;
  22. checkColon = false;
  23. this.scanner.next();
  24. } else if (this.scanner.tokenType !== VERTICALLINE) {
  25. this.scanner.eat(IDENTIFIER);
  26. }
  27. if (this.scanner.tokenType === VERTICALLINE) {
  28. if (this.scanner.lookupType(1) !== EQUALSSIGN) {
  29. this.scanner.next();
  30. this.scanner.eat(IDENTIFIER);
  31. } else if (expectIdentifier) {
  32. this.scanner.error('Identifier is expected', this.scanner.tokenEnd);
  33. }
  34. } else if (expectIdentifier) {
  35. this.scanner.error('Vertical line is expected');
  36. }
  37. if (checkColon && this.scanner.tokenType === COLON) {
  38. this.scanner.next();
  39. this.scanner.eat(IDENTIFIER);
  40. }
  41. return {
  42. type: 'Identifier',
  43. loc: this.getLocation(start, this.scanner.tokenStart),
  44. name: this.scanner.substrToCursor(start)
  45. };
  46. }
  47. function getOperator() {
  48. var start = this.scanner.tokenStart;
  49. var tokenType = this.scanner.tokenType;
  50. if (tokenType !== EQUALSSIGN && // =
  51. tokenType !== TILDE && // ~=
  52. tokenType !== CIRCUMFLEXACCENT && // ^=
  53. tokenType !== DOLLARSIGN && // $=
  54. tokenType !== ASTERISK && // *=
  55. tokenType !== VERTICALLINE // |=
  56. ) {
  57. this.scanner.error('Attribute selector (=, ~=, ^=, $=, *=, |=) is expected');
  58. }
  59. if (tokenType === EQUALSSIGN) {
  60. this.scanner.next();
  61. } else {
  62. this.scanner.next();
  63. this.scanner.eat(EQUALSSIGN);
  64. }
  65. return this.scanner.substrToCursor(start);
  66. }
  67. // '[' S* attrib_name ']'
  68. // '[' S* attrib_name S* attrib_matcher S* [ IDENT | STRING ] S* attrib_flags? S* ']'
  69. module.exports = {
  70. name: 'AttributeSelector',
  71. structure: {
  72. name: 'Identifier',
  73. matcher: [String, null],
  74. value: ['String', 'Identifier', null],
  75. flags: [String, null]
  76. },
  77. parse: function() {
  78. var start = this.scanner.tokenStart;
  79. var name;
  80. var matcher = null;
  81. var value = null;
  82. var flags = null;
  83. this.scanner.eat(LEFTSQUAREBRACKET);
  84. this.scanner.skipSC();
  85. name = getAttributeName.call(this);
  86. this.scanner.skipSC();
  87. if (this.scanner.tokenType !== RIGHTSQUAREBRACKET) {
  88. // avoid case `[name i]`
  89. if (this.scanner.tokenType !== IDENTIFIER) {
  90. matcher = getOperator.call(this);
  91. this.scanner.skipSC();
  92. value = this.scanner.tokenType === STRING
  93. ? this.String()
  94. : this.Identifier();
  95. this.scanner.skipSC();
  96. }
  97. // attribute flags
  98. if (this.scanner.tokenType === IDENTIFIER) {
  99. flags = this.scanner.getTokenValue();
  100. this.scanner.next();
  101. this.scanner.skipSC();
  102. }
  103. }
  104. this.scanner.eat(RIGHTSQUAREBRACKET);
  105. return {
  106. type: 'AttributeSelector',
  107. loc: this.getLocation(start, this.scanner.tokenStart),
  108. name: name,
  109. matcher: matcher,
  110. value: value,
  111. flags: flags
  112. };
  113. },
  114. generate: function(node) {
  115. var flagsPrefix = ' ';
  116. this.chunk('[');
  117. this.node(node.name);
  118. if (node.matcher !== null) {
  119. this.chunk(node.matcher);
  120. if (node.value !== null) {
  121. this.node(node.value);
  122. // space between string and flags is not required
  123. if (node.value.type === 'String') {
  124. flagsPrefix = '';
  125. }
  126. }
  127. }
  128. if (node.flags !== null) {
  129. this.chunk(flagsPrefix);
  130. this.chunk(node.flags);
  131. }
  132. this.chunk(']');
  133. }
  134. };