PageRenderTime 26ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/Resources/Public/Contrib/aloha/lib/util/trees.js

https://bitbucket.org/pixelant/aloha
JavaScript | 336 lines | 156 code | 23 blank | 157 comment | 18 complexity | bc2efa15a6c1c45a6a317b03880d6a79 MD5 | raw file
  1. /* trees.js is part of Aloha Editor project http://aloha-editor.org
  2. *
  3. * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
  4. * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
  5. * Contributors http://aloha-editor.org/contribution.php
  6. *
  7. * Aloha Editor is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or any later version.
  11. *
  12. * Aloha Editor is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. *
  21. * As an additional permission to the GNU GPL version 2, you may distribute
  22. * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
  23. * source code without the copy of the GNU GPL normally required,
  24. * provided you include this license notice and a URL through which
  25. * recipients can access the Corresponding Source.
  26. */
  27. /**
  28. * Tree walking functions.
  29. *
  30. * prewalk(form, fn, inplace)
  31. *
  32. * Descend into the given form, which is a tree of arrays andmaps
  33. * (javascript Object), and build a new tree with the result of
  34. * applying the given fn to each branch and leaf. Only arrays and
  35. * maps are descended into, everything else is considered a leaf.
  36. *
  37. * The given fn is applied as the tree is descended into - the
  38. * function application (pre)cedes descending into the tree.
  39. *
  40. * By default, an entirely new structure is returned. If the
  41. * optional inplace argument is true, the algorithm will not
  42. * allocate any new structures, but modify the given form in-place.
  43. * The benefit of this is more performance due to less allocation,
  44. * and reduced memory overhead, but see the "Note" below.
  45. *
  46. * postwalk(form, fn, inplace)
  47. *
  48. * the same as prewalk, except the given fn is applied as the tree
  49. * is ascended.
  50. *
  51. * preprune(form, pred, inplace)
  52. *
  53. * the same as prewalk, except pred is a predicate function and any
  54. * branch or leaf that is encountered and for which pred returns
  55. * true is removed from the tree.
  56. *
  57. * postprune(form, pred, inplace)
  58. *
  59. * the same as preprune, except the predicate function is applied as
  60. * the tree is ascended.
  61. *
  62. * Postpruning is potentially slower than prepruning since it always
  63. * descendes into the whole tree, even into pruned nodes, while
  64. * prepruning skips any pruned nodes.
  65. *
  66. * leaves(form, leaf, inplace)
  67. *
  68. * Like postwalk, except the leaf function is applied only to
  69. * leaves, and not to the arrays or maps that make up the tree
  70. * structure of form.
  71. *
  72. * Useful when one is only interested in tranforming leaves.
  73. *
  74. * flatten(form)
  75. *
  76. * Makes an array of all of the given form's leaves.
  77. *
  78. * clone(form)
  79. *
  80. * Constructs a deep clone of the given form.
  81. *
  82. * prepruneDom(form, pred, inplace)
  83. *
  84. * Like preprune() except:
  85. *
  86. * - the given form may be either an element or other DOM node, and
  87. * only elements are descended into, all other node types are
  88. * considered leaves.
  89. *
  90. * - the given form will be cloned before it is being traversed, unless
  91. * inplace is true.
  92. *
  93. * This is different from prewalk, where the subforms that are
  94. * passed to fn are not clones. Making a deep clone first
  95. * simplifies some things, basically because an array or map can
  96. * be the child of multiple arrays and maps at the same time,
  97. * while a node can only be the child of a single parent node at
  98. * any one time.
  99. *
  100. * postpruneDom(form, pred, inplace)
  101. *
  102. * Like prepruneDom(), except the given function is applied as the tree
  103. * is ascended.
  104. *
  105. * walk(form, recurse, inplace)
  106. *
  107. * If form is an array or map, calls recurse on each of its items.
  108. * If inplace is true, modifies the form and sets each item to the
  109. * result of the call to recurse. If inplace is false, creates a new
  110. * array/map containing the results of calling recurse. Returns
  111. * either form if inplace is true, or the newly created array/map.
  112. *
  113. * If form is not an array or map, it is simply returned.
  114. *
  115. * An example using walk() in a custom recursive traversal function:
  116. *
  117. * function doSomething(root) {
  118. * function step(form) {
  119. * form = Trees.walk(form, step);
  120. * // do something with form
  121. * return form ? [form] : [];
  122. * }
  123. * return step(root)[0] || null;
  124. * }
  125. *
  126. * walk(form, recurse)
  127. *
  128. * Short for walk(form, recurse, true)
  129. *
  130. * walkDomInplace(form, recurse)
  131. *
  132. * Similar to walk() but operates on DOM nodes.
  133. *
  134. * Elements are considered non-leaf, and everything else is
  135. * considerd a leaf.
  136. *
  137. * Note: All functions work on array+map trees, unless they are suffixed
  138. * with Dom, in which case they only work on DOM nodes.
  139. *
  140. * Note: When walking arrays and maps, if the fn and leaf functions
  141. * modify the parent or any ancestor of the passed form, the
  142. * resulting behaviour is undefined. Only modification of the
  143. * passed form and descendants of the passed form is valid.
  144. *
  145. * During DOM traversal, it is allowed to insert-into/remove-from
  146. * the children of the parent of the given form, as long the given
  147. * form itself is not removed.
  148. *
  149. * Note: the algorithms are recursive and the maximum nesting level of
  150. * the input set is therefore bound to the maximum stack depth.
  151. * IE7 and IE8 for example have a maximum stack depth of greater
  152. * than 1000, so the maximum input nesting level should not exceed
  153. * about 300 (3 stack frames are needed per nesting level).
  154. */
  155. define(['jquery'], function ($) {
  156. 'use strict';
  157. function walk(form, step, inplace) {
  158. var type = $.type(form),
  159. subResult,
  160. result,
  161. resultOff,
  162. len,
  163. i,
  164. key;
  165. if ('array' === type) {
  166. result = (inplace ? form : []);
  167. resultOff = 0;
  168. for (i = 0, len = form.length; i < len; i++) {
  169. subResult = step(form[i]);
  170. if (subResult.length) {
  171. result[resultOff++] = subResult[0];
  172. }
  173. }
  174. if (resultOff !== result.length) {
  175. // TODO is result.length = resultOff better?
  176. result = result.slice(0, resultOff);
  177. }
  178. } else if ('object' === type) {
  179. result = (inplace ? form : {});
  180. for (key in form) {
  181. if (form.hasOwnProperty(key)) {
  182. subResult = step(form[key]);
  183. if (subResult.length) {
  184. result[key] = subResult[0];
  185. } else {
  186. delete result[key];
  187. }
  188. }
  189. }
  190. } else {
  191. result = form;
  192. }
  193. return result;
  194. }
  195. function walkInplace(form, step) {
  196. return walk(form, step, true);
  197. }
  198. function walkDomInplace(form, step) {
  199. var subResult,
  200. child,
  201. nextChild;
  202. if (1 === form.nodeType) {
  203. child = form.firstChild;
  204. while (child) {
  205. subResult = step(child);
  206. // Advance to the next child _after stepping into child_
  207. // to pick up modifications of the DOM.
  208. nextChild = child.nextSibling;
  209. if (subResult.length) {
  210. if (subResult[0] !== child) {
  211. form.replaceChild(subResult[0], child);
  212. }
  213. } else {
  214. form.removeChild(child);
  215. }
  216. child = nextChild;
  217. }
  218. }
  219. return form;
  220. }
  221. function prewalkStep(step, fn, walk, form) {
  222. return [walk(fn(form), step)];
  223. }
  224. function postwalkStep(step, fn, walk, form) {
  225. return [fn(walk(form, step))];
  226. }
  227. function prepruneStep(step, fn, walk, form) {
  228. return fn(form) ? [] : [walk(form, step)];
  229. }
  230. function postpruneStep(step, fn, walk, form) {
  231. var subForm = walk(form, step);
  232. return fn(subForm) ? [] : [subForm];
  233. }
  234. function prepost(step, fnOrPred, walk, form) {
  235. function prepostStep(form) {
  236. return step(prepostStep, fnOrPred, walk, form);
  237. }
  238. return prepostStep(form)[0];
  239. }
  240. function prewalk(form, fn, inplace) {
  241. return prepost(prewalkStep, fn, inplace ? walkInplace : walk, form);
  242. }
  243. function postwalk(form, fn, inplace) {
  244. return prepost(postwalkStep, fn, inplace ? walkInplace : walk, form);
  245. }
  246. function preprune(form, pred, inplace) {
  247. return prepost(prepruneStep, pred, inplace ? walkInplace : walk, form);
  248. }
  249. function postprune(form, pred, inplace) {
  250. return prepost(postpruneStep, pred, inplace ? walkInplace : walk, form);
  251. }
  252. function prewalkDom(form, fn, inplace) {
  253. return prepost(prewalkStep, fn, walkDomInplace, inplace ? form : form.cloneNode(true));
  254. }
  255. function postwalkDom(form, fn, inplace) {
  256. return prepost(postwalkStep, fn, walkDomInplace, inplace ? form : form.cloneNode(true));
  257. }
  258. function prepruneDom(form, pred, inplace) {
  259. return prepost(prepruneStep, pred, walkDomInplace, inplace ? form : form.cloneNode(true));
  260. }
  261. function postpruneDom(form, pred, inplace) {
  262. return prepost(postpruneStep, pred, walkDomInplace, inplace ? form : form.cloneNode(true));
  263. }
  264. function isLeaf(form) {
  265. var type = $.type(form);
  266. return type !== 'object' && type !== 'array';
  267. }
  268. function identityStep(step, walk, form) {
  269. return [walk(form, step)];
  270. }
  271. function leaves(form, leaf, inplace) {
  272. var leafWalk = inplace ? walkInplace : walk;
  273. function leafStep(form) {
  274. if (isLeaf(form)) {
  275. return [leaf(form)];
  276. }
  277. return identityStep(leafStep, leafWalk, form);
  278. }
  279. return leafStep(form)[0];
  280. }
  281. function clone(form) {
  282. function cloneStep(form) {
  283. return identityStep(cloneStep, walk, form);
  284. }
  285. return cloneStep(form)[0];
  286. }
  287. function flatten(form) {
  288. var inplace = true;
  289. var result = [];
  290. leaves(form, function (leaf) {
  291. result.push(leaf);
  292. return leaf;
  293. }, inplace);
  294. return result;
  295. }
  296. return {
  297. prewalk: prewalk,
  298. postwalk: postwalk,
  299. preprune: preprune,
  300. postprune: postprune,
  301. prewalkDom: prewalkDom,
  302. postwalkDom: postwalkDom,
  303. prepruneDom: prepruneDom,
  304. postpruneDom: postpruneDom,
  305. isLeaf: isLeaf,
  306. leaves: leaves,
  307. clone: clone,
  308. flatten: flatten,
  309. walk: walk,
  310. walkInplace: walkInplace,
  311. walkDomInplace: walkDomInplace
  312. };
  313. });