Hi.
Here is the first attempt for an Allocine.fr video information grabber.
Allocine.fr is similar to IMDB, but for french speaking people. So this
grabber works like the video/plugin/imdb.py plugin.
Dishi, i have found one bug (i think) in the util/fxdparser.py : the
setattr method doe not work, i modify it. Moroever, i add a "setcdata"
method to allow cdata just below a root node. My modified fxdparser is
joined.
If you wand to test the plugin (with Freevo CVS only)
1 - save the joined fxdparser.py in src/util
2 - save the joined allocine.py in src/video/plugins
3 - Add a plugin.activate("video.allocine.py) in your config file.
Then you should see a new menu item allowing you to grab video information
from Allocine (again, only for french speaking people).
Current data downloaded :
- Title
- Plot
- Year
- Picture (if available)
Sylvain.
<french>
Si vous parlez français et que vous utilisez Freevo CVS, pourriez vous
faire des tests complémentaires ??
Merci.
</french>
#if 0 /*
# -----------------------------------------------------------------------
# allocine.py - Plugin for ALLOCINE support
# -----------------------------------------------------------------------
# $Id$
#
# Notes: IMDB plugin. You can add IMDB informations for video items
# with the plugin
# activate with plugin.activate('video.allocine')
# You can also set allocine_search on a key (e.g. '1') by setting
# EVENTS['menu']['1'] = Event(MENU_CALL_ITEM_ACTION, arg='allocine_search_or_cover_search')
#
# Todo: - Update existing FXD file
# - DVD/VCD support (discset ??)
#
# -----------------------------------------------------------------------
# $Log$
# -----------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002 Krister Lagerstrom, et al.
# Please see the file freevo/Docs/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ----------------------------------------------------------------------- */
#endif
import re
import urllib, urllib2, urlparse
import sys
import codecs
import os
import traceback
import menu
import config
import plugin
import time
from util import htmlenties2txt
from util import fxdparser
from gui.PopupBox import PopupBox
from mmpython.disc.discinfo import cdrom_disc_id
# headers for urllib2
txdata = None
txheaders = {
'User-Agent': 'freevo (%s)' % sys.platform,
'Accept-Language': 'fr-fr',
}
class PluginInterface(plugin.ItemPlugin):
def __init__(self, license=None):
"""Initialise class instance"""
# these are considered as private variables - don't mess with them unless
# no other choise is given
# fyi, the other choice always exists : add a subroutine or ask :)
if not config.USE_NETWORK:
self.reason = 'no network'
return
plugin.ItemPlugin.__init__(self)
def initmyself(self):
self.allocine_id_list = []
self.allocine_id = None
self.isdiscset = False
self.title = ''
self.info = {}
self.image = None # full path image filename
self.image_urls = [] # possible image url list
self.image_url = None # final image url
self.fxdfile = None # filename, full path, WITHOUT extension
self.append = False
self.device = None
self.regexp = None
self.mpl_global_opt = None
self.media_id = None
self.file_opts = []
self.video = []
self.variant = []
self.parts = []
self.var_mplopt = []
self.var_names = []
#image_url_handler stuff
self.image_url_handler = {}
def searchAllocine(self, name):
"""name (string), returns id list
Search for name and returns an id list with tuples:
(id , name, year, type)"""
# Clean internal variables
self.initmyself()
url = 'http://www.allocine.fr/recherche/rubrique.html?typerecherche=3&motcle=%s' % urllib.quote(name)
req = urllib2.Request(url, txdata, txheaders)
searchstring = name
try:
response = urllib2.urlopen(req)
except urllib2.HTTPError, error:
raise FxdAllocine_Net_Error("IMDB unreachable : " + error)
exit
regexp_idarea = re.compile('.*fichefilm_gen_cfilm=[0-9]*\.html.*', re.I)
regexp_id = re.compile('(.*?)\.html"><FONT color=#003399>(.*?)</FONT></A>(.*?)\(([0-9]{4})\)</TD>.*', re.I)
regexp_id2 = re.compile('(.*?)\.html"><FONT color=#003399>(.*?)</FONT>.*')
for line in response.read().split("\n"):
#print line
m = regexp_idarea.match(line)
if m:
#print "Found film id : %s" % line
split1 = line.split("fichefilm_gen_cfilm=")
for idfilm in split1:
idline = regexp_id.match(idfilm)
if idline:
#print "Found film id : %s - %s - %s" % (idline.group(1), idline.group(2), idline.group(4))
self.allocine_id_list += [ ( idline.group(1), idline.group(2), idline.group(4), 'Movies' ) ]
else:
idline = regexp_id2.match(idfilm)
if idline:
#print "Found film id : %s - %s - %s" % (idline.group(1), idline.group(2), 'Unknown')
self.allocine_id_list += [ ( idline.group(1), idline.group(2), 'Unknown', 'Movies' ) ]
return self.allocine_id_list
def guessAllocine(self, filename, label=False):
"""Guess possible imdb movies from filename. Same return as searchImdb"""
name = filename
name = vfs.basename(vfs.splitext(name)[0])
name = re.sub('([a-z])([A-Z])', point_maker, name)
name = re.sub('([a-zA-Z])([0-9])', point_maker, name)
name = re.sub('([0-9])([a-zA-Z])', point_maker, name.lower())
name = re.sub(',', ' ', name)
if label == True:
for r in config.IMDB_REMOVE_FROM_LABEL:
name = re.sub(r, '', name)
parts = re.split('[\._ -]', name)
name = ''
for p in parts:
if not p.lower() in config.IMDB_REMOVE_FROM_SEARCHSTRING and \
not re.search('[^0-9A-Za-z]', p):
# originally: not re.search(p, '[A-Za-z]'):
# not sure what's meant with that
name += '%s ' % p
return self.searchAllocine(name)
def setAllocineId(self, id):
"""id (number)
Set an allocine number for object, and fetch data"""
self.allocine_id = id
#print "Now trying to get %s" % self.allocine_id
url = 'http://www.allocine.fr/film/fichefilm_gen_cfilm=%s.html' % id
req = urllib2.Request(url, txdata, txheaders)
try:
idpage = urllib2.urlopen(req)
except urllib2.HTTPError, error:
raise FxdAllocine_Net_Error("ALLOCINE unreachable" + error)
return None
#print "Response : %s" % idpage.read()
self.parsedata(idpage, id)
idpage.close()
def setFxdFile(self, fxdfilename = None, overwrite = False):
"""
setFxdFile (string, full path)
Set fxd file to write to, may be omitted, may be an existing file
(data will be added) unless overwrite = True
"""
if fxdfilename:
if vfs.splitext(fxdfilename)[1] == '.fxd':
self.fxdfile = vfs.splitext(fxdfilename)[0]
else: self.fxdfile = fxdfilename
else:
if self.isdiscset == True:
self.fxdfile = vfs.join(config.OVERLAY_DIR, 'disc-set',
self.getmedia_id(self.device))
else:
self.fxdfile = vfs.splitext(file)[0]
if overwrite == False:
try:
vfs.open(self.fxdfile + '.fxd')
self.append = True
except:
pass
else:
self.append = False
# XXX: add this back in without using parseMovieFile
# if self.append == True and \
# parseMovieFile(self.fxdfile + '.fxd', None, []) == []:
# raise FxdAllocine_XML_Error("FXD file to be updated is invalid, please correct it.")
if not vfs.isdir(vfs.dirname(self.fxdfile)):
if vfs.dirname(self.fxdfile):
os.makedirs(vfs.dirname(self.fxdfile))
def allocine_get_disc_searchstring(self, item):
name = item.media.label
name = re.sub('([a-z])([A-Z])', point_maker, name)
name = re.sub('([a-zA-Z])([0-9])', point_maker, name)
name = re.sub('([0-9])([a-zA-Z])', point_maker, name.lower())
for r in config.IMDB_REMOVE_FROM_LABEL:
name = re.sub(r, '', name)
parts = re.split('[\._ -]', name)
name = ''
for p in parts:
if p:
name += '%s ' % p
if name:
return name[:-1]
else:
return ''
def actions(self, item):
self.item = item
if item.type == 'video' and (not item.files or not item.files.fxd_file):
if item.mode == 'file' or (item.mode in ('dvd', 'vcd') and \
item.info.has_key('tracks') and not \
item.media):
self.disc_set = False
return [ ( self.allocine_search , _('Search ALLOCINE for this file'),
'allocine_search_or_cover_search') ]
elif item.mode in ('dvd', 'vcd') and item.info.has_key('tracks'):
self.disc_set = True
s = self.allocine_get_disc_searchstring(self.item)
if s:
return [ ( self.allocine_search , _('Search ALLOCINE for [%s]') % s,
'allocine_search_or_cover_search') ]
if item.type == 'dir' and item.media and item.media.mountdir.find(item.dir) == 0:
self.disc_set = True
s = self.allocine_get_disc_searchstring(self.item)
if s:
return [ ( self.allocine_search , _('Search ALLOCINE for [%s]') % s,
'allocine_search_or_cover_search') ]
return []
def allocine_search(self, arg=None, menuw=None):
"""
search allocine for this item
"""
box = PopupBox(text=_('searching ALLOCINE...'))
box.show()
items = []
try:
duplicates = []
if self.disc_set:
self.searchstring = self.item.media.label
else:
self.searchstring = self.item.name
for id,name,year,type in self.guessAllocine(self.searchstring, self.disc_set):
try:
for i in self.item.parent.play_items:
if i.name == name:
if not i in duplicates:
duplicates.append(i)
except:
pass
items.append(menu.MenuItem('%s (%s, %s)' % (htmlenties2txt(name), year, type),
self.allocine_create_fxd, (id, year)))
except:
box.destroy()
box = PopupBox(text=_('Unknown error while connecting to ALLOCINE'))
box.show()
time.sleep(2)
box.destroy()
traceback.print_exc()
return
# for d in duplicates:
# items = [ menu.MenuItem('Add to "%s"' % d.name,
# self.imdb_add_to_fxd, (d, 'add')),
# menu.MenuItem('Variant to "%s"' % d.name,
# self.imdb_add_to_fxd, (d, 'variant')) ] + items
box.destroy()
if config.IMDB_AUTOACCEPT_SINGLE_HIT and len(items) == 1:
self.allocine_create_fxd(arg=items[0].arg, menuw=menuw)
return
if items:
moviemenu = menu.Menu(_('ALLOCINE Query'), items)
menuw.pushmenu(moviemenu)
return
box = PopupBox(text=_('No information available from ALLOCINE'))
box.show()
time.sleep(2)
box.destroy()
return
def allocine_menu_back(self, menuw):
"""
check how many menus we have to go back to see the item
"""
import directory
# check if we have to go one menu back (called directly) or
# two (called from the item menu)
back = 1
if menuw.menustack[-2].selected != self.item:
back = 2
# maybe we called the function directly because there was only one
# entry and we called it with an event
if menuw.menustack[-1].selected == self.item:
back = 0
# update the directory
if directory.dirwatcher:
directory.dirwatcher.scan()
# go back in menustack
for i in range(back):
menuw.delete_menu()
def allocine_create_fxd(self, arg=None, menuw=None):
"""
create fxd file for the item
"""
box = PopupBox(text=_('getting data...'))
box.show()
#if this exists we got a cdrom/dvdrom
if self.item.media and self.item.media.devicename:
devicename = self.item.media.devicename
else:
devicename = None
self.setAllocineId(arg[0])
if self.disc_set:
self.setDiscset(devicename, None)
else:
self.setFxdFile(os.path.splitext(self.item.filename)[0])
self.writeFxd()
self.allocine_menu_back(menuw)
box.destroy()
def allocine_add_to_fxd(self, arg=None, menuw=None):
"""
add item to fxd file
BROKEN, PLEASE FIX
"""
#if this exists we got a cdrom/dvdrom
if self.item.media and self.item.media.devicename:
devicename = self.item.media.devicename
else: devicename = None
self.setFxdFile(arg[0].fxd_file)
if self.item.mode in ('dvd', 'vcd'):
self.setDiscset(devicename, None)
else:
num = len(self.video) + 1
if arg[1] == 'variant':
part = makePart('Variant %d' % num, 'f%s' % num)
if self.variant:
part = [ makePart('Variant 1', 'f1'), part ]
self.setVariants(part)
self.writeFxd()
self.imdb_menu_back(menuw)
def writeFxd(self):
"""Write fxd file"""
#if fxdfile is empty, set it yourself
if not self.fxdfile:
self.setFxdFile()
try:
#should we add to an existing file?
if self.append == True :
if self.isdiscset == True:
self.update_discset()
else: self.update_movie()
else:
#fetch images
self.fetch_image()
#should we write a disc-set ?
if self.isdiscset == True:
self.write_discset()
else:
self.write_movie()
#check fxd
# XXX: add this back in without using parseMovieFile
# if parseMovieFile(self.fxdfile + '.fxd', None, []) == []:
# raise FxdImdb_XML_Error("""FXD file generated is invalid, please "+
# "post bugreport, tracebacks and fxd file.""")
except (IOError, FxdAllocine_IO_Error), error:
raise FxdAllocine_IO_Error('error saving the file: %s' % str(error))
def setDiscset(self, device, regexp, *file_opts, **mpl_global_opt):
"""
device (string), regexp (string), file_opts (tuple (mplayer-opts,file)),
mpl_global_opt (string)
Set media is dvd/vcd,
"""
if len(self.video) != 0 or len(self.variant) != 0:
raise FxdAllocine_XML_Error("<movie> already used, can't use both "+
"<movie> and <disc-set>")
self.isdiscset = True
if (not device and not regexp) or (device and regexp):
raise FxdAllocine_XML_Error("Can't use both media-id and regexp")
self.device = device
self.regexp = regexp
for opts in file_opts:
self.file_opts += [ opts ]
if mpl_global_opt and 'mplayer_opt' in mpl_global_opt:
self.mpl_global_opt = (mpl_global_opt['mplayer_opt'])
def isDiscset(self):
"""Check if fxd file describes a disc-set, returns 1 for true, 0 for false
None for invalid file"""
try:
file = vfs.open(self.fxdfile + '.fxd')
except IOError:
return None
content = file.read()
file.close()
if content.find('</disc-set>') != -1: return 1
return 0
#------ private functions below .....
def write_discset(self):
"""Write a <disc-set> to a fresh file"""
print "Discset not supported for the moment... Sorry"
def write_fxd_copyright(self, fxd, node):
fxd.setcdata(node, "The information in this file are from Allocine.fr.\n"+
"Please visit http://www.allocine.fr for more informations.\n")
fxd.add(fxd.XMLnode('source', [('url', "http://www.allocine.fr/")] ), node, 0)
def write_fxd_video(self, fxd, node):
fxd.setattr(node, 'title', self.title )
fxd.add(fxd.XMLnode('cover-img', (('source', self.image_url), ("test", "test")), self.image ), node, 0)
videonode = fxd.XMLnode('video')
fxd.add(videonode, node)
fxd.add(fxd.XMLnode('file', [('id', 'f1')], os.path.basename(self.item.filename) ), videonode, 0)
infonode = fxd.XMLnode('info')
fxd.add(infonode, node)
if self.info:
for k in self.info.keys():
fxd.add(fxd.XMLnode(k, [], self.info[k]), infonode, 0)
def write_movie(self):
"""Write <movie> to fxd file"""
try:
parser = fxdparser.FXD(self.fxdfile + '.fxd')
parser.set_handler('copyright', self.write_fxd_copyright, 'w', True)
parser.set_handler('movie', self.write_fxd_video, 'w', True)
parser.save()
except:
print "fxd file %s corrupt" % self.fxdfile
traceback.print_exc()
def update_movie(self):
"""Updates an existing file, adds exftra dvd|vcd|file and variant tags"""
print "Update not supported for the moment... Sorry"
def update_discset(self):
"""Updates an existing file, adds extra disc in discset"""
print "Update not supported for the moment... Sorry"
def parsedata(self, results, id=0):
"""results (allocine html page), allocine_id
Returns tuple of (title, info(dict), image_urls)"""
dvd = 0
regexp_title = re.compile('.*<TITLE>(.*?)</TITLE>', re.I)
regexp_plotfull = re.compile('.*<DIV Align=\'justify\'><FONT class="size2">(.*?)</FONT></DIV>.*')
regexp_plot1 = re.compile('.*<DIV Align=\'justify\'><FONT class="size2">(.*?)<br>$')
regexp_plot2 = re.compile('(.*?)</FONT></DIV>.*')
regexp_plot1a = re.compile('.*<DIV Align=\'justify\'><FONT class="size2">(.*?)$')
regexp_plot2a = re.compile('</FONT></DIV>.*')
regexp_year = re.compile('.*<A href=\'/film/agenda_gen_semaine=[0-9]{2}/[0-9]{2}/(.*?)\.html')
regexp_year2 = re.compile('.*<font >\(([0-9]{4})\)</font>.*')
regexp_url = re.compile('.*href="(http.*?)"', re.I)
regexp_pic1 = re.compile('.*<TABLE Border=0 CellPadding=0 CellSpacing=0><TR><TD><IMG Src=\'(.*?)\' Border=0>.*')
regexp_pic2 = re.compile('.*page=1.html"><IMG Src=\'(.*?)\' Border=0>')
plot1found = 0
plot1afound = 0
plotfull = 0
for line in results.read().split("\n"):
m = regexp_title.match(line)
if m:
self.title = m.group(1)
#print "Title found : %s" % self.title
m = regexp_pic1.match(line)
if m:
self.image_urls += [ m.group(1) ]
#print "Image URL 1 : %s" % m.group(1)
m = regexp_pic2.match(line)
if m:
self.image_urls += [ m.group(1) ]
#print "Image URL 2 : %s" % m.group(1)
m = regexp_year.match(line)
if m:
self.info['year'] = m.group(1)
#print "Year 1 found : %s" % self.info['year']
m = regexp_year2.match(line)
if m:
self.info['year'] = m.group(1)
#print "Year 2 found : %s" % self.info['year']
m = regexp_plotfull.match(line)
if m:
plotfull = 1
self.info['plot'] = m.group(1)
#print "Synopsis full : %s" % self.info['plot']
if plot1found:
m = regexp_plot2.match(line)
if m:
plot1found = 0
plotfull = 1
else:
self.info['plot'] += line
#print "Synopsis 2 : %s" % self.info['plot']
if plotfull == 0:
m = regexp_plot1.match(line)
if m:
plot1found = 1
self.info['plot'] = m.group(1)
#print "Synopsis 1 : %s" % self.info['plot']
if plot1afound:
m = regexp_plot2a.match(line)
if m:
plot1afound = 0
plotfull = 1
else:
self.info['plot'] += line
#print "Synopsis 2a : %s" % self.info['plot']
if plotfull == 0:
m = regexp_plot1a.match(line)
if m:
plot1afound = 1
self.info['plot'] = m.group(1)
#print "Synopsis 1a : %s" % self.info['plot']
# Clean the plot info
if plotfull:
plot = self.info['plot']
plot = plot.replace("<BR>", "")
plot = plot.replace("<br>", "")
self.info['plot'] = plot
if not id:
return (self.title, self.info, self.image_urls)
return (self.title, self.info, self.image_urls)
def fetch_image(self):
"""Fetch the best image"""
image_len = 0
if (len(self.image_urls) == 0): # No images
return
for image in self.image_urls:
try:
# get sizes of images
req = urllib2.Request(image, txdata, txheaders)
r = urllib2.urlopen(req)
length = int(r.info()['Content-Length'])
r.close()
if length > image_len:
image_len = length
self.image_url = image
except:
pass
if not self.image_url:
print "Image dowloading failed"
return
self.image = (self.fxdfile + '.jpg')
req = urllib2.Request(self.image_url, txdata, txheaders)
r = urllib2.urlopen(req)
i = vfs.open(self.image, 'w')
i.write(r.read())
i.close()
r.close()
# try to crop the image to avoid borders by imdb
try:
import Image
image = Image.open(filename)
width, height = image.size
image.crop((2,2,width-4, height-4)).save(filename)
except:
pass
self.image = vfs.basename(self.image)
print "Downloaded cover image from %s" % self.image_url
print "Freevo knows nothing about the copyright of this image, please"
print "go to %s to check for more informations about private." % self.image_url
print "use of this image"
class Error(Exception):
"""Base class for exceptions in Allocine_Fxd"""
def __str__(self):
return self.message
def __init__(self, message):
self.message = message
class FxdAllocine_Error(Error):
"""used to raise exceptions"""
pass
class FxdAllocine_XML_Error(Error):
"""used to raise exceptions"""
pass
class FxdAllocine_IO_Error(Error):
"""used to raise exceptions"""
pass
class FxdAllocine_Net_Error(Error):
"""used to raise exceptions"""
pass
def point_maker(matching):
return '%s.%s' % (matching.groups()[0], matching.groups()[1])
#if 0 /*
# -----------------------------------------------------------------------
# fxdparser.py - Parser for fxd files
# -----------------------------------------------------------------------
# $Id: fxdparser.py,v 1.14 2004/02/28 21:04:17 dischi Exp $
#
# Notes:
# Todo:
#
# -----------------------------------------------------------------------
# $Log: fxdparser.py,v $
# Revision 1.14 2004/02/28 21:04:17 dischi
# unicode fixes
#
# Revision 1.13 2004/01/16 16:23:07 dischi
# made a ugly unicode fix...I will never understand python character encoding
#
# Revision 1.12 2004/01/15 04:33:34 outlyer
# Check for the filename before trying to read it... without it, I couldn't
# use fxd files which were only in my overlay, but not in the path.
#
# Revision 1.11 2004/01/14 21:20:36 dischi
# fix writing
#
# Revision 1.10 2004/01/14 18:49:37 dischi
# >= is better
#
# Revision 1.9 2004/01/14 18:49:09 dischi
# also dump raw fxd at saving
#
# Revision 1.8 2004/01/14 18:42:47 dischi
# add new helper function
#
# Revision 1.7 2004/01/10 13:17:43 dischi
# o always check if fxd file contains <skin>
# o add filename to fxd object
# o also include info with 'name' when in map
#
# Revision 1.6 2004/01/03 17:43:15 dischi
# OVERLAY_DIR is always used
#
# Revision 1.5 2004/01/01 12:24:18 dischi
# cache fxd files with pickle
#
# Revision 1.4 2003/12/29 22:32:15 dischi
# small speed improvements
#
# Revision 1.3 2003/12/07 19:12:09 dischi
# int support in getattr
#
# Revision 1.2 2003/11/25 19:00:14 dischi
# add support for user data
#
# Revision 1.1 2003/11/23 16:56:28 dischi
# a fxd parser for all kinds of fxd files using callbacks
#
# -----------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002 Krister Lagerstrom, et al.
# Please see the file freevo/Docs/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ----------------------------------------------------------------------- */
#endif
import os
import stat
import traceback
# XML support
from xml.utils import qp_xml
import config
import util
import codecs
class XMLnode:
"""
One node for the FXDtree
"""
def __init__(self, name, attr = [], first_cdata=None, following_cdata=None):
self.name = name
self.attr_list = []
for name, val in attr:
self.attr_list.append(((None, name), val))
self.attrs = self
self.children = []
self.first_cdata = first_cdata
self.following_cdata = following_cdata
def items(self):
return self.attr_list
class FXDtree(qp_xml.Parser):
"""
class to parse and write fxd files
"""
def __init__(self, filename):
"""
Load the file and parse it. If the file does not exists, create
an empty <freevo> node.
"""
qp_xml.Parser.__init__(self)
self.filename = filename
if not vfs.isfile(filename):
self.tree = XMLnode('freevo')
else:
self.tree = None
cache = vfs.getoverlay(filename + '.raw')
if os.path.isfile(filename) and os.path.isfile(cache) and \
os.stat(cache)[stat.ST_MTIME] >= os.stat(filename)[stat.ST_MTIME]:
self.tree = util.read_pickle(cache)
if not self.tree:
f = vfs.open(filename)
self.tree = self.parse(f)
f.close()
if self.tree:
util.save_pickle(self.tree, cache)
def add(self, node, parent=None, pos=None):
"""
add a node to the parent at position pos. If parent is None, the
<freevo> node fil be taken, if pos is None, the node will be inserted
at the end of the children list.
"""
if not parent:
parent = self.tree
if pos == None:
parent.children.append(node)
else:
parent.children.insert(pos, node)
def save(self, filename=None):
"""
Save the tree
"""
if not filename:
filename = self.filename
if vfs.isfile(filename):
vfs.unlink(filename)
f = vfs.codecs_open(filename, 'wb', encoding='utf-8')
f.write('<?xml version="1.0" encoding="utf-8" ?>\n')
self._dump_recurse(f, self.tree)
f.write('\n')
f.close()
f = vfs.open(filename)
self.tree = self.parse(f)
f.close()
if self.tree:
util.save_pickle(self.tree, vfs.getoverlay(filename + '.raw'))
def _dump_recurse(self, f, elem, depth=0):
"""
Help function to dump all elements
"""
if not elem:
return
f.write('<' + elem.name)
for (ns, name), value in elem.attrs.items():
f.write(u' ' + Unicode(name) + u'="' + Unicode(value) + '"')
if elem.children or elem.first_cdata:
if elem.first_cdata == None:
f.write('>\n ')
for i in range(depth):
f.write(' ')
else:
data = Unicode(elem.first_cdata).replace(u'&', u'&')
f.write(u'>' + data)
for child in elem.children:
self._dump_recurse(f, child, depth=depth+1)
if child.following_cdata == None:
if child == elem.children[-1]:
f.write('\n')
else:
f.write('\n ')
for i in range(depth):
f.write(' ')
else:
f.write(child.following_cdata.replace('&', '&'))
f.write('</%s>' % elem.name)
else:
f.write('/>')
class FXD:
"""
class to help parsing fxd files
"""
class XMLnode(XMLnode):
"""
a new node
"""
pass
def __init__(self, filename):
self.tree = FXDtree(filename)
self.read_callback = {}
self.write_callback = {}
self.user_data = {}
self.is_skin_fxd = False
self.filename = filename
def set_handler(self, name, callback, mode='r', force=False):
"""
create callbacks for a node named 'name'. Mode can be 'r' when
reading the node (parse), or 'w' for writing (save). The reading
callback can return a special callback used for writing this node
again. If force is true and an element for a write handler doesn't
exists, it will be created.
"""
if mode == 'r':
self.read_callback[name] = callback
elif mode == 'w':
self.write_callback[name] = [ callback, force ]
else:
debug('unknown mode %s for fxd handler' % mode, 0)
def parse(self):
"""
parse the tree and call all the callbacks
"""
if self.tree.tree.name != 'freevo':
_debug_('first node not <freevo>')
return
for node in self.tree.tree.children:
if node.name == 'skin':
self.is_skin_fxd = True
break
for node in self.tree.tree.children:
if node.name in self.read_callback:
callback = self.read_callback[node.name](self, node)
if callback:
node.callback = callback
def save(self):
"""
save the tree and call all write callbacks before
"""
# create missing but forces elements
for name in self.write_callback:
callback, force = self.write_callback[name]
if force:
for node in self.tree.tree.children:
if node.name == name:
break
else:
# forced and missing
self.add(XMLnode(name), pos=0)
# now run all callbacks
for node in self.tree.tree.children:
if hasattr(node, 'callback'):
node.callback(self, node)
elif node.name in self.write_callback:
self.write_callback[node.name][0](self, node)
# and save
self.tree.save()
def get_children(self, node, name, deep=1):
"""
deep = 0, every deep, 1 = children, 2 = childrens children, etc.
"""
ret = []
for child in node.children:
if deep < 2 and child.name == name:
ret.append(child)
if deep == 0:
ret += self.get_children(child, name, 0)
elif deep > 1:
ret += self.get_children(child, name, deep-1)
return ret
def get_or_create_child(self, node, name):
"""
return the first child with name or create it (and add it)
"""
for child in node.children:
if child.name == name:
return child
child = XMLnode(name)
self.add(child, node)
return child
def childcontent(self, node, name):
"""
return the content of the child node with the given name
"""
for child in node.children:
if child.name == name:
return util.format_text(child.textof())
return ''
def getattr(self, node, name, default=''):
"""
return the attribute of the node or the 'default' if the atrribute is not
set. If 'node' is 'None', it return the user defined data in the fxd
object.
"""
r = default
if node:
try:
r = node.attrs[('',name)]
except KeyError:
pass
else:
try:
r = self.user_data[name]
except KeyError:
pass
if isinstance(default, int):
try:
r = int(r)
except:
r = default
return r
def setcdata(self, node, cdata):
if node:
node.first_cdata = cdata
def setattr(self, node, name, value):
"""
sets the attribute of the node or if node is 'None', set user defined data
for the fxd parser.
"""
if node:
node.attr_list.append(((None, name), value))
#node.attrs[('',name)] = value
else:
self.user_data[name] = value
def gettext(self, node):
"""
rerurn the text of the node
"""
return util.format_text(node.textof())
def parse_info(self, nodes, object, map={}):
"""
map is a hash, the keys are the names in the fxd file,
the content the variable names, e.g. {'content': 'description, }
All tags not in the map are stored with the same name in info.
"""
if not nodes:
return
if hasattr(nodes, 'children'):
for node in nodes.children:
if node.name == 'info':
nodes = [ node ]
break
else:
nodes = []
for node in nodes:
for child in node.children:
txt = child.textof()
if not txt:
continue
if child.name in map:
object.info[map[child.name]] = util.format_text(txt)
object.info[child.name] = util.format_text(txt)
def add(self, node, parent=None, pos=None):
"""
add an element to the tree
"""
self.tree.add(node, parent, pos)