Node.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /**
  2. * Node.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /**
  11. * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
  12. *
  13. * @example
  14. * var node = new tinymce.html.Node('strong', 1);
  15. * someRoot.append(node);
  16. *
  17. * @class tinymce.html.Node
  18. * @version 3.4
  19. */
  20. define("tinymce/html/Node", [], function() {
  21. var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
  22. '#text': 3,
  23. '#comment': 8,
  24. '#cdata': 4,
  25. '#pi': 7,
  26. '#doctype': 10,
  27. '#document-fragment': 11
  28. };
  29. // Walks the tree left/right
  30. function walk(node, root_node, prev) {
  31. var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
  32. // Walk into nodes if it has a start
  33. if (node[startName]) {
  34. return node[startName];
  35. }
  36. // Return the sibling if it has one
  37. if (node !== root_node) {
  38. sibling = node[siblingName];
  39. if (sibling) {
  40. return sibling;
  41. }
  42. // Walk up the parents to look for siblings
  43. for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
  44. sibling = parent[siblingName];
  45. if (sibling) {
  46. return sibling;
  47. }
  48. }
  49. }
  50. }
  51. /**
  52. * Constructs a new Node instance.
  53. *
  54. * @constructor
  55. * @method Node
  56. * @param {String} name Name of the node type.
  57. * @param {Number} type Numeric type representing the node.
  58. */
  59. function Node(name, type) {
  60. this.name = name;
  61. this.type = type;
  62. if (type === 1) {
  63. this.attributes = [];
  64. this.attributes.map = {};
  65. }
  66. }
  67. Node.prototype = {
  68. /**
  69. * Replaces the current node with the specified one.
  70. *
  71. * @example
  72. * someNode.replace(someNewNode);
  73. *
  74. * @method replace
  75. * @param {tinymce.html.Node} node Node to replace the current node with.
  76. * @return {tinymce.html.Node} The old node that got replaced.
  77. */
  78. replace: function(node) {
  79. var self = this;
  80. if (node.parent) {
  81. node.remove();
  82. }
  83. self.insert(node, self);
  84. self.remove();
  85. return self;
  86. },
  87. /**
  88. * Gets/sets or removes an attribute by name.
  89. *
  90. * @example
  91. * someNode.attr("name", "value"); // Sets an attribute
  92. * console.log(someNode.attr("name")); // Gets an attribute
  93. * someNode.attr("name", null); // Removes an attribute
  94. *
  95. * @method attr
  96. * @param {String} name Attribute name to set or get.
  97. * @param {String} value Optional value to set.
  98. * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
  99. */
  100. attr: function(name, value) {
  101. var self = this, attrs, i, undef;
  102. if (typeof name !== "string") {
  103. for (i in name) {
  104. self.attr(i, name[i]);
  105. }
  106. return self;
  107. }
  108. if ((attrs = self.attributes)) {
  109. if (value !== undef) {
  110. // Remove attribute
  111. if (value === null) {
  112. if (name in attrs.map) {
  113. delete attrs.map[name];
  114. i = attrs.length;
  115. while (i--) {
  116. if (attrs[i].name === name) {
  117. attrs = attrs.splice(i, 1);
  118. return self;
  119. }
  120. }
  121. }
  122. return self;
  123. }
  124. // Set attribute
  125. if (name in attrs.map) {
  126. // Set attribute
  127. i = attrs.length;
  128. while (i--) {
  129. if (attrs[i].name === name) {
  130. attrs[i].value = value;
  131. break;
  132. }
  133. }
  134. } else {
  135. attrs.push({name: name, value: value});
  136. }
  137. attrs.map[name] = value;
  138. return self;
  139. } else {
  140. return attrs.map[name];
  141. }
  142. }
  143. },
  144. /**
  145. * Does a shallow clones the node into a new node. It will also exclude id attributes since
  146. * there should only be one id per document.
  147. *
  148. * @example
  149. * var clonedNode = node.clone();
  150. *
  151. * @method clone
  152. * @return {tinymce.html.Node} New copy of the original node.
  153. */
  154. clone: function() {
  155. var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
  156. // Clone element attributes
  157. if ((selfAttrs = self.attributes)) {
  158. cloneAttrs = [];
  159. cloneAttrs.map = {};
  160. for (i = 0, l = selfAttrs.length; i < l; i++) {
  161. selfAttr = selfAttrs[i];
  162. // Clone everything except id
  163. if (selfAttr.name !== 'id') {
  164. cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
  165. cloneAttrs.map[selfAttr.name] = selfAttr.value;
  166. }
  167. }
  168. clone.attributes = cloneAttrs;
  169. }
  170. clone.value = self.value;
  171. clone.shortEnded = self.shortEnded;
  172. return clone;
  173. },
  174. /**
  175. * Wraps the node in in another node.
  176. *
  177. * @example
  178. * node.wrap(wrapperNode);
  179. *
  180. * @method wrap
  181. */
  182. wrap: function(wrapper) {
  183. var self = this;
  184. self.parent.insert(wrapper, self);
  185. wrapper.append(self);
  186. return self;
  187. },
  188. /**
  189. * Unwraps the node in other words it removes the node but keeps the children.
  190. *
  191. * @example
  192. * node.unwrap();
  193. *
  194. * @method unwrap
  195. */
  196. unwrap: function() {
  197. var self = this, node, next;
  198. for (node = self.firstChild; node; ) {
  199. next = node.next;
  200. self.insert(node, self, true);
  201. node = next;
  202. }
  203. self.remove();
  204. },
  205. /**
  206. * Removes the node from it's parent.
  207. *
  208. * @example
  209. * node.remove();
  210. *
  211. * @method remove
  212. * @return {tinymce.html.Node} Current node that got removed.
  213. */
  214. remove: function() {
  215. var self = this, parent = self.parent, next = self.next, prev = self.prev;
  216. if (parent) {
  217. if (parent.firstChild === self) {
  218. parent.firstChild = next;
  219. if (next) {
  220. next.prev = null;
  221. }
  222. } else {
  223. prev.next = next;
  224. }
  225. if (parent.lastChild === self) {
  226. parent.lastChild = prev;
  227. if (prev) {
  228. prev.next = null;
  229. }
  230. } else {
  231. next.prev = prev;
  232. }
  233. self.parent = self.next = self.prev = null;
  234. }
  235. return self;
  236. },
  237. /**
  238. * Appends a new node as a child of the current node.
  239. *
  240. * @example
  241. * node.append(someNode);
  242. *
  243. * @method append
  244. * @param {tinymce.html.Node} node Node to append as a child of the current one.
  245. * @return {tinymce.html.Node} The node that got appended.
  246. */
  247. append: function(node) {
  248. var self = this, last;
  249. if (node.parent) {
  250. node.remove();
  251. }
  252. last = self.lastChild;
  253. if (last) {
  254. last.next = node;
  255. node.prev = last;
  256. self.lastChild = node;
  257. } else {
  258. self.lastChild = self.firstChild = node;
  259. }
  260. node.parent = self;
  261. return node;
  262. },
  263. /**
  264. * Inserts a node at a specific position as a child of the current node.
  265. *
  266. * @example
  267. * parentNode.insert(newChildNode, oldChildNode);
  268. *
  269. * @method insert
  270. * @param {tinymce.html.Node} node Node to insert as a child of the current node.
  271. * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
  272. * @param {Boolean} before Optional state to insert the node before the reference node.
  273. * @return {tinymce.html.Node} The node that got inserted.
  274. */
  275. insert: function(node, ref_node, before) {
  276. var parent;
  277. if (node.parent) {
  278. node.remove();
  279. }
  280. parent = ref_node.parent || this;
  281. if (before) {
  282. if (ref_node === parent.firstChild) {
  283. parent.firstChild = node;
  284. } else {
  285. ref_node.prev.next = node;
  286. }
  287. node.prev = ref_node.prev;
  288. node.next = ref_node;
  289. ref_node.prev = node;
  290. } else {
  291. if (ref_node === parent.lastChild) {
  292. parent.lastChild = node;
  293. } else {
  294. ref_node.next.prev = node;
  295. }
  296. node.next = ref_node.next;
  297. node.prev = ref_node;
  298. ref_node.next = node;
  299. }
  300. node.parent = parent;
  301. return node;
  302. },
  303. /**
  304. * Get all children by name.
  305. *
  306. * @method getAll
  307. * @param {String} name Name of the child nodes to collect.
  308. * @return {Array} Array with child nodes matchin the specified name.
  309. */
  310. getAll: function(name) {
  311. var self = this, node, collection = [];
  312. for (node = self.firstChild; node; node = walk(node, self)) {
  313. if (node.name === name) {
  314. collection.push(node);
  315. }
  316. }
  317. return collection;
  318. },
  319. /**
  320. * Removes all children of the current node.
  321. *
  322. * @method empty
  323. * @return {tinymce.html.Node} The current node that got cleared.
  324. */
  325. empty: function() {
  326. var self = this, nodes, i, node;
  327. // Remove all children
  328. if (self.firstChild) {
  329. nodes = [];
  330. // Collect the children
  331. for (node = self.firstChild; node; node = walk(node, self)) {
  332. nodes.push(node);
  333. }
  334. // Remove the children
  335. i = nodes.length;
  336. while (i--) {
  337. node = nodes[i];
  338. node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
  339. }
  340. }
  341. self.firstChild = self.lastChild = null;
  342. return self;
  343. },
  344. /**
  345. * Returns true/false if the node is to be considered empty or not.
  346. *
  347. * @example
  348. * node.isEmpty({img: true});
  349. * @method isEmpty
  350. * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
  351. * @return {Boolean} true/false if the node is empty or not.
  352. */
  353. isEmpty: function(elements) {
  354. var self = this, node = self.firstChild, i, name;
  355. if (node) {
  356. do {
  357. if (node.type === 1) {
  358. // Ignore bogus elements
  359. if (node.attributes.map['data-mce-bogus']) {
  360. continue;
  361. }
  362. // Keep empty elements like <img />
  363. if (elements[node.name]) {
  364. return false;
  365. }
  366. // Keep elements with data attributes or name attribute like <a name="1"></a>
  367. i = node.attributes.length;
  368. while (i--) {
  369. name = node.attributes[i].name;
  370. if (name === "name" || name.indexOf('data-mce-') === 0) {
  371. return false;
  372. }
  373. }
  374. }
  375. // Keep comments
  376. if (node.type === 8) {
  377. return false;
  378. }
  379. // Keep non whitespace text nodes
  380. if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
  381. return false;
  382. }
  383. } while ((node = walk(node, self)));
  384. }
  385. return true;
  386. },
  387. /**
  388. * Walks to the next or previous node and returns that node or null if it wasn't found.
  389. *
  390. * @method walk
  391. * @param {Boolean} prev Optional previous node state defaults to false.
  392. * @return {tinymce.html.Node} Node that is next to or previous of the current node.
  393. */
  394. walk: function(prev) {
  395. return walk(this, null, prev);
  396. }
  397. };
  398. /**
  399. * Creates a node of a specific type.
  400. *
  401. * @static
  402. * @method create
  403. * @param {String} name Name of the node type to create for example "b" or "#text".
  404. * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
  405. */
  406. Node.create = function(name, attrs) {
  407. var node, attrName;
  408. // Create node
  409. node = new Node(name, typeLookup[name] || 1);
  410. // Add attributes if needed
  411. if (attrs) {
  412. for (attrName in attrs) {
  413. node.attr(attrName, attrs[attrName]);
  414. }
  415. }
  416. return node;
  417. };
  418. return Node;
  419. });