Text Processor Object Link Nesting Bug

Dcoder
03 Dec 2018, 06:05This works:
msg ("{object:pencil}")
Then pencil
will display as an object link.
This doesn't work, but should:
msg ("{object:{pencil.name}}")
{object:pencil}
will be printed out.
I.e., nesting any {object.attribute}
inside of {object:XXX}
won't work. This means that you can only print object links with the text processor for specifically named objects, never attributes or variables.

Dcoder
03 Dec 2018, 06:20The only other thing I could find that might be a workaround was "Local variables" found here:
http://docs.textadventures.co.uk/quest/text_processor.html
Couldn't get it to work and am not even entirely sure what this is supposed to do...
mrangel
03 Dec 2018, 08:52The text processor is fairly limited, and there's a lot of cases where nesting doesn't work properly; although I believe some of them were fixed in 5.8
In this case, the text processor looks for an object named {pencil.name}
, doesn't find one, and because it's failed it ignores the {object:
command and escapes the curly brackets before passing the string inside it to the text processor.
For the most part, the outermost text processor commands are processed first.
However, as of 5.8 if you do:
game.text_processor_variables = NewDictionary()
dictionary add (game.text_processor_variables, "obj", pencil)
msg ("I can see a {object:obj}.")
it will output I can see a pencil.
which is probably what you wanted. The parameter passed to {object:
can be either a variable or an object name, just like a bareword outside of the text processor.
mrangel
03 Dec 2018, 09:14Random aside
Just thinking… maybe it would be more convenient to have something like: <function name="ProcessText" type="string" parameters="text, variables">
data = NewDictionary()
dictionary add (data, "fulltext", text)
if (IsDefined("variables")) {
if (not EndsWith (TypeOf (variables), "dictionary")) {
variables = QuickParams ("this", variables)
}
if (EndsWith (TypeOf (game, "text_processor_variables"), "dictionary")) {
vars = NewDictionary()
foreach (key, game.text_processor_variables) {
if (not DictionaryContains (variables, key)) dictionary add (vars, key, DictionaryItem (game.text_processor_variables, key))
}
foreach (key, variables) {
dictionary add (vars, key, DictionaryItem (variables, key))
}
dictionary add (data, vars)
}
else {
dictionary add (data, variables)
}
}
else if (EndsWith (TypeOf (game, "text_processor_variables"), "dictionary")) {
dictionary add (data, game.text_processor_variables)
}
text = ProcessTextSection(text, data)
return (Replace(Replace(text, "@@@open@@@", "{"), "@@@close@@@", "}"))
</function>
and pass the data
dictionary to ParamsForTextProcessor and ObjectForTextProcessor.
The user could then do ProcessText ("The {object:thing} is blue.", QuickParams("thing", pencil))
or ProcessText ("The {object:this} is blue.", pen)
; while allowing text_processor_variables
to be used for variables which you want to persist for multiple calls to ProcessText; as well as for backwards compatibility
Not sure why, but looking at this made me wonder about a slightly more intuitive (to me, at least) way the text processor could be made to handle variables.

K.V.
03 Dec 2018, 13:06To me, the text processor is only for description text boxes -- like for when we are just filling in room or object descriptions in the GUI and don't feel like switching it to a script just to print an attribute or two in the prose.
So, I very rarely use the text processor in a msg()
, since it's just as easy (if not easier (and less stressful on Quest)) to just break in and out of a literal string with " +
.
Example: msg("All of a sudden, and out of nowhere, a " + ObjectLink(pencil.name) + " appears!")
...but I'm thinking you may just be using a msg()
in your example because it is merely an example? Or perhaps you just dig using the text processor...
Either way, I'm not judging.
I'm also assuming you are attempting to process pencil.name
as an example. Your goal must be to use obj.name
(with the variable obj
, or something of that nature) in the code. Otherwise, I don't know why you wouldn't just do {object:pencil}
, seeming how you already know the pencil's name.
Here are things that work for me (in Quest 5.8 and in Quest 5.7.2 (which is the version you (Dcoder) are using, if my calculations are correct)):
// Using text processor only
msg ("{object:pencil}")
msg("{=ObjectLink(GetObject(pencil.name))}")
// using whatever works
msg ("{object:" + pencil.name + "}")
msg (ObjectLink(pencil))
msg (ObjectLink(GetObject(pencil.name)))
Also, I'm pretty sure when the text processor sees {object: {whatever_string.here}}
, it immediately prints whatever is returned by something very much like this:
ObjectLink(GetObject("{whatever_string.here}"))
Here is the actual function (ProcessTextCommand_Object
) in Quest 5.7.2:
objectname = Mid(section, 8)
text = ""
colon = Instr(objectname, ":")
if (colon > 0) {
text = Mid(objectname, colon + 1)
objectname = Left(objectname, colon - 1)
}
if (not game.text_processor_this = null) {
objectname = Replace(objectname, "this", game.text_processor_this.name)
}
object = GetObject(objectname)
if (object = null) {
return ("@@@open@@@" + ProcessTextSection(section, data) + "@@@close@@@")
}
else {
if (LengthOf(text) = 0) {
text = SafeXML(GetDisplayAlias(object))
}
if (game.enablehyperlinks) {
linkid = ProcessTextCommand_GetNextLinkId()
colour = ""
if (HasString(object, "linkcolour") and GetUIOption("UseGameColours") = "true") {
colour = object.linkcolour
}
else {
colour = GetLinkTextColour()
}
style = GetCurrentTextFormat(colour)
return ("<a id=\"" + linkid + "\" style=\"" + style + "\" class=\"cmdlink elementmenu\" data-elementid=\"" + object.name + "\">" + text + "</a>")
}
else {
return (text)
}
}
Here's that same function in Quest 5.8 (it is a little different!):
<function name="ProcessTextCommand_Object" type="string" parameters="section, data">
<![CDATA[
objectname = Mid(section, 8)
text = ""
colon = Instr(objectname, ":")
if (colon > 0) {
text = Mid(objectname, colon + 1)
objectname = Left(objectname, colon - 1)
}
object = ObjectForTextProcessor(objectname)
if (object = null) {
return ("@@@open@@@" + ProcessTextSection(section, data) + "@@@close@@@")
}
else {
if (LengthOf(text) = 0) {
text = SafeXML(GetDisplayAlias(object))
}
if (game.enablehyperlinks) {
linkid = ProcessTextCommand_GetNextLinkId()
colour = ""
if (HasString(object, "linkcolour") and GetUIOption("UseGameColours") = "true") {
colour = object.linkcolour
}
else {
colour = GetLinkTextColour()
}
style = GetCurrentTextFormat(colour)
return ("<a id=\"" + linkid + "\" style=\"" + style + "\" class=\"cmdlink elementmenu\" data-elementid=\"" + object.name + "\">" + text + "</a>")
}
else {
return (text)
}
}
]]>
</function>
Here's the new function that revised function uses:
<function name="ObjectForTextProcessor" type="object" parameters="objectname">
if (not game.text_processor_this = null and objectname = "this") {
object = game.text_processor_this
if (TypeOf(object) = "object") {
return (object)
}
}
if (not game.text_processor_variables = null and DictionaryContains (game.text_processor_variables, objectname)) {
object = DictionaryItem(game.text_processor_variables, objectname)
if (TypeOf(object) = "object") {
return (object)
}
}
return (GetObject(objectname))
</function>
BONUS
Things that do not work:
msg ("{object:{pencil.name}}")
// Output: {object:pencil}
msg ("{object:{=GetObject(pencil.name)}}")
// Output: {object:Object: pencil}
msg ("{object:{=ToString(GetObject(pencil.name))}}")
// Output: {object:Object: pencil}
msg ("{object:{=ToString(GetDisplayName(GetObject(pencil.name)))}}")
// Output: {object:a pencil}
Maybe the ProcessTextCommand_Object
function should check for {
in the section
before trying to use GetObject()
?
mrangel
03 Dec 2018, 14:47To me, the text processor is only for description text boxes -- like for when we are just filling in room or object descriptions in the GUI and don't feel like switching it to a script just to print an attribute or two in the prose.
Also for things where you don't want to create a global function for every possible message string.
For example: msg(weapon.critmessage)
in a combat library, where you've loaded text_processor_variables with the name of the target, the attacker, and the damage dealt.