/****************************************************************** highlight.js Muze Helene ------------------------------------------------------------------ Author: Muze (info@muze.nl) Date: 28 februari 2004 Copyright 2002 Muze This file is part of Helene. Helene is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Helene is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Helene; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------- This file contains the syntax highlighting parser ******************************************************************/ // Parsing smarty tags http://smarty.php.net/ // Cofigure the smarty delimiters below ('{%' and '%}' by default) // Unset this flag if you need to parse only PHP var parseSmarty = true; /* states */ var YY_STATE_HTML = 0; var YY_STATE_PINP = 1; var YY_STATE_DQSTRING = 2; var YY_STATE_SQSTRING = 3; var YY_STATE_SCRIPT = 4; var YY_STATE_BLOCKCOMMENT = 5; /* Smarty states */ var YYS_STATE_TAG = 101; var YYS_STATE_PARAM = 102; var YYS_STATE_QPARAM = 103; /* tokens */ var T_VAR = 0; var T_IDENT = 1; var T_FUNCTION = 2; var T_TOKEN = 3; var T_UNKNOWN = 4; var T_PINP_START = 5; var T_PINP_BLOCK = 6; var T_PINP_END = 7; var T_SPACE = 8; var T_DQUOTE = 9; var T_SQUOTE = 10; var T_ESCAPE = 11; var T_SPECIAL_CHAR = 12; var T_OPERATOR = 13; var T_SINGLE_COMMENTS = 14; var T_BLOCKCOMMENT = 15; var T_BLOCKCOMMENT_END = 16; var T_PHP_START = 17; var T_PHP_END = 18; var T_SCRIPT_START = 19; var T_SCRIPT_END = 20; var T_TAB = 21; var T_EMPTY_LINE = 22; /*Smarty tokens*/ var TS_SMARTY_START = 101; var TS_SMARTY_END = 102; var TS_KEYWORD = 103; var TS_ATTRIBUTE = 104; var TS_ATTRVALUE = 105; var TS_VAR = 106; var TS_ENDTAG = 107; var hLines = new Array(); var debug = 0; var scannerPos = 0; var hStyles = new Array(); var hStateStyles = new Array(); // May be there is the better place for it but now there initStyleDefault(); // Making object from keywords, it makes a big perfomance overhead in IE // and slightly better perfomance in Mozilla // You can define any amount of keyword groups // Words should match ([a-z0-9][a-z0-9_]*) and should be separated by spaces var hPHPKeywords = cacheKeywords( /// TODO: Add more keywords here "", "" // standart functions ); var hSmartyKeywords = cacheKeywords( "", "", "" ); function hLineToken(tokenType, tokenData) { this.type = tokenType; this.data = tokenData; this.reallength = tokenData.length; this.newState = -1; switch (this.data) { case '<': this.data='<'; break; case '>': this.data='>'; break; case '&': this.data='&'; break; } switch (this.type) { case T_PINP_START: case T_PHP_START: case T_PINP_END: case T_PHP_END: case T_SCRIPT_START: case T_SINGLE_COMMENTS: case T_BLOCKCOMMENT: case T_BLOCKCOMMENT_END: this.data = this.data.replace(/[<]/g, '<'); this.data = this.data.replace(/[>]/g, '>'); var myTabRest = scannerPos % 8; var addLength = 8 - myTabRest; this.data = this.data.replace(/[\t]/g, '        '); scannerPos += (addLength -1); break; case T_SCRIPT_END: this.data = this.data.replace(/[<]/g, '<'); this.data = this.data.replace(/[>]/g, '>'); case T_SPACE: this.data = this.data.replace(/[ ]/g, ' '); break; case T_TAB: var myTabRest = scannerPos % 8; var addLength = 8 - myTabRest; // alert( 'scannerPos ' + scannerPos + ' myTabRest ' + myTabRest + ' addLength ' + addLength); this.data = this.data.replace(/[\t]/g, ''); scannerPos += (addLength -1); break; case T_EMPTY_LINE: this.data = ''; break; } scannerPos += tokenData.length; } function getToken(sData) { var re, match; /* empty line, required for Firefox 1.5.x */ if (sData == "") { result = new hLineToken(T_EMPTY_LINE, ""); return result; } /* white space */ re = /^([ ]+)/; match = re.exec(sData); if (match) { result = new hLineToken(T_SPACE, match[1]); return result; } re = /^([\t])/; match = re.exec(sData); if (match) { result = new hLineToken(T_TAB, match[1]); return result; } /* Smarty tokens */ if(parseSmarty) { re = /^\{\$[a-z_][a-z0-9_]*/i; // Matches $smarty->left_delimiter match = re.exec(sData); if(match) { return new hLineToken(TS_SMARTY_START, match[0]); } re = /^\{/; // Matches $smarty->left_delimiter match = re.exec(sData); if(match) { return new hLineToken(TS_SMARTY_START, match[0]); } re = /^\}/; // Matches $smarty->right_delimiter match = re.exec(sData); if(match) { return new hLineToken(TS_SMARTY_END, match[0]); } //re = /^\/[a-z0-9][a-z0-9_]*/i; //match = re.exec(sData); //if (match) { // result = new hLineToken(TS_ENDTAG, match[0]); // return result; //} } /* end of smarty tokens */ /* variable or ident */ re = /^([$]|->)?([a-z0-9_][a-z0-9_]*)/i; match = re.exec(sData); if (match) { if (match[1]) { result = new hLineToken(T_VAR, match[0]); } else { result = new hLineToken(T_IDENT, match[2]); } return result; } /* single tokens */ re = /^([(){},"'\\])/; match = re.exec(sData); if (match) { switch (match[1]) { case '\\': result = new hLineToken(T_ESCAPE, match[1]); break; case '"': result = new hLineToken(T_DQUOTE, match[1]); break; case "'": result = new hLineToken(T_SQUOTE, match[1]); break; default: result = new hLineToken(T_SPECIAL_CHAR, match[1]); break; } return result; } re = /^((\<\!\-\-)|(\-\-\>))/; match = re.exec(sData); if (match) { if (match[2]) { result = new hLineToken(T_BLOCKCOMMENT, match[2]); } else { result = new hLineToken(T_BLOCKCOMMENT_END, match[3]); } return result; } /* comments */ re = /^(\/\/.*)/; match = re.exec(sData); if (match) { result = new hLineToken(T_SINGLE_COMMENTS, match[1]); return result; } /* php end tags */ re = /^(>)/; match = re.exec(sData); if (match) { result = new hLineToken(T_PHP_END, match[0]); return result; } re = /^([\-\+\.\*\/\=\%])/; match = re.exec(sData); if (match) { result = new hLineToken(T_OPERATOR, match[1]); return result; } /* pinp/php tags */ re = /^((<))/i; match = re.exec(sData); if (match) { if (match[3]) { result = new hLineToken(T_PINP_END, match[0]); } else if (match[2]) { result = new hLineToken(T_PINP_START, match[0]); } else { result = new hLineToken(T_PINP_START, match[0]); } return result; } /* javascript */ re = /^<(\/)?script[^>]*>/; match = re.exec(sData); if (match) { if (match[1]) { result = new hLineToken(T_SCRIPT_END, match[0]); } else { result = new hLineToken(T_SCRIPT_START, match[0]); } return result; } return new hLineToken(T_UNKNOWN, sData.charAt(0)); } function hLineParseString(sData) { var token; this.tokens = new Array(); scannerPos = 0; do { token = getToken(sData); this.tokens[this.tokens.length] = token; sData=sData.substring(token.reallength); } while (sData != ''); } function getElmSpan(token) { /// In the past styles were here var result = ''; // UPDATE: it is better to produce whole span tag here var cls = hStyles[token.type]; if(cls!='') result = ''; else result = ''; return result; } function getStateSpan(token) { /// Style span for states var result = ''; // UPDATE: it is better to produce whole span tag here var cls = hStateStyles[token.newState]; if(cls!='') result = ''; else result = ''; return result; } function hLineDoHighlight(callback) { var state = new Array(); var result = ''; if (this.lineNo) { /* load parent state */ state = state.concat(hLines[this.lineNo-1].getEndState()); // alert((this.lineNo-1)+':'+state.length); } for (var i = 0; i'; state.push(token); break; //End of smarty highlighting case T_PHP_START: case T_PINP_START: token.newState = YY_STATE_PINP; // We fix the new state here if (i == 1 && this.tokens[i-1].type == T_SPACE) { result = getStateSpan(token) + result + getElmSpan(token) + token.data + ''; } else { result += getStateSpan(token) + getElmSpan(token) + token.data + ''; } state.push(token); // But we still saving opening token break; case T_SCRIPT_START: token.newState = YY_STATE_SCRIPT; if (i == 1 && this.tokens[i-1].type == T_SPACE) { result = getStateSpan(token) + result + getElmSpan(token) + token.data + ''; } else { result += getStateSpan(token) + getElmSpan(token) + token.data + ''; } state.push(token); break; case T_BLOCKCOMMENT: if(token.newState<0) token.newState = YY_STATE_BLOCKCOMMENT; result += getStateSpan(token); result += token.data; state.push(token); break; default: result += token.data; break; } break; case YY_STATE_SCRIPT: switch (token.type) { case T_PHP_START: case T_PINP_START: token.newState = YY_STATE_PINP; if (i == 1 && this.tokens[i-1].type == T_SPACE) { result = getStateSpan(token) + result + getElmSpan(token) + token.data + ''; } else { result += getStateSpan(token) + getElmSpan(token) + token.data + ''; } state.push(token); break; case T_SCRIPT_END: result += getElmSpan(token)+token.data+''; result += ''; state.pop(); break; default: result += token.data; break; } break; case YY_STATE_PINP: switch (token.type) { case T_IDENT: var lower_data = token.data.toLowerCase(); var t = hPHPKeywords[lower_data]; if(typeof(t)!='undefined') { result += ''; } else { result += getElmSpan(token); } result += token.data + ''; break; case T_DQUOTE: token.newState = YY_STATE_DQSTRING; case T_SQUOTE: if(token.newState<0) token.newState = YY_STATE_SQSTRING; case T_BLOCKCOMMENT: if(token.newState<0) token.newState = YY_STATE_BLOCKCOMMENT; result += getStateSpan(token); result += token.data; state.push(token); break; case T_PHP_END: case T_PINP_END: result += getElmSpan(token)+token.data+''; result += ''; state.pop(); break; case T_VAR: case T_OPERATOR: case T_SPECIAL_CHAR: case T_SINGLE_COMMENTS: result += getElmSpan(token); result += token.data; result += ''; break; default: result += token.data; break; } break; case YY_STATE_BLOCKCOMMENT: switch (token.type) { case T_BLOCKCOMMENT_END: result += token.data+''; state.pop(); break; default: result += token.data; break; } break; case YY_STATE_DQSTRING: switch (token.type) { case T_DQUOTE: result += token.data+''; state.pop(); break; case T_ESCAPE: result += token.data; token = this.tokens[++i]; result += token.data; break; case T_VAR: result += getElmSpan(token); result += token.data; result += ''; break; default: result += token.data; break; } break; case YY_STATE_SQSTRING: switch (token.type) { case T_SQUOTE: result += token.data+''; state.pop(); break; case T_ESCAPE: result += token.data; token = this.tokens[++i]; result += token.data; break; default: result += token.data; break; } break; case YYS_STATE_TAG: switch (token.type) { case TS_SMARTY_END: result += getElmSpan(token) + token.data + ''; state.pop(); break; case TS_ENDTAG: var t = hSmartyKeywords[token.data.substr(1)]; if(t==1) { // Only the first group has closing tags result += '' + token.data + ''; } else { result += token.data; } break; case T_VAR: result += getElmSpan(token) + token.data + ''; //result += token.data; break; case T_IDENT: var t = hSmartyKeywords[token.data]; if(typeof(t)!='undefined' /*&& (t>3 || // first three groups of keywords may appear (i>0 && this.tokens[i-1].type==TS_SMARTY_START)) // immediately after TS_SMARTY_START token only */) { result += '' + token.data + ''; } else { result += token.data; } break; default: result += token.data; break; } break; default: result += token.data; break; } } // alert(this.lineNo+'::'+this.tokens.length+'::'+result); } var stateChanged = 0; if (state.length != this.getEndState().length) { stateChanged = 1; } for (i=state.length-1; i>=0; i--) { if (!stateChanged && state[i].type!=this.getEndState()[i].type) { stateChanged = 1; } if (!state[i].noMultiLine) { result += getElmSpan(state[i]); } } /* report update */ if (callback) { // alert(this.lineNo+"::"+result); if (result) { // alert(this.lineNo+': 2 eol chars: "'+result.substr(result.length-2)+'"'); } callback(this.lineNo, result); } this.setEndState(state); if (stateChanged) { if (debug) alert('updating: '+this.lineNo+1); /// This makes a stack overflow /// so we are returning 'true' that means next line must be updated //~ hLines[this.lineNo+1].doHighlight(callback); return true; } /// Next line need not be updated return false; } function hLineSetEndState(newEndState) { // alert(this.lineNo+': new endstate: '+newEndState.length); var frop=new Array(); if (newEndState.length) { // alert(':'+newEndState.toString()+':'); this.endState=frop.concat(newEndState); //newEndState; //.toSource(); } else { this.endState=new Array(); } /* var line = hLines[2]; if (line) { alert(this.lineNo+'->'+line.endState.length); } */ } function hLineGetEndState() { return this.endState; } function hLineRemove() { if (this.lineNo < (hLines.length-1)) { var len = hLines.length-1; for (var i=this.lineNo; ilineNo; i--) { hLines[i] = hLines[i-1]; hLines[i].lineNo = i; } } hLines[lineNo] = this; this.tokens = new Array(); this.setEndState = hLineSetEndState; this.setEndState(new Array()); this.getEndState = hLineGetEndState; this.remove = hLineRemove; this.parseString = hLineParseString; this.parseString(lineString); if (debug) alert(this.lineNo); this.doHighlight = hLineDoHighlight; } function highlightUpdateLine(lineNo, lineContent, callback) { // alert('update line: '+lineNo+'::'+lineContent); hLines[lineNo].parseString(lineContent); while(hLines[lineNo].doHighlight(callback) && lineNo < hLines.length-1) lineNo++; } function highlightDeleteLine(lineNo, callback) { // alert('remove line: '+lineNo); line = hLines[lineNo]; line.remove(); if (hLines.length && (lineNo < hLines.length)) while(hLines[lineNo].doHighlight(callback) && lineNo < hLines.length-1) lineNo++; } function highlightReset() { hLines = new Array(); new hLine(0, ''); } function highlightInsertLine(lineNo, lineContent, callback) { if (lineNo) { lineNo -= 1; } // alert('insert at: '+lineNo+'::'+lineContent); line = new hLine(lineNo, lineContent); while(line.doHighlight(callback) && lineNo < hLines.length-1) lineNo++; } function highlightAppendLine(lineNo, lineContent, callback) { // alert('append at: '+(lineNo+1)+'::'+lineContent); line = new hLine(lineNo+1, new String(lineContent)); while(hLines[lineNo].doHighlight(callback) && lineNo < hLines.length-1) lineNo++; } function initStyleDefault() { hStyles[T_PINP_START] = hStyles[T_PINP_END] = hStyles[T_PHP_START] = //'h_pinp'; hStyles[T_PHP_END] = //'h_pinp'; hStyles[T_VAR] = 'h_var'; hStyles[T_IDENT] = 'h_ident'; hStyles[T_DQUOTE] = 'h_doublequote'; hStyles[T_SQUOTE] = 'h_singlequote'; hStyles[T_SPECIAL_CHAR] = 'h_special_char'; hStyles[T_OPERATOR] = 'h_operator'; hStyles[T_SINGLE_COMMENTS] = 'h_single_comments'; hStyles[T_BLOCKCOMMENT] = 'h_blockcomment'; hStyles[TS_SMARTY_START] = 'h_smartymarkers'; hStyles[TS_SMARTY_END] = 'h_smartymarkers'; hStateStyles[YY_STATE_HTML] = ''; hStateStyles[YY_STATE_PINP] = 'h_pinp_block'; hStateStyles[YY_STATE_DQSTRING] = 'h_doublequote'; hStateStyles[YY_STATE_SQSTRING] = 'h_singlequote'; hStateStyles[YY_STATE_BLOCKCOMMENT] = 'h_blockcomment'; hStateStyles[YY_STATE_SCRIPT] = 'h_scriptblock'; hStateStyles[YYS_STATE_TAG] = 'h_smartytag'; } function cacheKeywords() { var res = new Object(); for(var i=0;i