ElementPath.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. "use strict";
  2. var FieldRef = require('./FieldRef'),
  3. ErrorCodes = require('../../Errors.js').ErrorCodes;
  4. var ElementPath = module.exports = function ElementPath(){
  5. this._fieldRef = new FieldRef();
  6. this._shouldTraverseLeafArray = false;
  7. }, klass = ElementPath, base = Object , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  8. proto._fieldRef = undefined;
  9. proto._shouldTraverseLeafArray = undefined;
  10. /**
  11. * getFieldDottedOrArray
  12. *
  13. * @method getFieldDottedArray
  14. * @param doc
  15. * @param path
  16. * @param idxPathObj This is an object with a pathID element. This allows for pass by ref in calling function.
  17. * */
  18. klass.getFieldDottedOrArray = function getFieldDottedOrArray(doc, path, idxPathObj){
  19. if (path.numParts() === 0 ) { return doc; }
  20. var res,curr = doc,
  21. stop = false,
  22. partNum = 0;
  23. while (partNum < path.numParts() && !stop) {
  24. res = curr[path.getPart( partNum)];
  25. if(res instanceof Object && Object.keys(res).length === 0){
  26. stop = true;
  27. } else if (res instanceof Object) {
  28. curr = res;
  29. partNum++;
  30. } else if (res instanceof Array) {
  31. stop = true;
  32. } else {
  33. if (partNum + 1 < path.numParts() ) {
  34. res = {};
  35. }
  36. stop = true;
  37. }
  38. }
  39. idxPathObj.pathID = partNum;
  40. return res;
  41. };
  42. /**
  43. * isAllDigits does what it says on the tin.
  44. *
  45. * @method isAllDigits
  46. * @param str
  47. */
  48. klass.isAllDigits = function isAllDigits ( str ){
  49. var digitCheck = /\D/g;
  50. if (digitCheck.exec(str) === null){ return true; }
  51. return false;
  52. };
  53. /**
  54. *
  55. * return the internal fieldRef object
  56. * @method fieldRef
  57. * @param
  58. *
  59. */
  60. proto.fieldRef = function fieldRef(){
  61. return this._fieldRef;
  62. };
  63. /**
  64. *
  65. * Initialize necessary items on this instance
  66. * @method init
  67. * @param path
  68. *
  69. */
  70. proto.init = function init( path ){
  71. this._shouldTraverseLeafArray = true;
  72. this._fieldRef.parse( path );
  73. return {'code':ErrorCodes.OK};
  74. };
  75. /**
  76. *
  77. * Set whether paths should traverse leaves inside arrays
  78. * @method setTraverseLeafArray
  79. * @param
  80. *
  81. */
  82. proto.setTraverseLeafArray = function setTraverseLeafArray( b ){
  83. this._shouldTraverseLeafArray = b;
  84. };
  85. /**
  86. *
  87. * Return whether arrays should traverse leaf arrays
  88. * @method shouldTraverseLeafArray
  89. * @param
  90. *
  91. */
  92. proto.shouldTraverseLeafArray = function shouldTraverseLeafArray( ){
  93. return this._shouldTraverseLeafArray;
  94. };
  95. proto.objAtPath = function objAtPath(doc) {
  96. return klass.objAtPath(doc, this._fieldRef._path);
  97. };
  98. klass.objAtPath = function objAtPath(doc, path) {
  99. if (path.length === 0) {
  100. return doc;
  101. }
  102. if (path.length > 0 && Object.keys(doc).length === 0) {
  103. return {};
  104. }
  105. if (doc === null || doc === undefined) {
  106. return doc;
  107. }
  108. var tpath = path.split('.');
  109. return klass.objAtPath(doc[tpath[0]], tpath.slice(1).join('.'));
  110. };
  111. /**
  112. *
  113. * Helper to wrap our path into the static method
  114. * @method _matches
  115. * @param doc
  116. * @param details
  117. * @param function checker this function is used to check for a valid item at the end of the path
  118. *
  119. */
  120. proto._matches = function _matches(doc, details, checker) {
  121. return klass._matches(doc, this._fieldRef._array, this._shouldTraverseLeafArray, details, checker);
  122. };
  123. /**
  124. *
  125. * _matches exists because we don't have pathIterators, so we need a recursive function call
  126. * through the path pieces
  127. * @method _matches
  128. * @param doc
  129. * @param path
  130. * @param details
  131. * @param function checker this function is used to check for a valid item at the end of the path
  132. *
  133. */
  134. klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details, checker){
  135. var k, result, ii, il,
  136. curr = doc,
  137. item = doc;
  138. for (k = 0; k < path.length; k++) {
  139. if ((curr instanceof Object) && (path[k] in curr)) {
  140. item = curr[path[k]];
  141. }
  142. if (path[k].length === 0)
  143. continue;
  144. item = curr[path[k]];
  145. if (item instanceof Object && item.constructor === Object) {
  146. if (!(isNaN(parseInt(path[k], 10)))) {
  147. result = checker(item[path[k]]);
  148. if (result) {
  149. if (details && details.needRecord())
  150. details.setElemMatchKey(ii.toString());
  151. return result;
  152. }
  153. }
  154. curr = item;
  155. continue;
  156. } else if (item instanceof Object && item.constructor === Array) {
  157. if (k == path.length - 1) {
  158. if ((shouldTraverseLeafArray) && (isNaN(parseInt(path[k], 10)))) {
  159. for (ii = 0, il = item.length; ii < il; ii++) {
  160. result = checker(item[ii]);
  161. if (result) {
  162. if (details && details.needRecord())
  163. details.setElemMatchKey(ii.toString());
  164. return result;
  165. }
  166. }
  167. if (item.length === 0)
  168. return checker({});
  169. }
  170. curr = item;
  171. break; // this is the end of the path, so check this array
  172. } else if (!(isNaN(parseInt(path[k + 1], 10)))) {
  173. curr = item;
  174. continue; // the *next* path section is an item in the array so we don't check this whole array
  175. }
  176. // otherwise, check each item in the array against the rest of the path
  177. for (ii = 0, il = item.length; ii < il; ii++) {
  178. var subitem = item[ii];
  179. if (!subitem || subitem.constructor !== Object) continue; // can't look for a subfield in a non-object value.
  180. if (this._matches(subitem, path.slice(k), shouldTraverseLeafArray, null, checker)) { // check the item against the rest of the path
  181. if (details && details.needRecord())
  182. details.setElemMatchKey(ii.toString());
  183. return true;
  184. }
  185. }
  186. return false; // checked all items in the array and found no matches
  187. } else {
  188. if ( details === undefined && item !== null && curr[path[k+1]] !== undefined) {
  189. curr = curr[path[k+1]];
  190. }
  191. }
  192. }
  193. return checker(item);
  194. };