Add-on:Parsedom for xbmc plugins

= Testing/Status = Integration and unittests are run continously by Jenkins

http://tc.tobiasussing.dk/jenkins/view/Common%20Functions/

= Developers = This DOM parser is a fast replacement for Beautiful Soup.

And also a few other useful functions.

Development and support thread: http://forum.kodi.tv/showthread.php?t=116498

Setup
To use the parsedom functions edit your addon.xml like this.

  # Add this

And add the following to your py file.

import CommonFunctions common = CommonFunctions common.plugin = "Your Plugin name-1.0"

If you want fetchPage to support cookies you must include the following in your default.py

import cookielib import urllib2 cookiejar = cookielib.LWPCookieJar cookie_handler = urllib2.HTTPCookieProcessor(cookiejar) opener = urllib2.build_opener(cookie_handler)

Debugging
To enable debugging set the following values in default.py

common.dbg = True # Default common.dbglevel = 3 # Default

Whenever you debug your own code you should also debug in parseDOM.

parseDOM(self, html, name = "", attrs = {}, ret = False)

 * html(string or list) - String to parse, or list of strings to parse.
 * name(string) - Element to match ( for instance "span" )
 * attrs(dictionary) - Dictionary with attributes you want matched in the elment ( for instance { "id": "span3", "class": "oneclass.*anotherclass", "attribute": "a random tag" } )
 * ret(string or False) - Attribute in element to return value of. If not set(or False), returns content of DOM element.

returns list

Getting element content. link_html = "Link Test" ret = common.parseDOM(link_html, "a") print repr(ret) # Prints ['Link Test']

Getting an element attribute. link_html = "Link Test" ret = common.parseDOM(link_html, "a", ret = "href") print repr(ret) # Prints ['bla.html']

Get element with matching attribute. link_html = "Link Test1Link Test2Link Test3" ret1 = common.parseDOM(link_html, "a", attrs = { "id": "link1" }, ret = "href") ret2 = common.parseDOM(link_html, "a", attrs = { "id": "link2" }) ret3 = common.parseDOM(link_html, "a", attrs = { "id": "link3" }, ret = "id") print repr(ret1) # Prints ['bla1.html'] print repr(ret2) # Prints ['Link Test2'] print repr(ret3) # Prints ['link3']

When scraping sites it is prudent to scrape in steps, since real websites are often complicated.

Take this example where you want to get all the user uploads.

&lt;div id="content"&gt; &lt;div id="sidebar"&gt; &lt;div id="latest"&gt; Miley Cyrus - When I Look At You&gt;br /&lt; Puppet theater&lt;br /&gt; VBLOG #42</a>&lt;br /&gt; Fourth upload</a>&lt;br /&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id="user"&gt; &lt;div id="uploads"&gt; First upload</a>&lt;br /&gt; Second upload</a>&lt;br /&gt; Third upload</a>&lt;br /&gt; Fourth upload</a>&lt;br /&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;

The first step is to limit your search to the correct area.

One should always find the inner most DOM element that contains the needed data.

ret = common.parseDOM(html, "div", attrs = { "id": "uploads" })

The variable ret now contains

['First upload</a>&lt;br /&gt; Second upload</a>&lt;br /&gt; Third upload</a>&lt;br /&gt; Fourth upload</a>&lt;br /&gt;']

And now we get the video url.

videos = common.parseDOM(ret, "a", ret = "href") print repr(videos) # Prints [ "video?12", "video?23", "video?34", "video?41" ]

XML with parseDOM
The parseDOM function supports the XML syntax

We have used parseDOM for some simple XML structures.

There is no guarantee that parseDOM for XML will be better than minidom.

xml = '<?xml version="1.0" encoding="utf-8"?><rsp generated_in="0.1760" stat="ok" />' stat = self.common.parseDOM(xml, "rsp", ret = "stat") print stat # Prints ok

Here is a slightly more complex example.

xml = '<?xml version="1.0" encoding="utf-8"?> <rsp generated_in="0.0132" stat="ok"> <contacts on_this_page="1" page="1" perpage="25" total="1"> <contact display_name="MileyCyrus" id="458471" is_plus="0" is_pro="0" is_staff="0" mutual="1" profileurl="http://somesite/user458471" realname="MileyCyrus" username="user458471" videosurl="http://somesite/user458471/videos"> <portrait height="30" width="30">http://somesite/portraits/defaults/d.30.jpg <portrait height="75" width="75">http://somesite/portraits/defaults/d.75.jpg <portrait height="100" width="100">http://somesite/portraits/defaults/d.100.jpg <portrait height="300" width="300">http://somesite/portraits/defaults/d.300.jpg ' ids = self.common.parseDOM(xml, "contact", ret = "id") titles = self.common.parseDOM(xml, "contact", ret = "display_name") portraits = self.common.parseDOM(xml, "portraits") next = "false" result = []; for i in range(0, len(ids)): group = {} group['contact'] = ids[i] group['Title'] = titles[i] thumbs_width = self.common.parseDOM(portraits, "portrait", ret = "width") thumbs_url = self.common.parseDOM(portraits, "portrait") for j in range(0, len(thumbs_width)): if (int(thumbs_width[j]) <= 300): group['thumbnail'] = thumbs_url[j] result.append(group)

print repr(result) # Prints [{'contact': '458471', 'thumbnail': 'http://somesite/portraits/defaults/d.300.jpg', 'Title': 'MileyCyrus'}

Again, this function has not been tested with XML in any way.

fetchPage(self, dict)
Fetches a page from the internet.

returns dict.

Input dictinary variables:
 * link(string): The URL.
 * cookie(string): The cookies that need to be set.
 * refering(string): The refering URL.
 * post_data(dict): Data to POST to the link.

The dict returned contains
 * content: HTML content
 * new_url: Redirect url
 * header: Header information
 * status: http return status

result = common.fetchPage({"link": "http://www.example.com/index.html"}) if result["status"] == 200: print "content: %s" %result["content"] result = common.fetchPage({"link": "http://www.example.com/doesnotexist.html"}) if result["status"] == 500: print "redirect url: %s" %result["new_url"] print "header: %s" %result["header"] print "content: %s" %result["content"]

log(self, str, level = 0)
Sends the string to the xbmc.log function if the level provided is less than the level set in dbglevel.

Returns None

import CommonFunctions common = CommonFunctions common.plugin = "PluginName" common.dbg = True common.dbglevel = 3 def helloWorld: common.log("Ran this") common.log("Ignored this", 4) common.log("Ran this as well", 2) helloWorld

Will give the following output in the xbmc.log

[PluginName] helloWorld : 'Ran This' [PluginName] helloWorld : 'Ran This as well'

openFile(self, filepath, options = "w")
Opens a binary or text file handle

Returns filehandle

file = common.openFile("myfile.txt", "wb") file.write("my data") file.close

getUserInput(self, title = "Input", default="", hidden=False)
This function raises a keyboard for user input

Returns string

search = common.getUserInput("Artist", "") # Will ask the user to write an Artist to search for def_search = common.getUserInput("Artist", "Miley Cyrus") # Will default to Miley Cyrus if the user doesn't enter another artist.

getUserInputNumbers(self, title = "Input", default="", hidden=False)
This function raises a keyboard numpad for user input

Returns int

pin = common.getUserInputNumbers("Userpin", "") # Will ask the user to write a pin. def_pin = common.getUserInputNumbers("Userpin", "1234") # Will default to 1234 if the user doesn't enter another pin.

getParameters(self, dict)
Converts the request url passed on by xbmc to the plugin into a dict of key-value pairs

returns dict

params = common.getParameters(sys.argv[2]) # sys.argv[2] would be something like "?path=/root/favorites&login=true" print repr(params) # Prints '{ "path": "/root/favorites", "login": "true" }'

replaceHTMLCodes(self, str)
Replaces html codes with ascii.

Returns string

clean_string = common.replaceHtmlCodes("&amp;amp;&amp;quot;&amp;hellip;&amp;gt;&amp;lt;&amp;#39;") print clean_string # Prints &"...><'

stripTags(self, str)
Removes all DOM elements.

Returns string

clean_string = common.stripTags("I want this text &lt;img src='' alt='without this'&gt;") print clean_string # Prints "I want this text"

makeAscii(self, str)
This function implements a horrible hack related to python 2.4's terrible unicode handling.

Returns string

clean_string = common.makeAscii("test נלה מהי test") print clean_string # Prints "test  test"