Jump to content

Alfred 2.0.3: Passing multiline text out from a script filter not supported [Fixed v.2.0.4 pre-release]


mklement0

Recommended Posts

As of Alfred 2.0.3, it seems that passing multi-line text out from a script filter via the arg attribute (e.g., to the copy-to-clipboard action) is not supported: each newline is replaced with a space.

  • I suspect this is a side effect of XML parsing, since the desired output value is stored in an XML attribute, and even though newlines are legal in attribute values, on passing the value out, XML parsers are expected to normalize the value by replacing all non-space whitespace characters with a space each, including \n (and \r\n) - see http://www.w3.org/TR/1998/REC-xml-19980210#AVNormalize

Would be nice not to have this restriction, though, assuming my guess is correct, I'm not sure it's feasible with the current, XML-document-based approach.

A workaround is below, but it is somewhat cumbersome.

 

Workaround [updated on 2 May 2013 to incorporate David Ferguson's suggestion]:

  • Inside the script filter, create a custom encoding where you replace newline chars. with a placeholder, e.g.: 
  • Use the custom-encoded text as the value of the arg attribute.
  • Add an intermediate script action that decodes the custom encoding by substituting newlines for the placeholders to restore the original value and then passes the restored value out.
    • Sadly, since output from script actions cannot follow conditional paths based on keyboard modifiers, you'll have to duplicate the intermediate script action for every modifier combination you want to support, and attach the modifier-specific paths directly to the script filter.

Download a demonstration workflow here: https://dl.dropboxusercontent.com/u/10047483/MultilineTextScriptFilterLimitationDemo.alfredworkflow

  • Keyword "mt1" demonstrates the original limitation by attempting to copy a 2-line string passed from a script filter to the clipboard.
  • Keyword "mt2" demonstrates the workaround.

Workaround source code:

  • Sample script-filter action (using /bin/bash: escape only backquotes, double quotes, backslashes, dollars):

 

# Create sample multiline text.
text=$'line 1\nline 2'

# Derive a single-line representation for Alfred's result list.
textForDisplay="${text//$'\n'/ ⏎ }"

# Since linefeeds are inadvertently replaced by spaces when the value assigned to the `arg` attribute
# is retrieved later, we assign a custom-encoded form of the output text in which we replace newlines
# with placeholders.
# In a subsequent, auxiliary script-action step we then perform unencoding to restore the original text.
textEncoded=${text//$'\n'/⏎}

cat <<EOF
<?xml version="1.0"?>
<items>

  <item arg="$textEncoded">
    <title>$textForDisplay</title>
    <subtitle>Action to copy to clipboard and display notif.</subtitle>
  </item>

</items>
EOF

 

 

  • Generic, directly reusable script action that unencodes (decodes) the custom encoding and relays the restored value  (using /bin/bash; escape only backquotes, double quotes, backslashes, dollars):

# Input is assumed to be custom-encoded text in which newlines have
# been replaced with ⏎ chars.
# We UNencode (decode) here in order to restore the newlines, and pass the
# restored text out.
v="{query}"
echo -nE "${v//⏎/$'\n'}"
Edited by mklement0
Link to comment
Share on other sites

As of Alfred 2.0.3, it seems that passing multi-line text out from a script filter via the arg attribute (e.g., to the copy-to-clipboard action) is not supported: each newline is replaced with a space.

  • I suspect this is a side effect of XML parsing, since the desired output value is stored in an XML attribute, and even though newlines are legal in attribute values, on passing the value out, XML parsers are expected to normalize the value by replacing all non-space whitespace characters with a space each, including \n (and \r\n) - see http://www.w3.org/TR/1998/REC-xml-19980210#AVNormalize

Would be nice not to have this restriction, though, assuming my guess is correct, I'm not sure it's feasible with the current, XML-document-based approach.

A workaround is below, but it is somewhat cumbersome.

 

Workaround:

  • Inside the script filter, save the multiline text to a temporary file.
  • Instead of passing out the text directly (via the 'arg' attribute), pass out the path of the temp. file.
    • Sadly, this means that if the item is never actioned, the temp. file lingers until the next reboot.
  • Add an intermediate script action that simply echoes the content of the temp. file to pass it on unmodified and then deletes the temp. file.
    • Sadly, since output from script actions cannot follow conditional paths based on keyboard modifiers, you'll have to duplicate the intermediate script action for every modifier combination you want to support, and attach the modifier-specific paths directly to the script filter.

Download a demonstration workflow here: https://dl.dropboxusercontent.com/u/10047483/MultilineTextScriptFilterLimitationDemo.alfredworkflow

  • Keyword "mt1" demonstrates the original limitation by attempting to copy a 2-line string passed from a script filter to the clipboard.
  • Keyword "mt2" demonstrates the workaround.

Workaround source code:

  • Sample script-filter action (using /bin/bash: escape only backquotes, double quotes, backslashes, dollars):

 

# Create sample multiline text.
text=$'line 1\nline 2'
# Derive a single-line representation from it for Alfred's result list.
textForDisplay="${text//$'\n'/ ⏎ }"

# Save multiline text to temp. file.
# Note: To prevent accidental modification, we use `echo -nE` rather than `printf`, as the latter invariably interprets chars. such as '%'.
ftemp="$(mktemp -t mldemo)"
echo -nE "$text" > "$ftemp"

# Workaround: In the script filter, pass out the temp. file's path.
cat <<EOF
<?xml version="1.0"?>
<items>
  <item arg="$ftemp">
    <title>Action to copy to clipboard and display notif.</title>
    <subtitle>$textForDisplay</subtitle>
  </item>
</items>
EOF 

 

  • Generic, directly reusable script action that relays the output from the temp. file (using /bin/bash; deactivate all escaping):

 

# Input is assumed to be the path of a temp. file containing the intended output.

# Pass out the temp. file's content.
cat "{query}"

# Remove the temp. file.
rm "{query}" &> /dev/null

 

I was able to replicate this. I wonder if another alternative to the output file would be to replace a newline character with some special character just while you pass it to the next step. Then you could use a run script item to replace that character with a newline character before you pass it to the "Copy to Clipboard". I'm going to talk with Andrew about this and see if there are any other options.

Link to comment
Share on other sites

Thanks, David.

 

An easy solution would be to allow a child *element* to act as an alternative to the `arg` *attribute* on the `<item>` elements, and to use that element's value unmodified (no normalization of whitespace), e.g.:

 

 
  <item>
    <arg>line 1
line 2</arg>
    <title>line 1 ...</title>
    <subtitle>multi-line test</subtitle>
  </item>

 

 

Thanks for you workaround suggestion - it is preferable to mine in that no temp. files that could linger are involved.
I've updated my original post and the linked-to demo workflow accordingly.
Edited by mklement0
Link to comment
Share on other sites

Thanks, David.

 

An easy solution would be to allow a child *element* to act as an alternative to the `arg` *attribute* on the `<item>` elements, and to use that element's value unmodified (no normalization of whitespace), e.g.:

 

 
  <item>
    <arg>line 1
line 2</arg>
    <title>line 1 ...</title>
    <subtitle>multi-line test</subtitle>
  </item>

 

 

Thanks for you workaround suggestion - it is preferable to mine in that no temp. files that could linger are involved.
I've updated my original post and the linked-to demo workflow accordingly.

 

Thanks for investigating, I've added a ticket to look into this for a future release :)

Link to comment
Share on other sites

Thanks, Andrew.

 

One final thought: another advantage to using an *element* to pass a value is that a CDATA element can then be used to enclose the value, obviating the need for XML encoding (which is not easy to come by in a bash script, for instance).

Link to comment
Share on other sites

  • 3 weeks later...

Thanks, Andrew.

 

One final thought: another advantage to using an *element* to pass a value is that a CDATA element can then be used to enclose the value, obviating the need for XML encoding (which is not easy to come by in a bash script, for instance).

 

The XML CDATA could even make some workflows faster since there is not need to parse every single result.

 

Sometimes, using AppleScript, I get search results faster from an application than the workflow can encode XML.

 

I see some serious speed improvements in workflows. I hope it is possible to add in Alfred XML.

Link to comment
Share on other sites

  • 2 weeks later...

 

As of Alfred 2.0.3, it seems that passing multi-line text out from a script filter via the arg attribute (e.g., to the copy-to-clipboard action) is not supported: each newline is replaced with a space.

 

Improvements have been added to Alfred v2.0.4, currently on pre-release. You can download the pre-release to have a play and please report back :)

Link to comment
Share on other sites

Improvements have been added to Alfred v2.0.4, currently on pre-release. You can download the pre-release to have a play and please report back :)

 

I’ll play around with the new feature but here my thoughts:

 

- Alfred Quick Look: maybe Alfred could get the first line or first item (in a tab delimited argument) to use in Quick Look and let us use the argument for other workflow tasks too

- UID: it will be interesting to have an <uid></uid> too so we could use XML CDATA here instead of parse it; right now it seems the only attribute that can slow down things a bit; if it can’t slow down Alfred then please consider so we don’t have to parse/encode the UID.

 

Anyway, great improvement. Thank you.

Edited by Carlos-Sz
Link to comment
Share on other sites

Thanks, Vero - works great.

 

+1 on Carlos' suggestion re 'uid' as an element, too.

 

Carlos, I'm afraid I don't understand the Quick Look suggestion. Do you mean having Alfred show the value of 'arg' in the result list if there's no 'title' or 'subtitle' element? What other workflow tasks do you mean?

Link to comment
Share on other sites

The UID is used internally by Alfred and is stored in the knowledge, so I definitely don't want things like cdata in there, just a simple and short string. Remember that you can leave the UID out all together if you want Alfred to leave results in the passed order.

Link to comment
Share on other sites

Thanks, Vero - works great.

 

+1 on Carlos' suggestion re 'uid' as an element, too.

 

Carlos, I'm afraid I don't understand the Quick Look suggestion. Do you mean having Alfred show the value of 'arg' in the result list if there's no 'title' or 'subtitle' element? What other workflow tasks do you mean?

 

The Quick Look requires a path to a file so you can hit SHIFT key and Alfred brings the preview. Now that the arg accepts multiple lines I wish I could pass the file path along with additional arguments. But for the Quick Look to work Alfred has to read the first line only or the first item in a delimited argument.

 

The UID is used internally by Alfred and is stored in the knowledge, so I definitely don't want things like cdata in there, just a simple and short string. Remember that you can leave the UID out all together if you want Alfred to leave results in the passed order.

 

Thank you Andrew.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...