Wrapping text/element node with element node
[fnpeditor.git] / src / smartxml / smartxml.js
1 define([
2     'libs/jquery'
3 ], function($) {
4     
5 'use strict';
6
7
8 var TEXT_NODE = Node.TEXT_NODE, ELEMENT_NODE = Node.ELEMENT_NODE;
9
10 var parseXML = function(xml) {
11     return $(xml)[0];
12 };
13
14 var Document = function(nativeNode) {
15     var $document = $(nativeNode);
16
17
18     Object.defineProperty(this, 'root', {get: function() { return new ElementNode($document[0]);}});
19 };
20
21
22 var DocumentNode = function(nativeNode) {
23     this.nativeNode = nativeNode;
24     this._$ = $(nativeNode);
25 };
26
27 $.extend(DocumentNode.prototype, {
28     detach: function() { this._$.detach(); },
29
30     sameNode: function(otherNode) {
31         return this.nativeNode === otherNode.nativeNode;
32     },
33
34     parent: function() {
35         return this.nativeNode.parentNode ? new ElementNode(this.nativeNode.parentNode) : null;
36     },
37
38     before: function(node) {
39         this._$.before(node.nativeNode);
40     },
41
42     wrapWith: function(node) {
43         if(this.parent())
44             this.before(node);
45         node.append(this);
46     },
47 });
48
49 var ElementNode = function(nativeNode) {
50     DocumentNode.apply(this, arguments);
51 };
52
53 $.extend(ElementNode.prototype, DocumentNode.prototype, {
54     nodeType: Node.ELEMENT_NODE,
55
56     getTagName: function() {
57         return this.nativeNode.tagName.toLowerCase();
58     },
59
60     contents: function() {
61         var toret = [];
62         this._$.contents().each(function() {
63             if(this.nodeType === Node.ELEMENT_NODE)
64                 toret.push(new ElementNode(this));
65             else if(this.nodeType === Node.TEXT_NODE)
66                 toret.push(new TextNode(this));
67         });
68         return toret;
69     },
70
71     indexOf: function(node) {
72         return this._$.contents().index(node._$);
73     },
74
75     getAttr: function(name) {
76         return this._$.attr(name);
77     },
78
79     setAttr: function(name, value) {
80         this._$.attr(name, value);
81     },
82
83     append: function(documentNode) {
84         this._$.append(documentNode.nativeNode);
85     },
86
87     unwrapContent: function() {
88         var parent = this.parent();
89         if(!parent)
90             return;
91
92         var parentContents = parent.contents(),
93             myContents = this.contents(),
94             myIdx = parent.indexOf(this);
95
96         if(myContents.length === 0)
97             return this.detach();
98
99         var moveLeftRange, moveRightRange, leftMerged;
100
101         if(myIdx > 0 && (parentContents[myIdx-1].nodeType === TEXT_NODE) && (myContents[0].nodeType === TEXT_NODE)) {
102             parentContents[myIdx-1].appendText(myContents[0].getText());
103             myContents[0].detach();
104             moveLeftRange = true;
105             leftMerged = true;
106         } else {
107             leftMerged = false;
108         }
109
110         if(!(leftMerged && myContents.length === 1)) {
111             if(myIdx < parentContents.length - 1 && (parentContents[myIdx+1].nodeType === TEXT_NODE) && (myContents[myContents.length-1].nodeType === TEXT_NODE)) {
112                 parentContents[myIdx+1].prependText(myContents[myContents.length-1].getText());
113                 myContents[myContents.length-1].detach();
114                 moveRightRange = true;
115             }
116         }
117
118         var childrenLength = this.contents().length;
119         this.contents().forEach(function(child) {
120             this.before(child);
121         }.bind(this));
122
123         this.detach();
124
125         return {
126             element1: parent.contents()[myIdx + (moveLeftRange ? -1 : 0)],
127             element2: parent.contents()[myIdx + childrenLength-1 + (moveRightRange ? 1 : 0)]
128         };
129     }
130
131 });
132
133 var TextNode = function(nativeNode) {
134     DocumentNode.apply(this, arguments);
135 };
136
137 $.extend(TextNode.prototype, DocumentNode.prototype, {
138     nodeType: Node.TEXT_NODE,
139
140     getText: function() {
141         return this.nativeNode.data;
142     },
143
144     appendText: function(text) {
145         this.nativeNode.data = this.nativeNode.data + text;
146     },
147
148     prependText: function(text) {
149         this.nativeNode.data = text + this.nativeNode.data;
150     }
151 });
152
153
154 return {
155     documentFromXML: function(xml) {
156         return new Document(parseXML(xml));
157     },
158
159     elementNodeFromXML: function(xml) {
160         return new ElementNode(parseXML(xml));
161     }
162 };
163
164 });