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