diff --git a/core/modules/parsers/parseutils.js b/core/modules/parsers/parseutils.js index 05a85e1ec..f935cdc09 100644 --- a/core/modules/parsers/parseutils.js +++ b/core/modules/parsers/parseutils.js @@ -107,13 +107,14 @@ exports.parseStringLiteral = function(source,pos) { type: "string", start: pos }; - var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')/g; + var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[([^\]]*)\]\]/g; reString.lastIndex = pos; var match = reString.exec(source); if(match && match.index === pos) { node.value = match[1] !== undefined ? match[1] :( - match[2] !== undefined ? match[2] : match[3] - ); + match[2] !== undefined ? match[2] : ( + match[3] !== undefined ? match[3] : match[4] + )); node.end = pos + match[0].length; return node; } else { @@ -206,28 +207,164 @@ exports.parseMacroParameter = function(source,pos) { Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:} */ exports.parseMacroInvocationAsTransclusion = function(source,pos) { - var node = $tw.utils.parseMacroInvocation(source,pos); - if(node) { - var positionalName = 0, - transclusion = { - type: "transclude", - start: node.start, - end: node.end - }; - $tw.utils.addAttributeToParseTreeNode(transclusion,"$variable",node.name); - $tw.utils.each(node.params,function(param) { - var name = param.name; - if(name) { - if(name.charAt(0) === "$") { - name = "$" + name; - } - $tw.utils.addAttributeToParseTreeNode(transclusion,{name: name,type: "string", value: param.value, start: param.start, end: param.end}); - } else { - $tw.utils.addAttributeToParseTreeNode(transclusion,{name: (positionalName++) + "",type: "string", value: param.value, start: param.start, end: param.end}); - } - }); - return transclusion; +// console.log("parseMacroInvocationAsTransclusion",source,pos); + var node = { + type: "transclude", + start: pos, + attributes: {}, + orderedAttributes: [] + }; + // Define our regexps + var reVarName = /([^\s>"'=:]+)/g; + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Look for a double opening angle bracket + var token = $tw.utils.parseTokenString(source,pos,"<<"); + if(!token) { +// console.log("No opening << in",source,"at",pos); + return null; } + pos = token.end; + // Get the variable name for the macro + token = $tw.utils.parseTokenRegExp(source,pos,reVarName); + if(!token) { +// console.log("No macro name"); + return null; + } + $tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]); + pos = token.end; + // Check that the tag is terminated by a space or >> + if(!$tw.utils.parseWhiteSpace(source,pos) && !(source.charAt(pos) === ">" && source.charAt(pos + 1) === ">") ) { +// console.log("No space or >> after macro name"); + return null; + } + // Process attributes + pos = $tw.utils.parseMacroParametersAsAttributes(node,source,pos); + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Look for a double closing angle bracket + token = $tw.utils.parseTokenString(source,pos,">>"); + if(!token) { +// console.log("No closing >>"); + return null; + } + node.end = token.end; + return node; +}; + +/* +Parse macro parameters as attributes. Returns the position after the last attribute +*/ +exports.parseMacroParametersAsAttributes = function(node,source,pos) { + var position = 0, + attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos); + while(attribute) { + if(!attribute.name) { + attribute.name = (position++) + ""; + attribute.isPositional = true; + } + node.orderedAttributes.push(attribute); + node.attributes[attribute.name] = attribute; + pos = attribute.end; + // Get the next attribute + attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos); + } + node.end = pos; + return pos; +} + +/* +Parse a macro parameter as an attribute. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}, with the name being optional +*/ +exports.parseMacroParameterAsAttribute = function(source,pos) { + var node = { + start: pos + }; + // Define our regexps + var reAttributeName = /([^\/\s>"'`=:]+)/g, + reUnquotedAttribute = /((?:(?:>(?!>))|[^\s>"'])+)/g, + reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g, + reIndirectValue = /\{\{([^\}]+)\}\}/g, + reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g; + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Get the attribute name and the separator token + var nameToken = $tw.utils.parseTokenRegExp(source,pos,reAttributeName), + namePos = nameToken && $tw.utils.skipWhiteSpace(source,nameToken.end), + separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/g); +// console.log(`parseMacroParametersAtAttribute source is ${source} at ${pos} and namepos is ${namePos} with nameToken as ${JSON.stringify(nameToken)} and separatorToken as ${JSON.stringify(separatorToken)}`); + // If we have a name and a separator then we have a named attribute + if(nameToken && separatorToken) { + node.name = nameToken.match[1]; + // key value separator is `=` or `:` + node.assignmentOperator = separatorToken.match[0]; + pos = separatorToken.end; + } + // Skip whitespace + pos = $tw.utils.skipWhiteSpace(source,pos); + // Look for a string literal + var stringLiteral = $tw.utils.parseStringLiteral(source,pos); + if(stringLiteral) { + pos = stringLiteral.end; + node.type = "string"; + node.value = stringLiteral.value; + // Mark the value as having been quoted in the source + node.quoted = true; + } else { +// console.log(`Failed to parse string literal ${source} at ${pos} with node as ${JSON.stringify(node)}`); + // Look for a filtered value + var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue); + if(filteredValue) { + pos = filteredValue.end; + node.type = "filtered"; + node.filter = filteredValue.match[1]; + } else { +// console.log(`Failed to parse filtered value ${source} at ${pos} with node as ${JSON.stringify(node)}`); + // Look for an indirect value + var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue); + if(indirectValue) { + pos = indirectValue.end; + node.type = "indirect"; + node.textReference = indirectValue.match[1]; + } else { +// console.log(`Failed to parse indirect value ${source} at ${pos} with node as ${JSON.stringify(node)}`); + // Look for a unquoted value + var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute); + if(unquotedValue) { + pos = unquotedValue.end; + node.type = "string"; + node.value = unquotedValue.match[1]; +// console.log(`Parsed unquoted value ${source} at ${pos} with node as ${JSON.stringify(node)}`); + } else { +// console.log(`Failed to parse unquoted value ${source} at ${pos} with node as ${JSON.stringify(node)}`); + // Look for a macro invocation value + var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos); + if(macroInvocation) { + pos = macroInvocation.end; + node.type = "macro"; + node.value = macroInvocation; +// console.log(`Parsed macro invocation ${source} at ${pos} with node as ${JSON.stringify(node)}`); + } else { +// console.log(`Failed to parse macro invocation ${source} at ${pos} with node as ${JSON.stringify(node)}`); + var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue); + if(substitutedValue) { + pos = substitutedValue.end; + node.type = "substituted"; + node.rawValue = substitutedValue.match[1] || substitutedValue.match[2]; + } else { +// console.log(`Failed to parse substituted value ${source} at ${pos} with node as ${JSON.stringify(node)}`); + } + } + } + } + } + } + // Bail if we don't have a value + if(!node.type) { + return null; + } + // Update the end position + node.end = pos; return node; }; @@ -296,7 +433,7 @@ exports.parseFilterVariable = function(source) { }; /* -Look for an HTML attribute definition. Returns null if not found, otherwise returns {type: "attribute", name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,} +Look for an HTML attribute definition. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,} */ exports.parseAttribute = function(source,pos) { var node = { @@ -354,7 +491,7 @@ exports.parseAttribute = function(source,pos) { node.value = unquotedValue.match[1]; } else { // Look for a macro invocation value - var macroInvocation = $tw.utils.parseMacroInvocation(source,pos); + var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos); if(macroInvocation) { pos = macroInvocation.end; node.type = "macro"; @@ -375,6 +512,7 @@ exports.parseAttribute = function(source,pos) { } } } else { + // If there is no equals sign or colon, then this is an attribute with no value, defaulting to "true" node.type = "string"; node.value = "true"; } diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 67d32e976..66440aa04 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -414,7 +414,21 @@ Widget.prototype.computeAttribute = function(attribute,options) { value = [value]; } } else if(attribute.type === "macro") { - var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params}); + // Get the macro name + var macroName = attribute.value.attributes["$variable"].value; + // Collect macro parameters + var params = []; + $tw.utils.each(attribute.value.orderedAttributes,function(attr) { + var param = { + value: self.computeAttribute(attr) + }; + if(attr.name && !attr.isPositional) { + param.name = attr.name; + } + params.push(param); + }); + // Invoke the macro + var variableInfo = this.getVariableInfo(macroName,{params: params}); if(options.asList) { value = variableInfo.resultList; } else { diff --git a/editions/test/tiddlers/tests/data/macros/dynamic-macros/Attribute.tid b/editions/test/tiddlers/tests/data/macros/dynamic-macros/Attribute.tid new file mode 100644 index 000000000..71e02dd61 --- /dev/null +++ b/editions/test/tiddlers/tests/data/macros/dynamic-macros/Attribute.tid @@ -0,0 +1,34 @@ +title: Macros/Dynamic/Attribute +description: Attribute macrocall with dynamic paramters +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\define mamacromamacro(param:"red") +It is $param$ +\end + +<$text text=<>/> +- +<$text text=<>/> +- +<$text text=<>/> +- +<$text text=<>/> +- +<$text text=<>/> + ++ +title: ExpectedResult + +

It is red +- +It is ba +- +It is ab +- +It is ab +- +It is param +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/macros/dynamic-macros/Standalone.tid b/editions/test/tiddlers/tests/data/macros/dynamic-macros/Standalone.tid new file mode 100644 index 000000000..718b23e01 --- /dev/null +++ b/editions/test/tiddlers/tests/data/macros/dynamic-macros/Standalone.tid @@ -0,0 +1,26 @@ +title: Macros/Dynamic/Standalone +description: Standalone macrocall with dynamic paramters +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\whitespace trim + +\define mamacro(one:"red",two:"green") +It is $one$ and $two$ or <<__one__>> and <<__two__>>. +\end + +<> + +<> + +<> + +<> + +<> ++ +title: ExpectedResult + +

It is red and green or red and green.

It is ba and green or ba and green.

It is ab and green or ab and green.

It is ab and green or ab and green.

It is one and green or one and green.

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/serialize/DynamicMacroMixed.tid b/editions/test/tiddlers/tests/data/serialize/DynamicMacroMixed.tid new file mode 100644 index 000000000..66d7855d1 --- /dev/null +++ b/editions/test/tiddlers/tests/data/serialize/DynamicMacroMixed.tid @@ -0,0 +1,9 @@ +tags: $:/tags/wikitext-serialize-test-spec +title: Serialize/DynamicMacroMixed +type: text/vnd.tiddlywiki + +<> + +<$macrocall $name="mymacro" static="value" dynamic=<>/> + +<> diff --git a/editions/test/tiddlers/tests/data/serialize/DynamicMacroParams.tid b/editions/test/tiddlers/tests/data/serialize/DynamicMacroParams.tid new file mode 100644 index 000000000..0ec199970 --- /dev/null +++ b/editions/test/tiddlers/tests/data/serialize/DynamicMacroParams.tid @@ -0,0 +1,9 @@ +tags: $:/tags/wikitext-serialize-test-spec +title: Serialize/DynamicMacroParams +type: text/vnd.tiddlywiki + +<> + +<addprefix[https:]] }}}>> + +<$macrocall $name="outermacro" inner=<>/> diff --git a/editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid b/editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid new file mode 100644 index 000000000..c00895eb0 --- /dev/null +++ b/editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid @@ -0,0 +1,7 @@ +tags: $:/tags/wikitext-serialize-test-spec +title: Serialize/DynamicWidgetAttribute +type: text/vnd.tiddlywiki + +
>>content
+ +<$button actions=<>/> diff --git a/editions/test/tiddlers/tests/data/serialize/MacroCallBlock.tid b/editions/test/tiddlers/tests/data/serialize/MacroCallBlock.tid index 4bd30d451..f4316efd0 100644 --- a/editions/test/tiddlers/tests/data/serialize/MacroCallBlock.tid +++ b/editions/test/tiddlers/tests/data/serialize/MacroCallBlock.tid @@ -7,3 +7,7 @@ type: text/vnd.tiddlywiki <<.def "macro calls">> <> + +<> + +<> diff --git a/editions/test/tiddlers/tests/data/serialize/MacroCallInline.tid b/editions/test/tiddlers/tests/data/serialize/MacroCallInline.tid index 09127dce0..c8d2a71fc 100644 --- a/editions/test/tiddlers/tests/data/serialize/MacroCallInline.tid +++ b/editions/test/tiddlers/tests/data/serialize/MacroCallInline.tid @@ -3,3 +3,5 @@ title: Serialize/MacroCallInline type: text/vnd.tiddlywiki These are macro calls in a line: <> and <<.def "macro calls">> <> + +Testing unquoted parameters: <> and <>. diff --git a/editions/test/tiddlers/tests/test-html-parser.js b/editions/test/tiddlers/tests/test-html-parser.js index c6c160bb3..c8a6bcbb5 100644 --- a/editions/test/tiddlers/tests/test-html-parser.js +++ b/editions/test/tiddlers/tests/test-html-parser.js @@ -235,11 +235,307 @@ describe("HTML tag new parser tests", function() { expect(parser.parseTag("< $mytag attrib1='something' attrib2=else thing>",0)).toEqual( null ); - expect(parser.parseTag("<$mytag attrib3=<>>",0)).toEqual( - { type : "mytag", 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 } }, orderedAttributes: [ { 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 attrib3=<>>", 0)).toEqual( + { + "type": "mytag", + "start": 0, + "attributes": { + "attrib3": { + "start": 7, + "name": "attrib3", + "type": "macro", + "value": { + "type": "transclude", + "start": 16, + "attributes": { + "$variable": { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + "one": { + "start": 25, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 33 + }, + "three": { + "start": 33, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 55 + } + }, + "orderedAttributes": [ + { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + { + "start": 25, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 33 + }, + { + "start": 33, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 55 + } + ], + "end": 57 + }, + "end": 57 + } + }, + "orderedAttributes": [ + { + "start": 7, + "name": "attrib3", + "type": "macro", + "value": { + "type": "transclude", + "start": 16, + "attributes": { + "$variable": { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + "one": { + "start": 25, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 33 + }, + "three": { + "start": 33, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 55 + } + }, + "orderedAttributes": [ + { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + { + "start": 25, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 33 + }, + { + "start": 33, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 55 + } + ], + "end": 57 + }, + "end": 57 + } + ], + "tag": "$mytag", + "end": 58 + } ); expect(parser.parseTag("<$mytag attrib1='something' attrib2=else thing attrib3=<>>",0)).toEqual( - { type : "mytag", 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 } }, orderedAttributes: [ { type : "string", start : 7, name : "attrib1", value : "something", end : 27 }, { type : "string", start : 27, name : "attrib2", value : "else", end : 40 }, { type : "string", start : 40, name : "thing", value : "true", end : 47 }, { 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 } + { + "type": "mytag", + "start": 0, + "attributes": { + "attrib1": { + "start": 7, + "name": "attrib1", + "type": "string", + "value": "something", + "end": 27 + }, + "attrib2": { + "start": 27, + "name": "attrib2", + "type": "string", + "value": "else", + "end": 40 + }, + "thing": { + "start": 40, + "name": "thing", + "type": "string", + "value": "true", + "end": 47 + }, + "attrib3": { + "start": 47, + "name": "attrib3", + "type": "macro", + "value": { + "type": "transclude", + "start": 55, + "attributes": { + "$variable": { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + "one": { + "start": 64, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 72 + }, + "three": { + "start": 72, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 94 + } + }, + "orderedAttributes": [ + { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + { + "start": 64, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 72 + }, + { + "start": 72, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 94 + } + ], + "end": 96 + }, + "end": 96 + } + }, + "orderedAttributes": [ + { + "start": 7, + "name": "attrib1", + "type": "string", + "value": "something", + "end": 27 + }, + { + "start": 27, + "name": "attrib2", + "type": "string", + "value": "else", + "end": 40 + }, + { + "start": 40, + "name": "thing", + "type": "string", + "value": "true", + "end": 47 + }, + { + "start": 47, + "name": "attrib3", + "type": "macro", + "value": { + "type": "transclude", + "start": 55, + "attributes": { + "$variable": { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + "one": { + "start": 64, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 72 + }, + "three": { + "start": 72, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 94 + } + }, + "orderedAttributes": [ + { + "name": "$variable", + "type": "string", + "value": "myMacro" + }, + { + "start": 64, + "name": "one", + "assignmentOperator": ":", + "type": "string", + "value": "two", + "end": 72 + }, + { + "start": 72, + "name": "three", + "assignmentOperator": ":", + "type": "string", + "value": "four and five", + "quoted": true, + "end": 94 + } + ], + "end": 96 + }, + "end": 96 + } + ], + "tag": "$mytag", + "end": 97 + } ); }); diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js index 59172f867..1fffad01a 100644 --- a/editions/test/tiddlers/tests/test-wikitext-parser.js +++ b/editions/test/tiddlers/tests/test-wikitext-parser.js @@ -261,7 +261,7 @@ describe("WikiText parser tests", function() { ); expect(parse("text <>")).toEqual( - [{"type":"element","tag":"p",rule:"parseblock","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":92,"rule":"macrocallinline","attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","type":"string","value":"val1","start":11,"end":20},"two":{"name":"two","type":"string","value":"val \"2\"","start":20,"end":35},"three":{"name":"three","type":"string","value":"val '3'","start":35,"end":52},"four":{"name":"four","type":"string","value":"val 4\"5'","start":52,"end":73},"five":{"name":"five","type":"string","value":"val 5","start":73,"end":89}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","type":"string","value":"val1","start":11,"end":20},{"name":"two","type":"string","value":"val \"2\"","start":20,"end":35},{"name":"three","type":"string","value":"val '3'","start":35,"end":52},{"name":"four","type":"string","value":"val 4\"5'","start":52,"end":73},{"name":"five","type":"string","value":"val 5","start":73,"end":89}]}],"start":0,"end":92}] + [{"type":"element","tag":"p",rule:"parseblock","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":92,"rule":"macrocallinline","attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","assignmentOperator":":","type":"string","value":"val1","start":11,"end":20},"two":{"name":"two","assignmentOperator":":","type":"string","value":"val \"2\"","quoted":true,"start":20,"end":35},"three":{"name":"three","assignmentOperator":":","type":"string","value":"val '3'","quoted":true,"start":35,"end":52},"four":{"name":"four","assignmentOperator":":","type":"string","value":"val 4\"5'","quoted":true,"start":52,"end":73},"five":{"name":"five","assignmentOperator":":","type":"string","value":"val 5","quoted":true,"start":73,"end":89}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","assignmentOperator":":","type":"string","value":"val1","start":11,"end":20},{"name":"two","assignmentOperator":":","type":"string","value":"val \"2\"","quoted":true,"start":20,"end":35},{"name":"three","assignmentOperator":":","type":"string","value":"val '3'","quoted":true,"start":35,"end":52},{"name":"four","assignmentOperator":":","type":"string","value":"val 4\"5'","quoted":true,"start":52,"end":73},{"name":"five","assignmentOperator":":","type":"string","value":"val 5","quoted":true,"start":73,"end":89}]}],"start":0,"end":92}] ); expect(parse("ignored << carrots <>")).toEqual( @@ -287,7 +287,7 @@ describe("WikiText parser tests", function() { ); expect(parse("text <>' >>")).toEqual( - [{"type":"element","tag":"p",rule:"parseblock","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":34,"rule":"macrocallinline","attributes":{"$variable":{"name":"$variable","type":"string","value":"outie"},"one":{"name":"one","type":"string","value":"my <>","start":12,"end":31}},"orderedAttributes":[{"name":"$variable","type":"string","value":"outie"},{"name":"one","type":"string","value":"my <>","start":12,"end":31}]}],"start":0,"end":34}] + [{"type":"element","tag":"p",rule:"parseblock","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":34,"rule":"macrocallinline","attributes":{"$variable":{"name":"$variable","type":"string","value":"outie"},"one":{"name":"one","assignmentOperator":":","type":"string","value":"my <>","quoted":true,"start":12,"end":31}},"orderedAttributes":[{"name":"$variable","type":"string","value":"outie"},{"name":"one","assignmentOperator":":","type":"string","value":"my <>","quoted":true,"start":12,"end":31}]}],"start":0,"end":34}] ); @@ -301,7 +301,7 @@ describe("WikiText parser tests", function() { ); expect(parse("<>")).toEqual( - [{"type":"transclude","start":0,"end":87,"rule":"macrocallblock","attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","type":"string","value":"val1","start":6,"end":15},"two":{"name":"two","type":"string","value":"val \"2\"","start":15,"end":30},"three":{"name":"three","type":"string","value":"val '3'","start":30,"end":47},"four":{"name":"four","type":"string","value":"val 4\"5'","start":47,"end":68},"five":{"name":"five","type":"string","value":"val 5","start":68,"end":84}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","type":"string","value":"val1","start":6,"end":15},{"name":"two","type":"string","value":"val \"2\"","start":15,"end":30},{"name":"three","type":"string","value":"val '3'","start":30,"end":47},{"name":"four","type":"string","value":"val 4\"5'","start":47,"end":68},{"name":"five","type":"string","value":"val 5","start":68,"end":84}],"isBlock":true}] + [{"type":"transclude","start":0,"end":87,"rule":"macrocallblock","attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","assignmentOperator":":","type":"string","value":"val1","start":6,"end":15},"two":{"name":"two","assignmentOperator":":","type":"string","value":"val \"2\"","quoted":true,"start":15,"end":30},"three":{"name":"three","assignmentOperator":":","type":"string","value":"val '3'","quoted":true,"start":30,"end":47},"four":{"name":"four","assignmentOperator":":","type":"string","value":"val 4\"5'","quoted":true,"start":47,"end":68},"five":{"name":"five","assignmentOperator":":","type":"string","value":"val 5","quoted":true,"start":68,"end":84}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","assignmentOperator":":","type":"string","value":"val1","start":6,"end":15},{"name":"two","assignmentOperator":":","type":"string","value":"val \"2\"","quoted":true,"start":15,"end":30},{"name":"three","assignmentOperator":":","type":"string","value":"val '3'","quoted":true,"start":30,"end":47},{"name":"four","assignmentOperator":":","type":"string","value":"val 4\"5'","quoted":true,"start":47,"end":68},{"name":"five","assignmentOperator":":","type":"string","value":"val 5","quoted":true,"start":68,"end":84}],"isBlock":true}] ); expect(parse("<< carrots\n\n<>")).toEqual( @@ -321,12 +321,12 @@ describe("WikiText parser tests", function() { ); expect(parse("<>")).toEqual( - [{"type":"transclude","start":0,"end":36,"rule":"macrocallblock","attributes":{"$variable":{"name":"$variable","type":"string","value":"multiline"},"arg":{"name":"arg","type":"string","value":"\n\nwikitext\n","start":11,"end":33}},"orderedAttributes":[{"name":"$variable","type":"string","value":"multiline"},{"name":"arg","type":"string","value":"\n\nwikitext\n","start":11,"end":33}],"isBlock":true}] + [{"type":"transclude","start":0,"end":36,"rule":"macrocallblock","attributes":{"$variable":{"name":"$variable","type":"string","value":"multiline"},"arg":{"name":"arg","assignmentOperator":":","type":"string","value":"\n\nwikitext\n","quoted":true,"start":11,"end":33}},"orderedAttributes":[{"name":"$variable","type":"string","value":"multiline"},{"name":"arg","assignmentOperator":":","type":"string","value":"\n\nwikitext\n","quoted":true,"start":11,"end":33}],"isBlock":true}] ); expect(parse("<>' >>")).toEqual( - [ { type: "transclude", start: 0, rule: "macrocallblock", attributes: { $variable: {name: "$variable", type:"string", value: "outie"}, one: {name: "one", type:"string", value: "my <>", start: 7, end: 26} }, orderedAttributes: [ {name: "$variable", type:"string", value: "outie"}, {name: "one", type:"string", value: "my <>", start: 7, end: 26} ], end: 29, isBlock: true } ] + [ { type: "transclude", start: 0, rule: "macrocallblock", attributes: { $variable: {name: "$variable", type:"string", value: "outie"}, one: {name: "one", assignmentOperator: ":", type:"string", value: "my <>", quoted: true, start: 7, end: 26} }, orderedAttributes: [ {name: "$variable", type:"string", value: "outie"}, {name: "one", assignmentOperator: ":", type:"string", value: "my <>", quoted: true, start: 7, end: 26} ], end: 29, isBlock: true } ] ); }); @@ -334,23 +334,23 @@ describe("WikiText parser tests", function() { it("should parse tricky macrocall parameters", function() { expect(parse("<am>>")).toEqual( - [{"type":"transclude","start":0,"end":14,"attributes":{"0":{"name":"0","type":"string","value":"pa>am","start":6,"end":12},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"pa>am","start":6,"end":12}],"isBlock":true,"rule":"macrocallblock"}] + [{"type":"transclude","start":0,"end":14,"attributes":{"0":{"name":"0","type":"string","value":"pa>am","start":6,"end":12,"isPositional":true},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"pa>am","start":6,"end":12,"isPositional":true}],"isBlock":true,"rule":"macrocallblock"}] ); expect(parse("< >>")).toEqual( - [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"param>","start":6,"end":13},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param>","start":6,"end":13}],"isBlock":true,"rule":"macrocallblock"}] + [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"param>","start":6,"end":13,"isPositional":true},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param>","start":6,"end":13,"isPositional":true}],"isBlock":true,"rule":"macrocallblock"}] ); expect(parse("<>>")).toEqual( - [{"type":"element","tag":"p",rule:"parseblock","children":[{"type":"transclude","start":0,"end":14,"rule":"macrocallinline","attributes":{"0":{"name":"0","type":"string","value":"param","start":6,"end":12},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param","start":6,"end":12}]},{"type":"text","text":">","start":14,"end":15}],"start":0,"end":15}] + [{"type":"element","rule":"parseblock","tag":"p","children":[{"type":"transclude","start":0,"end":14,"rule":"macrocallinline","attributes":{"0":{"name":"0","type":"string","value":"param","start":6,"end":12,"isPositional":true},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param","start":6,"end":12,"isPositional":true}]},{"type":"text","text":">","start":14,"end":15}],"start":0,"end":15}] ); // equals signs should be allowed expect(parse("<=4 >>")).toEqual( - [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"var>=4","start":6,"end":13},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"var>=4","start":6,"end":13}],"isBlock":true,"rule":"macrocallblock"}] + [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"var>=4","start":6,"end":13,"isPositional":true},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"var>=4","start":6,"end":13,"isPositional":true}],"isBlock":true,"rule":"macrocallblock"}] ); diff --git a/editions/tw5.com/tiddlers/v5.4.0/Improvements to Macro Calls.tid b/editions/tw5.com/tiddlers/v5.4.0/Improvements to Macro Calls.tid new file mode 100644 index 000000000..c46bd93dc --- /dev/null +++ b/editions/tw5.com/tiddlers/v5.4.0/Improvements to Macro Calls.tid @@ -0,0 +1,48 @@ +title: Improvements to Macro Calls in v5.4.0 +tags: v5.4.0 + +<$macrocall $name='wikitext-example-without-html' +src="""\define testmacro(one) +Result: $one$. +\end testmacro + +<> +"""/> + +<$macrocall $name='wikitext-example-without-html' +src="""\function testfunction(one) +[addprefix[Hello]] +\end testfunction + +<> +"""/> + +<$macrocall $name='wikitext-example-without-html' +src="""\define testmacro(one) +Result: $one$. +\end testmacro + +<$text text=<>/> +"""/> + +<$macrocall $name='wikitext-example-without-html' +src="""\function testfunction(one) +[addprefix[Hello]] +\end testfunction + +<$text text=<>/> +"""/> + +<$macrocall $name='wikitext-example' +src="""\define innermacro(one) +:$one$:$one$: +\end innermacro + +\define mymacro(param) +|$param$|$param$| +\end mymacro + +
addprefix[The palette named ]] }}}>>> +Content +
+"""/> diff --git a/plugins/tiddlywiki/wikitext-serialize/rules/macrocallblock.js b/plugins/tiddlywiki/wikitext-serialize/rules/macrocallblock.js index fff204891..6e3499cf2 100644 --- a/plugins/tiddlywiki/wikitext-serialize/rules/macrocallblock.js +++ b/plugins/tiddlywiki/wikitext-serialize/rules/macrocallblock.js @@ -18,7 +18,7 @@ exports.serialize = function (node) { if(node.orderedAttributes) { node.orderedAttributes.forEach(function (attribute) { if(attribute.name !== "$variable") { - result += " " + $tw.utils.serializeAttribute(attribute,{assignmentSymbol:":"}); + result += " " + $tw.utils.serializeAttribute(attribute); } }); } diff --git a/plugins/tiddlywiki/wikitext-serialize/utils/parsetree.js b/plugins/tiddlywiki/wikitext-serialize/utils/parsetree.js index fb1c4e47b..456bf747d 100644 --- a/plugins/tiddlywiki/wikitext-serialize/utils/parsetree.js +++ b/plugins/tiddlywiki/wikitext-serialize/utils/parsetree.js @@ -62,14 +62,27 @@ exports.serializeAttribute = function(node,options) { } // If name is number, means it is a positional attribute and name is omitted var positional = parseInt(node.name) >= 0, - // `=` in a widget and might be `:` in a macro - assign = positional ? "" : (options.assignmentSymbol || "="), + // Use the original assignment operator if available, otherwise default to '=' + assign = positional ? "" : (node.assignmentOperator || "="), attributeString = positional ? "" : node.name; if(node.type === "string") { if(node.value === "true") { return attributeString; } - attributeString += assign + '"' + node.value + '"'; + // For macro parameters (using ':' separator), preserve unquoted values + // For widget attributes (using '=' separator), always use quotes + if(assign === ":" && !node.quoted) { + attributeString += assign + node.value; + } else if(assign === "") { + // Positional parameter + if(!node.quoted) { + attributeString += node.value; + } else { + attributeString += '"' + node.value + '"'; + } + } else { + attributeString += assign + '"' + node.value + '"'; + } } else if(node.type === "filtered") { attributeString += assign + "{{{" + node.filter + "}}}"; } else if(node.type === "indirect") { @@ -77,11 +90,36 @@ exports.serializeAttribute = function(node,options) { } else if(node.type === "substituted") { attributeString += assign + "`" + node.rawValue + "`"; } else if(node.type === "macro") { - if(node.value && typeof node.value === "object" && node.value.type === "macrocall") { - var params = node.value.params.map(function(param) { - return param.value; - }).join(" "); - attributeString += assign + "<<" + node.value.name + " " + params + ">>"; + if(node.value && typeof node.value === "object") { + if(node.value.type === "transclude") { + // Handle the transclude-based macro call structure + var macroName = node.value.attributes && node.value.attributes["$variable"] ? + node.value.attributes["$variable"].value : ""; + if(!macroName) { + return null; + } + var params = []; + if(node.value.orderedAttributes) { + node.value.orderedAttributes.forEach(function(attr) { + if(attr.name !== "$variable") { + var paramStr = exports.serializeAttribute(attr); + if(paramStr) { + params.push(paramStr); + } + } + }); + } + attributeString += assign + "<<" + macroName + (params.length > 0 ? " " + params.join(" ") : "") + ">>"; + } else if(node.value.type === "macrocall") { + // Handle the classical macrocall structure for backwards compatibility + var params = node.value.params.map(function(param) { + return param.value; + }).join(" "); + attributeString += assign + "<<" + node.value.name + " " + params + ">>"; + } else { + // Unsupported macro structure + return null; + } } else { // Unsupported macro structure return null;