smartxml: fix - handle creation of an empty text node
[fnpeditor.git] / src / wlxml / wlxml.js
1 define([
2     'libs/jquery',
3     'smartxml/smartxml'
4 ], function($, smartxml) {
5     
6 'use strict';
7
8 // utils
9
10 var isMetaAttribute = function(attrName) {
11     return attrName.substr(0, 5) === 'meta-';
12 };
13
14 //
15
16 var WLXMLElementNode = function(nativeNode, document) {
17     smartxml.ElementNode.call(this, nativeNode, document);
18 };
19 WLXMLElementNode.prototype = Object.create(smartxml.ElementNode.prototype);
20
21 $.extend(WLXMLElementNode.prototype, smartxml.ElementNode.prototype, {
22     getClass: function() {
23         return this.getAttr('class');
24     },
25     setClass: function(klass) {
26         return this.setAttr('class', klass);
27     },
28     getMetaAttributes: function() {
29         var toret = [];
30         this.getAttrs().forEach(function(attr) {
31             if(isMetaAttribute(attr.name)) {
32                 toret.push({name: attr.name.substr(5), value: attr.value});
33             }
34         });
35         return toret;
36     },
37     getOtherAttributes: function() {
38         var toret = {};
39         this.getAttrs().forEach(function(attr) {
40             if(attr.name !== 'class' && !isMetaAttribute(attr.name)) {
41                 toret[attr.name] = attr.value;
42             }
43         });
44         return toret;
45     },
46
47     _getXMLDOMToDump: function() {
48         var DOM = this._$.clone(true, true);
49
50         DOM.find('*').addBack().each(function() {
51             var el = $(this),
52                 parent = el.parent(),
53                 contents = parent.contents(),
54                 idx = contents.index(el),
55                 data = el.data();
56
57
58             var txt;
59
60             if(data[formatter_prefix+ 'orig_before']) {
61                 txt = idx > 0 && contents[idx-1].nodeType === Node.TEXT_NODE ? contents[idx-1] : null;
62                 if(txt && txt.data === data[formatter_prefix + 'orig_before_transformed']) {
63                     txt.data = data[formatter_prefix+ 'orig_before_original'];
64                 } else {
65                     el.before(data[formatter_prefix+ 'orig_before']);
66                 }
67             }
68             if(data[formatter_prefix+ 'orig_after']) {
69                 txt = idx < contents.length-1 && contents[idx+1].nodeType === Node.TEXT_NODE ? contents[idx+1] : null;
70                 if(txt && txt.data === data[formatter_prefix + 'orig_after_transformed']) {
71                     txt.data = data[formatter_prefix+ 'orig_after_original'];
72                 } else {
73                     el.after(data[formatter_prefix+ 'orig_after']);
74                 }
75             }
76             if(data[formatter_prefix+ 'orig_begin']) {
77                 el.prepend(data[formatter_prefix+ 'orig_begin']);
78             }
79             if(data[formatter_prefix+ 'orig_end']) {
80                 contents = el.contents();
81                 txt = (contents.length && contents[contents.length-1].nodeType === Node.TEXT_NODE) ? contents[contents.length-1] : null;
82                 if(txt && txt.data === data[formatter_prefix + 'orig_end_transformed']) {
83                     txt.data = data[formatter_prefix+ 'orig_end_original'];
84                 } else {
85                     el.append(data[formatter_prefix+ 'orig_end']);
86                 }
87             }
88         });
89
90         return DOM;
91     }
92 });
93
94
95
96
97
98 var WLXMLDocument = function(xml) {
99     smartxml.Document.call(this, xml);
100 };
101
102 var formatter_prefix = '_wlxml_formatter_';
103
104 WLXMLDocument.prototype = Object.create(smartxml.Document.prototype);
105 $.extend(WLXMLDocument.prototype, {
106     ElementNodeFactory: WLXMLElementNode,
107
108     loadXML: function(xml) {
109         smartxml.Document.prototype.loadXML.call(this, xml, {silent: true});
110         $(this.dom).find(':not(iframe)').addBack().contents()
111             .filter(function() {return this.nodeType === Node.TEXT_NODE;})
112             .each(function() {
113                 var el = $(this),
114                     text = {original: el.text(), trimmed: $.trim(el.text())},
115                     elParent = el.parent(),
116                     hasSpanParent = elParent.prop('tagName') === 'SPAN',
117                     hasSpanBefore = el.prev().length && $(el.prev()).prop('tagName') === 'SPAN',
118                     hasSpanAfter = el.next().length && $(el.next()).prop('tagName') === 'SPAN';
119
120
121                 var addInfo = function(toAdd, where, transformed, original) {
122                     var parentContents = elParent.contents(),
123                         idx = parentContents.index(el[0]),
124                         prev = idx > 0 ? parentContents[idx-1] : null,
125                         next = idx < parentContents.length - 1 ? parentContents[idx+1] : null,
126                         target, key;
127
128                     if(where === 'above') {
129                         target = prev ? $(prev) : elParent;
130                         key = prev ? 'orig_after' : 'orig_begin';
131                     } else if(where === 'below') {
132                         target = next ? $(next) : elParent;
133                         key = next ? 'orig_before' : 'orig_end';
134                     } else { throw new Error();}
135
136                     target.data(formatter_prefix + key, toAdd);
137                     if(transformed !== undefined) {
138                         target.data(formatter_prefix + key + '_transformed', transformed);
139                     }
140                     if(original !== undefined) {
141                         target.data(formatter_prefix + key + '_original', original);
142                     }
143                 };
144
145                 text.transformed = text.trimmed;
146
147                 if(hasSpanParent || hasSpanBefore || hasSpanAfter) {
148                     var startSpace = /\s/g.test(text.original.substr(0,1)),
149                         endSpace = /\s/g.test(text.original.substr(-1)) && text.original.length > 1;
150                     text.transformed = (startSpace && (hasSpanParent || hasSpanBefore) ? ' ' : '');
151                     text.transformed += text.trimmed;
152                     text.transformed += (endSpace && (hasSpanParent || hasSpanAfter) ? ' ' : '');
153                 } else {
154                     if(text.trimmed.length === 0 && text.original.length > 0 && elParent.contents().length === 1) {
155                         text.transformed = ' ';
156                     }
157                 }
158
159                 if(!text.transformed) {
160                     addInfo(text.original, 'below');
161                     el.remove();
162                     return true; // continue
163                 }
164
165                 if(text.transformed !== text.original) {
166                     // if(!text.trimmed) {
167                     //     addInfo(text.original, 'below');
168                     // } else {
169                         var startingMatch = text.original.match(/^\s+/g),
170                             endingMatch = text.original.match(/\s+$/g),
171                             startingWhiteSpace = startingMatch ? startingMatch[0] : null,
172                             endingWhiteSpace = endingMatch ? endingMatch[0] : null;
173
174                         if(endingWhiteSpace) {
175                             if(text.transformed[text.transformed.length - 1] === ' ' && endingWhiteSpace[0] === ' ') {
176                                 endingWhiteSpace = endingWhiteSpace.substr(1);
177                             }
178                             addInfo(endingWhiteSpace, 'below', !text.trimmed ? text.transformed : undefined, !text.trimmed ? text.original : undefined);
179                         }
180
181                         if(startingWhiteSpace && text.trimmed) {
182                             if(text.transformed[0] === ' ' && startingWhiteSpace[startingWhiteSpace.length-1] === ' ') {
183                                 startingWhiteSpace = startingWhiteSpace.substr(0, startingWhiteSpace.length -1);
184                             }
185                             addInfo(startingWhiteSpace, 'above', !text.trimmed ? text.transformed : undefined, !text.trimmed ? text.original : undefined);
186                         }
187                     //}
188                 }
189
190                 el.replaceWith(document.createTextNode(text.transformed));
191             });
192         this.trigger('contentSet');
193     }
194
195 });
196
197
198 return {
199     WLXMLDocumentFromXML: function(xml) {
200         return new WLXMLDocument(xml);
201     },
202
203     WLXMLElementNodeFromXML: function(xml) {
204         return this.WLXMLDocumentFromXML(xml).root;
205     }
206 };
207
208 });