diff --git a/js/App.js b/js/App.js index 066bc2aad..f5b174239 100644 --- a/js/App.js +++ b/js/App.js @@ -13,7 +13,7 @@ var WikiStore = require("./WikiStore.js").WikiStore, Tiddler = require("./Tiddler.js").Tiddler, tiddlerInput = require("./TiddlerInput.js"), tiddlerOutput = require("./TiddlerOutput.js"), - WikiTextProcessor = require("./WikiTextProcessor.js").WikiTextProcessor, + WikiTextParser = require("./WikiTextParser.js").WikiTextParser, JavaScriptParser = require("./JavaScriptParser.js").JavaScriptParser, Navigators = require("./Navigators.js").Navigators, StoryNavigator = require("./StoryNavigator.js").StoryNavigator; @@ -24,8 +24,8 @@ var App = function() { this.isBrowser = typeof window !== "undefined"; // Create the main store this.store = new WikiStore(); - // Register the wikitext processor - this.store.registerTextProcessor("text/x-tiddlywiki",new WikiTextProcessor({ + // Register the wikitext parser + this.store.registerParser("text/x-tiddlywiki",new WikiTextParser({ store: this.store })); // Register the standard tiddler serializers and deserializers diff --git a/js/WikiStore.js b/js/WikiStore.js index 02bc40b32..a9965fc1b 100755 --- a/js/WikiStore.js +++ b/js/WikiStore.js @@ -19,7 +19,7 @@ Available options are: var WikiStore = function WikiStore(options) { options = options || {}; this.tiddlers = {}; - this.textProcessors = {}; + this.parsers = {}; this.tiddlerSerializers = {}; this.tiddlerDeserializers = {}; this.sandbox = options.sandbox; @@ -28,8 +28,8 @@ var WikiStore = function WikiStore(options) { }); }; -WikiStore.prototype.registerTextProcessor = function(type,processor) { - this.textProcessors[type] = processor; +WikiStore.prototype.registerParser = function(type,parser) { + this.parsers[type] = parser; }; WikiStore.prototype.registerTiddlerSerializer = function(extension,mimeType,serializer) { @@ -255,12 +255,12 @@ WikiStore.prototype.listTiddlers = function(type,template,emptyMessage) { */ WikiStore.prototype.parseText = function(type,text) { - var processor = this.textProcessors[type]; - if(!processor) { - processor = this.textProcessors["text/x-tiddlywiki"]; + var parser = this.parsers[type]; + if(!parser) { + parser = this.parsers["text/x-tiddlywiki"]; } - if(processor) { - return processor.parse(text); + if(parser) { + return parser.parse(text); } else { return null; } diff --git a/js/WikiTextParseTree.js b/js/WikiTextParseTree.js new file mode 100644 index 000000000..8287ddc24 --- /dev/null +++ b/js/WikiTextParseTree.js @@ -0,0 +1,250 @@ +/*\ +title: js/WikiTextParseTree.js + +Compile a wikitext parse tree into a JavaScript function that renders the required +representation of the tree. + +\*/ +(function(){ + +/*jslint node: true */ +"use strict"; + +var ArgParser = require("./ArgParser.js").ArgParser, + JavaScriptParseTree = require("./JavaScriptParseTree.js").JavaScriptParseTree, + utils = require("./Utils.js"), + util = require("util"); + +// Intialise the parse tree object +var WikiTextParseTree = function(tree,store) { + this.tree = tree; + this.store = store; +}; + +WikiTextParseTree.prototype.render = function(type,treenode,store,title) { + /*jslint evil: true */ + var code = this.compile(type,treenode); + var fn = eval(code); + var tiddler = store.getTiddler(title); + return fn(tiddler,store,utils); +}; + +// Compile the parse tree into a JavaScript function that returns the required +// representation of the tree +WikiTextParseTree.prototype.compile = function(type,treenode) { + treenode = treenode || this.tree; + this.output = []; + if(type === "text/html") { + this.compileSubTreeHtml(treenode); + } else if(type === "text/plain") { + this.compileSubTreePlain(treenode); + } else { + return null; + } + // And then wrap the javascript tree and render it back into JavaScript code + var parseTree = this.store.jsParser.createTree( + [ + { + type: "Function", + name: null, + params: ["tiddler","store","utils"], + elements: [ + { + type: "ReturnStatement", + value: { + type: "FunctionCall", + name: { + type: "PropertyAccess", + base: { + type: "ArrayLiteral", + elements: this.output + }, + name: "join" + }, + "arguments": [ { + type: "StringLiteral", + value: "" + } + ] + } + } + ] + } + ]); + return parseTree.render(); +}; + +WikiTextParseTree.prototype.pushString = function(s) { + var last = this.output[this.output.length-1]; + if(this.output.length > 0 && last.type === "StringLiterals") { + last.value.push(s); + } else if (this.output.length > 0 && last.type === "StringLiteral") { + last.type = "StringLiterals"; + last.value = [last.value,s]; + } else { + this.output.push({type: "StringLiteral", value: s}); + } +}; + +WikiTextParseTree.prototype.compileMacroCall = function(type,name,params) { + var me = this, + macro = this.store.macros[name]; + if(macro) { + var args = new ArgParser(params,{defaultName: "anon"}), + paramsProps = {}; + var insertParam = function(name,arg) { + if(arg.evaluated) { + paramsProps[name] = me.store.jsParser.parse(arg.string).tree.elements[0]; + } else { + paramsProps[name] = {type: "StringLiteral", value: arg.string}; + } + }; + for(var m in macro.params) { + var param = macro.params[m]; + if("byPos" in param && args.byPos[param.byPos]) { + insertParam(m,args.byPos[param.byPos].v); + } else if("byName" in param) { + var arg = args.getValueByName(m); + if(!arg && param.byName === "default") { + arg = args.getValueByName("anon"); + } + if(arg) { + insertParam(m,arg); + } + } + } + var macroCall = { + type: "FunctionCall", + name: { + type: "Function", + name: null, + params: ["params"], + elements: []}, + "arguments": [ { + type: "ObjectLiteral", + properties: [] + }] + }; + macroCall.name.elements = macro.code[type].tree.elements; + for(m in paramsProps) { + macroCall["arguments"][0].properties.push({ + type: "PropertyAssignment", + name: m, + value: paramsProps[m] + }); + } + this.output.push(macroCall); + } else { + this.pushString("Unknown macro '" + name + "'"); + } +}; + +WikiTextParseTree.prototype.compileElementHtml = function(element, options) { + options = options || {}; + var tagBits = [element.type]; + if(element.attributes) { + for(var a in element.attributes) { + var r = element.attributes[a]; + if(a === "style") { + var s = []; + for(var t in r) { + s.push(t + ":" + r[t] + ";"); + } + r = s.join(""); + } + tagBits.push(a + "=\"" + utils.htmlEncode(r) + "\""); + } + } + this.pushString("<" + tagBits.join(" ") + (options.selfClosing ? " /" : "")); + if(options.insertAfterAttributes) { + this.pushString(" "); + this.output.push(options.insertAfterAttributes); + } + this.pushString(">"); + if(!options.selfClosing) { + if(element.children) { + this.compileSubTreeHtml(element.children); + } + this.pushString(""); + } +}; + +WikiTextParseTree.prototype.compileSubTreeHtml = function(tree) { + for(var t=0; t this.nextMatch) @@ -52,18 +78,18 @@ WikiTextParser.prototype.subWikifyUnterm = function(output) { this.matchStart = ruleMatch.index; this.matchLength = ruleMatch[0].length; this.matchText = ruleMatch[0]; - this.nextMatch = this.processor.rulesRegExp.lastIndex; + this.nextMatch = this.rulesRegExp.lastIndex; // Figure out which rule matched and call its handler var t; for(t=1; t"); - if(!selfClosing) { - if(element.children) { - renderSubTree(element.children); - } - output.push(""); - } - }; - renderSubTree = function(tree) { - for(var t=0; t>", - filter = args.getValueByName("filter",null), - tiddlers, - lastGroup = "", - ul, - last = 0, - t; - limit = limit ? parseInt(limit,10) : null; - template = template ? this.store.getTiddler(template) : null; - if(template) { - templateType = template.fields.type; - templateText = template.fields.text; - } - groupTemplate = groupTemplate ? this.store.getTiddler(groupTemplate) : null; - if(groupTemplate) { - groupTemplateType = groupTemplate.fields.type; - groupTemplateText = groupTemplate.fields.text; - } - if(filter) { - // Filtering not implemented yet - tiddlers = this.store.getTitles(field,"excludeLists"); - } else { - tiddlers = this.store.getTitles(field,"excludeLists"); - } - if(limit !== null) { - last = tiddlers.length - Math.min(tiddlers.length,limit); - } - for(t=tiddlers.length-1; t>=last; t--) { - var tiddler = tiddlers[t], - theGroupParseTree = this.store.parseText(groupTemplateType,groupTemplateText), - theGroup = theGroupParseTree.render("text/plain",theGroupParseTree.children,this.store,tiddler); - if(theGroup !== "") { - if(ul === undefined || theGroup !== lastGroup) { - ul = {type: "ul", attributes: {"class": "timeline"}, children: []}; - macroNode.output.push(ul); - ul.children.push({type: "li", attributes: {"class": "listTitle"}, children: [{type: "text", value: theGroup}]}); - lastGroup = theGroup; - } - var item = { - type: "li", - attributes: { - "class": "listLink"}, - children: [ { - type: "context", - tiddler: tiddler, - children: [] - }]}; - ul.children.push(item); - item.children[0].children = this.store.parseText(templateType,templateText).children; - } - } - this.executeMacros(macroNode.output,title); - } - }, - list: { - argOptions: {defaultName:"type"}, - handler: function(macroNode,args,title) { - var type = args.getValueByName("type","all"), - template = args.getValueByName("template",null), - templateType = "text/x-tiddlywiki", templateText = "<>", - emptyMessage = args.getValueByName("emptyMessage",null); - // Get the template to use - template = template ? this.store.getTiddler(template) : null; - if(template) { - templateType = template.fields.type; - templateText = template.fields.text; - } - // Get the handler and the tiddlers - var handler = WikiTextRenderer.macros.list.types[type]; - handler = handler || WikiTextRenderer.macros.list.types.all; - var tiddlers = handler.call(this); - // Render them as a list - var ul = {type: "ul", children: []}; - for(var t=0; t 0) { - macroNode.output.push(ul); - this.executeMacros(macroNode.output,title); - } else if (emptyMessage) { - macroNode.output.push({type: "text", value: emptyMessage}); - } - }, - types: { - all: function() { - return this.store.getTitles("title","excludeLists"); - }, - missing: function() { - return this.store.getMissingTitles(); - }, - orphans: function() { - return this.store.getOrphanTitles(); - }, - shadowed: function() { - return this.store.getShadowTitles(); - }, - touched: function() { - // Server syncing isn't implemented yet - return []; - }, - filter: function() { - // Filters aren't implemented yet - return []; - } - } - }, - slider: { - handler: function(macroNode,args,title) { - } - }, - tabs: { - handler: function(macroNode,args,title) { - } - }, - tag: { - handler: function(macroNode,args,title) { - } - }, - tagging: { - handler: function(macroNode,args,title) { - } - }, - tags: { - handler: function(macroNode,args,title) { - } - }, - tiddler: { - argOptions: {defaultName:"name",cascadeDefaults:true}, - handler: function(macroNode,args,title) { - var targetTitle = args.getValueByName("name",null), - withTokens = args.getValuesByName("with",[]), - tiddler = this.store.getTiddler(targetTitle), - text = this.store.getTiddlerText(targetTitle,""), - t; - for(t=0; t