1686abdfbf68520a6c40082caec4f7b2cd96bd44
[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 });
139
140 var TextNode = function(nativeNode, document) {
141     DocumentNode.call(this, nativeNode, document);
142 };
143
144 $.extend(TextNode.prototype, DocumentNode.prototype, {
145     nodeType: Node.TEXT_NODE,
146
147     getText: function() {
148         return this.nativeNode.data;
149     },
150
151     appendText: function(text) {
152         this.nativeNode.data = this.nativeNode.data + text;
153     },
154
155     prependText: function(text) {
156         this.nativeNode.data = text + this.nativeNode.data;
157     }
158 });
159
160
161 var parseXML = function(xml) {
162     return $(xml)[0];
163 };
164
165 var Document = function(xml) {
166     var $document = $(parseXML(xml));
167
168     var doc = this;
169     Object.defineProperty(this, 'root', {get: function() {
170         return doc.createElementNode($document[0]);
171     }});
172 };
173 $.extend(Document.prototype, {
174     ElementNodeFactory: ElementNode,
175     TextNodeFactory: TextNode,
176
177     createElementNode: function(nativeNode) {
178         return new this.ElementNodeFactory(nativeNode, this);
179     },
180
181     createTextNode: function(nativeNode) {
182         return new this.TextNodeFactory(nativeNode, this);
183     }
184 });
185
186
187 return {
188     documentFromXML: function(xml) {
189         return new Document(parseXML(xml));
190     },
191
192     elementNodeFromXML: function(xml) {
193         return this.documentFromXML(xml).root;
194     },
195
196     Document: Document,
197     DocumentNode: DocumentNode,
198     ElementNode: ElementNode
199 };
200
201 });