248333ee1846dafb124246791d9506b085fc4371
[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;
9
10
11 var DocumentNode = function(nativeNode, document) {
12     if(!document) {
13         throw new Error('undefined document for a node');
14     }
15     this.document = document;
16     this.nativeNode = nativeNode;
17     this._$ = $(nativeNode);
18 };
19
20 $.extend(DocumentNode.prototype, {
21     detach: function() { this._$.detach(); },
22
23     sameNode: function(otherNode) {
24         return this.nativeNode === otherNode.nativeNode;
25     },
26
27     parent: function() {
28         return this.nativeNode.parentNode ? this.document.createElementNode(this.nativeNode.parentNode) : null;
29     },
30
31     before: function(node) {
32         this._$.before(node.nativeNode);
33     },
34
35     wrapWith: function(node) {
36         if(this.parent()) {
37             this.before(node);
38         }
39         node.append(this);
40     },
41 });
42
43 var ElementNode = function(nativeNode, document) {
44     DocumentNode.call(this, nativeNode, document);
45 };
46
47 $.extend(ElementNode.prototype, DocumentNode.prototype, {
48     nodeType: Node.ELEMENT_NODE,
49
50     getTagName: function() {
51         return this.nativeNode.tagName.toLowerCase();
52     },
53
54     contents: function() {
55         var toret = [],
56             document = this.document;
57         this._$.contents().each(function() {
58             if(this.nodeType === Node.ELEMENT_NODE) {
59                 toret.push(document.createElementNode(this));
60             }
61             else if(this.nodeType === Node.TEXT_NODE) {
62                 toret.push(document.createTextNode(this));
63             }
64         });
65         return toret;
66     },
67
68     indexOf: function(node) {
69         return this._$.contents().index(node._$);
70     },
71
72     getAttr: function(name) {
73         return this._$.attr(name);
74     },
75
76     setAttr: function(name, value) {
77         this._$.attr(name, value);
78     },
79
80     getAttrs: function() {
81         var toret = [];
82         for(var i = 0; i < this.nativeNode.attributes.length; i++) {
83             toret.push(this.nativeNode.attributes[i]);
84         }
85         return toret;
86     },
87
88     append: function(documentNode) {
89         this._$.append(documentNode.nativeNode);
90     },
91
92     unwrapContent: function() {
93         var parent = this.parent();
94         if(!parent) {
95             return;
96         }
97
98         var parentContents = parent.contents(),
99             myContents = this.contents(),
100             myIdx = parent.indexOf(this);
101
102         if(myContents.length === 0) {
103             return this.detach();
104         }
105
106         var moveLeftRange, moveRightRange, leftMerged;
107
108         if(myIdx > 0 && (parentContents[myIdx-1].nodeType === TEXT_NODE) && (myContents[0].nodeType === TEXT_NODE)) {
109             parentContents[myIdx-1].appendText(myContents[0].getText());
110             myContents[0].detach();
111             moveLeftRange = true;
112             leftMerged = true;
113         } else {
114             leftMerged = false;
115         }
116
117         if(!(leftMerged && myContents.length === 1)) {
118             if(myIdx < parentContents.length - 1 && (parentContents[myIdx+1].nodeType === TEXT_NODE) && (myContents[myContents.length-1].nodeType === TEXT_NODE)) {
119                 parentContents[myIdx+1].prependText(myContents[myContents.length-1].getText());
120                 myContents[myContents.length-1].detach();
121                 moveRightRange = true;
122             }
123         }
124
125         var childrenLength = this.contents().length;
126         this.contents().forEach(function(child) {
127             this.before(child);
128         }.bind(this));
129
130         this.detach();
131
132         return {
133             element1: parent.contents()[myIdx + (moveLeftRange ? -1 : 0)],
134             element2: parent.contents()[myIdx + childrenLength-1 + (moveRightRange ? 1 : 0)]
135         };
136     },
137
138     toXML: function() {
139         var wrapper = $('<div>');
140         wrapper.append(this._$);
141         return wrapper.html();
142     }
143 });
144
145 var TextNode = function(nativeNode, document) {
146     DocumentNode.call(this, nativeNode, document);
147 };
148
149 $.extend(TextNode.prototype, DocumentNode.prototype, {
150     nodeType: Node.TEXT_NODE,
151
152     getText: function() {
153         return this.nativeNode.data;
154     },
155
156     appendText: function(text) {
157         this.nativeNode.data = this.nativeNode.data + text;
158     },
159
160     prependText: function(text) {
161         this.nativeNode.data = text + this.nativeNode.data;
162     }
163 });
164
165
166 var parseXML = function(xml) {
167     return $(xml)[0];
168 };
169
170 var Document = function(xml) {
171     var $document = $(parseXML(xml));
172
173     var doc = this;
174     Object.defineProperty(this, 'root', {get: function() {
175         return doc.createElementNode($document[0]);
176     }});
177 };
178 $.extend(Document.prototype, {
179     ElementNodeFactory: ElementNode,
180     TextNodeFactory: TextNode,
181
182     createElementNode: function(nativeNode) {
183         return new this.ElementNodeFactory(nativeNode, this);
184     },
185
186     createTextNode: function(nativeNode) {
187         return new this.TextNodeFactory(nativeNode, this);
188     },
189
190     toXML: function() {
191         return this.root.toXML();
192     }
193 });
194
195
196 return {
197     documentFromXML: function(xml) {
198         return new Document(parseXML(xml));
199     },
200
201     elementNodeFromXML: function(xml) {
202         return this.documentFromXML(xml).root;
203     },
204
205     Document: Document,
206     DocumentNode: DocumentNode,
207     ElementNode: ElementNode
208 };
209
210 });