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