From 12b471b8fb9f08fb46dae47acb03190c6a4c97ab Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 18 Jun 2013 15:37:19 +0100 Subject: [PATCH] Extend the HTML rendering mechanism to support attributes specified as macro invocations --- core/modules/parsers/wikiparser/rules/html.js | 8 ++--- core/modules/rendertree/renderers/element.js | 30 ++++++++++------ .../modules/rendertree/renderers/macrocall.js | 35 +------------------ core/modules/rendertree/wikirendertree.js | 34 ++++++++++++++++++ .../test/tiddlers/tests/test-html-parser.js | 16 ++++----- editions/test/tiddlers/tests/test-wikitext.js | 7 +++- .../tw5.com/tiddlers/concepts/WikiText.tid | 14 ++++++++ 7 files changed, 87 insertions(+), 57 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js index 52acbe663..de130ec1f 100644 --- a/core/modules/parsers/wikiparser/rules/html.js +++ b/core/modules/parsers/wikiparser/rules/html.js @@ -190,13 +190,13 @@ exports.parseMacroParameter = function(source,pos) { }; /* -Look for a macro invocation. Returns null if not found, or {type: "macro-invocation", name:, parameters:, start:, end:} +Look for a macro invocation. Returns null if not found, or {type: "macrocall", name:, parameters:, start:, end:} */ exports.parseMacroInvocation = function(source,pos) { var node = { - type: "macro-invocation", + type: "macrocall", start: pos, - parameters: [] + params: [] } // Define our regexps var reMacroName = /([^\s>"'=]+)/g; @@ -218,7 +218,7 @@ exports.parseMacroInvocation = function(source,pos) { // Process parameters var parameter = this.parseMacroParameter(source,pos); while(parameter) { - node.parameters.push(parameter); + node.params.push(parameter); pos = parameter.end; // Get the next parameter parameter = this.parseMacroParameter(source,pos); diff --git a/core/modules/rendertree/renderers/element.js b/core/modules/rendertree/renderers/element.js index 3e2ef93fe..9802d7c09 100644 --- a/core/modules/rendertree/renderers/element.js +++ b/core/modules/rendertree/renderers/element.js @@ -95,20 +95,30 @@ var ElementRenderer = function(renderTree,parentRenderer,parseTreeNode) { }; ElementRenderer.prototype.computeAttributes = function() { - var changedAttributes = {}; - var self = this; + var changedAttributes = {}, + self = this, + value; $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { if(attribute.type === "indirect") { - var value = self.renderTree.wiki.getTextReference(attribute.textReference,"",self.tiddlerTitle); - if(self.attributes[name] !== value) { - self.attributes[name] = value; - changedAttributes[name] = true; + value = self.renderTree.wiki.getTextReference(attribute.textReference,"",self.tiddlerTitle); + } else if(attribute.type === "macro") { + // Get the macro definition + var macro = self.renderTree.findMacroDefinition(self.parentRenderer,attribute.value.name); + if(!macro) { + value = ""; + } else { + // Substitute the macro parameters + value = self.renderTree.substituteParameters(macro,attribute.value); + // Parse the text and render it as text + value = self.renderTree.wiki.renderText("text/plain","text/vnd.tiddlywiki",value,self.context); } } else { // String attribute - if(self.attributes[name] !== attribute.value) { - self.attributes[name] = attribute.value; - changedAttributes[name] = true; - } + value = attribute.value; + } + // Check whether the attribute has changed + if(self.attributes[name] !== value) { + self.attributes[name] = value; + changedAttributes[name] = true; } }); return changedAttributes; diff --git a/core/modules/rendertree/renderers/macrocall.js b/core/modules/rendertree/renderers/macrocall.js index 7e4ae7288..0ae7ec203 100644 --- a/core/modules/rendertree/renderers/macrocall.js +++ b/core/modules/rendertree/renderers/macrocall.js @@ -28,7 +28,7 @@ var MacroCallRenderer = function(renderTree,parentRenderer,parseTreeNode) { childTree = [{type: "text", text: "<>"}]; } else { // Substitute the macro parameters - var text = this.substituteParameters(macro.text,this.parseTreeNode,macro); + var text = this.renderTree.substituteParameters(macro,this.parseTreeNode); // Parse the text childTree = this.renderTree.wiki.parseText("text/vnd.tiddlywiki",text,{parseAsInline: !this.parseTreeNode.isBlock}).tree; } @@ -36,39 +36,6 @@ var MacroCallRenderer = function(renderTree,parentRenderer,parseTreeNode) { this.children = this.renderTree.createRenderers(this,childTree); }; -/* -Expand the parameters in a block of text -*/ -MacroCallRenderer.prototype.substituteParameters = function(text,macroCallParseTreeNode,macroDefinition) { - var nextAnonParameter = 0; // Next candidate anonymous parameter in macro call - // Step through each of the parameters in the macro definition - for(var p=0; p 0) { - while(macroCallParseTreeNode.params[nextAnonParameter].name && nextAnonParameter < macroCallParseTreeNode.params.length-1) { - nextAnonParameter++; - } - if(!macroCallParseTreeNode.params[nextAnonParameter].name) { - paramValue = macroCallParseTreeNode.params[nextAnonParameter].value; - nextAnonParameter++; - } - } - // If we've still not got a value, use the default, if any - paramValue = paramValue || paramInfo["default"] || ""; - // Replace any instances of this parameter - text = text.replace(new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue); - } - return text; -}; - MacroCallRenderer.prototype.renderInDom = function() { // Create the element this.domNode = this.renderTree.document.createElement(this.parseTreeNode.isBlock ? "div" : "span"); diff --git a/core/modules/rendertree/wikirendertree.js b/core/modules/rendertree/wikirendertree.js index 01e7b2fdc..d862c9bbd 100644 --- a/core/modules/rendertree/wikirendertree.js +++ b/core/modules/rendertree/wikirendertree.js @@ -155,6 +155,40 @@ WikiRenderTree.prototype.findMacroDefinition = function(renderer,name) { return undefined; }; +/* +Expand the parameters of a macro +*/ +WikiRenderTree.prototype.substituteParameters = function(macroDefinition,macroCallParseTreeNode) { + var text = macroDefinition.text, + nextAnonParameter = 0; // Next candidate anonymous parameter in macro call + // Step through each of the parameters in the macro definition + for(var p=0; p 0) { + while(macroCallParseTreeNode.params[nextAnonParameter].name && nextAnonParameter < macroCallParseTreeNode.params.length-1) { + nextAnonParameter++; + } + if(!macroCallParseTreeNode.params[nextAnonParameter].name) { + paramValue = macroCallParseTreeNode.params[nextAnonParameter].value; + nextAnonParameter++; + } + } + // If we've still not got a value, use the default, if any + paramValue = paramValue || paramInfo["default"] || ""; + // Replace any instances of this parameter + text = text.replace(new RegExp("\\$" + $tw.utils.escapeRegExp(paramInfo.name) + "\\$","mg"),paramValue); + } + return text; +}; + exports.WikiRenderTree = WikiRenderTree; })(); diff --git a/editions/test/tiddlers/tests/test-html-parser.js b/editions/test/tiddlers/tests/test-html-parser.js index 158079ab1..9b9396f9e 100644 --- a/editions/test/tiddlers/tests/test-html-parser.js +++ b/editions/test/tiddlers/tests/test-html-parser.js @@ -99,22 +99,22 @@ describe("HTML tag new parser tests", function() { null ); expect(parser.parseMacroInvocation("<>",0)).toEqual( - { type : 'macro-invocation', start : 0, parameters : [ ], name : 'mymacro', end : 11 } + { type : 'macrocall', start : 0, params : [ ], name : 'mymacro', end : 11 } ); expect(parser.parseMacroInvocation("<>",0)).toEqual( - { type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one', end : 13 }, { type : 'macro-parameter', start : 13, value : 'two', end : 17 }, { type : 'macro-parameter', start : 17, value : 'three', end : 23 } ], name : 'mymacro', end : 25 } + { type : 'macrocall', start : 0, params : [ { type : 'macro-parameter', start : 9, value : 'one', end : 13 }, { type : 'macro-parameter', start : 13, value : 'two', end : 17 }, { type : 'macro-parameter', start : 17, value : 'three', end : 23 } ], name : 'mymacro', end : 25 } ); expect(parser.parseMacroInvocation("<>",0)).toEqual( - { type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one', name : 'p', end : 15 }, { type : 'macro-parameter', start : 15, value : 'two', name : 'q', end : 21 }, { type : 'macro-parameter', start : 21, value : 'three', end : 27 } ], name : 'mymacro', end : 29 } + { type : 'macrocall', start : 0, params : [ { type : 'macro-parameter', start : 9, value : 'one', name : 'p', end : 15 }, { type : 'macro-parameter', start : 15, value : 'two', name : 'q', end : 21 }, { type : 'macro-parameter', start : 21, value : 'three', end : 27 } ], name : 'mymacro', end : 29 } ); expect(parser.parseMacroInvocation("<>",0)).toEqual( - { type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one two three', end : 25 } ], name : 'mymacro', end : 27 } + { type : 'macrocall', start : 0, params : [ { type : 'macro-parameter', start : 9, value : 'one two three', end : 25 } ], name : 'mymacro', end : 27 } ); expect(parser.parseMacroInvocation("<>",0)).toEqual( - { type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one two three', name : 'r', end : 27 } ], name : 'mymacro', end : 29 } + { type : 'macrocall', start : 0, params : [ { type : 'macro-parameter', start : 9, value : 'one two three', name : 'r', end : 27 } ], name : 'mymacro', end : 29 } ); expect(parser.parseMacroInvocation("<>",0)).toEqual( - { type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'two', name : 'one', end : 17 }, { type : 'macro-parameter', start : 17, value : 'four and five', name : 'three', end : 39 } ], name : 'myMacro', end : 41 } + { type : 'macrocall', start : 0, params : [ { type : 'macro-parameter', start : 9, value : 'two', name : 'one', end : 17 }, { type : 'macro-parameter', start : 17, value : 'four and five', name : 'three', end : 39 } ], name : 'myMacro', end : 41 } ); }); @@ -177,10 +177,10 @@ describe("HTML tag new parser tests", function() { null ); expect(parser.parseTag("<$mytag attrib3=<>>",0)).toEqual( - { type : 'element', start : 0, attributes : { attrib3 : { type : 'macro', start : 7, name : 'attrib3', value : { type : 'macro-invocation', start : 16, parameters : [ { type : 'macro-parameter', start : 25, value : 'two', name : 'one', end : 33 }, { type : 'macro-parameter', start : 33, value : 'four and five', name : 'three', end : 55 } ], name : 'myMacro', end : 57 }, end : 57 } }, tag : '$mytag', end : 58 } + { type : 'element', start : 0, attributes : { attrib3 : { type : 'macro', start : 7, name : 'attrib3', value : { type : 'macrocall', start : 16, params : [ { type : 'macro-parameter', start : 25, value : 'two', name : 'one', end : 33 }, { type : 'macro-parameter', start : 33, value : 'four and five', name : 'three', end : 55 } ], name : 'myMacro', end : 57 }, end : 57 } }, tag : '$mytag', end : 58 } ); expect(parser.parseTag("<$mytag attrib1='something' attrib2=else thing attrib3=<>>",0)).toEqual( - { type : 'element', start : 0, attributes : { attrib1 : { type : 'string', start : 7, name : 'attrib1', value : 'something', end : 27 }, attrib2 : { type : 'string', start : 27, name : 'attrib2', value : 'else', end : 40 }, thing : { type : 'string', start : 40, name : 'thing', value : 'true', end : 47 }, attrib3 : { type : 'macro', start : 47, name : 'attrib3', value : { type : 'macro-invocation', start : 55, parameters : [ { type : 'macro-parameter', start : 64, value : 'two', name : 'one', end : 72 }, { type : 'macro-parameter', start : 72, value : 'four and five', name : 'three', end : 94 } ], name : 'myMacro', end : 96 }, end : 96 } }, tag : '$mytag', end : 97 } + { type : 'element', start : 0, attributes : { attrib1 : { type : 'string', start : 7, name : 'attrib1', value : 'something', end : 27 }, attrib2 : { type : 'string', start : 27, name : 'attrib2', value : 'else', end : 40 }, thing : { type : 'string', start : 40, name : 'thing', value : 'true', end : 47 }, attrib3 : { type : 'macro', start : 47, name : 'attrib3', value : { type : 'macrocall', start : 55, params : [ { type : 'macro-parameter', start : 64, value : 'two', name : 'one', end : 72 }, { type : 'macro-parameter', start : 72, value : 'four and five', name : 'three', end : 94 } ], name : 'myMacro', end : 96 }, end : 96 } }, tag : '$mytag', end : 97 } ); }); diff --git a/editions/test/tiddlers/tests/test-wikitext.js b/editions/test/tiddlers/tests/test-wikitext.js index dfddeedec..250f29f10 100644 --- a/editions/test/tiddlers/tests/test-wikitext.js +++ b/editions/test/tiddlers/tests/test-wikitext.js @@ -20,8 +20,9 @@ describe("WikiText tests", function() { wiki.addTiddler({title: "TiddlerOne", text: "The quick brown fox"}); wiki.addTiddler({title: "TiddlerTwo", text: "The rain in Spain\nfalls mainly on the plain"}); wiki.addTiddler({title: "TiddlerThree", text: "The speed of sound\n\nThe light of speed"}); + wiki.addTiddler({title: "TiddlerFour", text: "\\define my-macro(adjective:'groovy')\nThis is my ''amazingly'' $adjective$ macro!\n\\end\n\n<$link to=<>>This is a link"}); - it("should render tiddlers with no special markup render as-is", function() { + it("should render tiddlers with no special markup as-is", function() { expect(wiki.renderTiddler("text/plain","TiddlerOne")).toBe("The quick brown fox"); }); it("should preserve single new lines", function() { @@ -41,6 +42,10 @@ describe("WikiText tests", function() { it("should use double new lines to create paragraphs", function() { expect(wiki.renderTiddler("text/html","TiddlerThree")).toBe("

\nThe speed of sound

\nThe light of speed

"); }); + it("should support attributes specified as macro invocations", function() { + expect(wiki.renderTiddler("text/html","TiddlerFour")).toBe("

\n\nThis is a link

"); + }); + }); diff --git a/editions/tw5.com/tiddlers/concepts/WikiText.tid b/editions/tw5.com/tiddlers/concepts/WikiText.tid index 956836efe..f951f54f9 100644 --- a/editions/tw5.com/tiddlers/concepts/WikiText.tid +++ b/editions/tw5.com/tiddlers/concepts/WikiText.tid @@ -230,6 +230,20 @@ This is my nice and simple block of text. HelloThere ``` +Attributes in HTML tags can be specified as a transclusion or a macro invocation. For example, here the value of the `href` attribute will be set to the value of the tiddler MyLinkDestination: + +``` +link +``` + +Here an attribute is specified as a macro invocation: + +``` +>>link +``` + +* As a macro invocation + ! Widgets Widgets provide rich functionality within WikiText. For example, the `<$video>` widget can be used to embed videos from YouTube, Vimeo or the Internet Archive: