4 * Copyright 2010, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
12 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
18 '#document-fragment' : 11
21 // Walks the tree left/right
22 function walk(node, root_node, prev) {
23 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
25 // Walk into nodes if it has a start
27 return node[startName];
29 // Return the sibling if it has one
30 if (node !== root_node) {
31 sibling = node[siblingName];
36 // Walk up the parents to look for siblings
37 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
38 sibling = parent[siblingName];
47 * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
50 * var node = new tinymce.html.Node('strong', 1);
51 * someRoot.append(node);
53 * @class tinymce.html.Node
58 * Constructs a new Node instance.
62 * @param {String} name Name of the node type.
63 * @param {Number} type Numeric type representing the node.
65 function Node(name, type) {
71 this.attributes.map = {};
75 tinymce.extend(Node.prototype, {
77 * Replaces the current node with the specified one.
80 * someNode.replace(someNewNode);
83 * @param {tinymce.html.Node} node Node to replace the current node with.
84 * @return {tinymce.html.Node} The old node that got replaced.
86 replace : function(node) {
92 self.insert(node, self);
99 * Gets/sets or removes an attribute by name.
102 * someNode.attr("name", "value"); // Sets an attribute
103 * console.log(someNode.attr("name")); // Gets an attribute
104 * someNode.attr("name", null); // Removes an attribute
107 * @param {String} name Attribute name to set or get.
108 * @param {String} value Optional value to set.
109 * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
111 attr : function(name, value) {
112 var self = this, attrs, i, undef;
114 if (typeof name !== "string") {
116 self.attr(i, name[i]);
121 if (attrs = self.attributes) {
122 if (value !== undef) {
124 if (value === null) {
125 if (name in attrs.map) {
126 delete attrs.map[name];
130 if (attrs[i].name === name) {
131 attrs = attrs.splice(i, 1);
141 if (name in attrs.map) {
145 if (attrs[i].name === name) {
146 attrs[i].value = value;
151 attrs.push({name: name, value: value});
153 attrs.map[name] = value;
157 return attrs.map[name];
163 * Does a shallow clones the node into a new node. It will also exclude id attributes since
164 * there should only be one id per document.
167 * var clonedNode = node.clone();
170 * @return {tinymce.html.Node} New copy of the original node.
173 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
175 // Clone element attributes
176 if (selfAttrs = self.attributes) {
180 for (i = 0, l = selfAttrs.length; i < l; i++) {
181 selfAttr = selfAttrs[i];
183 // Clone everything except id
184 if (selfAttr.name !== 'id') {
185 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
186 cloneAttrs.map[selfAttr.name] = selfAttr.value;
190 clone.attributes = cloneAttrs;
193 clone.value = self.value;
194 clone.shortEnded = self.shortEnded;
200 * Wraps the node in in another node.
203 * node.wrap(wrapperNode);
207 wrap : function(wrapper) {
210 self.parent.insert(wrapper, self);
211 wrapper.append(self);
217 * Unwraps the node in other words it removes the node but keeps the children.
224 unwrap : function() {
225 var self = this, node, next;
227 for (node = self.firstChild; node; ) {
229 self.insert(node, self, true);
237 * Removes the node from it's parent.
243 * @return {tinymce.html.Node} Current node that got removed.
245 remove : function() {
246 var self = this, parent = self.parent, next = self.next, prev = self.prev;
249 if (parent.firstChild === self) {
250 parent.firstChild = next;
258 if (parent.lastChild === self) {
259 parent.lastChild = prev;
267 self.parent = self.next = self.prev = null;
274 * Appends a new node as a child of the current node.
277 * node.append(someNode);
280 * @param {tinymce.html.Node} node Node to append as a child of the current one.
281 * @return {tinymce.html.Node} The node that got appended.
283 append : function(node) {
284 var self = this, last;
289 last = self.lastChild;
293 self.lastChild = node;
295 self.lastChild = self.firstChild = node;
303 * Inserts a node at a specific position as a child of the current node.
306 * parentNode.insert(newChildNode, oldChildNode);
309 * @param {tinymce.html.Node} node Node to insert as a child of the current node.
310 * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
311 * @param {Boolean} before Optional state to insert the node before the reference node.
312 * @return {tinymce.html.Node} The node that got inserted.
314 insert : function(node, ref_node, before) {
320 parent = ref_node.parent || this;
323 if (ref_node === parent.firstChild)
324 parent.firstChild = node;
326 ref_node.prev.next = node;
328 node.prev = ref_node.prev;
329 node.next = ref_node;
330 ref_node.prev = node;
332 if (ref_node === parent.lastChild)
333 parent.lastChild = node;
335 ref_node.next.prev = node;
337 node.next = ref_node.next;
338 node.prev = ref_node;
339 ref_node.next = node;
342 node.parent = parent;
348 * Get all children by name.
351 * @param {String} name Name of the child nodes to collect.
352 * @return {Array} Array with child nodes matchin the specified name.
354 getAll : function(name) {
355 var self = this, node, collection = [];
357 for (node = self.firstChild; node; node = walk(node, self)) {
358 if (node.name === name)
359 collection.push(node);
366 * Removes all children of the current node.
369 * @return {tinymce.html.Node} The current node that got cleared.
372 var self = this, nodes, i, node;
374 // Remove all children
375 if (self.firstChild) {
378 // Collect the children
379 for (node = self.firstChild; node; node = walk(node, self))
382 // Remove the children
386 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
390 self.firstChild = self.lastChild = null;
396 * Returns true/false if the node is to be considered empty or not.
399 * node.isEmpty({img : true});
401 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
402 * @return {Boolean} true/false if the node is empty or not.
404 isEmpty : function(elements) {
405 var self = this, node = self.firstChild, i, name;
409 if (node.type === 1) {
410 // Ignore bogus elements
411 if (node.attributes.map['data-mce-bogus'])
414 // Keep empty elements like <img />
415 if (elements[node.name])
418 // Keep elements with data attributes or name attribute like <a name="1"></a>
419 i = node.attributes.length;
421 name = node.attributes[i].name;
422 if (name === "name" || name.indexOf('data-') === 0)
427 // Keep non whitespace text nodes
428 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
430 } while (node = walk(node, self));
437 tinymce.extend(Node, {
439 * Creates a node of a specific type.
443 * @param {String} name Name of the node type to create for example "b" or "#text".
444 * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
446 create : function(name, attrs) {
450 node = new Node(name, typeLookup[name] || 1);
452 // Add attributes if needed
454 for (attrName in attrs)
455 node.attr(attrName, attrs[attrName]);
462 tinymce.html.Node = Node;