7 var TEXT_NODE = Node.TEXT_NODE;
9 var INSERTION = function(implementation) {
10 var toret = function(node) {
11 var insertion = this.getNodeInsertion(node),
12 nodeWasContained = this.document.containsNode(insertion.ofNode),
14 if(!(this.document.containsNode(this))) {
15 nodeParent = insertion.ofNode.parent();
17 implementation.call(this, insertion.ofNode.nativeNode);
18 this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent, nodeWasContained);
19 return insertion.ofNode;
24 var documentNodeTransformations = {
26 var parent = this.parent();
28 this.triggerChangeEvent('nodeDetached', {parent: parent});
32 replaceWith: function(node) {
35 return this.document.replaceRoot(node);
37 toret = this.after(node);
42 after: INSERTION(function(nativeNode) {
43 return this._$.after(nativeNode);
46 before: INSERTION(function(nativeNode) {
47 return this._$.before(nativeNode);
50 wrapWith: function(node) {
51 var insertion = this.getNodeInsertion(node);
53 this.before(insertion.ofNode);
55 insertion.ofNode.append(this);
56 return insertion.ofNode;
60 * Removes parent of a node if node has no siblings.
66 var parent = this.parent(),
68 if(parent.contents().length === 1) {
69 grandParent = parent.parent();
70 parent.unwrapContent();
76 var elementNodeTransformations = {
80 if(this.parent() && this.isSurroundedByTextElements()) {
82 this.prev().appendText(next.getText());
85 return this.__super__.detach();
88 setTag: function(tagName) {
89 var node = this.document.createDocumentNode({tagName: tagName}),
90 oldTagName = this.getTagName(),
91 myContents = this._$.contents();
93 this.getAttrs().forEach(function(attribute) {
94 node.setAttr(attribute.name, attribute.value, true);
96 node.setData(this.getData());
98 if(this.sameNode(this.document.root)) {
99 this.document._defineDocumentProperties(node._$);
101 this._$.replaceWith(node._$);
102 this._setNativeNode(node._$[0]);
103 this._$.append(myContents);
104 this.triggerChangeEvent('nodeTagChange', {oldTagName: oldTagName, newTagName: this.getTagName()});
108 setAttr: function(name, value, silent) {
109 var oldVal = this.getAttr(name);
110 this._$.attr(name, value);
112 this.triggerChangeEvent('nodeAttrChange', {attr: name, oldVal: oldVal, newVal: value});
116 append: INSERTION(function(nativeNode) {
117 this._$.append(nativeNode);
120 prepend: INSERTION(function(nativeNode) {
121 this._$.prepend(nativeNode);
124 insertAtIndex: function(nativeNode, index) {
125 var contents = this.contents();
126 if(index < contents.length) {
127 return contents[index].before(nativeNode);
128 } else if(index === contents.length) {
129 return this.append(nativeNode);
133 unwrapContent: function() {
134 var parent = this.parent();
139 var myContents = this.contents(),
140 myIdx = parent.indexOf(this);
143 if(myContents.length === 0) {
144 return this.detach();
147 var prev = this.prev(),
149 moveLeftRange, moveRightRange, leftMerged;
151 if(prev && (prev.nodeType === TEXT_NODE) && (myContents[0].nodeType === TEXT_NODE)) {
152 prev.appendText(myContents[0].getText());
153 myContents[0].detach();
154 moveLeftRange = true;
160 if(!(leftMerged && myContents.length === 1)) {
161 var lastContents = _.last(myContents);
162 if(next && (next.nodeType === TEXT_NODE) && (lastContents.nodeType === TEXT_NODE)) {
163 next.prependText(lastContents.getText());
164 lastContents.detach();
165 moveRightRange = true;
169 var childrenLength = this.contents().length;
170 this.contents().forEach(function(child) {
177 element1: parent.contents()[myIdx + (moveLeftRange ? -1 : 0)],
178 element2: parent.contents()[myIdx + childrenLength-1 + (moveRightRange ? 1 : 0)]
182 wrapText: function(params) {
183 return this.document._wrapText(_.extend({inside: this}, params));
187 var textNodeTransformations = {
188 setText: function(text) {
189 //console.log('smartxml: ' + text);
190 this.nativeNode.data = text;
191 this.triggerTextChangeEvent();
194 appendText: function(text) {
195 this.nativeNode.data = this.nativeNode.data + text;
196 this.triggerTextChangeEvent();
199 prependText: function(text) {
200 this.nativeNode.data = text + this.nativeNode.data;
201 this.triggerTextChangeEvent();
204 wrapWith: function(desc) {
205 if(typeof desc.start === 'number' && typeof desc.end === 'number') {
206 return this.document._wrapText({
207 inside: this.parent(),
208 textNodeIdx: this.parent().indexOf(this),
209 offsetStart: Math.min(desc.start, desc.end),
210 offsetEnd: Math.max(desc.start, desc.end),
211 _with: {tagName: desc.tagName, attrs: desc.attrs}
214 return this.__super__.wrapWith.call(this, desc);
218 split: function(params) {
219 var parentElement = this.parent(),
221 succeedingChildren = [],
222 prefix = this.getText().substr(0, params.offset),
223 suffix = this.getText().substr(params.offset);
225 parentElement.contents().forEach(function(child) {
227 succeedingChildren.push(child);
229 if(child.sameNode(this)) {
234 if(prefix.length > 0) {
235 this.setText(prefix);
242 parentElement.getAttrs().forEach(function(attr) {attrs[attr.name] = attr.value; });
243 var newElement = this.document.createDocumentNode({tagName: parentElement.getTagName(), attrs: attrs});
244 parentElement.after(newElement);
246 if(suffix.length > 0) {
247 newElement.append({text: suffix});
249 succeedingChildren.forEach(function(child) {
250 newElement.append(child);
253 return {first: parentElement, second: newElement};
257 var documentTransformations = {
258 wrapNodes: function(params) {
259 if(!(params.node1.parent().sameNode(params.node2.parent()))) {
260 throw new Error('Wrapping non-sibling nodes not supported.');
263 var parent = params.node1.parent(),
264 parentContents = parent.contents(),
265 wrapper = this.createDocumentNode({
266 tagName: params._with.tagName,
267 attrs: params._with.attrs}),
268 idx1 = parent.indexOf(params.node1),
269 idx2 = parent.indexOf(params.node2);
277 var insertingMethod, insertingTarget;
279 insertingMethod = 'prepend';
280 insertingTarget = parent;
282 insertingMethod = 'after';
283 insertingTarget = parentContents[idx1-1];
286 for(var i = idx1; i <= idx2; i++) {
287 wrapper.append(parentContents[i].detach());
290 insertingTarget[insertingMethod](wrapper);
294 _wrapText: function(params) {
295 params = _.extend({textNodeIdx: 0}, params);
296 if(typeof params.textNodeIdx === 'number') {
297 params.textNodeIdx = [params.textNodeIdx];
300 var contentsInside = params.inside.contents(),
301 idx1 = Math.min.apply(Math, params.textNodeIdx),
302 idx2 = Math.max.apply(Math, params.textNodeIdx),
303 textNode1 = contentsInside[idx1],
304 textNode2 = contentsInside[idx2],
305 sameNode = textNode1.sameNode(textNode2),
306 prefixOutside = textNode1.getText().substr(0, params.offsetStart),
307 prefixInside = textNode1.getText().substr(params.offsetStart),
308 suffixInside = textNode2.getText().substr(0, params.offsetEnd),
309 suffixOutside = textNode2.getText().substr(params.offsetEnd)
312 if(!(textNode1.parent().sameNode(textNode2.parent()))) {
313 throw new Error('Wrapping text in non-sibling text nodes not supported.');
316 var wrapperElement = this.createDocumentNode({tagName: params._with.tagName, attrs: params._with.attrs});
317 textNode1.after(wrapperElement);
320 if(prefixOutside.length > 0) {
321 wrapperElement.before({text:prefixOutside});
324 var core = textNode1.getText().substr(params.offsetStart, params.offsetEnd - params.offsetStart);
325 wrapperElement.append({text: core});
328 if(prefixInside.length > 0) {
329 wrapperElement.append({text: prefixInside});
331 for(var i = idx1 + 1; i < idx2; i++) {
332 wrapperElement.append(contentsInside[i]);
334 if(suffixInside.length > 0) {
335 wrapperElement.append({text: suffixInside});
338 if(suffixOutside.length > 0) {
339 wrapperElement.after({text: suffixOutside});
341 return wrapperElement;
343 replaceRoot: function(node) {
344 var insertion = this.getNodeInsertion(node);
346 this._defineDocumentProperties(insertion.ofNode._$);
347 insertion.ofNode.triggerChangeEvent('nodeAdded');
348 return insertion.ofNode;
354 transformations: documentTransformations
357 transformations: documentNodeTransformations
360 transformations: elementNodeTransformations
363 transformations: textNodeTransformations