Jump to content

Python Alfred feedback via print(); what's wrong?


Recommended Posts

Hallo

 

I am fiddling  since 5 hours now and still cannot make anything work =(

minimized my testing to the following:

xml = 'EOB' '\n\n'
xml += '<?xml version="1.0"?>' '\n'
xml += '<items>' '\n'
xml += '\t' '<item uid="whatever" arg="myResult">' '\n'
xml += '\t' '\t' '<title>hallo</title>' '\n'
xml += '\t' '\t' '<subtitle>who is there?</subtitle>' '\n'
xml += '\t' '</item>' '\n'
xml += '</items>'
xml += '\n\nEOB'

print(xml)

I used a script filter.

Tried to paste it into the window, while selecting the python interpreter.

Tried to use the bash shell interpreter with "python myScriptname.py".

 

Any help appreciated. Is there any Tutorial on this topic? Cannot find appropriate documentation either =( Am I blind? Sorry if I could not find a proper documentation when there is one out there...

Edited by LittleEntity
Link to comment

Try this code:

xml = '<?xml version="1.0"?>' '\n'
xml += '<items>' '\n'
xml += '\t' '<item uid="whatever" arg="myResult">' '\n'
xml += '\t' '\t' '<title>hallo</title>' '\n'
xml += '\t' '\t' '<subtitle>who is there?</subtitle>' '\n'
xml += '\t' '</item>' '\n'
xml += '</items>'

print(xml)

Those 'EOB' strings turned out to be the culprit. They were making the XML invalid, and Alfred can't parse invalid XML.

 

Say the word if you need more help :)

Edited by Tyler Eich
Link to comment

lol... I thought they were needed to tell alfred that a msg block is coming. I copied them from the shell Example. Can somebody explain, why they are need in the example?

Thankyou for the fast reply.

 

Shell example "Script Filter XML format":

cat << EOB

<?xml version="1.0"?>

<items>

<!--
  Example of using icon type 'fileicon' to load the file icon directly.
  This item is of type "file" which means it will be treated as a file in
  Alfred's results, so can be actioned and revealed in finder.
  Autocomplete sets what will complete when the user autocompletes.
-->

  <item uid="desktop" arg="~/Desktop" valid="YES" autocomplete="Desktop" type="file">
    <title>Desktop</title>
    <subtitle>~/Desktop</subtitle>
    <icon type="fileicon">~/Desktop</icon>
  </item>

<!--
  Example of loading an icon from the Workflow's folder.
  This item is set as valid no, which means it won't be actioned
-->

  <item uid="flickr" valid="no" autocomplete="flickr">
    <title>Flickr</title>
    <icon>flickr.png</icon>
  </item>

<!--
  Example of using icon type 'filetype' to load the icon for the file type.
  This item is of type "file" which means it will be treated as a file in
  Alfred's results, so can be actioned and revealed in finder.
-->

  <item uid="image" autocomplete="My holiday photo" type="file">
    <title>My holiday photo</title>
    <subtitle>~/Pictures/My holiday photo.jpg</subtitle>
    <icon type="filetype">public.jpeg</icon>
  </item>

</items>

EOB
Link to comment

It's called a heredoc. It's a bash thing. PHP  can do it, too. It's a simple way of creating really long, multi-line strings.

 

It's not necessary in Python because you can do this (triple-quoted strings):

xml = """<?xml version="1.0"?>
<items>
	<item uid="whatever" arg="myResult">
		<title>hallo</title>
		<subtitle>who is there?</subtitle>
		</item>
</items>"""

In a language like Python, PHP, Ruby or anything else that has built-in XML support, you should probably use those feature to generate your output. If you don't, sooner or later you'll end up with & or < or > in your output, which will break Alfred (invalid XML).

Edited by deanishe
Link to comment
(...)

 

In a language like Python, PHP, Ruby or anything else that has built-in XML support, you should probably use those feature to generate your output. If you don't, sooner or later you'll end up with & or < or > in your output, which will break Alfred (invalid XML).

 

thankyou for your helpful reply =D what do you mean with '<' in the output? You mean escaping '<' signs in text content?

Link to comment

It's just really easy to write invalid XML when generating it automatically. So, if someone was, say, pulling titles that had illegal characters, then they could be automatically escaped with certain PHP, Python, or Ruby functions. Without escaping those, it would break the XML. You can't always anticipate everything that a user is going to pipe into the workflow (or any bit of code, really), so escaping them prevents many bugs via illegal characters.

Link to comment

I'd generally recommend using one of the available workflow libraries (they help with more than just generating XML). If you want to build everything yourself, read the source code of the libraries to see how they do things. They've all been thoroughly tested, so how they do things is a way that works. Generating XML in Python is pretty simple, but there are things that may catch you out.

See here: http://www.alfredforum.com/topic/2030-workflow-libraries-and-helpers/

One thing you have to pay attention to with Python is text decoding/encoding. You must be sure to UTF-8-encode anything you output to Alfred or it will break for somebody. Always test your code with non-ASCII text, like üøß etc. This will pick up errors that we English-speakers tend to miss but will show up very quickly for folks using other languages.

Link to comment

I've already visited the link you posted. And I've already visited some libraries. But for what I wanted to build the libraries are far too complex. I wanted something dead simple to use. Another argument to write my own code and only use the std library is I want to learn python =)

 

Because I am from germany I know how frustrating it can be for users when letters like 'ä' 'ö' 'ü' or 'ß' aren't available for writing names.

 

Here are two links I found which explain how to convert strings to python-xml objects and vice versa:

http://stackoverflow.com/questions/3605680/creating-a-simple-xml-file-using-python

http://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring

Link to comment

What you could do is to write a function that can create the xml itself after sending it some arguments. You have the template above, so you could just feed an array into some sort of loop (for, foreach, while), and generate all of the individual xml items and then put the <xml.... ><items>...</items> strings on the results. After that, just print it so that Alfred can see it.

Link to comment

I've already visited the link you posted. And I've already visited some libraries. But for what I wanted to build the libraries are far too complex. I wanted something dead simple to use. Another argument to write my own code and only use the std library is I want to learn python =)

 

Because I am from germany I know how frustrating it can be for users when letters like 'ä' 'ö' 'ü' or 'ß' aren't available for writing names.

 

Here are two links I found which explain how to convert strings to python-xml objects and vice versa:

http://stackoverflow.com/questions/3605680/creating-a-simple-xml-file-using-python

http://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring

That's exactly what you want! ElementTree is what all the workflow libraries use, and is the standard Python way of generating XML. Even if the workflow libraries are too heavyweight for your purposes, it's still educational to look at the code to see how they do things. Personally, I use this library, as it's nice and lightweight and includes only the bare essentials:

https://github.com/nikipore/alfred-python

With regard to non-ASCII characters, Alfred and workflows can absolutely handle them, as long as you do it right. It can, however, be a bit tricky with Python.

Have you in this connection problems with German special characters, can you you calmly by me melden or my own workflows angucken, as they with non-ASCII text onewallfree function because I am in Python as well as Denglisch fluent ;)

(Sorry, I've had a few beers.)

Link to comment

What you could do is to write a function that can create the xml itself after sending it some arguments. You have the template above, so you could just feed an array into some sort of loop (for, foreach, while), and generate all of the individual xml items and then put the <xml.... ><items>...</items> strings on the results. After that, just print it so that Alfred can see it.

Have to add to that, you can't just print it, you have to encode it to UTF-8 first, or it may produce invalid XML.

Link to comment

Here is a AlfredFeedback class I wrote with the etree.ElementTree module for xml handling. This is far easier than the last version x)

now... let's speak about utf-8 characters. When I try to insert a 'ß' or 'ä' I get the error:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)

which is weird because I set the encoding to 'UTF-8' in line 42 of the AlfredFeedback module.

Do you have any advice or suggestions?

 

feedback module code:

# -*- coding: UTF-8 -*-
import xml.etree.ElementTree as ET

class AlfredFeedback:
	itemsElement = ET.Element( 'items' )

	def addItem( self, 
	uniqueIdentifier, 
	argument, 
	title, 
	subtitle = None, 
	icon = None, 
	itemtype = None ):
		uniqueIdentifier = str( uniqueIdentifier )
		self.assertUIdenIsNew( uniqueIdentifier )
		self.assertValidItemType( itemtype )
		itemElement = ET.SubElement( self.itemsElement, 'item' )
		itemElement.set( 'uid', uniqueIdentifier )
		itemElement.set( 'arg', argument )
		if itemtype is not None: itemElement.set( 'type', itemtype )
		titleElement = ET.SubElement( itemElement, 'title' )
		titleElement.text = title
		if subtitle is not None:
			subtitleElement = ET.SubElement( itemElement, 'subtitle' )
			subtitleElement.text = subtitle
		if icon is not None:
			iconElement = ET.SubElement( itemElement, 'icon' )
			iconElement.text = icon

	def assertUIdenIsNew( self, uniqueIdentifier ):
		for child in self.itemsElement:
			if uniqueIdentifier == child.get( 'uid' ):
				errorString = 'the identifier \"' + uniqueIdentifier + '\" was already added!'
				raise NameError(errorString)

	def assertValidItemType( self, itemtype ):
		if itemtype is not None:
			if itemtype != 'file':
				raise Exception('The value of parameter itemtype must be a string containing "file"!')

	def writeFeedback( self ):
		response = ET.tostring( self.itemsElement, 'UTF-8', 'xml' )
		print( response )

test code:

# -*- coding: UTF-8 -*-
from AlfredFeedback import AlfredFeedback

feedback = AlfredFeedback( )
feedback.addItem( 1, 'FRST', 'first', 'First Item', '/path/anIcon.png')
feedback.addItem( 2, 'SCND', 'ßecond', 'Second Item', '/anotherPath/myIcon.png', 'file' )
feedback.writeFeedback( )
Edited by LittleEntity
Link to comment

There are 2 problems with the script.

 

Firstly, your addItem method will only accept unicode/strings, so:

feedback.addItem( 1, ...)

needs to be:

feedback.addItem( '1', ...)

Secondly, you're not using unicode strings, but rather byte strings without an encoding. When you try to print them, Python will assume they're ASCII and try to use that codec, which is what's causing your error.

 

You need to either prefix all your strings with u, e.g. u'ßecond', or insert from __future__ import unicode_literals right at the top of your script. My personal preference is the former (it's more explicit) and the latter should only be used for Python 3 compatibility. In this case I used unicode_literals because I've had a few beers and can't be bothered to change all the strings to u"...".

 

Here are the altered scripts. Note that I've not only added from __future__ import unicode_literals, but also changed the formatting so it matches the Python standard. It will still run with extraneous spaces and overly long lines, but the original code does not meet the criteria for properly formatted Python code (i.e. you'll never get anything written that way into the official Python distribution, and any Python dev will furiously tut when reading the code).

# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import xml.etree.ElementTree as ET


class AlfredFeedback:

    itemsElement = ET.Element('items')

    def addItem(self, uniqueIdentifier, argument, title, subtitle=None,
                icon=None,
                itemtype=None):
        # This assignment is pointless: it assigns the existing variable to itself.
        uniqueIdentifier = uniqueIdentifier
        self.assertUIdenIsNew(uniqueIdentifier)
        self.assertValidItemType(itemtype)
        itemElement = ET.SubElement(self.itemsElement, 'item')
        itemElement.set('uid', uniqueIdentifier)
        itemElement.set('arg', argument)
        if itemtype is not None:
            itemElement.set('type', itemtype)
        titleElement = ET.SubElement(itemElement, 'title')
        titleElement.text = title
        if subtitle is not None:
            subtitleElement = ET.SubElement(itemElement, 'subtitle')
            subtitleElement.text = subtitle
        if icon is not None:
            iconElement = ET.SubElement(itemElement, 'icon')
            iconElement.text = icon

    def assertUIdenIsNew(self, uniqueIdentifier):
        for child in self.itemsElement:
            if uniqueIdentifier == child.get('uid'):
                errorString = ('the identifier "' + uniqueIdentifier +
                               '" was already added!')
                raise NameError(errorString)

    def assertValidItemType(self, itemtype):
        if itemtype is not None:
            if itemtype != 'file':
                raise Exception('The value of parameter itemtype'
                                ' must be a string containing "file"!')

    def writeFeedback(self):
        response = ET.tostring(self.itemsElement, encoding='utf-8')
        print(response)

# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from AlfredFeedback import AlfredFeedback

feedback = AlfredFeedback()
feedback.addItem('1', 'FRST', 'first', 'First Item', '/path/anIcon.png')
feedback.addItem('2', 'SCND', 'ßecond', 'Second Item',
                 '/anotherPath/myIcon.png', 'file')
feedback.writeFeedback()

Edited by deanishe
Link to comment

 

There are 2 problems with the script.

 

Firstly, your addItem method will only accept unicode/strings, so:

feedback.addItem( 1, ...)

needs to be:

feedback.addItem( '1', ...)

that's not true... in line 14 says:

 

uniqueIdentifier = str( uniqueIdentifier )

 

that's why it works with an integer as argument ;)

 

thankyou very much for your help and effort ^^

I agree with the format changing except that the spaces between '(' and ')' are lost. I am programming with "Times New Roman" because I can read text with not monospaced fonts faster. Sadly when I do this and I do not insert those spaces the code becomes too much condensed which is bad.

Link to comment

If you are using eclipse or sublime text and you have to restructure code like you need to insert a "u" in front of any "'" like in this case, use regular expressions. In sublime text 2 you I used the regex pattern ('.*?') and the replace pattern u\1 . The replace pattern u$1 would have also worked.

see http://stackoverflow.com/questions/11819886/regular-expression-search-replace-in-sublime-text-2 for more information =)

Edited by LittleEntity
Link to comment

that's not true... in line 14 says:

 

uniqueIdentifier = str( uniqueIdentifier )

 

that's why it works with an integer as argument ;)

 

thankyou very much for your help and effort ^^

I agree with the format changing except that the spaces between '(' and ')' are lost. I am programming with "Times New Roman" because I can read text with not monospaced fonts faster. Sadly when I do this and I do not insert those spaces the code becomes too much condensed which is bad.

 

 

You're right. I seem to have deleted that while editing the script. Sorry. As I said early, I've had a few beers.

Link to comment

I edited the files and I still get the same error =(
any further suggestions? Thanks alot =)

# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import xml.etree.ElementTree as ET

class AlfredFeedback:
    itemsElement = ET.Element( u'items' )

    def addItem(    
            self,
            uniqueIdentifier,
            argument,
            title,
            subtitle = None,
            icon = None,
            itemtype = None ):
        uniqueIdentifier = str( uniqueIdentifier )
        self.assertUIdenIsNew( uniqueIdentifier )
        self.assertValidItemType( itemtype )
        itemElement = ET.SubElement( self.itemsElement, u'item' )
        itemElement.set( u'uid', uniqueIdentifier )
        itemElement.set( u'arg', argument )
        if itemtype is not None: itemElement.set( u'type', itemtype )
        titleElement = ET.SubElement( itemElement, u'title' )
        titleElement.text = title
        if subtitle is not None:
            subtitleElement = ET.SubElement( itemElement, u'subtitle' )
            subtitleElement.text = subtitle
        if icon is not None:
            iconElement = ET.SubElement( itemElement, u'icon' )
            iconElement.text = icon

    def assertUIdenIsNew( self, uniqueIdentifier ):
        for child in self.itemsElement:
            if uniqueIdentifier == child.get( u'uid' ):
                errorString = ( u'the identifier \"' + uniqueIdentifier
                    + u'\" was already added!' )
                raise NameError( errorString )

    def assertValidItemType( self, itemtype ):
        if itemtype is not None:
            if itemtype != u'file':
                raise Exception( u'The value of parameter itemtype'
                    u'must be a string containing "file"!' )

    def writeFeedback( self ):
        response = ET.tostring( self.itemsElement, u'UTF-8', u'xml' )
        print( response )
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from AlfredFeedback import AlfredFeedback

feedback = AlfredFeedback( )
feedback.addItem( 1, u'FRST', u'first', u'First Item', u'/path/anIcon.png')
feedback.addItem( 2, u'SCND', u'ßecond', u'Second Item', u'/anotherPath/myIcon.png', u'file' )
feedback.writeFeedback( )

Edited by LittleEntity
Link to comment
    def writeFeedback( self ):
        response = ET.tostring( self.itemsElement, u'UTF-8', u'xml' )
        print( response.encode( 'UTF-8' ) )

hmm.. doesn't work either. =(

 

error log:

Traceback (most recent call last):
  File "testing.py", line 8, in <module>
    feedback.writeFeedback( )
  File "/Users/benutzer/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/user.workflow.26FD225E-0254-4E36-996D-B1166C5D36A5/AlfredFeedback.py", line 46, in writeFeedback
    response = ET.tostring( self.itemsElement, u'UTF-8', u'xml' )
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.py", line 1127, in tostring
    return "".join(data)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)

mwahaha ***ROFLMAO***

but guess what? your code WORKS! cannot believe it! awsome !!! how did you know this? you should definitly never stop drinking whish I could spend you a beer some day =D

 

hug >( ^.^)> you <(^.^ )<

 

working piece:

	def writeFeedback( self ):
		response = ET.tostring( self.itemsElement )
		print( response.encode( 'UTF-8' ) )

so.. the error appears when there is

response = ET.tostring( self.itemsElement, u'UTF-8' )

with this version I cannot insert special letters like 'ß'

but it will generate the following as first line of the xml:

<?xml version='1.0' encoding='UTF-8'?>

when I use this one instead:

response = ET.tostring( self.itemsElement )

using 'ß' or any other special letter won't break the script

but it won't generate the first line with the version and encoding.

Alfred works without first line declaring version and encoding =) didn't know that either.

Edited by LittleEntity
Link to comment

At a guess, I'd say the encoding parameter of ET.tostring is to tell it the encoding of the strings you've already given it, not how it should try to encode them itself, i.e. it simply causes it to generate the appropriate XML header. The docs are not clear about this, and I can't be bothered checking the source.

 

So it was throwing the error because it was trying to join unicode strings with a plain string, which causes Python to try to ASCII-encode the unicode.

Link to comment

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...