095a044a0015b8e0edb49283ce86353c2035bfdb
[Packages/TYPO3.CMS.git] / typo3 / sysext / t3editor / res / jslib / ts_codecompletion / tsparser.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2008-2010 Stephan Petzl <spetzl@gmx.at> and Christian Kartnig <office@hahnepeter.de>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /**
27 * @fileoverview contains the TsParser class and the TreeNode helper class
28 */
29
30 /**
31 * Construct a new TsParser object.
32 * @class This class takes care of the parsing and builds the codeTree
33 *
34 * @constructor
35 * @param tsRef typoscript reference tree
36 * @param extTsObjTree codeTree for all typoscript templates
37 * excluding the current one.
38 * @return A new TsParser instance
39 */
40 var TsParser = function(tsRef,extTsObjTree){
41
42 /**
43 * @class data structure for the nodes of the code tree
44 * mainly used for retrieving the externals templates childnodes
45 * @constructor
46 * @param {String} name
47 */
48 function TreeNode(nodeName){
49 this.name = nodeName;
50 //this.tsObjTree = tsObjTree;
51 this.childNodes = new Array();
52 //has to be set, so the node can retrieve the childnodes of the external templates
53 this.extPath = "";
54 // the TS-objecttype ID (TSREF)
55 this.value = "";
56 //this.extTsObjTree = null;
57 // current template or external template
58 this.isExternal = false;
59
60 /**
61 * returns local properties and the properties of the external templates
62 * @returns {Array} ChildNodes
63 */
64 this.getChildNodes = function(){
65 var node = this.getExtNode();
66 if(node){
67 for(key in node.c){
68 var tn = new TreeNode(key,this.tsObjTree);
69 tn.global = true;
70 tn.value = (node.c[key].v)? node.c[key].v : "";
71 tn.isExternal = true;
72 this.childNodes[key] = tn;
73 }
74 }
75 return this.childNodes;
76 }
77
78 this.getValue = function(){
79 if(this.value) {
80 return this.value;
81 } else {
82 var node = this.getExtNode();
83 if(node && node.v) {
84 return node.v;
85 } else {
86 var type = this.getNodeTypeFromTsref();
87 if(type) {
88 return type;
89 } else {
90 return '';
91 }
92 }
93 }
94 }
95
96 /**
97 * This method will try to resolve the properties recursively from right
98 * to left. If the node's value property is not set, it will look for the
99 * value of its parent node, and if there is a matching childProperty
100 * (according to the TSREF) it will return the childProperties value.
101 * If there is no value in the parent node it will go one step further
102 * and look into the parent node of the parent node,...
103 */
104 this.getNodeTypeFromTsref = function(){
105 var path = this.extPath.split('.');
106 var lastSeg = path.pop();
107 // attention: there will be recursive calls if necessary
108 var parentValue = this.parent.getValue();
109 if(parentValue){
110 if(tsRef.typeHasProperty(parentValue,lastSeg)){
111 var type = tsRef.getType(parentValue);
112 var propertyTypeId = type.properties[lastSeg].value;
113 return propertyTypeId;
114 }
115 }
116 return '';
117 }
118
119 /**
120 * Will look in the external ts-tree (static templates, templates on other pages)
121 * if there is a value or childproperties assigned to the current node.
122 * The method uses the extPath of the current node to navigate to the corresponding
123 * node in the external tree
124 */
125 this.getExtNode = function(){
126 var extTree = extTsObjTree;
127 var path = this.extPath.split('.');
128 var pathSeg;
129 if (path == "") {
130 return extTree;
131 }
132 var i;
133 for(i=0;i<path.length;i++){
134 pathSeg = path[i];
135 if(extTree.c == null || extTree.c[pathSeg] == null) {
136 return null;
137 }
138 extTree = extTree.c[pathSeg];
139 }
140 return extTree;
141 }
142
143 }
144
145 // the top level treenode
146 var tsTree = new TreeNode("_L_");
147 var currentLine = "";
148
149
150 /**
151 * build Tree of TsObjects from beginning of editor to actual cursorPos
152 * and store it in tsTree.
153 * also store string from cursor position to the beginning of the line in currentLine
154 * and return the reference to the last path before the cursor position in currentTsTreeNode
155 * @param startNode DOM Node containing the first word in the editor
156 * @param cursorNode DOM Node containing the word at cursor position
157 * @return currentTsTreeNode
158 */
159 this.buildTsObjTree = function(startNode, cursorNode){
160 return buildTsObjTree(startNode, cursorNode);
161 }
162 function buildTsObjTree(startNode, cursorNode) {
163 var currentNode = startNode;
164 var line = "";
165 tsTree = new TreeNode("");
166 tsTree.value = "TLO";
167 function Stack() {
168 }
169
170 Stack.prototype = new Array();
171
172 Stack.prototype.lastElementEquals = function(str) {
173 if (this.length > 0 && this[this.length-1]==str) {
174 return true;
175 }else {
176 return false;
177 }
178 }
179
180 Stack.prototype.popIfLastElementEquals = function(str) {
181 if(this.length > 0 && this[this.length-1]==str) {
182 this.pop();
183 return true;
184 }else {
185 return false;
186 }
187 }
188
189 var stack = new Stack();
190 var prefixes = new Array();
191 var ignoreLine = false;
192 //var cursorReached = false;
193 var insideCondition = false;
194
195 while(true) {
196 if(currentNode.hasChildNodes() && currentNode.firstChild.nodeType==3 && currentNode.currentText.length>0) {
197 node = currentNode.currentText;
198 if (node[0] == '#') {
199 stack.push('#');
200 }
201 if (node == '(') {
202 stack.push('(');
203 }
204 if (node[0] == '/' && node[1]=='*') {
205 stack.push('/*');
206 }
207 if (node == '{') {
208 // TODO: ignore whole block if wrong whitespaces in this line
209 if (getOperator(line)==-1) {
210 stack.push('{');
211 prefixes.push(line.strip());
212 ignoreLine = true;
213 }
214 }
215 // TODO: conditions
216 // if condition starts -> ignore everything until end of condition
217 if (node.search(/^\s*\[.*\]/) != -1
218 && line.search(/\S/) == -1
219 && node.search(/^\s*\[(global|end|GLOBAL|END)\]/) == -1
220 && !stack.lastElementEquals('#')
221 && !stack.lastElementEquals('/*')
222 && !stack.lastElementEquals('{')
223 && !stack.lastElementEquals('(')
224 ) {
225 insideCondition = true;
226 ignoreLine = true;
227 }
228
229 // if end of condition reached
230 if (line.search(/\S/) == -1
231 && !stack.lastElementEquals('#')
232 && !stack.lastElementEquals('/*')
233 && !stack.lastElementEquals('(')
234 && (
235 (node.search(/^\s*\[(global|end|GLOBAL|END)\]/) != -1
236 && !stack.lastElementEquals('{'))
237 || (node.search(/^\s*\[(global|GLOBAL)\]/) != -1)
238 )
239 ) {
240 insideCondition = false;
241 ignoreLine = true;
242 }
243
244 if (node == ')') {
245 stack.popIfLastElementEquals('(');
246 }
247 if (node[0] == '*' && node[1]=='/') {
248 stack.popIfLastElementEquals('/*');
249 ignoreLine = true;
250 }
251 if (node == '}') {
252 //no characters except whitespace allowed before closing bracket
253 trimmedLine = line.replace(/\s/g,"");
254 if (trimmedLine=="") {
255 stack.popIfLastElementEquals('{');
256 if (prefixes.length>0) prefixes.pop();
257 ignoreLine = true;
258 }
259 }
260 if (!stack.lastElementEquals('#')) {
261 line += node;
262 }
263
264 } else {
265 //end of line? divide line into path and text and try to build a node
266 if (currentNode.tagName == "BR") {
267 // ignore comments, ...
268 if(!stack.lastElementEquals('/*') && !stack.lastElementEquals('(') && !ignoreLine && !insideCondition) {
269 line = line.strip();
270 // check if there is any operator in this line
271 var op = getOperator(line);
272 if (op != -1) {
273 // figure out the position of the operator
274 var pos = line.indexOf(op);
275 // the target objectpath should be left to the operator
276 var path = line.substring(0,pos);
277 // if we are in between curly brackets: add prefixes to object path
278 if (prefixes.length>0) {
279 path = prefixes.join('.') + '.' + path;
280 }
281 // the type or value should be right to the operator
282 var str = line.substring(pos+op.length, line.length);
283 path = path.strip();
284 str = str.strip();
285 switch(op) { // set a value or create a new object
286 case '=':
287 //ignore if path is empty or contains whitespace
288 if (path.search(/\s/g) == -1 && path.length > 0) {
289 setTreeNodeValue(path, str);
290 }
291 break;
292 case '=<': // reference to another object in the tree
293 // resolve relative path
294 if(prefixes.length > 0 && str.substr(0, 1) == '.') {
295 str = prefixes.join('.') + str;
296 }
297 //ignore if either path or str is empty or contains whitespace
298 if (path.search(/\s/g) == -1
299 && path.length > 0
300 && str.search(/\s/g) == -1
301 && str.length > 0) {
302 setReference(path, str);
303 }
304 break;
305 case '<': // copy from another object in the tree
306 // resolve relative path
307 if(prefixes.length > 0 && str.substr(0, 1) == '.') {
308 str = prefixes.join('.') + str;
309 }
310 //ignore if either path or str is empty or contains whitespace
311 if (path.search(/\s/g) == -1
312 && path.length > 0
313 && str.search(/\s/g) == -1
314 && str.length > 0) {
315 setCopy(path, str);
316 }
317 break;
318 case '>': // delete object value and properties
319 deleteTreeNodeValue(path);
320 break;
321 case ':=': // function operator
322 // TODO: function-operator
323 break;
324 }
325 }
326 }
327 stack.popIfLastElementEquals('#');
328 ignoreLine = false;
329 line = "";
330 }
331 }
332 // todo: fix problem: occurs if you type something, delete it with backspace and press ctrl+space
333 // hack: cursor.start does not always return the node on the same level- so we have to check both
334 // if (currentNode == cursor.start.node.parentNode || currentNode == cursor.start.node.previousSibling){
335 // another problem: also the filter is calculated wrong, due to the buggy cursor, so this hack is useless
336 if (currentNode == cursorNode) {
337 break;
338 } else {
339 currentNode = currentNode.nextSibling;
340 }
341 }
342 // when node at cursorPos is reached:
343 // save currentLine, currentTsTreeNode and filter if necessary
344 // if there is a reference or copy operator ('<' or '=<')
345 // return the treeNode of the path right to the operator,
346 // else try to build a path from the whole line
347
348 if(!stack.lastElementEquals('/*') && !stack.lastElementEquals('(') && !ignoreLine) {
349 currentLine = line;
350 var i = line.indexOf('<');
351 if (i != -1) {
352 var path = line.substring(i+1, line.length);
353 path = path.strip();
354 if ( prefixes.length > 0 && path.substr(0,1) == '.') {
355 path = prefixes.join('.') + path;
356 }
357 } else {
358 var path = line;
359 if (prefixes.length>0) {
360 path = prefixes.join('.') + '.' + path;
361 path = path.replace(/\s/g,"");
362 }
363 }
364 var lastDot = path.lastIndexOf(".");
365 path = path.substring(0, lastDot);
366 }
367 return getTreeNode(path);
368 }
369
370
371 /**
372 * check if there is an operator in the line and return it
373 * if there is none, return -1
374 */
375 function getOperator(line) {
376 var operators = new Array(":=", "=<", "<", ">", "=");
377 for (var i=0; i<operators.length; i++) {
378 var op = operators[i];
379 if (line.indexOf(op) != -1) {
380 // check if there is some HTML in this line (simple check, however it's the only difference between a reference operator and HTML)
381 // we do this check only in case of the two operators "=<" and "<" since the delete operator would trigger our "HTML-finder"
382 if((op == "=<" || op == "<") && line.indexOf(">") != -1){
383 // if there is a ">" in the line suppose there's some HTML
384 return "=";
385 }
386 return op;
387 }
388 }
389 return -1;
390 }
391
392
393 /**
394 * iterates through the object tree, and creates treenodes
395 * along the path, if necessary
396 */
397 function getTreeNode(path){
398 var aPath = path.strip().split(".");
399 if (aPath == "") {
400 return tsTree;
401 }
402 var subTree = tsTree.childNodes;
403 var pathSeg;
404 var parent = tsTree;
405 var currentNodePath = '';
406 // step through the path from left to right
407 for(i=0;i<aPath.length;i++){
408 pathSeg = aPath[i];
409
410 // if there isn't already a treenode
411 if(subTree[pathSeg] == null || subTree[pathSeg].childNodes == null){ // if this subpath is not defined in the code
412 // create a new treenode
413 subTree[pathSeg] = new TreeNode(pathSeg);
414 subTree[pathSeg].parent = parent;
415 // the extPath has to be set, so the TreeNode can retrieve the respecting node in the external templates
416 var extPath = parent.extPath;
417 if(extPath) {
418 extPath += '.';
419 }
420 extPath += pathSeg;
421 subTree[pathSeg].extPath = extPath;
422 }
423 if(i==aPath.length-1){
424 return subTree[pathSeg];
425 }
426 parent = subTree[pathSeg];
427 subTree = subTree[pathSeg].childNodes;
428 }
429 }
430
431
432 /**
433 * navigates to the respecting treenode,
434 * create nodes in the path, if necessary, and sets the value
435 */
436 function setTreeNodeValue(path, value) {
437 var treeNode = getTreeNode(path);
438 // if we are inside a GIFBUILDER Object
439 if(treeNode.parent != null && (treeNode.parent.value == "GIFBUILDER" || treeNode.parent.getValue() == "GMENU_itemState") && value == "TEXT") {
440 value = "GB_TEXT";
441 }
442 if(treeNode.parent != null && (treeNode.parent.value == "GIFBUILDER" || treeNode.parent.getValue() == "GMENU_itemState") && value == "IMAGE") {
443 value = "GB_IMAGE";
444 }
445 // just override if it is a real objecttype
446 if (tsRef.isType(value)) {
447 treeNode.value = value;
448 }
449 }
450
451
452 /**
453 * navigates to the respecting treenode,
454 * creates nodes if necessary, empties the value and childNodes-Array
455 */
456 function deleteTreeNodeValue(path) {
457 var treeNode = getTreeNode(path);
458 // currently the node is not deleted really, its just not displayed cause value == null
459 // deleting it would be a cleaner solution
460 treeNode.value = null;
461 treeNode.childNodes = null;
462 treeNode = null;
463 }
464
465
466 /**
467 * copies a reference of the treeNode specified by path2
468 * to the location specified by path1
469 */
470 function setReference(path1, path2) {
471 path1arr = path1.split('.');
472 lastNodeName = path1arr[path1arr.length-1];
473 var treeNode1 = getTreeNode(path1);
474 var treeNode2 = getTreeNode(path2);
475 if(treeNode1.parent != null) {
476 treeNode1.parent.childNodes[lastNodeName] = treeNode2;
477 } else {
478 tsTree.childNodes[lastNodeName] = treeNode2;
479 }
480 }
481
482 /**
483 * copies a treeNode specified by path2
484 * to the location specified by path1
485 */
486 function setCopy(path1,path2){
487 this.clone = function(myObj) {
488 if (myObj == null || typeof(myObj) != 'object') {
489 return myObj;
490 }
491
492 var myNewObj = new Object();
493
494 for(var i in myObj){
495 // disable recursive cloning for parent object -> copy by reference
496 if(i != "parent"){
497 if (typeof myObj[i] == 'object') {
498 myNewObj[i] = clone(myObj[i]);
499 } else {
500 myNewObj[i] = myObj[i];
501 }
502 } else {
503 myNewObj.parent = myObj.parent;
504 }
505 }
506 return myNewObj;
507 }
508 var path1arr = path1.split('.');
509 var lastNodeName = path1arr[path1arr.length-1];
510 var treeNode1 = getTreeNode(path1);
511 var treeNode2 = getTreeNode(path2);
512
513 if(treeNode1.parent != null) {
514 treeNode1.parent.childNodes[lastNodeName] = this.clone(treeNode2);
515 //treeNode1.parent.childNodes[lastNodeName].extTsObjTree = extTsObjTree;
516 } else {
517 tsTree.childNodes[lastNodeName] = this.clone(treeNode2);
518 //tsTree[lastNodeName].extTsObjTree = extTsObjTree;
519 }
520 }
521 }