normalize-selector.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /* normalize-selector v0.1.0 (c) 2014 Kyle Simpson */
  2. (function UMD(name,context,definition){
  3. if (typeof module !== "undefined" && module.exports) { module.exports = definition(); }
  4. else if (typeof define === "function" && define.amd) { define(definition); }
  5. else { context[name] = definition(name,context); }
  6. })("normalizeSelector",this,function DEF(name,context){
  7. "use strict";
  8. function normalizeSelector(sel) {
  9. // save unmatched text, if any
  10. function saveUnmatched() {
  11. if (unmatched) {
  12. // whitespace needed after combinator?
  13. if (tokens.length > 0 &&
  14. /^[~+>]$/.test(tokens[tokens.length-1])
  15. ) {
  16. tokens.push(" ");
  17. }
  18. // save unmatched text
  19. tokens.push(unmatched);
  20. }
  21. }
  22. var tokens = [], match, unmatched, regex, state = [0],
  23. next_match_idx = 0, prev_match_idx,
  24. not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/,
  25. whitespace_pattern = /^\s+$/,
  26. attribute_nonspecial_pattern = /[^\s=~!^|$*\[\]\(\)]{2}/,
  27. state_patterns = [
  28. /\s+|\/\*|["'>~+\[\(]/g, // general
  29. /\s+|\/\*|["'\[\]\(\)]/g, // [..] set
  30. /\s+|\/\*|["'\[\]\(\)]/g, // (..) set
  31. null, // string literal (placeholder)
  32. /\*\//g // comment
  33. ]
  34. ;
  35. sel = sel.trim();
  36. while (true) {
  37. unmatched = "";
  38. regex = state_patterns[state[state.length-1]];
  39. regex.lastIndex = next_match_idx;
  40. match = regex.exec(sel);
  41. // matched text to process?
  42. if (match) {
  43. prev_match_idx = next_match_idx;
  44. next_match_idx = regex.lastIndex;
  45. // collect the previous string chunk not matched before this token
  46. if (prev_match_idx < next_match_idx - match[0].length) {
  47. unmatched = sel.substring(prev_match_idx,next_match_idx - match[0].length);
  48. }
  49. // need to force a space (possibly skipped
  50. // previously by the parser)?
  51. if (
  52. state[state.length-1] === 1 &&
  53. attribute_nonspecial_pattern.test(
  54. tokens[tokens.length-1].substr(-1) +
  55. unmatched.charAt(0)
  56. )
  57. ) {
  58. tokens.push(" ");
  59. }
  60. // general, [ ] pair, ( ) pair?
  61. if (state[state.length-1] < 3) {
  62. saveUnmatched();
  63. // starting a [ ] pair?
  64. if (match[0] === "[") {
  65. state.push(1);
  66. }
  67. // starting a ( ) pair?
  68. else if (match[0] === "(") {
  69. state.push(2);
  70. }
  71. // starting a string literal?
  72. else if (/^["']$/.test(match[0])) {
  73. state.push(3);
  74. state_patterns[3] = new RegExp(match[0],"g");
  75. }
  76. // starting a comment?
  77. else if (match[0] === "/*") {
  78. state.push(4);
  79. }
  80. // ending a [ ] or ( ) pair?
  81. else if (/^[\]\)]$/.test(match[0]) && state.length > 0) {
  82. state.pop();
  83. }
  84. // handling whitespace or a combinator?
  85. else if (/^(?:\s+|[~+>])$/.test(match[0])) {
  86. // need to insert whitespace before?
  87. if (tokens.length > 0 &&
  88. !whitespace_pattern.test(tokens[tokens.length-1]) &&
  89. state[state.length-1] === 0
  90. ) {
  91. // add normalized whitespace
  92. tokens.push(" ");
  93. }
  94. // whitespace token we can skip?
  95. if (whitespace_pattern.test(match[0])) {
  96. continue;
  97. }
  98. }
  99. // save matched text
  100. tokens.push(match[0]);
  101. }
  102. // otherwise, string literal or comment
  103. else {
  104. // save unmatched text
  105. tokens[tokens.length-1] += unmatched;
  106. // unescaped terminator to string literal or comment?
  107. if (not_escaped_pattern.test(tokens[tokens.length-1])) {
  108. // comment terminator?
  109. if (state[state.length-1] === 4) {
  110. // ok to drop comment?
  111. if (tokens.length < 2 ||
  112. whitespace_pattern.test(tokens[tokens.length-2])
  113. ) {
  114. tokens.pop();
  115. }
  116. // otherwise, turn comment into whitespace
  117. else {
  118. tokens[tokens.length-1] = " ";
  119. }
  120. // handled already
  121. match[0] = "";
  122. }
  123. state.pop();
  124. }
  125. // append matched text to existing token
  126. tokens[tokens.length-1] += match[0];
  127. }
  128. }
  129. // otherwise, end of processing (no more matches)
  130. else {
  131. unmatched = sel.substr(next_match_idx);
  132. saveUnmatched();
  133. break;
  134. }
  135. }
  136. return tokens.join("").trim();
  137. }
  138. return normalizeSelector;
  139. });