/* * Copyright (c) 2005-2008 * Authors: KSS Project Contributors (see doc/CREDITS.txt) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /* Tokens of the KSS parser */ kukit.kssp = new function() { /// MODULE START var kssp = this; /* Tokens */ kssp.openComment = kukit.tk.mkToken('openComment', "\/\*"); kssp.closeComment = kukit.tk.mkToken('closeComment', "\*\/"); kssp.openBrace = kukit.tk.mkToken('openBrace', "{"); kssp.closeBrace = kukit.tk.mkToken('closeBrace', "}"); kssp.openBracket = kukit.tk.mkToken('openBracket', "["); kssp.closeBracket = kukit.tk.mkToken('closeBracket', "]"); kssp.openParent = kukit.tk.mkToken('openParent', "("); kssp.closeParent = kukit.tk.mkToken('closeParent', ")"); kssp.semicolon = kukit.tk.mkToken('semicolon', ";"); kssp.colon = kukit.tk.mkToken('colon', ":"); kssp.quote = kukit.tk.mkToken('quote', "'"); kssp.dquote = kukit.tk.mkToken('dquote', '"'); kssp.backslash = kukit.tk.mkToken('backslash', '\x5c'); kssp.comma = kukit.tk.mkToken('comma', ","); kssp.equals = kukit.tk.mkToken('equals', "="); /* Parsers */ /* Helpers */ var _emitAndReturn = function() { return this.emitAndReturn(); }; var _mkEmitAndReturnToken = function(klass) { return function() { var token = new klass(this.cursor); return this.emitAndReturn(token); }; }; var _mkReturnToken = function(klass) { return function() { return new klass(this.cursor); }; }; var _returnComment = function() { return new kssp.Comment(this.cursor, kssp.openComment); }; var _returnString = function() { return new kssp.String(this.cursor, kssp.quote); }; var _returnString2 = function() { return new kssp.String2(this.cursor, kssp.dquote); }; var _returnMethodArgs = function() { return new kssp.MethodArgs(this.cursor, kssp.openParent); }; var _returnBackslashed = function() { return new kssp.Backslashed(this.cursor, kssp.backslash); }; /* * class Document */ var _Document = function() { this.process = function() { this.eventRules = []; // Parse all tokens (including first and last) var context = {'nextTokenIndex': 0}; while (context.nextTokenIndex < this.result.length) { this.digestTxt(context, kukit.tk.Fraction, kssp.Comment); var key = context.txt; if (! key) { break; } this.expectToken(context, kssp.Block); var block = context.token; var rules = block.parseSelectors(key); this.addRules(rules); } this.result = []; this.txt = ''; }; this.addRules = function(rules) { // Create the event rules. for(var i=0; i-: // evt---: ;;; if (splitkey.length < 3) { ;;; kukit.E = 'Wrong rule key : "' + key + '". '; ;;; kukit.E += 'KSS rule key must be "-"'; ;;; kukit.E += ' or "--" or '; ;;; kukit.E += '"evt--" or '; ;;; kukit.E += '"evt---".'; ;;; this.emitError(kukit.E); ;;; } var eventNamespace; var eventName; var eventKey; var eventFullName; if (splitkey.length == 3) { // evt--: eventName = splitkey[1]; eventKey = splitkey[2]; eventFullName = eventName; } else { // evt---: eventNamespace = splitkey[1]; eventName = splitkey[2]; eventKey = splitkey[3]; eventFullName = eventNamespace + '-' + eventName; } // preprocess values var allowedReturnTypes; ;;; allowedReturnTypes = {string: true}; ;;; kukit.E = 'event parameter [' + key + ']'; var value = this.preprocessValues(values, allowedReturnTypes, kukit.E).string; ;;; if (value.isMethod != false) { ;;; kukit.E = 'Wrong value for key [' + key + '] : '; ;;; kukit.E += 'value providers are not '; ;;; kukit.E += 'allowed as value for '; ;;; kukit.E += 'evt-[-]- keys.'; ;;; this.emitError(kukit.E); ;;; } var eventParameters = this.eventFullNames[eventFullName]; if (typeof(eventParameters) == 'undefined') { this.eventFullNames[eventFullName] = {}; eventParameters = this.eventFullNames[eventFullName]; } eventParameters[eventKey] = value.txt; }; this.addActionDeclaration = function(key, splitkey, values) { // action-server: // action-client: // action-client: - // action-cancel: // action-cancel: - ;;; if (splitkey.length != 2) { ;;; kukit.E = 'Wrong key [' + key + '] : '; ;;; kukit.E += 'action- keys can have only one dash.'; ;;; this.emitError(kukit.E); ;;; } var atab = {'server': 'S', 'client': 'C', 'cancel': 'X'}; var actionType = atab[splitkey[1]]; ;;; if (! actionType) { ;;; kukit.E = 'Wrong key [' + key + '] : '; ;;; kukit.E += 'qualifier in action- keys must be '; ;;; kukit.E += '"server" or "client" or "cancel".'; ;;; this.emitError(kukit.E); ;;; } // preprocess values var allowedReturnTypes; ;;; if (actionType == 'S') { ;;; // action-server ;;; allowedReturnTypes = {string: true, formquery: true, url: true}; ;;; } else if (actionType == 'C') { ;;; // action-client ;;; allowedReturnTypes = {string: true, selection: true, alias: true}; ;;; } else { ;;; // action-cancel ;;; allowedReturnTypes = {string: true}; ;;; } ;;; kukit.E = 'action definition [' + key + ']'; var valuesByReturnType = this.preprocessValues(values, allowedReturnTypes, kukit.E); var value = valuesByReturnType.string; // ;;; if (value.isMethod != false) { ;;; kukit.E = 'Wrong value for key [' + key + '] : '; ;;; kukit.E += 'value providers are not '; ;;; kukit.E += 'allowed for action- keys.'; ;;; this.emitError(kukit.E); ;;; } ;;; // force value to be or - ;;; var splitvalue = value.txt.split('-'); ;;; if (splitvalue.length > 2) { ;;; kukit.E = 'Wrong value for key [' + key + '] : '; ;;; kukit.E += 'value must be or '; ;;; kukit.E += '- for action- keys.'; ;;; this.emitError(kukit.E); ;;; } // set it var action = this.actions.getOrCreateAction(value.txt, valuesByReturnType); if (actionType == 'X' && action.type != null) { // action-cancel, and the action existed already in the same block: // we delete it straight ahead this.actions.deleteAction(value.txt); } else { // any other qualifier then delete, // or action-cancel but there has been no action defined yet in the block: // set the action type. // (Remark: in case of action-cancel we set action's type to X, and // the cancellation will possibly happen later, during the merging.) action.setType(actionType); } }; this.addActionError = function(action, key, values) { // -error: // default-error: // // This can only accept string. var allowedReturnTypes; ;;; allowedReturnTypes = {string: true}; ;;; kukit.E = 'action error parameter [' + key + ']'; var value = this.preprocessValues(values, allowedReturnTypes, kukit.E).string; ;;; // It cannot be a provider, it must be a real string. ;;; if (value.isMethod == true) { ;;; kukit.E = 'Wrong value for key [' + key + '] : '; ;;; kukit.E += 'value providers are not '; ;;; kukit.E += 'allowed for -error keys.'; ;;; this.emitError(kukit.E); ;;; } action.setError(value.txt); // also create the action for the error itself. var err_action = this.actions.getOrCreateAction(value.txt, {}); err_action.setType('E'); }; this.addActionParameter = function(action, key, values) { // -: // default-: // // value may be either txt or method parms, // and they get stored with the wrapper. // // Check the syntax of the value at this point. // This will also set the value providers on the value // (from check). // var value; if (key.substr(0, 3) == 'kss') { ;;; // Special selector types can have only one value ;;; if (values.length != 1) { ;;; kukit.E = 'Must have exactly one value, and got [' + values.length; ;;; kukit.E += '] in the kss action parameter [' + key + '].'; ;;; this.emitError(kukit.E); ;;; } value = values[0]; ;;; // kss special parameter need special checking of the strings. ;;; // (not needed in production mode, since we have the value already) ;;; var allowedReturnTypes = {}; ;;; if (key == 'kssSelector') { ;;; // for kssSelector, one of string or formquery expected ;;; allowedReturnTypes = {string: true, selection: true}; ;;; } else if (key == 'kssSubmitForm') { ;;; // for kssSubmitForm string or formquery expected ;;; allowedReturnTypes = {string: true, formquery: true}; ;;; } else if (key == 'kssUrl') { ;;; // for kssUrl string or url expected ;;; allowedReturnTypes = {string: true, url: true}; ;;; } ;;; // We ignore actual results here, and just check. ;;; kukit.E = 'kss action parameter [' + key + ']'; // Call preprocessValues in both production and development mode: // it is always needed, since it calls check() on the value. // Last parameter is true: means we do _not_ require the existence // of a string type. this.preprocessValues(values, allowedReturnTypes, kukit.E, true); } else { // Normal selectors: can have more values // check its return types var allowedReturnTypes; ;;; allowedReturnTypes = {string: true}; ;;; kukit.E = 'action parameter [' + key + ']'; var valuesByReturnType = this.preprocessValues(values, allowedReturnTypes, kukit.E); value = valuesByReturnType.string; } // store the (main, string) value action.parms[key] = value; }; this.addDeclaration = function(key, values) { // values contains a list of arguments (KssTextValue or KssMethodValue) // // the key looks like this: // // evt--: // evt---: // // action-server: // action-client: // action-client: - // action-cancel: // action-cancel: - // // -: // --: // -error: // --error: // // default-: // default-error: // var splitkey = key.split('-'); ;;; if (splitkey.length < 2 || splitkey.length > 4) { ;;; kukit.E = 'Wrong rule key : "' + key + '". '; ;;; kukit.E += 'KSS rule key must be "-" or '; ;;; kukit.E += '"--" or '; ;;; kukit.E += '"evt--" or '; ;;; kukit.E += '"evt---".'; ;;; this.emitError(kukit.E); ;;; } // Preprocess the values // var name = splitkey[0]; if (name == 'evt') { this.addEventDeclaration(key, splitkey, values); } else if (name == 'action') { this.addActionDeclaration(key, splitkey, values); } else { // -: // --: // -error: // --error: // default-: // default-error: var actionName; var actionKey; if (splitkey.length == 2) { // -: // -error: // default-: // default-error: actionName = splitkey[0]; actionKey = splitkey[1]; } else { // --: // --error: actionName = splitkey[0] + '-' + splitkey[1]; actionKey = splitkey[2]; } var action = this.actions.getOrCreateAction(actionName, {}); if (actionKey == 'error') { this.addActionError(action, key, values); } else { this.addActionParameter(action, actionKey, values); } } }; this.preprocessValues = function(values, allowedReturnTypes, errInfo, noStringRequired) { // allowedReturnTypes is a dict keyed by the returnType, containing true as value. // key is only used for the error reporting // This will also call check on all the value names! // noStringRequired is set to true at the kss special parameters. All other // occasions require at least a string to be present, so we check for that too. var valuesByReturnType = {}; for (var i=0; i 0) { // Produce all strings except the last one for (var i=0; i selector:name(id) * KSS method selector: (has no spaces in it) * document:name(id) or behaviour:name(id) */ var _KssSelectors = function() { this.process = function() { this.selectors = []; // Parse all tokens (including first and last) var context = {'nextTokenIndex': 0}; while (context.nextTokenIndex < this.result.length) { this.digestTxt(context, kukit.tk.Fraction, kssp.Comment, kssp.String, kssp.String2); var cursor = new kukit.tk.Cursor(context.txt + ' '); var parser = new kssp.KssSelector(cursor, null, true); this.selectors.push(parser.kssSelector); if (context.nextTokenIndex == this.result.length) break; this.expectToken(context, kssp.comma); if (context.nextTokenIndex == this.result.length) { ;;; kukit.E = 'Wrong event selector : trailing comma'; this.emitError(kukit.E); } }; this.result = []; this.txt = ''; }; }; kssp.KssSelectors = kukit.tk.mkParser('kssselectors', { "'": function() { return new kssp.StringInSelector(this.cursor, kssp.quote); }, '"': function() { return new kssp.String2InSelector(this.cursor, kssp.dquote); }, ",": _mkReturnToken(kssp.comma), "{": _emitAndReturn, "\/\*": _returnComment }, _KssSelectors ); /* * class KssSelector * * embedded parser to parse the selector * KSS event selector: (has spaces in it) * selector:name(id) * selector:name(pprov(id)) * kss method selector: (has no spaces in it) * document:name(id) or behaviour:name(id) * document:name(pprov(id)) or behaviour:name(pprov(id)) */ var _KssSelector = function() { this.process = function() { var name; var namespace = null; var id = null; var tokenIndex = this.result.length - 1; // Find the method parms and calculate the end of css parms. (RL) var cycle = true; while (cycle && tokenIndex >= 0) { var token = this.result[tokenIndex]; switch (token.symbol) { case kukit.tk.Fraction.prototype.symbol: { // if all spaces, go to previous one if (token.txt.match(/^[\r\n\t ]*$/) != null) { tokenIndex -= 1; } else { ;;; kukit.E = 'Wrong event selector : missing event '; ;;; kukit.E += 'qualifier : '; ;;; kukit.E += 'or :().'; this.emitError(kukit.E); } } break; case kssp.Comment.prototype.symbol: { tokenIndex -= 1; } break; default: { cycle = false; } break; } } // Now we found the token that must be . tokenIndex -= 2; if (tokenIndex < 0 || (this.result[tokenIndex+2].symbol != kssp.EventValue.prototype.symbol) || (this.result[tokenIndex+1].symbol != kssp.colon.prototype.symbol) || (this.result[tokenIndex].symbol != kukit.tk.Fraction.prototype.symbol)) { ;;; kukit.E = 'Wrong event selector : missing event qualifier '; ;;; kukit.E += ': or :().'; this.emitError(kukit.E); } // See that the last fraction does not end with space. var lasttoken = this.result[tokenIndex]; var commatoken = this.result[tokenIndex+1]; var pseudotoken = this.result[tokenIndex+2]; var txt = lasttoken.txt; if (txt.match(/[\r\n\t ]$/) != null) { ;;; kukit.E = 'Wrong event selector :'; ;;; kukit.E += ' space before the colon.'; this.emitError(kukit.E); } if (! pseudotoken.value.methodName) { ;;; kukit.E = 'Wrong event selector :'; ;;; kukit.E += ' event name cannot have spaces.'; this.emitError(kukit.E); } css = this.cursor.text.substring(this.startpos, commatoken.startpos); // Decide if we have an event or a method selector. // We have a method selector if a single word "document" or "behaviour". var singleword = css.replace(/[\r\n\t ]/g, ' '); if (singleword && singleword.charAt(0) == ' ') { singleword = singleword.substring(1); } var isEvent = (singleword != 'document' && singleword != 'behaviour'); if (! isEvent) { // just store the single word, in case of event selectors css = singleword; } // create the selector. var id = null; var ppid = null; if (pseudotoken.value.arg) { // We have something in the parentheses after the event name. if (pseudotoken.value.arg.isMethod) { // we have a param provider here. Just store. ppid = pseudotoken.value.arg; // Check its syntax too. ppid.check(kukit.pprovidersGlobalRegistry); } else { // just an id. Express in txt. id = pseudotoken.value.arg.txt; } } var name = pseudotoken.value.methodName; var splitname = name.split('-'); var namespace = null; if (splitname.length > 2) { ;;; kukit.E = 'Wrong event selector [' + name + '] : '; ;;; kukit.E += 'qualifier should be : or '; ;;; kukit.E += ':-.'; this.emitError(kukit.E); } else if (splitname.length == 2) { name = splitname[1]; namespace = splitname[0]; } // Protect the error for better logging ;;; try { this.kssSelector = new kukit.rd.KssSelector(isEvent, css, name, namespace, id, ppid, kukit.eventsGlobalRegistry); ;;; } catch(e) { ;;; if (e.name == 'KssSelectorError') { ;;; // Log the message ;;; this.emitError(e.toString()); ;;; } else { ;;; throw e; ;;; } ;;; }; this.txt = ''; this.result = []; }; }; kssp.KssSelector = kukit.tk.mkParser('kssselector', { ":": function() { return [new kssp.colon(this.cursor), new kssp.EventValue(this.cursor)]; }, "{": _emitAndReturn, "\/\*": _returnComment }, _KssSelector ); /* * class KssRuleProcessor * * Rule processor that interfaces with kukit core */ kssp.KssRuleProcessor = function(href) { this.initialize = function() { this.href = href; this.loaded = false; this.rules = []; }; this.load = function() { // Opera does not support getDomDocument.load, so we use XMLHttpRequest var domDoc = new XMLHttpRequest(); domDoc.open("GET", this.href, false); domDoc.send(null); this.txt = domDoc.responseText; this.loaded = true; }; this.parse = function() { ;;; try { //Build a parser and parse the text into it var cursor = new kukit.tk.Cursor(this.txt); var parser = new kssp.Document(cursor, null, true); // Store event rules in the common list for (var i=0; i