Fix transclude inefficiency (#7647)

* Refactor parse mode out of getTransclusionTarget

* Refactor missing transclusion target

* Add a test to avoid regressions on the handling of macros vs procedures

* Refactor condition logic

* Preparing to split getTransclusionTarget into two separate functions

* Split getTransclusionTarget into getTransclusionTargetIncludingParseTreeNodes

* Resolve another inefficiency

The transclusion target was sometimes being parsed twice when transcluding as text/plain

Associated test results are also made more consistent

* Simplify method naming

* Neatening up
This commit is contained in:
Jeremy Ruston 2023-07-30 18:04:05 +01:00 committed by GitHub
parent c1ff85c205
commit 4bdac09872
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 36 deletions

View File

@ -41,30 +41,43 @@ TranscludeWidget.prototype.execute = function() {
this.collectAttributes(); this.collectAttributes();
this.collectStringParameters(); this.collectStringParameters();
this.collectSlotFillParameters(); this.collectSlotFillParameters();
// Get the target text and parse tree nodes that we are transcluding // Determine whether we're being used in inline or block mode
var target = this.getTransclusionTarget(), var parseAsInline = !this.parseTreeNode.isBlock;
parseTreeNodes; if(this.transcludeMode === "inline") {
this.sourceText = target.text; parseAsInline = true;
this.parserType = target.type; } else if(this.transcludeMode === "block") {
this.parseAsInline = target.parseAsInline; parseAsInline = false;
}
// Set 'thisTiddler' // Set 'thisTiddler'
this.setVariable("thisTiddler",this.transcludeTitle); this.setVariable("thisTiddler",this.transcludeTitle);
var parseTreeNodes, target;
// Process the transclusion according to the output type // Process the transclusion according to the output type
switch(this.transcludeOutput || "text/html") { switch(this.transcludeOutput || "text/html") {
case "text/html": case "text/html":
// Return the parse tree nodes // Return the parse tree nodes of the target
target = this.parseTransclusionTarget(parseAsInline);
this.parseAsInline = target.parseAsInline;
parseTreeNodes = target.parseTreeNodes; parseTreeNodes = target.parseTreeNodes;
break; break;
case "text/raw": case "text/raw":
// Just return the raw text // Just return the raw text
parseTreeNodes = [{type: "text", text: this.sourceText}]; target = this.getTransclusionTarget();
parseTreeNodes = [{type: "text", text: target.text}];
break; break;
default: default:
// text/plain // "text/plain" is the plain text result of wikifying the text
var plainText = this.wiki.renderText("text/plain",this.parserType,this.sourceText,{parentWidget: this}); target = this.parseTransclusionTarget(parseAsInline);
parseTreeNodes = [{type: "text", text: plainText}]; var widgetNode = this.wiki.makeWidget(target.parser,{
parentWidget: this,
document: $tw.fakeDocument
});
var container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
parseTreeNodes = [{type: "text", text: container.textContent}];
break; break;
} }
this.sourceText = target.text;
this.parserType = target.type;
// Set the legacy transclusion context variables only if we're not transcluding a variable // Set the legacy transclusion context variables only if we're not transcluding a variable
if(!this.transcludeVariable) { if(!this.transcludeVariable) {
var recursionMarker = this.makeRecursionMarker(); var recursionMarker = this.makeRecursionMarker();
@ -161,17 +174,44 @@ TranscludeWidget.prototype.collectSlotFillParameters = function() {
}; };
/* /*
Get transcluded parse tree nodes as an object {text:,type:,parseTreeNodes:,parseAsInline:} Get transcluded details as an object {text:,type:}
*/ */
TranscludeWidget.prototype.getTransclusionTarget = function() { TranscludeWidget.prototype.getTransclusionTarget = function() {
var self = this; var self = this;
// Determine whether we're being used in inline or block mode var text;
var parseAsInline = !this.parseTreeNode.isBlock; // Return the text and type of the target
if(this.transcludeMode === "inline") { if(this.hasAttribute("$variable")) {
parseAsInline = true; if(this.transcludeVariable) {
} else if(this.transcludeMode === "block") { // Transcluding a variable
parseAsInline = false; var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()});
text = variableInfo.text;
return {
text: variableInfo.text,
type: this.transcludeType
};
}
} else {
// Transcluding a text reference
var parserInfo = this.wiki.getTextReferenceParserInfo(
this.transcludeTitle,
this.transcludeField,
this.transcludeIndex,
{
subTiddler: this.transcludeSubTiddler,
defaultType: this.transcludeType
});
return {
text: parserInfo.text,
type: parserInfo.type
};
} }
};
/*
Get transcluded parse tree nodes as an object {text:,type:,parseTreeNodes:,parseAsInline:}
*/
TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
var self = this;
var parser; var parser;
// Get the parse tree // Get the parse tree
if(this.hasAttribute("$variable")) { if(this.hasAttribute("$variable")) {
@ -237,7 +277,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
} }
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]) $tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"])
}); });
} else if(srcVariable && (srcVariable.isMacroDefinition || !srcVariable.isFunctionDefinition)) { } else if(srcVariable && !srcVariable.isFunctionDefinition) {
// For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__" // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
parser = { parser = {
tree: [ tree: [
@ -269,22 +309,13 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}); });
} }
// Return the parse tree // Return the parse tree
if(parser) { return {
return { parser: parser,
parseTreeNodes: parser.tree, parseTreeNodes: parser ? parser.tree : (this.slotFillParseTrees["ts-missing"] || []),
parseAsInline: parseAsInline, parseAsInline: parseAsInline,
text: parser.source, text: parser && parser.source,
type: parser.type type: parser && parser.type
}; };
} else {
// If there's no parse tree then return the missing slot value
return {
parseTreeNodes: (this.slotFillParseTrees["ts-missing"] || []),
parseAsInline: parseAsInline,
text: null,
type: null
};
}
}; };
/* /*

View File

@ -33,4 +33,4 @@ $param$ with a ''buffalo''
+ +
title: ExpectedResult title: ExpectedResult
<p>Going to lunch with a ''buffalo''</p><p>Going to breakfastwith a<strong>buffalo</strong></p><p>Going to dinner with a <strong>buffalo</strong></p>Going to lunch with a buffalo with a buffaloGoing to dinner with a buffalo <p>Going to lunch with a ''buffalo''</p><p>Going to breakfastwith a<strong>buffalo</strong></p><p>Going to dinner with a <strong>buffalo</strong></p>Going to lunch with a ''buffalo''Going to breakfastwith abuffaloGoing to dinner with a buffalo

View File

@ -0,0 +1,26 @@
title: Procedures/Double/Underscore
description: Checking that procedures don't expose parameters as variables wrapped in double underscores
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure mamacro(one:"red",two:"green")
It is $one$ and $two$<<__one__>><<__two__>>.
\end
<$macrocall $name="mamacro"/>
<$transclude $variable="mamacro"/>
<$transclude $variable="mamacro" one="orange"/>
<$transclude $variable="mamacro" 0="pink"/>
<$transclude $variable="mamacro" one="purple" 1="pink"/>
+
title: ExpectedResult
<p>It is $one$ and $two$.</p><p>It is $one$ and $two$.</p><p>It is $one$ and $two$.</p><p>It is $one$ and $two$.</p><p>It is $one$ and $two$.</p>