LittleEntity Posted February 10, 2014 Share Posted February 10, 2014 (edited) 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 February 11, 2014 by LittleEntity Link to comment
Tyler Eich Posted February 10, 2014 Share Posted February 10, 2014 (edited) 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 February 10, 2014 by Tyler Eich Link to comment
LittleEntity Posted February 10, 2014 Author Share Posted February 10, 2014 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
deanishe Posted February 10, 2014 Share Posted February 10, 2014 (edited) 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 February 10, 2014 by deanishe Tyler Eich 1 Link to comment
LittleEntity Posted February 11, 2014 Author Share Posted February 11, 2014 (...) 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
rice.shawn Posted February 11, 2014 Share Posted February 11, 2014 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. deanishe 1 Link to comment
LittleEntity Posted February 11, 2014 Author Share Posted February 11, 2014 ok, good point. Thankyou for your help =) do you have a link to share explaining how to work with python and XML? Link to comment
deanishe Posted February 11, 2014 Share Posted February 11, 2014 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
LittleEntity Posted February 11, 2014 Author Share Posted February 11, 2014 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
rice.shawn Posted February 11, 2014 Share Posted February 11, 2014 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
deanishe Posted February 11, 2014 Share Posted February 11, 2014 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
deanishe Posted February 11, 2014 Share Posted February 11, 2014 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
LittleEntity Posted February 11, 2014 Author Share Posted February 11, 2014 (edited) 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 February 11, 2014 by LittleEntity Link to comment
deanishe Posted February 11, 2014 Share Posted February 11, 2014 (edited) 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 February 11, 2014 by deanishe Link to comment
LittleEntity Posted February 12, 2014 Author Share Posted February 12, 2014 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
LittleEntity Posted February 12, 2014 Author Share Posted February 12, 2014 (edited) 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 February 12, 2014 by LittleEntity Link to comment
deanishe Posted February 12, 2014 Share Posted February 12, 2014 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
LittleEntity Posted February 12, 2014 Author Share Posted February 12, 2014 (edited) 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 February 12, 2014 by LittleEntity Link to comment
deanishe Posted February 12, 2014 Share Posted February 12, 2014 Don't ask me why (I'm drunk, remember), but this works: def writeFeedback(self): response = ET.tostring(self.itemsElement) print(response.encode('utf-8')) Link to comment
LittleEntity Posted February 12, 2014 Author Share Posted February 12, 2014 I like you when you are drunk ^^ you seem to become especially helpful. I should advice you to never stop drinking Link to comment
deanishe Posted February 12, 2014 Share Posted February 12, 2014 BTW, you don't need unicode_literals and u"". One or the other is fine. Link to comment
LittleEntity Posted February 12, 2014 Author Share Posted February 12, 2014 (edited) 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 February 12, 2014 by LittleEntity Link to comment
deanishe Posted February 12, 2014 Share Posted February 12, 2014 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
LittleEntity Posted February 12, 2014 Author Share Posted February 12, 2014 well it works now. Thankyou for your help =) greetings Link to comment
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now