xpath.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. var isStr = require('./isStr');
  2. var Class = require('./Class');
  3. exports = function(xpath, optimized) {
  4. if (isStr(xpath)) {
  5. return findEl(xpath);
  6. } else {
  7. return getXpath(xpath, optimized);
  8. }
  9. };
  10. function findEl(xpath) {
  11. var ret = [];
  12. var nodesSnapshot = document.evaluate(
  13. xpath,
  14. document,
  15. null,
  16. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  17. null
  18. );
  19. for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
  20. ret.push(nodesSnapshot.snapshotItem(i));
  21. }
  22. return ret;
  23. }
  24. function getXpath(node) {
  25. var optimized =
  26. arguments.length > 1 && arguments[1] !== undefined
  27. ? arguments[1]
  28. : false;
  29. if (node.nodeType === Node.DOCUMENT_NODE) {
  30. return '/';
  31. }
  32. var steps = [];
  33. var contextNode = node;
  34. while (contextNode) {
  35. var step = xPathValue(contextNode, optimized);
  36. if (!step) {
  37. break;
  38. }
  39. steps.push(step);
  40. if (step.optimized) {
  41. break;
  42. }
  43. contextNode = contextNode.parentNode;
  44. }
  45. steps.reverse();
  46. return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/');
  47. }
  48. function xPathValue(node, optimized) {
  49. var ownValue;
  50. var ownIndex = xPathIndex(node);
  51. if (ownIndex === -1) {
  52. return null;
  53. }
  54. switch (node.nodeType) {
  55. case Node.ELEMENT_NODE:
  56. if (optimized && node.getAttribute('id')) {
  57. return new Step(
  58. '//*[@id="' + node.getAttribute('id') + '"]',
  59. true
  60. );
  61. }
  62. ownValue = node.localName;
  63. break;
  64. case Node.ATTRIBUTE_NODE:
  65. ownValue = '@' + node.nodeName();
  66. break;
  67. case Node.TEXT_NODE:
  68. case Node.CDATA_SECTION_NODE:
  69. ownValue = 'text()';
  70. break;
  71. case Node.PROCESSING_INSTRUCTION_NODE:
  72. ownValue = 'processing-instruction()';
  73. break;
  74. case Node.COMMENT_NODE:
  75. ownValue = 'comment()';
  76. break;
  77. case Node.DOCUMENT_NODE:
  78. ownValue = '';
  79. break;
  80. default:
  81. ownValue = '';
  82. break;
  83. }
  84. if (ownIndex > 0) {
  85. ownValue += '[' + ownIndex + ']';
  86. }
  87. return new Step(ownValue, node.nodeType === Node.DOCUMENT_NODE);
  88. }
  89. function xPathIndex(node) {
  90. function areNodesSimilar(left, right) {
  91. if (left === right) {
  92. return true;
  93. }
  94. if (
  95. left.nodeType === Node.ELEMENT_NODE &&
  96. right.nodeType === Node.ELEMENT_NODE
  97. ) {
  98. return left.localName === right.localName;
  99. }
  100. if (left.nodeType === right.nodeType) {
  101. return true;
  102. }
  103. var leftType =
  104. left.nodeType === Node.CDATA_SECTION_NODE
  105. ? Node.TEXT_NODE
  106. : left.nodeType;
  107. var rightType =
  108. right.nodeType === Node.CDATA_SECTION_NODE
  109. ? Node.TEXT_NODE
  110. : right.nodeType;
  111. return leftType === rightType;
  112. }
  113. var siblings = node.parentNode ? node.parentNode.children : null;
  114. if (!siblings) {
  115. return 0;
  116. }
  117. var hasSameNamedElements;
  118. for (var i = 0; i < siblings.length; ++i) {
  119. if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
  120. hasSameNamedElements = true;
  121. break;
  122. }
  123. }
  124. if (!hasSameNamedElements) {
  125. return 0;
  126. }
  127. var ownIndex = 1;
  128. for (var _i = 0; _i < siblings.length; ++_i) {
  129. if (areNodesSimilar(node, siblings[_i])) {
  130. if (siblings[_i] === node) {
  131. return ownIndex;
  132. }
  133. ++ownIndex;
  134. }
  135. }
  136. return -1;
  137. }
  138. var Step = Class({
  139. initialize: function(value, optimized) {
  140. this.value = value;
  141. this.optimized = optimized || false;
  142. },
  143. toString: function() {
  144. return this.value;
  145. }
  146. });
  147. module.exports = exports;