smartxml wip: preperation for extensibility
[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     append: function(documentNode) {
75         this._$.append(documentNode.nativeNode);
76     },
77
78     unwrapContent: function() {
79         var parent = this.parent();
80         if(!parent)
81             return;
82
83         var parentContents = parent.contents(),
84             myContents = this.contents(),
85             myIdx = parent.indexOf(this);
86
87         if(myContents.length === 0)
88             return this.detach();
89
90         var moveLeftRange, moveRightRange, leftMerged;
91
92         if(myIdx > 0 && (parentContents[myIdx-1].nodeType === TEXT_NODE) && (myContents[0].nodeType === TEXT_NODE)) {
93             parentContents[myIdx-1].appendText(myContents[0].getText());
94             myContents[0].detach();
95             moveLeftRange = true;
96             leftMerged = true;
97         } else {
98             leftMerged = false;
99         }
100
101         if(!(leftMerged && myContents.length === 1)) {
102             if(myIdx < parentContents.length - 1 && (parentContents[myIdx+1].nodeType === TEXT_NODE) && (myContents[myContents.length-1].nodeType === TEXT_NODE)) {
103                 parentContents[myIdx+1].prependText(myContents[myContents.length-1].getText());
104                 myContents[myContents.length-1].detach();
105                 moveRightRange = true;
106             }
107         }
108
109         var childrenLength = this.contents().length;
110         this.contents().forEach(function(child) {
111             this.before(child);
112         }.bind(this));
113
114         this.detach();
115
116         return {
117             element1: parent.contents()[myIdx + (moveLeftRange ? -1 : 0)],
118             element2: parent.contents()[myIdx + childrenLength-1 + (moveRightRange ? 1 : 0)]
119         };
120     }
121
122 });
123
124 var TextNode = function(nativeNode) {
125     DocumentNode.apply(this, arguments);
126 };
127
128 $.extend(TextNode.prototype, DocumentNode.prototype, {
129     nodeType: Node.TEXT_NODE,
130
131     getText: function() {
132         return this.nativeNode.data;
133     },
134
135     appendText: function(text) {
136         this.nativeNode.data = this.nativeNode.data + text;
137     },
138
139     prependText: function(text) {
140         this.nativeNode.data = text + this.nativeNode.data;
141     }
142 });
143
144
145 var parseXML = function(xml) {
146     return $(xml)[0];
147 };
148
149 var Document = function(xml) {
150     var $document = $(parseXML(xml));
151
152     var doc = this;
153     Object.defineProperty(this, 'root', {get: function() {
154         return doc.createElementNode($document[0]);
155     }});
156 };
157 $.extend(Document.prototype, {
158     ElementNodeFactory: ElementNode,
159     TextNodeFactory: TextNode,
160
161     createElementNode: function(nativeNode) {
162         return new this.ElementNodeFactory(nativeNode, this);
163     },
164
165     createTextNode: function(nativeNode) {
166         return new this.TextNodeFactory(nativeNode, this);
167     }
168 });
169
170
171 return {
172     documentFromXML: function(xml) {
173         return new Document(parseXML(xml));
174     },
175
176     elementNodeFromXML: function(xml) {
177         return this.documentFromXML(xml).root;
178     },
179
180     Document: Document,
181     DocumentNode: DocumentNode,
182     ElementNode: ElementNode
183 };
184
185 });