1 /**
  2  * @fileoverview A user interface component to visually edit 
  3  * XML Mixed Content like in a Rich Text Editor.
  4  */
  5 
  6 goog.provide('xrx.richxml');
  7 
  8 
  9 goog.require('goog.dom');
 10 goog.require('xrx.codemirror');
 11 goog.require('xrx.label');
 12 goog.require('xrx.richxml.cursor');
 13 goog.require('xrx.richxml.mode');
 14 goog.require('xrx.richxml.tagname');
 15 goog.require('xrx.stream');
 16 goog.require('xrx.view');
 17 
 18 
 19 
 20 /**
 21  * @constructor
 22  */
 23 xrx.richxml = function(element, opt_tagname) {
 24 
 25 
 26 
 27   goog.base(this, element);
 28 
 29 
 30 
 31   /**
 32    * @private
 33    */
 34   this.cursor_;
 35 
 36 
 37 
 38   /**
 39    * @private
 40    */
 41   this.tagname_ = opt_tagname;
 42 };
 43 goog.inherits(xrx.richxml, xrx.codemirror);
 44 
 45 
 46 
 47 xrx.richxml.prototype.cursor = function() {
 48   return this.cursor_ || new xrx.richxml.cursor(this);
 49 };
 50 
 51 
 52 
 53 xrx.richxml.prototype.tagname = function() {
 54   return this.tagname_ ? this.tagname_ : this.tagname_ = new xrx.richxml.tagname();
 55 };
 56 
 57 
 58 xrx.richxml.placeholder = {
 59   startTag: unescape('%BB'),
 60   endTag: unescape('%AB'),
 61   emptyTag: unescape('%D7')
 62 };
 63 
 64 
 65 
 66 xrx.richxml.placeholder.matches = function(ch) {
 67 
 68   for(var p in xrx.richxml.placeholder) {
 69     if (xrx.richxml.placeholder[p] === ch) {
 70       return true;
 71     }
 72   }
 73   return false;
 74 };
 75 
 76 
 77 
 78 xrx.richxml.prototype.cursorIsNearTag = function() {
 79   return xrx.richxml.placeholder.matches(this.cursor().leftTokenOutside().string);
 80 };
 81 
 82 
 83 
 84 xrx.richxml.className = {
 85   startTag: 'richxml-start-tag',
 86   startTagActive: 'richxml-start-tag-active',
 87   endTag: 'richxml-end-tag',
 88   endTagActive: 'richxml-end-tag-active',
 89   emptyTag: 'richxml-empty-tag',
 90   emptyTagActive: 'richxml-empty-tag-active',
 91   notTag: 'richxml-not-tag',
 92   notTagActive: 'richxml-not-tag-active'
 93 };
 94 
 95 
 96 
 97 /**
 98  * @private
 99  */
100 xrx.richxml.prototype.transformXml_ = function(xml) {
101   var visualXml = '';
102   var self = this;
103   var stream = new xrx.stream(xml);
104 
105   stream.rowStartTag = function(offset, length1, length2) {
106     visualXml += xrx.richxml.placeholder.startTag;
107     if(length1 !== length2) visualXml += xml.substr(offset + length1, length2 - length1);
108   };
109 
110   stream.rowEndTag = function(offset, length1, length2) {
111     visualXml += xrx.richxml.placeholder.endTag;
112     if(length1 !== length2) visualXml += xml.substr(offset + length1, length2 - length1);
113   };
114 
115   stream.rowEmptyTag = function(offset, length1, length2) {
116     visualXml += xrx.richxml.placeholder.emptyTag;
117     if(length1 !== length2) visualXml += xml.substr(offset + length1, length2 - length1);
118   };
119 
120   stream.forward();
121 
122   return visualXml;
123 };
124 
125 
126 
127 xrx.richxml.prototype.options = {
128   'mode': 'richxml'
129 };
130 
131 
132 
133 xrx.richxml.prototype.refresh = function() {
134   var visualXml = this.transformXml_(this.getNode().xml());
135   this.codemirror_.setValue(visualXml);
136 };
137 
138 
139 
140 xrx.richxml.prototype.clear = function() {
141   this.unmarkActiveTags();
142   this.hideElements();
143   this.hideAttributes();
144   this.hideTagName();
145 };
146 
147 
148 
149 xrx.richxml.prototype.unmarkActiveTags = function() {
150   this.markActiveTags(true);
151 };
152 
153 
154 
155 xrx.richxml.prototype.markActiveTags = function(unmarkflag) {
156 
157   var cm = this.codemirror_;
158   var cursor = this.cursor();
159   var self = this;
160   
161   var markedElements = function() { 
162     return self.markedElements_ || (self.markedElements_ = []); 
163   };
164 
165   var correspondingTag = function() {
166     var text = cm.getValue();
167     var index = cm.indexFromPos(cm.getCursor()) - 1;
168     var ch;
169     var stack = 0;
170 
171     if(cursor.leftTokenOutside().string === xrx.richxml.placeholder.startTag) {
172 
173       for(var i = index; i < text.length; i++) {
174         ch = text[i];
175         if(ch === xrx.richxml.placeholder.endTag) stack -= 1;
176         if(ch === xrx.richxml.placeholder.startTag) stack += 1;
177         if(stack === 0) {
178           index = i + 1;
179           break;
180         } 
181       }
182     } else if(cursor.leftTokenOutside().string === xrx.richxml.placeholder.endTag) {
183 
184       for(var i = index; i >= 0; i--) {
185         ch = text[i];
186         if(ch == xrx.richxml.placeholder.endTag) stack += 1;
187         if(ch == xrx.richxml.placeholder.startTag) stack -= 1;
188         if(stack == 0) {
189           index = i + 1;
190           break;
191         } 
192       }
193     } else {};
194 
195     return cm.posFromIndex(index);
196   };
197   
198   var mark = function(from, to, style) {
199     var mark = cm.markText(from, to, style);
200 
201     markedElements().push(mark);
202   };
203   
204   var unmark = function() {
205 
206     for(var i = 0; i < markedElements().length; ++i) {
207       markedElements()[i].clear();
208     }
209     self.markedElements_ = [];
210   };
211 
212   unmark();
213   if (!this.cursorIsNearTag()) return;
214   if (unmarkflag) return;
215 
216   var firstMark = cursor.leftTokenOutside().string === xrx.richxml.placeholder.startTag ? 
217       xrx.richxml.className.startTagActive : xrx.richxml.className.endTagActive;
218   var secondMark = cursor.leftTokenOutside().string === xrx.richxml.placeholder.startTag ? 
219       xrx.richxml.className.endTagActive : xrx.richxml.className.startTagActive;
220 
221   // mark matched element
222   mark(
223       { line: cursor.leftPosition().line, ch: cursor.leftPosition().ch - 1 }, 
224       { line: cursor.leftPosition().line, ch: cursor.leftPosition().ch }, 
225       { className: firstMark }
226   );
227   
228   if (cursor.leftTokenOutside().string === xrx.richxml.placeholder.emptyTag) return;
229   
230   // mark corresponding element
231   var corresponding = correspondingTag();
232   mark(
233       { line: corresponding.line, ch: corresponding.ch - 1 },
234       { line: corresponding.line, ch: corresponding.ch },
235       { className: secondMark }
236   );
237   
238   // mark text
239   var from = { line: cursor.leftPosition().line, ch: cursor.leftPosition().ch };
240   var to = { line: corresponding.line, ch: corresponding.ch - 1 };
241   cursor.leftTokenOutside().string === xrx.richxml.placeholder.startTag ? 
242       mark(from, to, { className: xrx.richxml.className.notTagActive }) : 
243       mark(to, from, { className: xrx.richxml.className.notTagActive });
244 };
245 
246 
247 
248 xrx.richxml.prototype.showElements = function() {
249   
250 };
251 
252 
253 
254 xrx.richxml.prototype.hideElements = function() {
255   
256 };
257 
258 
259 
260 xrx.richxml.prototype.showAttributes = function() {
261   
262 };
263 
264 
265 
266 xrx.richxml.prototype.hideAttributes = function() {
267   
268 };
269 
270 
271 
272 xrx.richxml.prototype.showTagName = function() {
273   this.cursorIsNearTag() ? this.tagname().show(this.cursor().getNode()) : 
274       this.hideTagName();
275 };
276 
277 
278 
279 xrx.richxml.prototype.hideTagName = function() {
280   this.tagname().hide();
281 };
282 
283 
284 
285 xrx.richxml.prototype.setReadonly = function(readonly) {
286   readonly ? this.codemirror_.setOption('readOnly', true) : 
287       this.codemirror_.setOption('readOnly', false);
288 };
289 
290 
291 
292 xrx.richxml.prototype.eventBlur = function() {
293   this.clear();
294 };
295 
296 
297 
298 xrx.richxml.prototype.cursorActivitySelection = function() {
299   var cm = this.codemirror_;
300   var cursor = this.cursor();
301   var valid = false;
302 
303   if(cursor.leftAtStartPosition() || cursor.rightAtEndPosition()) return;
304 
305   var leftTokenOutside = cursor.leftTokenOutside().state.context.token;
306   var rightTokenInside = cursor.rightTokenInside().state.context.token;
307   var leftToken
308   var rightToken
309 };
310 
311 
312 
313 /**
314  * 
315  */
316 xrx.richxml.prototype.eventCursorActivity = function() {
317   var cm = this.codemirror_;
318 
319   if (cm.somethingSelected()) {
320     this.setReadonly(true);
321     this.cursorActivitySelection();
322   } else {
323     this.setReadonly(false);
324     this.markActiveTags();
325     this.showElements();
326     this.showAttributes();
327     this.showTagName();
328   }
329 };
330 
331 
332 
333 
334 
335