Merge branch 'mythboxee4'

This commit is contained in:
Erik Kristensen 2010-09-01 23:12:41 -04:00
commit 57cabb7445
99 changed files with 23757 additions and 0 deletions

15
descriptor.xml Normal file
View File

@ -0,0 +1,15 @@
<app>
<id>mythboxy</id>
<name>MythBoxy</name>
<version>4.0.beta</version>
<description>Watch all your MythTV recordings from Boxee.</description>
<thumb>http://erikkristensen.com/boxee/logo4.jpg</thumb>
<media>video</media>
<copyright>Erik Kristensen</copyright>
<email>erik@erikkristensen.com</email>
<type>skin</type>
<startWindow>launch</startWindow>
<platform>all</platform>
<minversion>0.9.20</minversion>
<test-app>true</test-app>
</app>

8
launch.py Normal file
View File

@ -0,0 +1,8 @@
import mc
from mythboxee import MythBoxee
# DEBUG #
#mc.GetApp().GetLocalConfig().ResetAll()
# DEBUG #
mc.ActivateWindow(14001)

0
mysql/__init__.py Normal file
View File

View File

@ -0,0 +1,74 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""
MySQL Connector/Python - MySQL drive written in Python
"""
import sys
_name = 'MySQL Connector/Python'
if not hasattr(sys, "version_info") or sys.version_info < (2,4):
raise RuntimeError("%s requires Python 2.4 or higher." % (_name))
elif sys.version_info >= (3,0):
raise RuntimeError("%s does not yet support Python v3." % (_name))
del _name
del sys
# Python Db API v2
apilevel = '2.0'
threadsafety = 1
paramstyle = 'pyformat'
# Read the version from an generated file
import _version
__version__ = _version.version
from mysql import MySQL
from errors import *
from constants import FieldFlag, FieldType, CharacterSet, RefreshOption
from dbapi import *
def Connect(*args, **kwargs):
"""Shortcut for creating a mysql.MySQL object."""
return MySQL(*args, **kwargs)
connect = Connect
__all__ = [
'MySQL', 'Connect',
# Some useful constants
'FieldType','FieldFlag','CharacterSet','RefreshOption',
# Error handling
'Error','Warning',
'InterfaceError','DatabaseError',
'NotSupportedError','DataError','IntegrityError','ProgrammingError',
'OperationalError','InternalError',
# DBAPI PEP 249 required exports
'connect','apilevel','threadsafety','paramstyle',
'Date', 'Time', 'Timestamp', 'Binary',
'DateFromTicks', 'DateFromTicks', 'TimestampFromTicks',
'STRING', 'BINARY', 'NUMBER',
'DATETIME', 'ROWID',
]

View File

@ -0,0 +1,28 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Holds version of MySQL Connector/Python
"""
# Next line is generated
version = (0, 1, 0, 'devel', '')

View File

@ -0,0 +1,148 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Implementing communication to MySQL servers
"""
import socket
import os
import protocol
import errors
from constants import CharacterSet
class MySQLBaseConnection(object):
"""Base class for MySQL Connections subclasses.
Should not be used directly but overloaded, changing the
open_connection part. Examples over subclasses are
MySQLTCPConnection
MySQLUNIXConnection
"""
def __init__(self, prtcls=None):
self.sock = None # holds the socket connection
self.connection_timeout = None
self.protocol = None
self.socket_flags = 0
try:
self.protocol = prtcls(self)
except:
self.protocol = protocol.MySQLProtocol(self)
self._set_socket_flags()
def open_connection(self):
pass
def close_connection(self):
try:
self.sock.close()
except:
pass
def send(self, buf):
"""
Send packets using the socket to the server.
"""
pktlen = len(buf)
try:
while pktlen:
pktlen -= self.sock.send(buf)
except Exception, e:
raise errors.OperationalError('%s' % e)
def recv(self):
"""
Receive packets using the socket from the server.
"""
try:
header = self.sock.recv(4, self.socket_flags)
(pktsize, pktnr) = self.protocol.handle_header(header)
buf = header + self.sock.recv(pktsize, self.socket_flags)
self.protocol.is_error(buf)
except:
raise
return (buf, pktsize, pktnr)
def set_protocol(self, prtcls):
try:
self.protocol = prtcls(self, self.protocol.handshake)
except:
self.protocol = protocol.MySQLProtocol(self)
def set_connection_timeout(self, timeout):
self.connection_timeout = timeout
def _set_socket_flags(self, flags=None):
self.socket_flags = 0
if flags is None:
if os.name == 'nt':
flags = 0
else:
flags = socket.MSG_WAITALL
if flags is not None:
self.socket_flags = flags
class MySQLUnixConnection(MySQLBaseConnection):
"""Opens a connection through the UNIX socket of the MySQL Server."""
def __init__(self, prtcls=None,unix_socket='/tmp/mysql.sock'):
MySQLBaseConnection.__init__(self, prtcls=prtcls)
self.unix_socket = unix_socket
self.socket_flags = socket.MSG_WAITALL
def open_connection(self):
"""Opens a UNIX socket and checks the MySQL handshake."""
try:
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.settimeout(self.connection_timeout)
self.sock.connect(self.unix_socket)
except StandardError, e:
raise errors.OperationalError('%s' % e)
buf = self.recv()[0]
self.protocol.handle_handshake(buf)
class MySQLTCPConnection(MySQLBaseConnection):
"""Opens a TCP connection to the MySQL Server."""
def __init__(self, prtcls=None, host='127.0.0.1', port=3306):
MySQLBaseConnection.__init__(self, prtcls=prtcls)
self.server_host = host
self.server_port = port
def open_connection(self):
"""Opens a TCP Connection and checks the MySQL handshake."""
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.connection_timeout)
self.sock.connect( (self.server_host, self.server_port) )
except StandardError, e:
raise errors.OperationalError('%s' % e)
buf = self.recv()[0]
self.protocol.handle_handshake(buf)

View File

@ -0,0 +1,571 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Various MySQL constants and character sets
"""
from errors import ProgrammingError
class _constants(object):
prefix = ''
desc = {}
def __new__(cls):
raise TypeError, "Can not instanciate from %s" % cls.__name__
@classmethod
def get_desc(cls,name):
res = ''
try:
res = cls.desc[name][1]
except KeyError, e:
raise KeyError, e
else:
return res
@classmethod
def get_info(cls,n):
res = ()
for k,v in cls.desc.items():
if v[0] == n:
return v[1]
raise KeyError, e
@classmethod
def get_full_info(cls):
res = ()
try:
res = ["%s : %s" % (k,v[1]) for k,v in cls.desc.items()]
except StandardError, e:
res = ('No information found in constant class.%s' % e)
return res
class FieldType(_constants):
prefix = 'FIELD_TYPE_'
DECIMAL = 0x00
TINY = 0x01
SHORT = 0x02
LONG = 0x03
FLOAT = 0x04
DOUBLE = 0x05
NULL = 0x06
TIMESTAMP = 0x07
LONGLONG = 0x08
INT24 = 0x09
DATE = 0x0a
TIME = 0x0b
DATETIME = 0x0c
YEAR = 0x0d
NEWDATE = 0x0e
VARCHAR = 0x0f
BIT = 0x10
NEWDECIMAL = 0xf6
ENUM = 0xf7
SET = 0xf8
TINY_BLOB = 0xf9
MEDIUM_BLOB = 0xfa
LONG_BLOB = 0xfb
BLOB = 0xfc
VAR_STRING = 0xfd
STRING = 0xfe
GEOMETRY = 0xff
desc = {
'DECIMAL': (0x00, 'DECIMAL'),
'TINY': (0x01, 'TINY'),
'SHORT': (0x02, 'SHORT'),
'LONG': (0x03, 'LONG'),
'FLOAT': (0x04, 'FLOAT'),
'DOUBLE': (0x05, 'DOUBLE'),
'NULL': (0x06, 'NULL'),
'TIMESTAMP': (0x07, 'TIMESTAMP'),
'LONGLONG': (0x08, 'LONGLONG'),
'INT24': (0x09, 'INT24'),
'DATE': (0x0a, 'DATE'),
'TIME': (0x0b, 'TIME'),
'DATETIME': (0x0c, 'DATETIME'),
'YEAR': (0x0d, 'YEAR'),
'NEWDATE': (0x0e, 'NEWDATE'),
'VARCHAR': (0x0f, 'VARCHAR'),
'BIT': (0x10, 'BIT'),
'NEWDECIMAL': (0xf6, 'NEWDECIMAL'),
'ENUM': (0xf7, 'ENUM'),
'SET': (0xf8, 'SET'),
'TINY_BLOB': (0xf9, 'TINY_BLOB'),
'MEDIUM_BLOB': (0xfa, 'MEDIUM_BLOB'),
'LONG_BLOB': (0xfb, 'LONG_BLOB'),
'BLOB': (0xfc, 'BLOB'),
'VAR_STRING': (0xfd, 'VAR_STRING'),
'STRING': (0xfe, 'STRING'),
'GEOMETRY': (0xff, 'GEOMETRY'),
}
@classmethod
def get_string_types(cls):
return [
cls.VARCHAR,
cls.ENUM,
cls.VAR_STRING, cls.STRING,
]
@classmethod
def get_binary_types(cls):
return [
cls.TINY_BLOB, cls.MEDIUM_BLOB,
cls.LONG_BLOB, cls.BLOB,
]
@classmethod
def get_number_types(cls):
return [
cls.DECIMAL, cls.NEWDECIMAL,
cls.TINY, cls.SHORT, cls.LONG,
cls.FLOAT, cls.DOUBLE,
cls.LONGLONG, cls.INT24,
cls.BIT,
cls.YEAR,
]
@classmethod
def get_timestamp_types(cls):
return [
cls.DATETIME, cls.TIMESTAMP,
]
class FieldFlag(_constants):
"""
Field flags as found in MySQL sources mysql-src/include/mysql_com.h
"""
_prefix = ''
NOT_NULL = 1 << 0
PRI_KEY = 1 << 1
UNIQUE_KEY = 1 << 2
MULTIPLE_KEY = 1 << 3
BLOB = 1 << 4
UNSIGNED = 1 << 5
ZEROFILL = 1 << 6
BINARY = 1 << 7
ENUM = 1 << 8
AUTO_INCREMENT = 1 << 9
TIMESTAMP = 1 << 10
SET = 1 << 11
NO_DEFAULT_VALUE = 1 << 12
ON_UPDATE_NOW = 1 << 13
NUM = 1 << 14
PART_KEY = 1 << 15
GROUP = 1 << 14 # SAME AS NUM !!!!!!!????
UNIQUE = 1 << 16
BINCMP = 1 << 17
GET_FIXED_FIELDS = 1 << 18
FIELD_IN_PART_FUNC = 1 << 19
FIELD_IN_ADD_INDEX = 1 << 20
FIELD_IS_RENAMED = 1 << 21
desc = {
'NOT_NULL': (1 << 0, "Field can't be NULL"),
'PRI_KEY': (1 << 1, "Field is part of a primary key"),
'UNIQUE_KEY': (1 << 2, "Field is part of a unique key"),
'MULTIPLE_KEY': (1 << 3, "Field is part of a key"),
'BLOB': (1 << 4, "Field is a blob"),
'UNSIGNED': (1 << 5, "Field is unsigned"),
'ZEROFILL': (1 << 6, "Field is zerofill"),
'BINARY': (1 << 7, "Field is binary "),
'ENUM': (1 << 8, "field is an enum"),
'AUTO_INCREMENT': (1 << 9, "field is a autoincrement field"),
'TIMESTAMP': (1 << 10, "Field is a timestamp"),
'SET': (1 << 11, "field is a set"),
'NO_DEFAULT_VALUE': (1 << 12, "Field doesn't have default value"),
'ON_UPDATE_NOW': (1 << 13, "Field is set to NOW on UPDATE"),
'NUM': (1 << 14, "Field is num (for clients)"),
'PART_KEY': (1 << 15, "Intern; Part of some key"),
'GROUP': (1 << 14, "Intern: Group field"), # Same as NUM
'UNIQUE': (1 << 16, "Intern: Used by sql_yacc"),
'BINCMP': (1 << 17, "Intern: Used by sql_yacc"),
'GET_FIXED_FIELDS': (1 << 18, "Used to get fields in item tree"),
'FIELD_IN_PART_FUNC': (1 << 19, "Field part of partition func"),
'FIELD_IN_ADD_INDEX': (1 << 20, "Intern: Field used in ADD INDEX"),
'FIELD_IS_RENAMED': (1 << 21, "Intern: Field is being renamed"),
}
class ServerCmd(_constants):
_prefix = 'COM_'
SLEEP = 0
QUIT = 1
INIT_DB = 2
QUERY = 3
FIELD_LIST = 4
CREATE_DB = 5
DROP_DB = 6
REFRESH = 7
SHUTDOWN = 8
STATISTICS = 9
PROCESS_INFO = 10
CONNECT = 11
PROCESS_KILL = 12
DEBUG = 13
PING = 14
TIME = 15
DELAYED_INSERT = 16
CHANGE_USER = 17
BINLOG_DUMP = 18
TABLE_DUMP = 19
CONNECT_OUT = 20
REGISTER_SLAVE = 21
STMT_PREPARE = 22
STMT_EXECUTE = 23
STMT_SEND_LONG_DATA = 24
STMT_CLOSE = 25
STMT_RESET = 26
SET_OPTION = 27
STMT_FETCH = 28
DAEMON = 29
class ClientFlag(_constants):
"""
Client Options as found in the MySQL sources mysql-src/include/mysql_com.h
"""
LONG_PASSWD = 1 << 0
FOUND_ROWS = 1 << 1
LONG_FLAG = 1 << 2
CONNECT_WITH_DB = 1 << 3
NO_SCHEMA = 1 << 4
COMPRESS = 1 << 5
ODBC = 1 << 6
LOCAL_FILES = 1 << 7
IGNORE_SPACE = 1 << 8
PROTOCOL_41 = 1 << 9
INTERACTIVE = 1 << 10
SSL = 1 << 11
IGNORE_SIGPIPE = 1 << 12
TRANSACTIONS = 1 << 13
RESERVED = 1 << 14
SECURE_CONNECTION = 1 << 15
MULTI_STATEMENTS = 1 << 16
MULTI_RESULTS = 1 << 17
SSL_VERIFY_SERVER_CERT = 1 << 30
REMEMBER_OPTIONS = 1 << 31
desc = {
'LONG_PASSWD': (1 << 0, 'New more secure passwords'),
'FOUND_ROWS': (1 << 1, 'Found instead of affected rows'),
'LONG_FLAG': (1 << 2, 'Get all column flags'),
'CONNECT_WITH_DB': (1 << 3, 'One can specify db on connect'),
'NO_SCHEMA': (1 << 4, "Don't allow database.table.column"),
'COMPRESS': (1 << 5, 'Can use compression protocol'),
'ODBC': (1 << 6, 'ODBC client'),
'LOCAL_FILES': (1 << 7, 'Can use LOAD DATA LOCAL'),
'IGNORE_SPACE': (1 << 8, "Ignore spaces before ''"),
'PROTOCOL_41': (1 << 9, 'New 4.1 protocol'),
'INTERACTIVE': (1 << 10, 'This is an interactive client'),
'SSL': (1 << 11, 'Switch to SSL after handshake'),
'IGNORE_SIGPIPE': (1 << 12, 'IGNORE sigpipes'),
'TRANSACTIONS': (1 << 13, 'Client knows about transactions'),
'RESERVED': (1 << 14, 'Old flag for 4.1 protocol'),
'SECURE_CONNECTION': (1 << 15, 'New 4.1 authentication'),
'MULTI_STATEMENTS': (1 << 16, 'Enable/disable multi-stmt support'),
'MULTI_RESULTS': (1 << 17, 'Enable/disable multi-results'),
'SSL_VERIFY_SERVER_CERT': (1 << 30, ''),
'REMEMBER_OPTIONS': (1 << 31, ''),
}
default = [
LONG_PASSWD,
LONG_FLAG,
CONNECT_WITH_DB,
PROTOCOL_41,
TRANSACTIONS,
SECURE_CONNECTION,
MULTI_STATEMENTS,
MULTI_RESULTS,
]
@classmethod
def get_default(cls):
flags = 0
for f in cls.default:
flags |= f
return flags
class ServerFlag(_constants):
"""
Server flags as found in the MySQL sources mysql-src/include/mysql_com.h
"""
_prefix = 'SERVER_'
STATUS_IN_TRANS = 1 << 0
STATUS_AUTOCOMMIT = 1 << 1
MORE_RESULTS_EXISTS = 1 << 3
QUERY_NO_GOOD_INDEX_USED = 1 << 4
QUERY_NO_INDEX_USED = 1 << 5
STATUS_CURSOR_EXISTS = 1 << 6
STATUS_LAST_ROW_SENT = 1 << 7
STATUS_DB_DROPPED = 1 << 8
STATUS_NO_BACKSLASH_ESCAPES = 1 << 9
desc = {
'SERVER_STATUS_IN_TRANS': (1 << 0, 'Transaction has started'),
'SERVER_STATUS_AUTOCOMMIT': (1 << 1, 'Server in auto_commit mode'),
'SERVER_MORE_RESULTS_EXISTS': (1 << 3, 'Multi query - next query exists'),
'SERVER_QUERY_NO_GOOD_INDEX_USED': (1 << 4, ''),
'SERVER_QUERY_NO_INDEX_USED': (1 << 5, ''),
'SERVER_STATUS_CURSOR_EXISTS': (1 << 6, ''),
'SERVER_STATUS_LAST_ROW_SENT': (1 << 7, ''),
'SERVER_STATUS_DB_DROPPED': (1 << 8, 'A database was dropped'),
'SERVER_STATUS_NO_BACKSLASH_ESCAPES': (1 << 9, ''),
}
class RefreshOption(_constants):
"""Options used when sending the COM_REFRESH server command."""
_prefix = 'REFRESH_'
GRANT = 1 << 0
LOG = 1 << 1
TABLES = 1 << 2
HOST = 1 << 3
STATUS = 1 << 4
THREADS = 1 << 5
SLAVE = 1 << 6
desc = {
'GRANT': (1 << 0, 'Refresh grant tables'),
'LOG': (1 << 1, 'Start on new log file'),
'TABLES': (1 << 2, 'close all tables'),
'HOSTS': (1 << 3, 'Flush host cache'),
'STATUS': (1 << 4, 'Flush status variables'),
'THREADS': (1 << 5, 'Flush thread cache'),
'SLAVE': (1 << 6, 'Reset master info and restart slave thread'),
}
class CharacterSet(_constants):
"""
List of supported character sets with their collations. This maps to the
character set we get from the server within the handshake packet.
To update this list, use the following query:
SELECT ID,CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLLATIONS
ORDER BY ID
This list is hardcoded because we want to avoid doing each time the above
query to get the name of the character set used.
"""
_max_id = 211 # SELECT MAX(ID)+1 FROM INFORMATION_SCHEMA.COLLATIONS
@classmethod
def _init_desc(cls):
if not cls.__dict__.has_key('desc'):
# Do not forget to update the tests in test_constants!
cls.desc = [ None for i in range(cls._max_id)]
cls.desc[1] = ('big5','big5_chinese_ci')
cls.desc[2] = ('latin2','latin2_czech_cs')
cls.desc[3] = ('dec8','dec8_swedish_ci')
cls.desc[4] = ('cp850','cp850_general_ci')
cls.desc[5] = ('latin1','latin1_german1_ci')
cls.desc[6] = ('hp8','hp8_english_ci')
cls.desc[7] = ('koi8r','koi8r_general_ci')
cls.desc[8] = ('latin1','latin1_swedish_ci')
cls.desc[9] = ('latin2','latin2_general_ci')
cls.desc[10] = ('swe7','swe7_swedish_ci')
cls.desc[11] = ('ascii','ascii_general_ci')
cls.desc[12] = ('ujis','ujis_japanese_ci')
cls.desc[13] = ('sjis','sjis_japanese_ci')
cls.desc[14] = ('cp1251','cp1251_bulgarian_ci')
cls.desc[15] = ('latin1','latin1_danish_ci')
cls.desc[16] = ('hebrew','hebrew_general_ci')
cls.desc[18] = ('tis620','tis620_thai_ci')
cls.desc[19] = ('euckr','euckr_korean_ci')
cls.desc[20] = ('latin7','latin7_estonian_cs')
cls.desc[21] = ('latin2','latin2_hungarian_ci')
cls.desc[22] = ('koi8u','koi8u_general_ci')
cls.desc[23] = ('cp1251','cp1251_ukrainian_ci')
cls.desc[24] = ('gb2312','gb2312_chinese_ci')
cls.desc[25] = ('greek','greek_general_ci')
cls.desc[26] = ('cp1250','cp1250_general_ci')
cls.desc[27] = ('latin2','latin2_croatian_ci')
cls.desc[28] = ('gbk','gbk_chinese_ci')
cls.desc[29] = ('cp1257','cp1257_lithuanian_ci')
cls.desc[30] = ('latin5','latin5_turkish_ci')
cls.desc[31] = ('latin1','latin1_german2_ci')
cls.desc[32] = ('armscii8','armscii8_general_ci')
cls.desc[33] = ('utf8','utf8_general_ci')
cls.desc[34] = ('cp1250','cp1250_czech_cs')
cls.desc[35] = ('ucs2','ucs2_general_ci')
cls.desc[36] = ('cp866','cp866_general_ci')
cls.desc[37] = ('keybcs2','keybcs2_general_ci')
cls.desc[38] = ('macce','macce_general_ci')
cls.desc[39] = ('macroman','macroman_general_ci')
cls.desc[40] = ('cp852','cp852_general_ci')
cls.desc[41] = ('latin7','latin7_general_ci')
cls.desc[42] = ('latin7','latin7_general_cs')
cls.desc[43] = ('macce','macce_bin')
cls.desc[44] = ('cp1250','cp1250_croatian_ci')
cls.desc[47] = ('latin1','latin1_bin')
cls.desc[48] = ('latin1','latin1_general_ci')
cls.desc[49] = ('latin1','latin1_general_cs')
cls.desc[50] = ('cp1251','cp1251_bin')
cls.desc[51] = ('cp1251','cp1251_general_ci')
cls.desc[52] = ('cp1251','cp1251_general_cs')
cls.desc[53] = ('macroman','macroman_bin')
cls.desc[57] = ('cp1256','cp1256_general_ci')
cls.desc[58] = ('cp1257','cp1257_bin')
cls.desc[59] = ('cp1257','cp1257_general_ci')
cls.desc[63] = ('binary','binary')
cls.desc[64] = ('armscii8','armscii8_bin')
cls.desc[65] = ('ascii','ascii_bin')
cls.desc[66] = ('cp1250','cp1250_bin')
cls.desc[67] = ('cp1256','cp1256_bin')
cls.desc[68] = ('cp866','cp866_bin')
cls.desc[69] = ('dec8','dec8_bin')
cls.desc[70] = ('greek','greek_bin')
cls.desc[71] = ('hebrew','hebrew_bin')
cls.desc[72] = ('hp8','hp8_bin')
cls.desc[73] = ('keybcs2','keybcs2_bin')
cls.desc[74] = ('koi8r','koi8r_bin')
cls.desc[75] = ('koi8u','koi8u_bin')
cls.desc[77] = ('latin2','latin2_bin')
cls.desc[78] = ('latin5','latin5_bin')
cls.desc[79] = ('latin7','latin7_bin')
cls.desc[80] = ('cp850','cp850_bin')
cls.desc[81] = ('cp852','cp852_bin')
cls.desc[82] = ('swe7','swe7_bin')
cls.desc[83] = ('utf8','utf8_bin')
cls.desc[84] = ('big5','big5_bin')
cls.desc[85] = ('euckr','euckr_bin')
cls.desc[86] = ('gb2312','gb2312_bin')
cls.desc[87] = ('gbk','gbk_bin')
cls.desc[88] = ('sjis','sjis_bin')
cls.desc[89] = ('tis620','tis620_bin')
cls.desc[90] = ('ucs2','ucs2_bin')
cls.desc[91] = ('ujis','ujis_bin')
cls.desc[92] = ('geostd8','geostd8_general_ci')
cls.desc[93] = ('geostd8','geostd8_bin')
cls.desc[94] = ('latin1','latin1_spanish_ci')
cls.desc[95] = ('cp932','cp932_japanese_ci')
cls.desc[96] = ('cp932','cp932_bin')
cls.desc[97] = ('eucjpms','eucjpms_japanese_ci')
cls.desc[98] = ('eucjpms','eucjpms_bin')
cls.desc[128] = ('ucs2','ucs2_unicode_ci')
cls.desc[129] = ('ucs2','ucs2_icelandic_ci')
cls.desc[130] = ('ucs2','ucs2_latvian_ci')
cls.desc[131] = ('ucs2','ucs2_romanian_ci')
cls.desc[132] = ('ucs2','ucs2_slovenian_ci')
cls.desc[133] = ('ucs2','ucs2_polish_ci')
cls.desc[134] = ('ucs2','ucs2_estonian_ci')
cls.desc[135] = ('ucs2','ucs2_spanish_ci')
cls.desc[136] = ('ucs2','ucs2_swedish_ci')
cls.desc[137] = ('ucs2','ucs2_turkish_ci')
cls.desc[138] = ('ucs2','ucs2_czech_ci')
cls.desc[139] = ('ucs2','ucs2_danish_ci')
cls.desc[140] = ('ucs2','ucs2_lithuanian_ci')
cls.desc[141] = ('ucs2','ucs2_slovak_ci')
cls.desc[142] = ('ucs2','ucs2_spanish2_ci')
cls.desc[143] = ('ucs2','ucs2_roman_ci')
cls.desc[144] = ('ucs2','ucs2_persian_ci')
cls.desc[145] = ('ucs2','ucs2_esperanto_ci')
cls.desc[146] = ('ucs2','ucs2_hungarian_ci')
cls.desc[192] = ('utf8','utf8_unicode_ci')
cls.desc[193] = ('utf8','utf8_icelandic_ci')
cls.desc[194] = ('utf8','utf8_latvian_ci')
cls.desc[195] = ('utf8','utf8_romanian_ci')
cls.desc[196] = ('utf8','utf8_slovenian_ci')
cls.desc[197] = ('utf8','utf8_polish_ci')
cls.desc[198] = ('utf8','utf8_estonian_ci')
cls.desc[199] = ('utf8','utf8_spanish_ci')
cls.desc[200] = ('utf8','utf8_swedish_ci')
cls.desc[201] = ('utf8','utf8_turkish_ci')
cls.desc[202] = ('utf8','utf8_czech_ci')
cls.desc[203] = ('utf8','utf8_danish_ci')
cls.desc[204] = ('utf8','utf8_lithuanian_ci')
cls.desc[205] = ('utf8','utf8_slovak_ci')
cls.desc[206] = ('utf8','utf8_spanish2_ci')
cls.desc[207] = ('utf8','utf8_roman_ci')
cls.desc[208] = ('utf8','utf8_persian_ci')
cls.desc[209] = ('utf8','utf8_esperanto_ci')
cls.desc[210] = ('utf8','utf8_hungarian_ci')
@classmethod
def get_info(cls,setid):
"""Returns information about the charset for given MySQL ID."""
cls._init_desc()
res = ()
errmsg = "Character set with id '%d' unsupported." % (setid)
try:
res = cls.desc[setid]
except:
raise ProgrammingError, errmsg
if res is None:
raise ProgrammingError, errmsg
return res
@classmethod
def get_desc(cls,setid):
"""Returns info string about the charset for given MySQL ID."""
res = ()
try:
res = "%s/%s" % self.get_info(setid)
except ProgrammingError, e:
raise
else:
return res
@classmethod
def get_charset_info(cls, name, collation=None):
"""Returns information about the charset and optional collation."""
cls._init_desc()
l = len(cls.desc)
errmsg = "Character set '%s' unsupported." % (name)
if collation is None:
collation = '%s_general_ci' % (name)
# Search the list and return when found
idx = 0
for info in cls.desc:
if info and info[0] == name and info[1] == collation:
return (idx,info[0],info[1])
idx += 1
# If we got here, we didn't find the charset
raise ProgrammingError, errmsg
@classmethod
def get_supported(cls):
"""Returns a list with names of all supproted character sets."""
res = []
for info in cls.desc:
if info and info[0] not in res:
res.append(info[0])
return tuple(res)

View File

@ -0,0 +1,401 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Converting MySQL and Python types
"""
from types import NoneType
import re
import datetime
import time
from decimal import Decimal
import errors
from constants import FieldType, FieldFlag
class ConverterBase(object):
def __init__(self, charset='utf8', use_unicode=True):
self.python_types = None
self.mysql_types = None
self.set_charset(charset)
self.set_unicode(use_unicode)
def set_charset(self, charset):
if charset is not None:
self.charset = charset
else:
# default to utf8
self.charset = 'utf8'
def set_unicode(self, value=True):
self.use_unicode = value
def to_mysql(self, value):
return value
def to_python(self, vtype, value):
return value
def escape(self, buf):
return buf
def quote(self, buf):
return str(buf)
class MySQLConverter(ConverterBase):
"""
A converted class grouping:
o escape method: for escpaing values send to MySQL
o quoting method: for quoting values send to MySQL in statements
o conversion mapping: maps Python and MySQL data types to
function for converting them.
This class should be overloaded whenever one needs differences
in how values are to be converted. Each MySQLConnection object
has a default_converter property, which can be set like
MySQL.converter(CustomMySQLConverter)
"""
def __init__(self, charset=None, use_unicode=True):
ConverterBase.__init__(self, charset, use_unicode)
# Python types
self.python_types = {
int : int,
str : self._str_to_mysql,
long : long,
float : float,
unicode : self._unicode_to_mysql,
bool : self._bool_to_mysql,
NoneType : self._none_to_mysql,
datetime.datetime : self._datetime_to_mysql,
datetime.date : self._date_to_mysql,
datetime.time : self._time_to_mysql,
time.struct_time : self._struct_time_to_mysql,
datetime.timedelta : self._timedelta_to_mysql,
Decimal : self._decimal_to_mysql,
}
# MySQL types
self.mysql_types = {
FieldType.TINY : self._int,
FieldType.SHORT : self._int,
FieldType.INT24 : self._int,
FieldType.LONG : self._long,
FieldType.LONGLONG : self._long,
FieldType.FLOAT : self._float,
FieldType.DOUBLE : self._float,
FieldType.DECIMAL : self._decimal,
FieldType.NEWDECIMAL : self._decimal,
FieldType.VAR_STRING : self._STRING_to_python,
FieldType.STRING : self._STRING_to_python,
FieldType.SET : self._SET_to_python,
FieldType.TIME : self._TIME_to_python,
FieldType.DATE : self._DATE_to_python,
FieldType.NEWDATE : self._DATE_to_python,
FieldType.DATETIME : self._DATETIME_to_python,
FieldType.TIMESTAMP : self._DATETIME_to_python,
FieldType.BLOB : self._STRING_to_python,
}
def escape(self, value):
"""
Escapes special characters as they are expected to by when MySQL
receives them.
As found in MySQL source mysys/charset.c
Returns the value if not a string, or the escaped string.
"""
if value is None:
return value
elif isinstance(value, (int,float,long,Decimal)):
return value
backslash = re.compile(r'\134')
res = value
res = backslash.sub(r'\\\\', res)
res = res.replace('\n','\\n')
res = res.replace('\r','\\r')
res = res.replace('\047','\134\047') # single quotes
res = res.replace('\042','\134\042') # double quotes
res = res.replace('\032','\134\032') # for Win32
return res
def quote(self, buf):
"""
Quote the parameters for commands. General rules:
o numbers are returns as str type (because operation expect it)
o None is returned as str('NULL')
o String are quoted with single quotes '<string>'
Returns a string.
"""
if isinstance(buf, (int,float,long,Decimal)):
return str(buf)
elif isinstance(buf, NoneType):
return "NULL"
else:
# Anything else would be a string
return "'%s'" % buf
def to_mysql(self, value):
vtype = type(value)
return self.python_types[vtype](value)
def _str_to_mysql(self, value):
return str(value)
def _unicode_to_mysql(self, value):
"""
Encodes value, a Python unicode string, to whatever the
character set for this converter is set too.
"""
return value.encode(self.charset)
def _bool_to_mysql(self, value):
if value:
return 1
else:
return 0
def _none_to_mysql(self, value):
"""
This would return what None would be in MySQL, but instead we
leave it None and return it right away. The actual convertion
from None to NULL happens in the quoting functionality.
Return None.
"""
return None
def _datetime_to_mysql(self, value):
"""
Converts a datetime instance to a string suitable for MySQL.
The returned string has format: %Y-%m-%d %H:%M:%S
If the instance isn't a datetime.datetime type, it return None.
Returns a string or None when not valid.
"""
if isinstance(value, datetime.datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
return None
def _date_to_mysql(self, value):
"""
Converts a date instance to a string suitable for MySQL.
The returned string has format: %Y-%m-%d
If the instance isn't a datetime.date type, it return None.
Returns a string or None when not valid.
"""
if isinstance(value, datetime.date):
return value.strftime('%Y-%m-%d')
return None
def _time_to_mysql(self, value):
"""
Converts a time instance to a string suitable for MySQL.
The returned string has format: %H:%M:%S
If the instance isn't a datetime.time type, it return None.
Returns a string or None when not valid.
"""
if isinstance(value, datetime.time):
return value.strftime('%H:%M:%S')
return None
def _struct_time_to_mysql(self, value):
"""
Converts a time.struct_time sequence to a string suitable
for MySQL.
The returned string has format: %Y-%m-%d %H:%M:%S
Returns a string or None when not valid.
"""
if isinstance(value, time.struct_time):
return time.strftime('%Y-%m-%d %H:%M:%S',value)
return None
def _timedelta_to_mysql(self, value):
"""
Converts a timedelta instance to a string suitable for MySQL.
The returned string has format: %H:%M:%S
Returns a string or None when not valid.
"""
if isinstance(value, datetime.timedelta):
secs = value.seconds%60
mins = value.seconds%3600/60
hours = value.seconds/3600+(value.days*24)
return '%d:%02d:%02d' % (hours,mins,secs)
return None
def _decimal_to_mysql(self, value):
"""
Converts a decimal.Decimal instance to a string suitable for
MySQL.
Returns a string or None when not valid.
"""
if isinstance(value, Decimal):
return str(value)
return None
def to_python(self, flddsc, value):
"""
Converts a given value coming from MySQL to a certain type in Python.
The flddsc contains additional information for the field in the
table. It's an element from MySQLCursor.description.
Returns a mixed value.
"""
res = value
if value == '\x00':
# Don't go further when we hit a NULL value
return None
if value is None:
return None
try:
res = self.mysql_types[flddsc[1]](value, flddsc)
except KeyError:
# If one type is not defined, we just return the value as str
return str(value)
except ValueError, e:
raise ValueError, "%s (field %s)" % (e, flddsc[0])
except TypeError, e:
raise TypeError, "%s (field %s)" % (e, flddsc[0])
except:
raise
return res
def _float(self, v, desc=None):
"""
Returns v as float type.
"""
return float(v)
def _int(self, v, desc=None):
"""
Returns v as int type.
"""
return int(v)
def _long(self, v, desc=None):
"""
Returns v as long type.
"""
return long(v)
def _decimal(self, v, desc=None):
"""
Returns v as a decimal.Decimal.
"""
return Decimal(v)
def _str(self, v, desc=None):
"""
Returns v as str type.
"""
return str(v)
def _DATE_to_python(self, v, dsc=None):
"""
Returns DATE column type as datetime.date type.
"""
pv = None
try:
pv = datetime.date(*[ int(s) for s in v.split('-')])
except ValueError:
return None
else:
return pv
def _TIME_to_python(self, v, dsc=None):
"""
Returns TIME column type as datetime.time type.
"""
pv = None
try:
(h, m, s) = [ int(s) for s in v.split(':')]
pv = datetime.timedelta(hours=h,minutes=m,seconds=s)
except ValueError:
raise ValueError, "Could not convert %s to python datetime.timedelta" % v
else:
return pv
def _DATETIME_to_python(self, v, dsc=None):
"""
Returns DATETIME column type as datetime.datetime type.
"""
pv = None
try:
pv = datetime.datetime(*time.strptime(v, "%Y-%m-%d %H:%M:%S")[0:6])
except ValueError:
pv = None
return pv
def _SET_to_python(self, v, dsc=None):
"""
Actually, MySQL protocol sees a SET as a string type field. So this
code isn't called directly, but used by STRING_to_python() method.
Returns SET column type as string splitted using a comma.
"""
pv = None
try:
pv = v.split(',')
except ValueError:
raise ValueError, "Could not convert set %s to a sequence." % v
return pv
def _STRING_to_python(self, v, dsc=None):
"""
Note that a SET is a string too, but using the FieldFlag we can see
whether we have to split it.
Returns string typed columns as string type.
"""
if dsc is not None:
# Check if we deal with a SET
if dsc[7] & FieldFlag.SET:
return self._SET_to_python(v, dsc)
if self.use_unicode:
try:
return unicode(v, self.charset)
except:
raise
return str(v)

542
mysql/connector/cursor.py Normal file
View File

@ -0,0 +1,542 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Cursor classes
"""
import exceptions
import mysql
import connection
import protocol
import errors
import utils
class CursorBase(object):
"""
Base for defining MySQLCursor. This class is a skeleton and defines
methods and members as required for the Python Database API
Specification v2.0.
It's better to inherite from MySQLCursor.
"""
def __init__(self):
self.description = None
self.rowcount = -1
self.arraysize = 1
def __del__(self):
self.close()
def callproc(self, procname, args=()):
pass
def close(self):
pass
def execute(self, operation, params=()):
pass
def executemany(self, operation, seqparams):
pass
def fetchone(self):
pass
def fetchmany(self, size=1):
pass
def fetchall(self):
pass
def nextset(self):
pass
def setinputsizes(self, sizes):
pass
def setoutputsize(self, size, column=None):
pass
class MySQLCursor(CursorBase):
"""
Default cursor which fetches all rows and stores it for later
usage. It uses the converter set for the MySQLConnection to map
MySQL types to Python types automatically.
This class should be inherited whenever other functionallity is
required. An example would to change the fetch* member functions
to return dictionaries instead of lists of values.
Implements the Python Database API Specification v2.0.
Possible parameters are:
db
A MySQLConnection instance.
"""
def __init__(self, db=None):
CursorBase.__init__(self)
self.db = None
self.fields = ()
self.nrflds = 0
self._result = []
self._nextrow = (None, None)
self.lastrowid = None
self._warnings = None
self._warning_count = 0
self._executed = None
self._have_result = False
self._get_warnings = False
if db is not None:
self.set_connection(db)
def __iter__(self):
"""
Iteration over the result set which calls self.fetchone()
and returns the next row.
"""
return iter(self.fetchone, None)
def _valid_protocol(self,db):
if not hasattr(db,'conn'):
raise errors.InterfaceError(
"MySQL connection object connection not valid.")
try:
if not isinstance(db.conn.protocol,protocol.MySQLProtocol):
raise errors.InterfaceError(
"MySQL connection has no protocol set.")
except AttributeError:
raise errors.InterfaceError(
"MySQL connection object connection not valid.")
return True
def set_connection(self, db):
if isinstance(db,mysql.MySQLBase):
if self._valid_protocol(db):
self.db = db
self.protocol = db.conn.protocol
self.db.register_cursor(self)
self._get_warnings = self.db.get_warnings
else:
raise errors.InterfaceError(
"MySQLCursor db-argument must subclass of mysql.MySQLBase")
def _reset_result(self):
del self._result[:]
self.rowcount = -1
self._nextrow = (None, None)
self._have_result = False
self._warnings = None
self._warning_count = 0
self._fields = ()
def next(self):
"""
Used for iterating over the result set. Calles self.fetchone()
to get the next row.
"""
try:
row = self.fetchone()
except errors.InterfaceError:
raise StopIteration
if not row:
raise StopIteration
return row
def close(self):
"""
Close the cursor, disconnecting it from the MySQL object.
Returns True when succesful, otherwise False.
"""
if self.db is None:
return False
try:
self.db.remove_cursor(self)
self.db = None
except:
return False
del self._result[:]
return True
def _process_params_dict(self, params):
try:
to_mysql = self.db.converter.to_mysql
escape = self.db.converter.escape
quote = self.db.converter.quote
res = {}
for k,v in params.items():
c = v
c = to_mysql(c)
c = escape(c)
c = quote(c)
res[k] = c
except StandardError, e:
raise errors.ProgrammingError(
"Failed processing pyformat-parameters; %s" % e)
else:
return res
return None
def _process_params(self, params):
"""
Process the parameters which were given when self.execute() was
called. It does following using the MySQLConnection converter:
* Convert Python types to MySQL types
* Escapes characters required for MySQL.
* Quote values when needed.
Returns a list.
"""
if isinstance(params,dict):
return self._process_params_dict(params)
try:
res = params
to_mysql = self.db.converter.to_mysql
escape = self.db.converter.escape
quote = self.db.converter.quote
res = map(to_mysql,res)
res = map(escape,res)
res = map(quote,res)
except StandardError, e:
raise errors.ProgrammingError(
"Failed processing format-parameters; %s" % e)
else:
return tuple(res)
return None
def _get_description(self, res=None):
"""
Gets the description of the fields out of a result we got from
the MySQL Server. If res is None then self.description is
returned (which can be None).
Returns a list or None when no descriptions are available.
"""
if not res:
return self.description
desc = []
try:
for fld in res[1]:
if not isinstance(fld, protocol.FieldPacket):
raise errors.ProgrammingError(
"Can only get description from protocol.FieldPacket")
desc.append(fld.get_description())
except TypeError:
raise errors.ProgrammingError(
"_get_description needs a list as argument."
)
return desc
def _row_to_python(self, rowdata, desc=None):
res = ()
try:
to_python = self.db.converter.to_python
if not desc:
desc = self.description
for idx,v in enumerate(rowdata):
flddsc = desc[idx]
res += (to_python(flddsc, v),)
except StandardError, e:
raise errors.InterfaceError(
"Failed converting row to Python types; %s" % e)
else:
return res
return None
def _handle_noresultset(self, res):
"""Handles result of execute() when there is no result set."""
try:
self.rowcount = res.affected_rows
self.lastrowid = res.insert_id
self._warning_count = res.warning_count
if self._get_warnings is True and self._warning_count:
self._warnings = self._fetch_warnings()
except StandardError, e:
raise errors.ProgrammingError(
"Failed handling non-resultset; %s" % e)
def _handle_resultset(self):
pass
def execute(self, operation, params=None):
"""
Executes the given operation. The parameters given through params
are used to substitute %%s in the operation string.
For example, getting all rows where id is 5:
cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,))
If warnings where generated, and db.get_warnings is True, then
self._warnings will be a list containing these warnings.
Raises exceptions when any error happens.
"""
if not operation:
return 0
self._reset_result()
stmt = ''
# Make sure we send the query in correct character set
try:
if isinstance(operation, unicode):
operation.encode(self.db.charset_name)
if params is not None:
stmt = operation % self._process_params(params)
else:
stmt = operation
res = self.protocol.cmd_query(stmt)
if isinstance(res, protocol.OKResultPacket):
self._have_result = False
self._handle_noresultset(res)
else:
self.description = self._get_description(res)
self._have_result = True
self._handle_resultset()
except errors.ProgrammingError:
raise
except errors.OperationalError:
raise
except StandardError, e:
raise errors.InterfaceError(
"Failed executing the operation; %s" % e)
else:
self._executed = stmt
return self.rowcount
return 0
def executemany(self, operation, seq_params):
"""Loops over seq_params and calls excute()"""
if not operation:
return 0
rowcnt = 0
try:
for params in seq_params:
self.execute(operation, params)
if self._have_result:
self.fetchall()
rowcnt += self.rowcount
except (ValueError,TypeError), e:
raise errors.InterfaceError(
"Failed executing the operation; %s" % e)
except:
# Raise whatever execute() raises
raise
return rowcnt
def callproc(self, procname, args=()):
"""Calls a stored procedue with the given arguments
The arguments will be set during this session, meaning
they will be called like _<procname>__arg<nr> where
<nr> is an enumeration (+1) of the arguments.
Coding Example:
1) Definining the Stored Routine in MySQL:
CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
BEGIN
SET pProd := pFac1 * pFac2;
END
2) Executing in Python:
args = (5,5,0) # 0 is to hold pprod
cursor.callproc(multiply, args)
print cursor.fetchone()
The last print should output ('5', '5', 25L)
Does not return a value, but a result set will be
available when the CALL-statement execute succesfully.
Raises exceptions when something is wrong.
"""
argfmt = "@_%s_arg%d"
try:
procargs = self._process_params(args)
argnames = []
for idx,arg in enumerate(procargs):
argname = argfmt % (procname, idx+1)
argnames.append(argname)
setquery = "SET %s=%%s" % argname
self.execute(setquery, (arg,))
call = "CALL %s(%s)" % (procname,','.join(argnames))
res = self.protocol.cmd_query(call)
select = "SELECT %s" % ','.join(argnames)
self.execute(select)
except errors.ProgrammingError:
raise
except StandardError, e:
raise errors.InterfaceError(
"Failed calling stored routine; %s" % e)
def getlastrowid(self):
return self.lastrowid
def _fetch_warnings(self):
"""
Fetch warnings doing a SHOW WARNINGS. Can be called after getting
the result.
Returns a result set or None when there were no warnings.
"""
res = []
try:
c = self.db.cursor()
cnt = c.execute("SHOW WARNINGS")
res = c.fetchall()
c.close()
except StandardError, e:
raise errors.ProgrammingError(
"Failed getting warnings; %s" % e)
else:
if len(res):
return res
return None
def _handle_eof(self, eof):
self._have_result = False
self._nextrow = (None, None)
self._warning_count = eof.warning_count
if self.db.get_warnings is True and eof.warning_count:
self._warnings = self._fetch_warnings()
def _fetch_row(self):
if self._have_result is False:
return None
row = None
try:
if self._nextrow == (None, None):
(row, eof) = self.protocol.result_get_row()
else:
(row, eof) = self._nextrow
if row:
(foo, eof) = self._nextrow = self.protocol.result_get_row()
if eof is not None:
self._handle_eof(eof)
if self.rowcount == -1:
self.rowcount = 1
else:
self.rowcount += 1
if eof:
self._handle_eof(eof)
except:
raise
else:
return row
return None
def fetchwarnings(self):
return self._warnings
def fetchone(self):
row = self._fetch_row()
if row:
return self._row_to_python(row)
return None
def fetchmany(self,size=None):
res = []
cnt = (size or self.arraysize)
while cnt > 0 and self._have_result:
cnt -= 1
row = self.fetchone()
if row:
res.append(row)
return res
def fetchall(self):
if self._have_result is False:
raise errors.InterfaceError("No result set to fetch from.")
res = []
row = None
while self._have_result:
row = self.fetchone()
if row:
res.append(row)
return res
def __unicode__(self):
fmt = "MySQLCursor: %s"
if self._executed:
if len(self._executed) > 30:
res = fmt % (self._executed[:30] + '..')
else:
res = fmt % (self._executed)
else:
res = fmt % '(Nothing executed yet)'
return res
def __str__(self):
return repr(self.__unicode__())
class MySQLCursorBuffered(MySQLCursor):
"""Cursor which fetches rows within execute()"""
def __init__(self, db=None):
MySQLCursor.__init__(self, db)
self._rows = []
self._next_row = 0
def _handle_resultset(self):
self._get_all_rows()
def _get_all_rows(self):
(self._rows, eof) = self.protocol.result_get_rows()
self.rowcount = len(self._rows)
self._handle_eof(eof)
self._next_row = 0
self._have_result = True
def _fetch_row(self):
row = None
try:
row = self._rows[self._next_row]
except:
self._have_result = False
return None
else:
self._next_row += 1
return row
return None

64
mysql/connector/dbapi.py Normal file
View File

@ -0,0 +1,64 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""DB API v2.0 required
"""
import time
import datetime
import constants
class _DBAPITypeObject:
def __init__(self,*values):
self.values = values
def __cmp__(self,other):
if other in self.values:
return 0
if other < self.values:
return 1
else:
return -1
Date = datetime.date
Time = datetime.time
Timestamp = datetime.datetime
def DateFromTicks(ticks):
return Date(*time.localtime(ticks)[:3])
def TimeFromTicks(ticks):
return Time(*time.localtime(ticks)[3:6])
def TimestampFromTicks(ticks):
return Timestamp(*time.localtime(ticks)[:6])
Binary = str
STRING = _DBAPITypeObject(constants.FieldType.get_string_types())
BINARY = _DBAPITypeObject(constants.FieldType.get_binary_types())
NUMBER = _DBAPITypeObject(constants.FieldType.get_number_types())
DATETIME = _DBAPITypeObject(constants.FieldType.get_timestamp_types())
ROWID = _DBAPITypeObject()

87
mysql/connector/errors.py Normal file
View File

@ -0,0 +1,87 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Python exceptions
"""
import exceptions
import protocol
class Error(StandardError):
def __init__(self, m):
if isinstance(m,protocol.ErrorResultPacket):
# process MySQL error packet
self._process_packet(m)
else:
# else the message should be a string
self.errno = -1
self.errmsg = str(m)
self.sqlstate = -1
self.msg = str(m)
def _process_packet(self, packet):
self.errno = packet.errno
self.errmsg = packet.errmsg
self.sqlstate = packet.sqlstate
if self.sqlstate:
m = '%d (%s): %s' % (self.errno, self.sqlstate, self.errmsg)
else:
m = '%d: %s' % (self.errno, self.errmsg)
self.errmsglong = m
self.msg = m
def __str__(self):
return self.msg
def __unicode__(self):
return self.msg
class Warning(StandardError):
pass
class InterfaceError(Error):
def __init__(self, msg):
Error.__init__(self, msg)
class DatabaseError(Error):
def __init__(self, msg):
Error.__init__(self, msg)
class InternalError(DatabaseError):
pass
class OperationalError(DatabaseError):
pass
class ProgrammingError(DatabaseError):
pass
class IntegrityError(DatabaseError):
pass
class DataError(DatabaseError):
pass
class NotSupportedError(DatabaseError):
pass

414
mysql/connector/mysql.py Normal file
View File

@ -0,0 +1,414 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Main classes for interacting with MySQL
"""
import socket, string, os
from connection import *
import constants
import conversion
import protocol
import errors
import utils
import cursor
class MySQLBase(object):
"""MySQLBase"""
def __init__(self):
"""Initializing"""
self.conn = None # Holding the connection
self.converter = None
self.client_flags = constants.ClientFlag.get_default()
(self.charset,
self.charset_name,
self.collation_name) = constants.CharacterSet.get_charset_info('utf8')
self.username = ''
self.password = ''
self.database = ''
self.client_host = ''
self.client_port = 0
self.affected_rows = 0
self.server_status = 0
self.warning_count = 0
self.field_count = 0
self.insert_id = 0
self.info_msg = ''
self.use_unicode = True
self.get_warnings = False
self.autocommit = False
self.connection_timeout = None
self.buffered = False
def connect(self):
"""To be implemented while subclassing MySQLBase."""
pass
def _set_connection(self, prtcls=None):
"""Automatically chooses based on configuration which connection type to setup."""
if self.unix_socket and os.name != 'nt':
self.conn = MySQLUnixConnection(prtcls=prtcls,
unix_socket=self.unix_socket)
else:
self.conn = MySQLTCPConnection(prtcls=prtcls,
host=self.server_host, port=self.server_port)
self.conn.set_connection_timeout(self.connection_timeout)
def _open_connection(self):
"""Opens the connection and sets the appropriated protocol."""
# We don't know yet the MySQL version we connect too
self._set_connection()
try:
self.conn.open_connection()
version = self.conn.protocol.server_version
if version < (4,1):
raise InterfaceError("MySQL Version %s is not supported." % version)
else:
self.conn.set_protocol(protocol.MySQLProtocol)
self.protocol = self.conn.protocol
self.protocol.do_auth(username=self.username, password=self.password,
database=self.database)
except:
raise
def _post_connection(self):
"""Should be called after a connection was established"""
self.get_characterset_info()
self.set_converter_class(conversion.MySQLConverter)
try:
self.set_charset(self.charset_name)
self.set_autocommit(self.autocommit)
except:
raise
def is_connected(self):
"""
Check whether we are connected to the MySQL server.
"""
return self.protocol.cmd_ping()
ping = is_connected
def disconnect(self):
"""
Disconnect from the MySQL server.
"""
if not self.conn:
return
if self.conn.sock is not None:
self.protocol.cmd_quit()
try:
self.conn.close_connection()
except:
pass
self.protocol = None
self.conn = None
def set_converter_class(self, convclass):
"""
Set the converter class to be used. This should be a class overloading
methods and members of conversion.MySQLConverter.
"""
self.converter_class = convclass
self.converter = self.converter_class(self.charset_name, self.use_unicode)
def get_characterset_info(self):
try:
(self.charset_name, self.collation_name) = constants.CharacterSet.get_info(self.charset)
except:
raise ProgrammingError, "Illegal character set information (id=%d)" % self.charset
return (self.charset_name, self.collation_name)
def get_server_version(self):
"""Returns the server version as a tuple"""
try:
return self.protocol.server_version
except:
pass
return None
def get_server_info(self):
"""Returns the server version as a string"""
return self.protocol.server_version_original
def get_server_threadid(self):
"""Returns the MySQL threadid of the connection."""
threadid = None
try:
threadid = self.protocol.server_threadid
except:
pass
return threadid
def set_host(self, host):
"""
Set the host for connection to the MySQL server.
"""
self.server_host = host
def set_port(self, port):
"""
Set the TCP port to be used when connecting to the server, usually 3306.
"""
self.server_port = port
def set_login(self, username=None, password=None):
"""
Set the username and/or password for the user connecting to the MySQL Server.
"""
self.username = username
self.password = password
def set_unicode(self, value=True):
"""
Set whether we return string fields as unicode or not.
Default is True.
"""
self.use_unicode = value
if self.converter:
self.converter.set_unicode(value)
def set_database(self, database):
"""
Set the database to be used after connection succeeded.
"""
self.database = database
def set_charset(self, name):
"""
Set the character set used for the connection. This is the recommended
way of change it per connection basis. It does execute SET NAMES
internally, but it's good not to use this command directly, since we
are setting some other members accordingly.
"""
if name not in constants.CharacterSet.get_supported():
raise errors.ProgrammingError, "Character set '%s' not supported." % name
return
try:
info = constants.CharacterSet.get_charset_info(name)
except errors.ProgrammingError, e:
raise
try:
self.protocol.cmd_query("SET NAMES '%s'" % name)
except:
raise
else:
(self.charset, self.charset_name, self.collation_name) = info
self.converter.set_charset(self.charset_name)
def set_getwarnings(self, bool):
"""
Set wheter we should get warnings whenever an operation produced some.
"""
self.get_warnings = bool
def set_autocommit(self, switch):
"""
Set auto commit on or off. The argument 'switch' must be a boolean type.
"""
if not isinstance(switch, bool):
raise ValueError, "The switch argument must be boolean."
s = 'OFF'
if switch:
s = 'ON'
try:
self.protocol.cmd_query("SET AUTOCOMMIT = %s" % s)
except:
raise
else:
self.autocommit = switch
def set_unixsocket(self, loc):
"""Set the UNIX Socket location. Does not check if it exists."""
self.unix_socket = loc
def set_connection_timeout(self, timeout):
self.connection_timeout = timeout
def set_client_flags(self, flags):
self.client_flags = flags
def set_buffered(self, val=False):
"""Sets whether cursor .execute() fetches rows"""
self.buffered = val
class MySQL(MySQLBase):
"""
Class implementing Python DB API v2.0.
"""
def __init__(self, *args, **kwargs):
"""
Initializes the MySQL object. Calls connect() to open the connection
when an instance is created.
"""
MySQLBase.__init__(self)
self.cursors = []
self.affected_rows = 0
self.server_status = 0
self.warning_count = 0
self.field_count = 0
self.insert_id = 0
self.info_msg = ''
self.connect(*args, **kwargs)
def connect(self, dsn='', user='', password='', host='127.0.0.1',
port=3306, db=None, database=None, use_unicode=True, charset='utf8', get_warnings=False,
autocommit=False, unix_socket=None,
connection_timeout=None, client_flags=None, buffered=False):
"""
Establishes a connection to the MySQL Server. Called also when instansiating
a new MySQLConnection object through the __init__ method.
Possible parameters are:
dsn
(not used)
user
The username used to authenticate with the MySQL Server.
password
The password to authenticate the user with the MySQL Server.
host
The hostname or the IP address of the MySQL Server we are connecting with.
(default 127.0.0.1)
port
TCP port to use for connecting to the MySQL Server.
(default 3306)
database
db
Initial database to use once we are connected with the MySQL Server.
The db argument is synonym, but database takes precedence.
use_unicode
If set to true, string values received from MySQL will be returned
as Unicode strings.
Default: True
charset
Which character shall we use for sending data to MySQL. One can still
override this by using the SET NAMES command directly, but this is
discouraged. Instead, use the set_charset() method if you
want to change it.
Default: Whatever the MySQL server has default.
get_warnings
If set to true, whenever a query gives a warning, a SHOW WARNINGS will
be done to fetch them. They will be available as MySQLCursor.warnings.
The default is to ignore these warnings, for debugging it's good to
enable it though, or use strict mode in MySQL to make most of these
warnings errors.
Default: False
autocommit
Auto commit is OFF by default, which is required by the Python Db API
2.0 specification.
Default: False
unix_socket
Full path to the MySQL Server UNIX socket. By default TCP connection will
be used using the address specified by the host argument.
connection_timeout
Timeout for the TCP and UNIX socket connection.
client_flags
Allows to set flags for the connection. Check following for possible flags:
>>> from mysql.connector.constants import ClientFlag
>>> print '\n'.join(ClientFlag.get_full_info())
buffered
When set to True .execute() will fetch the rows immediatly.
"""
# db is not part of Db API v2.0, but MySQLdb supports it.
if db and not database:
database = db
self.set_host(host)
self.set_port(port)
self.set_database(database)
self.set_getwarnings(get_warnings)
self.set_unixsocket(unix_socket)
self.set_connection_timeout(connection_timeout)
self.set_client_flags(client_flags)
self.set_buffered(buffered)
if user or password:
self.set_login(user, password)
self.disconnect()
self._open_connection()
self._post_connection()
def close(self):
del self.cursors[:]
self.disconnect()
def remove_cursor(self, c):
try:
self.cursors.remove(c)
except ValueError:
raise errors.ProgrammingError(
"Cursor could not be removed.")
def register_cursor(self, c):
try:
self.cursors.append(c)
except:
raise
def cursor(self):
if self.buffered:
c = (cursor.MySQLCursorBuffered)(self)
else:
c = (cursor.MySQLCursor)(self)
self.register_cursor(c)
return c
def commit(self):
"""Shortcut for executing COMMIT."""
self.protocol.cmd_query("COMMIT")
def rollback(self):
"""Shortcut for executing ROLLBACK"""
self.protocol.cmd_query("ROLLBACK")

863
mysql/connector/protocol.py Normal file
View File

@ -0,0 +1,863 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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 USAs
"""Implementing the MySQL Client/Server protocol
"""
import string
import socket
import re
import struct
try:
from hashlib import sha1
except ImportError:
from sha import new as sha1
from datetime import datetime
from time import strptime
from decimal import Decimal
from constants import *
import errors
import utils
class MySQLProtocol(object):
"""Class handling the MySQL Protocol.
MySQL v4.1 Client/Server Protocol is currently supported.
"""
def __init__(self, conn, handshake=None):
self.client_flags = 0
self.conn = conn # MySQL Connection
if handshake:
self.set_handshake(handshake)
def handle_header(self, buf):
"""Takes a buffer and readers information from header.
Returns a tuple (pktsize, pktnr)
"""
pktsize = utils.int3read(buf[0:3])
pktnr = utils.int1read(buf[3])
return (pktsize, pktnr)
def do_auth(self, username=None, password=None, database=None,
client_flags=None):
"""
Make and send the authentication using information found in the
handshake packet.
"""
if not client_flags:
client_flags = ClientFlag.get_default()
auth = Auth(client_flags=client_flags,
pktnr=self.handshake.pktnr+1)
auth.create(username=username, password=password,
seed=self.handshake.info['seed'], database=database)
self.conn.send(auth.get())
buf = self.conn.recv()[0]
if self.is_eof(buf):
raise errors.InterfaceError("Found EOF after Auth, expecting OK. Using old passwords?")
connect_with_db = client_flags & ClientFlag.CONNECT_WITH_DB
if self.is_ok(buf) and database and not connect_with_db:
self.cmd_init_db(database)
def handle_handshake(self, buf):
"""
Check whether the buffer is a valid handshake. If it is, we set some
member variables for later usage. The handshake packet is returned for later
usuage, e.g. authentication.
"""
if self.is_error(buf):
# an ErrorPacket is returned by the server
self._handle_error(buf)
handshake = None
try:
handshake = Handshake(buf)
except errors.InterfaceError, msg:
raise errors.InterfaceError(msg)
self.set_handshake(handshake)
def set_handshake(self, handshake):
"""Gather data from the given handshake."""
ver = re.compile("^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)")
version = handshake.info['version']
m = ver.match(version)
if not m:
raise errors.InterfaceError("Could not parse MySQL version, was '%s'" % version)
else:
self.server_version = tuple([ int(v) for v in m.groups()[0:3]])
self.server_version_original = handshake.info['version']
self.server_threadid = handshake.info['thrdid']
self.capabilities = handshake.info['capabilities']
self.charset = handshake.info['charset']
self.threadid = handshake.info['thrdid']
self.handshake = handshake
def _handle_error(self, buf):
"""Raise an OperationalError if result is an error
"""
try:
err = ErrorResultPacket(buf)
except errors.InterfaceError, e:
raise e
else:
raise errors.OperationalError(err)
def is_error(self, buf):
"""Check if the given buffer is a MySQL Error Packet.
Buffer should start with \xff.
Returns boolean.
"""
if buf and buf[4] == '\xff':
self._handle_error(buf)
return True
return False
def _handle_ok(self, buf):
"""
Handle an OK Result Packet. If we got an InterfaceError, raise that
instead.
"""
try:
ok = OKResultPacket(buf)
except errors.InterfaceError, e:
raise e
else:
self.server_status = ok.server_status
self.warning_count = ok.warning_count
self.field_count = ok.field_count
self.affected_rows = ok.affected_rows
self.info_msg = ok.info_msg
def is_ok(self, buf):
"""
Check if the given buffer is a MySQL OK Packet. It should
start with \x00.
Returns boolean.
"""
if buf and buf[4] == '\x00':
self._handle_ok(buf)
return True
return False
def _handle_fields(self, nrflds):
"""Reads a number of fields from a result set."""
i = 0
fields = []
while i < nrflds:
buf = self.conn.recv()[0]
fld = FieldPacket(buf)
fields.append(fld)
i += 1
return fields
def is_eof(self, buf):
"""
Check if the given buffer is a MySQL EOF Packet. It should
start with \xfe and be smaller 9 bytes.
Returns boolean.
"""
l = utils.read_int(buf, 3)[1]
if buf and buf[4] == '\xfe' and l < 9:
return True
return False
def _handle_resultset(self, pkt):
"""Processes a resultset getting fields information.
The argument pkt must be a protocol.Packet with length 1, a byte
which contains the number of fields.
"""
if not isinstance(pkt, PacketIn):
raise ValueError("%s is not a protocol.PacketIn" % pkt)
if len(pkt) == 1:
(buf,nrflds) = utils.read_lc_int(pkt.data)
# Get the fields
fields = self._handle_fields(nrflds)
buf = self.conn.recv()[0]
eof = EOFPacket(buf)
return (nrflds, fields, eof)
else:
raise errors.InterfaceError('Something wrong reading result after query.')
def result_get_row(self):
"""Get data for 1 row
Get one row's data. Should be called after getting the field
descriptions.
Returns a tuple with 2 elements: a row's data and the
EOF packet.
"""
buf = self.conn.recv()[0]
if self.is_eof(buf):
eof = EOFPacket(buf)
rowdata = None
else:
eof = None
rowdata = utils.read_lc_string_list(buf[4:])
return (rowdata, eof)
def result_get_rows(self, cnt=None):
"""Get all rows
Returns a tuple with 2 elements: a list with all rows and
the EOF packet.
"""
rows = []
eof = None
rowdata = None
while eof is None:
(rowdata,eof) = self.result_get_row()
if eof is None and rowdata is not None:
rows.append(rowdata)
return (rows,eof)
def cmd_query(self, query):
"""
Sends a query to the server.
Returns a tuple, when the query returns a result. The tuple
consist number of fields and a list containing their descriptions.
If the query doesn't return a result set, the an OKResultPacket
will be returned.
"""
try:
cmd = CommandPacket()
cmd.set_command(ServerCmd.QUERY)
cmd.set_argument(query)
cmd.create()
self.conn.send(cmd.get()) # Errors handled in _handle_error()
buf = self.conn.recv()[0]
if self.is_ok(buf):
# Query does not return a result (INSERT/DELETE/..)
return OKResultPacket(buf)
p = PacketIn(buf)
(nrflds, fields, eof) = self._handle_resultset(p)
except:
raise
else:
return (nrflds, fields)
return (0, ())
def _cmd_simple(self, servercmd, arg=''):
"""Makes a simple CommandPacket with no arguments"""
cmd = CommandPacket()
cmd.set_command(servercmd)
cmd.set_argument(arg)
cmd.create()
return cmd
def cmd_refresh(self, opts):
"""Send the Refresh command to the MySQL server.
The argument should be a bitwise value using the protocol.RefreshOption
constants.
Usage:
RefreshOption = mysql.connector.RefreshOption
refresh = RefreshOption.LOG | RefreshOption.THREADS
db.cmd_refresh(refresh)
"""
cmd = self._cmd_simple(ServerCmd.REFRESH, opts)
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
raise
if self.is_ok(buf):
return True
return False
def cmd_quit(self):
"""Closes the current connection with the server."""
cmd = self._cmd_simple(ServerCmd.QUIT)
self.conn.send(cmd.get())
def cmd_init_db(self, database):
"""
Send command to server to change databases.
"""
cmd = self._cmd_simple(ServerCmd.INIT_DB, database)
self.conn.send(cmd.get())
self.conn.recv()[0]
def cmd_shutdown(self):
"""Shuts down the MySQL Server.
Careful with this command if you have SUPER privileges! (Which your
scripts probably don't need!)
Returns True if it succeeds.
"""
cmd = self._cmd_simple(ServerCmd.SHUTDOWN)
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
raise
return True
def cmd_statistics(self):
"""Sends statistics command to the MySQL Server
Returns a dictionary with various statistical information.
"""
cmd = self._cmd_simple(ServerCmd.STATISTICS)
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
raise
p = Packet(buf)
info = str(p.data)
res = {}
pairs = info.split('\x20\x20') # Information is separated by 2 spaces
for pair in pairs:
(lbl,val) = [ v.strip() for v in pair.split(':') ]
# It's either an integer or a decimal
try:
res[lbl] = long(val)
except:
try:
res[lbl] = Decimal(val)
except:
raise ValueError(
"Got wrong value in COM_STATISTICS information (%s : %s)." % (lbl, val))
return res
def cmd_process_info(self):
"""Gets the process list from the MySQL Server.
Returns a list of dictionaries which corresponds to the output of
SHOW PROCESSLIST of MySQL. The data is converted to Python types.
"""
raise errors.NotSupportedError(
"Not implemented. Use a cursor to get processlist information.")
def cmd_process_kill(self, mypid):
"""Kills a MySQL process using it's ID.
The mypid must be an integer.
"""
cmd = KillPacket(mypid)
cmd.create()
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
raise
if self.is_eof(buf):
return True
return False
def cmd_debug(self):
"""Send DEBUG command to the MySQL Server
Needs SUPER privileges. The output will go to the MySQL server error log.
Returns True when it was succesful.
"""
cmd = self._cmd_simple(ServerCmd.DEBUG)
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
raise
if self.is_eof(buf):
return True
return False
def cmd_ping(self):
"""
Ping the MySQL server to check if the connection is still alive.
Returns True when alive, False when server doesn't respond.
"""
cmd = self._cmd_simple(ServerCmd.PING)
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
return False
else:
if self.is_ok(buf):
return True
return False
def cmd_change_user(self, username, password, database=None):
"""Change the user with given username and password to another optional database.
"""
if not database:
database = self.database
cmd = ChangeUserPacket()
cmd.create(username=username, password=password, database=database,
charset=self.charset, seed=self.handshake.info['seed'])
try:
self.conn.send(cmd.get())
buf = self.conn.recv()[0]
except:
raise
if not self.is_ok(buf):
raise errors.OperationalError(
"Failed getting OK Packet after changing user")
return True
class BasePacket(object):
def __len__(self):
try:
return len(self.data)
except:
return 0
def is_valid(self, buf=None):
if buf is None:
buf = self.data
(l, n) = (buf[0:3], buf[3])
hlength = utils.int3read(l)
rlength = len(buf) - 4
if hlength != rlength:
return False
res = self._is_valid_extra(buf)
if res != None:
return res
return True
def _is_valid_extra(self, buf):
return True
class PacketIn(BasePacket):
def __init__(self, buf=None, pktnr=0):
self.data = ''
self.pktnr = pktnr
self.protocol = 10
if buf:
self.is_valid(buf)
self.data = buf[4:]
if self.data:
self._parse()
def _parse(self):
pass
class PacketOut(BasePacket):
"""
Each packet type used in the MySQL Client Protocol is build on the Packet
class. It defines lots of useful functions for parsing and sending
data to and from the MySQL Server.
"""
def __init__(self, buf=None, pktnr=0):
self.data = ''
self.pktnr = pktnr
self.protocol = 10
if buf:
self.set(buf)
if self.data:
self._parse()
def _make_header(self):
h = utils.int3store(len(self)) + utils.int1store(self.pktnr)
return h
def _parse(self):
pass
def add(self, s):
if not s:
self.add_null()
else:
self.data = self.data + s
def add_1_int(self, i):
self.add(utils.int1store(i))
def add_2_int(self, i):
self.add(utils.int2store(i))
def add_3_int(self, i):
self.add(utils.int3store(i))
def add_4_int(self, i):
self.add(utils.int4store(i))
def add_null(self, nr=1):
self.add('\x00'*nr)
def get(self):
return self._make_header() + self.data
def get_header(self):
return self._make_header()
def set(self, buf):
if not self.is_valid(buf):
raise errors.InterfaceError('Packet not valid.')
self.data = buf[4:]
def _is_valid_extra(self, buf=None):
return None
class Handshake(PacketIn):
def __init__(self, buf=None):
PacketIn.__init__(self, buf)
def _parse(self):
version = ''
options = 0
srvstatus = 0
buf = self.data
(buf,self.protocol) = utils.read_int(buf,1)
(buf,version) = utils.read_string(buf,end='\x00')
(buf,thrdid) = utils.read_int(buf,4)
(buf,scramble) = utils.read_bytes(buf, 8)
buf = buf[1:] # Filler 1 * \x00
(buf,srvcap) = utils.read_int(buf,2)
(buf,charset) = utils.read_int(buf,1)
(buf,serverstatus) = utils.read_int(buf,2)
buf = buf[13:] # Filler 13 * \x00
(buf,scramble_next) = utils.read_bytes(buf,12)
scramble += scramble_next
self.info = {
'version' : version,
'thrdid' : thrdid,
'seed' : scramble,
'capabilities' : srvcap,
'charset' : charset,
'serverstatus' : serverstatus,
}
def get_dict(self):
self._parse()
return self.info
def _is_valid_extra(self, buf):
if buf[3] != '\x00':
return False
return True
class Auth(PacketOut):
def __init__(self, packet=None, client_flags=0, pktnr=0):
PacketOut.__init__(self, packet, pktnr)
self.client_flags = 0
self.username = None
self.password = None
self.database = None
if client_flags:
self.set_client_flags(client_flags)
def set_client_flags(self, flags):
self.client_flags = flags
def set_login(self, username, password, database=None):
self.username = username
self.password = password
self.database = database
def scramble(self, passwd, seed):
hash4 = None
try:
hash1 = sha1(passwd).digest()
hash2 = sha1(hash1).digest() # Password as found in mysql.user()
hash3 = sha1(seed + hash2).digest()
xored = [ utils.int1read(h1) ^ utils.int1read(h3)
for (h1,h3) in zip(hash1, hash3) ]
hash4 = struct.pack('20B', *xored)
except StandardError, e:
raise errors.ProgrammingError('Failed scrambling password; %s' % e)
else:
return hash4
return None
def create(self, username=None, password=None, database=None, seed=None):
self.add_4_int(self.client_flags)
self.add_4_int(10 * 1024 * 1024)
self.add_1_int(8)
self.add_null(23)
self.add(username + '\x00')
if password:
self.add_1_int(20)
self.add(self.scramble(password,seed))
else:
self.add_null(1)
if database:
self.add(database + '\x00')
else:
self.add_null()
class ChangeUserPacket(Auth):
def __init__(self):
self.command = ServerCmd.CHANGE_USER
Auth.__init__(self)
def create(self, username=None, password=None, database=None, charset=8, seed=None):
self.add_1_int(self.command)
self.add(username + '\x00')
if password:
self.add_1_int(20)
self.add(self.scramble(password,seed))
else:
self.add_null(1)
if database:
self.add(database + '\x00')
else:
self.add_null()
self.add_2_int(charset)
class ErrorResultPacket(PacketIn):
def __init__(self, buf=None):
self.errno = 0
self.errmsg = ''
self.sqlstate = None
PacketIn.__init__(self, buf)
def _parse(self):
buf = self.data
if buf[0] != '\xff':
raise errors.InterfaceError('Expected an Error Packet.')
buf = buf[1:]
(buf,self.errno) = utils.read_int(buf, 2)
if buf[0] != '\x23':
# Error without SQLState
self.errmsg = buf
else:
(buf,self.sqlstate) = utils.read_bytes(buf[1:],5)
self.errmsg = buf
class OKResultPacket(PacketIn):
def __init__(self, buf=None):
self.affected_rows = None
self.insert_id = None
self.server_status = 0
self.warning_count = 0
self.field_count = 0
self.info_msg = ''
PacketIn.__init__(self, buf)
def __str__(self):
if self.affected_rows == 1:
lbl_rows = 'row'
else:
lbl_rows = 'rows'
xtr = []
if self.insert_id:
xtr.append('last insert: %d ' % self.insert_id)
if self.warning_count:
xtr.append('warnings: %d' % self.warning_count)
return "Query OK, %d %s affected %s( sec)" % (self.affected_rows,
lbl_rows, ', '.join(xtr))
def _parse(self):
buf = self.data
(buf,self.field_count) = utils.read_int(buf,1)
(buf,self.affected_rows) = utils.read_lc_int(buf)
(buf,self.insert_id) = utils.read_lc_int(buf)
(buf,self.server_status) = utils.read_int(buf,2)
(buf,self.warning_count) = utils.read_int(buf,2)
if buf:
(buf,self.info_msg) = utils.read_lc_string(buf)
class CommandPacket(PacketOut):
def __init__(self, cmd=None, arg=None):
self.command = cmd
self.argument = arg
PacketOut.__init__(self)
def create(self):
self.add_1_int(self.command)
self.add(str(self.argument))
def set_command(self, cmd):
self.command = cmd
def set_argument(self, arg):
self.argument = arg
class KillPacket(CommandPacket):
def __init__(self, arg):
CommandPacket.__init__(self)
self.set_command(ServerCmd.PROCESS_KILL)
self.set_argument(arg)
def create(self):
""""""
self.add_1_int(self.command)
self.add_4_int(self.argument)
def set_argument(self, arg):
if arg and not isinstance(int, long) and arg > 2**32:
raise ValueError, "KillPacket needs integer value as argument not larger than 2^32."
self.argument = arg
class FieldPacket(PacketIn):
def __init__(self, buf=None):
self.catalog = None
self.db = None
self.table = None
self.org_table = None
self.name = None
self.length = None
self.org_name = None
self.charset = None
self.type = None
self.flags = None
PacketIn.__init__(self, buf)
def __str__(self):
flags = []
for k,f in FieldFlag.desc.items():
if int(self.flags) & f[0]:
flags.append(k)
return """
Field: catalog: %s ; db:%s ; table:%s ; org_table: %s ;
name: %s ; org_name: %s ;
charset: %s ; lenght: %s ;
type: %02x ;
flags(%d): %s;
""" % (self.catalog,self.db,self.table,self.org_table,self.name,self.org_name,
self.charset, len(self), self.type,
self.flags, '|'.join(flags))
def _parse(self):
buf = self.data
(buf,self.catalog) = utils.read_lc_string(buf)
(buf,self.db) = utils.read_lc_string(buf)
(buf,self.table) = utils.read_lc_string(buf)
(buf,self.org_table) = utils.read_lc_string(buf)
(buf,self.name) = utils.read_lc_string(buf)
(buf,self.org_name) = utils.read_lc_string(buf)
buf = buf[1:] # filler 1 * \x00
(buf,self.charset) = utils.read_int(buf, 2)
(buf,self.length) = utils.read_int(buf, 4)
(buf,self.type) = utils.read_int(buf, 1)
(buf,self.flags) = utils.read_int(buf, 2)
(buf,self.decimal) = utils.read_int(buf, 1)
buf = buf[2:] # filler 2 * \x00
def get_description(self):
"""Returns a description as a list useful for cursors.
This function returns a list as defined in the Python Db API v2.0
specification.
"""
return (
self.name,
self.type,
None, # display_size
None, # internal_size
None, # precision
None, # scale
~self.flags & FieldFlag.NOT_NULL, # null_ok
self.flags, # MySQL specific
)
class EOFPacket(PacketIn):
def __init__(self, buf=None):
self.warning_count = None
self.status_flag = None
PacketIn.__init__(self, buf)
def __str__(self):
return "EOFPacket: warnings %d / status: %d" % (self.warning_count,self.status_flag)
def _is_valid_extra(self, buf=None):
if not buf:
buf = self.data
else:
buf = buf[4:]
if buf[0] == '\xfe' and len(buf) == 5:
# An EOF should always start with \xfe and smaller than 9 bytes
return True
return False
def _parse(self):
buf = self.data
buf = buf[1:] # disregard the first checking byte
(buf, self.warning_count) = utils.read_int(buf, 2)
(buf, self.status_flag) = utils.read_int(buf, 2)

392
mysql/connector/utils.py Normal file
View File

@ -0,0 +1,392 @@
# MySQL Connector/Python - MySQL driver written in Python.
# Copyright 2009 Sun Microsystems, Inc. All rights reserved
# Use is subject to license terms. (See COPYING)
# 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.
#
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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
"""Utilities
"""
__MYSQL_DEBUG__ = False
import struct
def int1read(c):
"""
Takes a bytes and returns it was an integer.
Returns integer.
"""
if isinstance(c,int):
if c < 0 or c > 254:
raise ValueError('excepts int 0 <= x <= 254')
return c
elif len(c) > 1:
raise ValueError('excepts 1 byte long bytes-object or int')
return int('%02x' % ord(c),16)
def int2read(s):
"""
Takes a string of 2 bytes and unpacks it as unsigned integer.
Returns integer.
"""
if len(s) > 2:
raise ValueError('int2read require s length of maximum 3 bytes')
elif len(s) < 2:
s = s + '\x00'
return struct.unpack('<H', s)[0]
def int3read(s):
"""
Takes a string of 3 bytes and unpacks it as integer.
Returns integer.
"""
if len(s) > 3:
raise ValueError('int3read require s length of maximum 3 bytes')
elif len(s) < 4:
s = s + '\x00'*(4-len(s))
return struct.unpack('<I', s)[0]
def int4read(s):
"""
Takes a string of 4 bytes and unpacks it as integer.
Returns integer.
"""
if len(s) > 4:
raise ValueError('int4read require s length of maximum 4 bytes')
elif len(s) < 4:
s = s + '\x00'*(4-len(s))
return struct.unpack('<I', s)[0]
def int8read(s):
"""
Takes a string of 8 bytes and unpacks it as integer.
Returns integer.
"""
if len(s) > 8:
raise ValueError('int4read require s length of maximum 8 bytes')
elif len(s) < 8:
s = s + '\x00'*(8-len(s))
return struct.unpack('<Q', s)[0]
def intread(s):
"""
Takes a string and unpacks it as an integer.
This function uses int1read, int2read, int3read and int4read by
checking the length of the given string.
Returns integer.
"""
l = len(s)
if l < 1 or l > 4:
raise ValueError('intread expects a string not longer than 4 bytes')
if not isinstance(s, str):
raise ValueError('intread expects a string')
fs = {
1 : int1read,
2 : int2read,
3 : int3read,
4 : int4read,
8 : int8read,
}
return fs[l](s)
def int1store(i):
"""
Takes an unsigned byte (1 byte) and packs it as string.
Returns string.
"""
if i < 0 or i > 255:
raise ValueError('int1store requires 0 <= i <= 255')
else:
return struct.pack('<B',i)
def int2store(i):
"""
Takes an unsigned short (2 bytes) and packs it as string.
Returns string.
"""
if i < 0 or i > 65535:
raise ValueError('int2store requires 0 <= i <= 65535')
else:
return struct.pack('<H',i)
def int3store(i):
"""
Takes an unsigned integer (3 bytes) and packs it as string.
Returns string.
"""
if i < 0 or i > 16777215:
raise ValueError('int3store requires 0 <= i <= 16777215')
else:
return struct.pack('<I',i)[0:3]
def int4store(i):
"""
Takes an unsigned integer (4 bytes) and packs it as string.
Returns string.
"""
if i < 0 or i > 4294967295L:
raise ValueError('int4store requires 0 <= i <= 4294967295')
else:
return struct.pack('<I',i)
def intstore(i):
"""
Takes an unsigned integers and packs it as a string.
This function uses int1store, int2store, int3store and
int4store depending on the integer value.
returns string.
"""
if i < 0 or i > 4294967295L:
raise ValueError('intstore requires 0 <= i <= 4294967295')
if i <= 255:
fs = int1store
elif i <= 65535:
fs = int2store
elif i <= 16777215:
fs = int3store
else:
fs = int4store
return fs(i)
def read_bytes(buf, size):
"""
Reads bytes from a buffer.
Returns a tuple with buffer less the read bytes, and the bytes.
"""
s = buf[0:size]
return (buf[size:], s)
def read_lc_string(buf):
"""
Takes a buffer and reads a length coded string from the start.
This is how Length coded strings work
If the string is 250 bytes long or smaller, then it looks like this:
<-- 1b -->
+----------+-------------------------
| length | a string goes here
+----------+-------------------------
If the string is bigger than 250, then it looks like this:
<- 1b -><- 2/3/4 ->
+------+-----------+-------------------------
| type | length | a string goes here
+------+-----------+-------------------------
if type == \xfc:
length is code in next 2 bytes
elif type == \xfd:
length is code in next 3 bytes
elif type == \xfe:
length is code in next 4 bytes
NULL has a special value. If the buffer starts with \xfb then
it's a NULL and we return None as value.
Returns a tuple (trucated buffer, string).
"""
if buf[0] == '\xfb':
# NULL value
return (buf[1:], None)
l = lsize = start = 0
fst = buf[0]
# Remove the type byte, we got the length information.
buf = buf[1:]
if fst <= '\xFA':
# Returns result right away.
l = ord(fst)
s = buf[:l]
return (buf[l:], s)
elif fst == '\xFC':
lsize = 2
elif fst == '\xFD':
lsize = 3
elif fst == '\xFE':
lsize = 4
l = intread(buf[0:lsize])
# Chop of the bytes which hold the length
buf = buf[lsize:]
# Get the actual string
s = buf[0:l]
# Set the buffer so we can return it
buf = buf[l:]
return (buf, s)
def read_lc_string_list(buf):
"""
Reads all length encoded strings from the given buffer.
This is exact same function as read_lc_string() but duplicated
in hopes for performance gain when reading results.
"""
strlst = []
while buf:
if buf[0] == '\xfb':
# NULL value
buf = buf[1:]
strlst.append(None)
continue
l = lsize = start = 0
fst = buf[0]
# Remove the type byte, we got the length information.
buf = buf[1:]
if fst <= '\xFA':
# Returns result right away.
l = ord(fst)
strlst.append(buf[:l])
buf = buf[l:]
continue
elif fst == '\xFC':
lsize = 2
elif fst == '\xFD':
lsize = 3
elif fst == '\xFE':
lsize = 4
l = intread(buf[0:lsize])
# Chop of the bytes which hold the length
buf = buf[lsize:]
# Get the actual string
s = buf[0:l]
# Set the buffer so we can return it
buf = buf[l:]
strlst.append(s)
return strlst
def read_string(buf, end=None, size=None):
"""
Reads a string up until a character or for a given size.
Returns a tuple (trucated buffer, string).
"""
if end is None and size is None:
raise ValueError('read_string() needs either end or size')
if end is not None:
try:
idx = buf.index(end)
except (ValueError), e:
raise ValueError("end byte not precent in buffer")
return (buf[idx+1:], buf[0:idx])
elif size is not None:
return read_bytes(buf,size)
raise ValueError('read_string() needs either end or size (weird)')
def read_int(buf, size):
"""
Take a buffer and reads an integer of a certain size (1 <= size <= 4).
Returns a tuple (truncated buffer, int)
"""
if len(buf) == 0:
raise ValueError("Empty buffer.")
if not isinstance(size,int) or (size not in [1,2,3,4,8]):
raise ValueError('size should be int in range of 1..4 or 8')
i = None
if size == 1:
i = int1read(buf[0])
elif size == 2:
i = int2read(buf[0:2])
elif size == 3:
i = int3read(buf[0:3])
elif size == 4:
i = int4read(buf[0:4])
elif size == 8:
i = int8read(buf[0:8])
else:
raise ValueError('size should be int in range of 1..4 or 8 (weird)')
return (buf[size:], int(i))
def read_lc_int(buf):
"""
Takes a buffer and reads an length code string from the start.
Returns a tuple with buffer less the integer and the integer read.
"""
if len(buf) == 0:
raise ValueError("Empty buffer.")
(buf,s) = read_int(buf,1)
if s == 251:
l = 0
return (buf,None)
elif s == 252:
(buf,i) = read_int(buf,2)
elif s == 253:
(buf,i) = read_int(buf,3)
elif s == 254:
(buf,i) = read_int(buf,8)
else:
i = s
return (buf, int(i))
#
# For debugging
#
def _dump_buffer(buf, label=None):
import __main__
if not __main__.__dict__.has_key('__MYSQL_DEBUG__'):
return
else:
debug = __main__.__dict__['__MYSQL_DEBUG__']
try:
if debug:
if len(buf) == 0:
print "%s : EMPTY BUFFER" % label
import string
print "%s: %s" % (label,string.join( [ "%02x" % ord(c) for c in buf ], ' '))
if debug > 1:
print "%s: %s" % (label,string.join( [ "%s" % chr(ord(c)) for c in buf ], ''))
except:
raise

793
mythboxee.py Normal file
View File

@ -0,0 +1,793 @@
import mc
import re
import sys
import random
import time
import pickle
import mythtv
from mythtv import MythError
from operator import itemgetter, attrgetter
class MythBoxee:
logLevel = 1
version = "4.0.1.beta"
userAgent = "MythBoxee"
tvdb_apikey = "6BEAB4CB5157AAE0"
be = None
db = None
recs = None
titles = []
recordings = []
banners = {}
shows = {}
series = {}
"""
__init__ - Lets get things rolling
"""
def __init__(self):
self.log("def(__init__): Start =========================================================")
self.log("def(__init__): MythBoxee Version: " + self.version)
self.log("def(__init__): Python Version: " + str(sys.version_info))
self.log()
self.config = mc.GetApp().GetLocalConfig()
# We'll use this to determine when to reload data.
self.config.SetValue("LastRunTime", str(time.time()))
self.config.SetValue("CurrentShowItemID", "0")
# If this is the first time the app is being run, lets set some default options.
if not self.config.GetValue("app.firstrun"):
self.config.SetValue("SortBy", "Original Air Date")
self.config.SetValue("SortDir", "Descending")
self.config.SetValue("Filter", "All")
self.config.SetValue("StreamMethod", "XML")
self.config.SetValue("app.firstrun", "true")
# If dbconn isn't set, we'll assume we haven't found the backend.
if not self.config.GetValue("dbconn"):
mc.ActivateWindow(14004)
else:
# Now that the backend has been discovered, lets connect.
try:
self.log("def(__init__): Attempting Database Connection ...")
self.dbconf = eval(self.config.GetValue("dbconn"))
self.db = mythtv.MythDB(**self.dbconf)
except MythError, e:
self.log("def(__init__): Error: " + e.message)
mc.ShowDialogNotification("Failed to connect to the MythTV Backend")
mc.ActivateWindow(14004)
else:
self.log("def(__init__): Database Connection Successful")
try:
self.log("def(__init__): Attempting Connection to Backend")
self.be = mythtv.MythBE(db=self.db)
except MythError, e:
self.log("def(__init__): Connection to Backend FAILED")
self.log("def(__init__): MythBE Error: " + e.message)
mc.CloseWindow()
else:
self.log("def(__init__): Connection to Backend Successful")
self.GetRecordings()
self.log("def(__init__): End ===========================================================")
"""
DiscoverBackend - just as it sounds
Attempt to discover the MythTV Backend using UPNP protocol, once found
try and gather MySQL database connection information using default PIN
via the XML interface. If that fails then prompt user to enter their
custom SecurityPin, if we fail to gather database information that way
finally prompt user to enter their credentials manually.
"""
def DiscoverBackend(self):
self.log("def(DiscoverBackend): Start =========================================================")
pin = self.config.GetValue("pin")
dbconn = self.config.GetValue("dbconn")
if not pin:
pin = 0000
try:
self.log("def(DiscoverBackend): Attempting Database Connection ...")
self.db = mythtv.MythDB(SecurityPin=pin)
except Exception, e:
self.log("def(DiscoverBackend): Exception: " + str(e.ename))
self.log("def(DiscoverBackend): End ===========================================================")
return False
else:
# We have a successful connection, save database information
self.config.SetValue("dbconn", str(self.db.dbconn))
self.log("def(DiscoverBackend): Database Connection Successful")
self.log("def(DiscoverBackend): End ===========================================================")
return True
"""
LoadMain - Loader for Main Window
"""
def LoadMain(self):
self.log("def(LoadMain): Start =========================================================")
self.config.SetValue("loadingmain", "true")
if not self.config.GetValue("dbconn"):
return False
## Put focus on last selected item
itemId = int(self.config.GetValue("CurrentShowItemID"))
if itemId and itemId != 0:
mc.GetWindow(14001).GetList(1030).SetFocusedItem(itemId)
cacheTime = self.config.GetValue("cache.time")
mainItems = len(mc.GetWindow(14001).GetList(1030).GetItems())
if not cacheTime or mainItems == 0 or cacheTime <= str(time.time() - 2400):
self.SetShows()
self.config.Reset("loadingmain")
self.log("def(LoadMain): End ===========================================================")
"""
RefreshMain - Refresh the Main Window
"""
def RefreshMain(self):
self.log("def(RefreshMain): Start =========================================================")
self.config.SetValue("loadingmain", "true")
self.config.Reset("cache.time")
self.GetRecordings()
self.SetShows()
self.config.Reset("loadingmain")
self.log("def(RefreshMain): End ===========================================================")
"""
GetRecordings - Pulls all of the recordings out of the backend and prepares them for use in the app.
This function also creates some dictionarys and lists of information
that is used throughout the app for different functions.
"""
def GetRecordings(self):
self.log("def(GetRecordings): Start =========================================================")
self.config.SetValue("loadingmain", "true")
# Empty out crucial info
self.titles = []
self.banners = {}
self.series = {}
self.shows = {}
cacheTime = self.config.GetValue("cache.time")
mainItems = len(mc.GetWindow(14001).GetList(1030).GetItems())
self.recs = self.be.getRecordings()
self.banners = pickle.loads(self.config.GetValue("cache.banners"))
self.series = pickle.loads(self.config.GetValue("cache.series"))
if not cacheTime or mainItems == 0 or cacheTime <= str(time.time() - 2400):
self.log("def(GetRecordings): Cached Expired, Processing Recordings")
x=0
for recording in self.recs:
if recording.title not in self.titles:
self.titles.append(str(recording.title))
self.shows[str(recording.title)] = []
if recording.title not in self.banners:
self.banners[str(recording.title)] = self.GetRecordingArtwork(str(recording.title))
if recording.title not in self.series:
self.series[str(recording.title)] = self.GetRecordingSeriesID(str(recording.title))
single = [str(recording.title), str(recording.subtitle), str(recording.description), str(recording.chanid), str(recording.airdate), str(recording.starttime), str(recording.endtime), recording.getRecorded().watched, x]
self.shows[str(recording.title)].append(single)
x = x + 1
# Lets cache our findings for now.
self.config.SetValue("cache.time", str(time.time()))
self.config.SetValue("cache.titles", pickle.dumps(self.titles))
self.config.SetValue("cache.banners", pickle.dumps(self.banners))
self.config.SetValue("cache.series", pickle.dumps(self.series))
self.config.SetValue("cache.shows", pickle.dumps(self.shows))
else:
self.log("def(GetRecordings): Cache OK, Retrieving from Cache")
self.titles = pickle.loads(self.config.GetValue("cache.titles"))
self.banners = pickle.loads(self.config.GetValue("cache.banners"))
self.series = pickle.loads(self.config.GetValue("cache.series"))
self.shows = pickle.loads(self.config.GetValue("cache.shows"))
self.titles.sort()
self.config.Reset("loadingmain")
self.log("def(GetRecordings): End ===========================================================")
"""
SetShows - Populate the Shows List on the Main Window
"""
def SetShows(self):
self.log("def(SetShows): Start =========================================================")
items = mc.ListItems()
for title in self.titles:
item = mc.ListItem( mc.ListItem.MEDIA_UNKNOWN )
item.SetLabel(str(title))
item.SetThumbnail(self.banners[title])
item.SetProperty("videos", str(len(self.shows[title])))
item.SetProperty("seriesid", str(self.series[title]))
items.append(item)
mc.GetWindow(14001).GetList(1030).SetItems(items)
self.log("def(SetShows): End ===========================================================")
"""
GetRecordingArtwork - Get the Artwork for a show.
"""
def GetRecordingArtwork(self, title):
self.log("def(GetRecordingArtwork): Start =========================================================")
sg = mc.Http()
sg.SetUserAgent(self.userAgent + " " + self.version)
html = sg.Get("http://www.thetvdb.com/api/GetSeries.php?seriesname=" + str(title.replace(" ", "%20")))
banners = re.compile("<banner>(.*?)</banner>").findall(html)
## Sometimes we can't find the show, so we have to provide our own artwork
try:
artwork = "http://www.thetvdb.com/banners/" + banners[0]
except:
artwork = "mb_artwork_error.png"
self.log("def(GetRecordingArtwork): URL: " + str(artwork))
self.log("def(GetRecordingArtwork): End =========================================================")
return artwork
"""
GetRecordingSeriesID - Get the Series ID of a show.
TODO: rewrite this entire function
"""
def GetRecordingSeriesID(self, title):
self.log("def(GetRecordingSeriesID): Start =========================================================")
sg = mc.Http()
sg.SetUserAgent(self.userAgent + " " + self.version)
html = sg.Get("http://www.thetvdb.com/api/GetSeries.php?seriesname=" + title.replace(" ", "%20"))
series = re.compile("<seriesid>(.*?)</seriesid>").findall(html)
## Sometimes we can't determine the series ID
try:
seriesid = series[0]
except:
seriesid = 00000
self.log("def(GetRecordingSeriesID): Title: " + title)
self.log("def(GetRecordingSeriesID): SeriesID: " + str(seriesid))
self.log("def(GetRecordingSeriesID): End ===========================================================")
return seriesid
"""
DisplayShow
TODO: rewrite this entire function
"""
def DisplayShow(self):
self.log("def(DisplaySingleShow): Start =========================================================")
recordingList = mc.GetWindow(14001).GetList(1030)
itemId = recordingList.GetFocusedItem()
item = recordingList.GetItem(itemId)
title = item.GetLabel()
# Save the Latest Show Title to what was clicked
# this way the show window has a way to load the data.
self.config.SetValue("CurrentShowItemID", str(itemId))
self.config.SetValue("CurrentShowTitle", title)
self.config.SetValue("CurrentShowID", item.GetProperty("seriesid"))
# Show the Single Show Window
mc.ActivateWindow(14002)
itemList = mc.ListItems()
itemList.append(item)
mc.GetWindow(14002).GetList(2070).SetItems(itemList)
self.log("def(DisplaySingleShow): Title[" + title + "]")
self.log("def(DisplaySingleShow): Current Show Title: " + title)
self.log("def(DisplaySingleShow): Current Show Item ID: " + str(itemId))
self.log("def(DisplaySingleShow): Current Show Series ID: " + item.GetProperty("seriesid"))
self.log("def(DisplaySingleShow): End ===========================================================")
"""
LoadShow - init function for Show Window
"""
def LoadShow(self):
self.log("def(LoadShow): Start =========================================================")
self.config.SetValue("loading", "true")
## Get Current Show Information
title = self.config.GetValue("CurrentShowTitle")
seriesid = self.config.GetValue("CurrentShowID")
self.log("def(LoadSingleShow): Title[" + title + "]")
## Setup the Show Window and Populate the Window's Lists
self.SetSortableOptions()
self.SetSeriesDetails(title, seriesid)
self.LoadShowRecordings(title)
self.config.Reset("loading")
self.log("def(LoadShow): End ===========================================================")
"""
LoadShowRecordings
Determine which show is being displayed and find all the recordings for it.
Then populate the recording list for the singular show for viewer to watch.
"""
def LoadShowRecordings(self, title):
self.log("def(LoadShowRecordings): Start =========================================================")
dbconf = eval(self.config.GetValue("dbconn"))
## Get current sort and filter settings
sortBy = self.config.GetValue("SortBy")
sortDir = self.config.GetValue("SortDir")
theFilter = self.config.GetValue("Filter")
self.log("def(LoadShowRecordings): Sort By: " + sortBy)
self.log("def(LoadShowRecordings): Sort Dir: " + sortDir)
self.log("def(LoadShowRecordings): Filter: " + theFilter)
## Clear the episodes container
episodes = None
## Sort based on Sorting Criteria
if sortBy == "Original Air Date" and sortDir == "Ascending":
episodes = sorted(self.shows[title], key=itemgetter(4))
elif sortBy == "Original Air Date" and sortDir == "Descending":
episodes = sorted(self.shows[title], key=itemgetter(4), reverse=True)
elif sortBy == "Recorded Date" and sortDir == "Ascending":
episodes = sorted(self.shows[title], key=itemgetter(5))
elif sortBy == "Recorded Date" and sortDir == "Descending":
episodes = sorted(self.shows[title], key=itemgetter(5), reverse=True)
elif sortBy == "Title" and sortDir == "Ascending":
episodes = sorted(self.shows[title], key=itemgetter(1))
elif sortBy == "Title" and sortDir == "Descending":
episodes = sorted(self.shows[title], key=itemgetter(1), reverse=True)
else:
episodes = self.shows[title]
## Loop through all our recordings, and add them to the list.
showitems = mc.ListItems()
for title,subtitle,desc,chanid,airdate,starttime,endtime,watched,ref in episodes:
#recording = self.recs[ref]
# Filter the Episodes
if theFilter == "Watched" and watched == 0:
continue
elif theFilter == "Unwatched" and watched == 1:
continue
# Create the Item list and populate it
showitem = mc.ListItem( mc.ListItem.MEDIA_VIDEO_EPISODE )
showitem.SetLabel(subtitle)
showitem.SetTitle(subtitle)
showitem.SetTVShowTitle(title)
showitem.SetDescription(desc)
showitem.SetProperty("starttime", starttime)
#showitem.SetProperty("ref", ref)
## Sometimes dates aren't set, so generate one if not.
try:
date = airdate.split("-")
showitem.SetDate(int(date[0]), int(date[1]), int(date[2]))
except:
showitem.SetDate(2010, 01, 01)
# Determine Stream Method, Generate Proper Path
streamMethod = self.config.GetValue("StreamMethod")
if streamMethod == "XML":
time = starttime.replace("T", "%20")
path = "http://" + self.dbconf['DBHostName'] + ":6544/Myth/GetRecording?ChanId=" + chanid + "&StartTime=" + time
showitem.SetThumbnail("http://" + self.dbconf['DBHostName'] + ":6544/Myth/GetPreviewImage?ChanId=" + chanid + "&StartTime=" + starttime.replace("T", "%20"))
showitem.SetPath(path)
elif streamMethod == "SMB":
time = starttime.replace("T", "").replace("-", "").replace(":", "").replace(" ","")
path = "smb://" + self.config.GetValue("smb.username") + ":" + self.config.GetValue("smb.password") + "@" + self.dbconf["DBHostName"] + "/" + self.config.GetValue("smb.share") + "/" + chanid + "_" + time + ".mpg"
showitem.SetPath(path)
#showitem.AddAlternativePath("XML Source", "http://" + self.dbconf['DBHostName'] + ":6544/Myth/GetRecording?ChanId=" + chanid + "&StartTime=" + starttime.replace("T", "%20"), )
self.log("def(LoadShowRecordings): Thumbnail: " + showitem.GetThumbnail())
self.log("def(LoadShowRecordings): Path: " + showitem.GetPath())
showitems.append(showitem)
mc.GetWindow(14002).GetList(2040).SetItems(showitems)
self.log("def(LoadShowRecordings): End ===========================================================")
"""
SetSortableOptions - Setup the show options; sort by, sort direction, watched vs unwatched
"""
def SetSortableOptions(self):
self.log("def(SetSortableOptions): Start =========================================================")
## Setup Sortable Field Options
sortable = ['Original Air Date', 'Recorded Date', 'Title']
items = mc.ListItems()
for sorttype in sortable:
item = mc.ListItem( mc.ListItem.MEDIA_UNKNOWN )
item.SetLabel(sorttype)
items.append(item)
mc.GetWindow(14002).GetList(2051).SetItems(mc.ListItems())
mc.GetWindow(14002).GetList(2051).SetItems(items)
## Setup Sortable Direction Options
sortableby = ['Ascending', 'Descending']
items = mc.ListItems()
for sorttype in sortableby:
item = mc.ListItem( mc.ListItem.MEDIA_UNKNOWN )
item.SetLabel(sorttype)
items.append(item)
mc.GetWindow(14002).GetList(2061).SetItems(mc.ListItems())
mc.GetWindow(14002).GetList(2061).SetItems(items)
## Setup Filter Options
filters = ['All', 'Watched', 'Unwatched']
items = mc.ListItems()
for single_filter in filters:
item = mc.ListItem( mc.ListItem.MEDIA_UNKNOWN )
item.SetLabel(single_filter)
items.append(item)
mc.GetWindow(14002).GetList(2082).SetItems(mc.ListItems())
mc.GetWindow(14002).GetList(2082).SetItems(items)
## Set the currently selected option in each category
mc.GetWindow(14002).GetList(2051).SetSelected(sortable.index(self.config.GetValue("SortBy")), True)
mc.GetWindow(14002).GetList(2061).SetSelected(sortableby.index(self.config.GetValue("SortDir")), True)
mc.GetWindow(14002).GetList(2082).SetSelected(filters.index(self.config.GetValue("Filter")), True)
self.log("def(SetSortableOptions): End ===========================================================")
"""
SetSeriesDetails - Get Show Series Information
TODO -- rewrite this entire function
"""
def SetSeriesDetails(self, title, seriesid):
self.log("def(SetSeriesDetails): Start =========================================================")
sg = mc.Http()
sg.SetUserAgent(self.userAgent + " " + self.version)
html = sg.Get("http://thetvdb.com/api/" + self.tvdb_apikey + "/series/" + seriesid + "/")
overview = re.compile("<Overview>(.*?)</Overview>").findall(html)
poster = re.compile("<poster>(.*?)</poster>").findall(html)
items = mc.ListItems()
item = mc.ListItem( mc.ListItem.MEDIA_UNKNOWN )
item.SetLabel(title)
item.SetTitle(title)
try:
item.SetDescription(overview[0])
item.SetProperty("description", overview[0])
except:
item.SetDescription("No Description")
item.SetProperty("description", "No Description")
try:
item.SetThumbnail("http://www.thetvdb.com/banners/" + poster[0])
except:
item.SetThumbnail("mb_poster_error.png")
items.append(item)
mc.GetWindow(14002).GetList(2070).SetItems(items)
self.log("def(SetSeriesDetails): End ===========================================================")
"""
FilterEpisodes - Filter the list of Episodes (All, Watched, Unwatched)
"""
def FilterEpisodes(self):
self.log("def(FilterEpisodes): Start =========================================================")
## Figure out how we need to filter
FilterItems = FilterItemNumber = mc.GetWindow(14002).GetList(2082).GetSelected()
mc.GetWindow(14002).GetList(2082).UnselectAll()
mc.GetWindow(14002).GetList(2082).SetSelected(mc.GetWindow(14002).GetList(2082).GetFocusedItem(), True)
## Update the Filter Criteria
self.config.SetValue("Filter", mc.GetWindow(14002).GetList(2082).GetItem(mc.GetWindow(14002).GetList(2082).GetFocusedItem()).GetLabel())
## Now that the filter has changed, reload the list of episodes
self.LoadShowRecordings(self.config.GetValue("CurrentShowTitle"))
self.log("def(FilterEpisodes): End ===========================================================")
"""
SortBy - Set the Field that the List of Episodes is Sorted By
"""
def SortBy(self):
self.log("def(SortBy): Start =========================================================")
## Figure out how we need to Sort
sortByItems = sortByItemNumber = mc.GetWindow(14002).GetList(2051).GetSelected()
mc.GetWindow(14002).GetList(2051).UnselectAll()
mc.GetWindow(14002).GetList(2051).SetSelected(mc.GetWindow(14002).GetList(2051).GetFocusedItem(), True)
## Update the Sort Criteria
self.config.SetValue("SortBy", mc.GetWindow(14002).GetList(2051).GetItem(mc.GetWindow(14002).GetList(2051).GetFocusedItem()).GetLabel())
self.log("def(SortBy): Direction: " + self.config.GetValue("SortBy"))
## Now that we have updated the Sort Criteria, reload the shows
self.LoadShowRecordings(self.config.GetValue("CurrentShowTitle"))
self.log("def(SortBy): End ===========================================================")
"""
SortDir - Set the Direction that the List of Episodes is Sorted By
"""
def SortDir(self):
self.log("def(SortDir): Start =========================================================")
## Figure out how we need to Sort
sortDirectionItems = sortDirectionItemNumber = mc.GetWindow(14002).GetList(2061).GetSelected()
mc.GetWindow(14002).GetList(2061).UnselectAll()
mc.GetWindow(14002).GetList(2061).SetSelected(mc.GetWindow(14002).GetList(2061).GetFocusedItem(), True)
## Update the Sort Criteria
self.config.SetValue("SortDir", mc.GetWindow(14002).GetList(2061).GetItem(mc.GetWindow(14002).GetList(2061).GetFocusedItem()).GetLabel())
self.log("def(SortDir): Direction: " + self.config.GetValue("SortDir"))
## Now that we have updated the Sort Criteria, reload the shows
self.LoadShowRecordings(self.config.GetValue("CurrentShowTitle"))
self.log("def(SortDir): End =========================================================")
"""
LoadSettings - Stuff that needs to be executed when settings window is loaded
"""
def LoadSettings(self):
self.log("def(LoadSettings): Start =========================================================")
## Grab the current StreamMethod
streamMethod = self.config.GetValue("StreamMethod")
self.log("def(LoadSettings): Stream Method: " + streamMethod)
# Grab and Set the Database Information
if self.config.GetValue("dbconn"):
dbconf = eval(self.config.GetValue("dbconn"))
mc.GetWindow(14004).GetEdit(1042).SetText(dbconf['DBHostName'])
mc.GetWindow(14004).GetEdit(1043).SetText(dbconf['DBUserName'])
mc.GetWindow(14004).GetEdit(1044).SetText(dbconf['DBPassword'])
mc.GetWindow(14004).GetEdit(1045).SetText(dbconf['DBName'])
mc.GetWindow(14004).GetControl(1032).SetFocus()
else:
mc.GetWindow(14004).GetControl(1042).SetFocus()
# Setup Stream Methods for user to choose
methods = ['XML', 'SMB']
items = mc.ListItems()
for method in methods:
item = mc.ListItem( mc.ListItem.MEDIA_UNKNOWN )
item.SetLabel(method)
items.append(item)
mc.GetWindow(14004).GetList(1022).SetItems(items)
mc.GetWindow(14004).GetList(1022).SetSelected(methods.index(streamMethod), True)
# Depending on StreamMethod Enable Options
if not streamMethod or streamMethod == "XML":
mc.GetWindow(14004).GetControl(1032).SetEnabled(False)
mc.GetWindow(14004).GetControl(1033).SetEnabled(False)
mc.GetWindow(14004).GetControl(1034).SetEnabled(False)
else:
if not self.config.GetValue("smb.username"):
self.config.SetValue("smb.username", "guest")
if not self.config.GetValue("smb.password"):
self.config.SetValue("smb.password", "guest")
self.log("def(LoadSettings): smb.share: " + self.config.GetValue("smb.share"))
self.log("def(LoadSettings): smb.username: " + self.config.GetValue("smb.username"))
self.log("def(LoadSettings): smb.password: " + self.config.GetValue("smb.password"))
## Since the Stream Method is SMB enable controls for setting info
mc.GetWindow(14004).GetControl(1032).SetEnabled(True)
mc.GetWindow(14004).GetControl(1033).SetEnabled(True)
mc.GetWindow(14004).GetControl(1034).SetEnabled(True)
## Update the fields with current SMB settings.
mc.GetWindow(14004).GetEdit(1032).SetText(self.config.GetValue("smb.share"))
mc.GetWindow(14004).GetEdit(1033).SetText(self.config.GetValue("smb.username"))
mc.GetWindow(14004).GetEdit(1034).SetText(self.config.GetValue("smb.password"))
self.log("def(LoadSettings): End ===========================================================")
"""
SetStreamMethod - Change the Streaming Method
"""
def SetStreamMethod(self):
self.log("def(SetStreamMethod): Start =========================================================")
## Figure out what Stream Method the user has selected
streamMethodItems = streamMethodItemNumber = mc.GetWindow(14004).GetList(1022).GetSelected()
mc.GetWindow(14004).GetList(1022).UnselectAll()
mc.GetWindow(14004).GetList(1022).SetSelected(mc.GetWindow(14004).GetList(1022).GetFocusedItem(), True)
streamMethod = mc.GetWindow(14004).GetList(1022).GetItem(mc.GetWindow(14004).GetList(1022).GetFocusedItem()).GetLabel()
## Disabled some UI pieces depending on stream method
if not streamMethod or streamMethod == "XML":
mc.GetWindow(14004).GetControl(1032).SetEnabled(False)
mc.GetWindow(14004).GetControl(1033).SetEnabled(False)
mc.GetWindow(14004).GetControl(1034).SetEnabled(False)
else:
mc.GetWindow(14004).GetControl(1032).SetEnabled(True)
mc.GetWindow(14004).GetControl(1033).SetEnabled(True)
mc.GetWindow(14004).GetControl(1034).SetEnabled(True)
## Save the Stream Method
self.config.SetValue("StreamMethod", streamMethod)
## Notify the User
mc.ShowDialogNotification("Stream Method Changed to " + streamMethod)
self.log("def(SetStreamMethod): Stream Method Changed to -- " + streamMethod)
self.log("def(SetStreamMethod): End =========================================================")
"""
SaveDbSettings - Save Database Settings
"""
def SaveDbSettings(self):
self.log("def(SaveDbSettings): Start =========================================================")
dbconf = {}
dbconf['DBHostName'] = mc.GetWindow(14004).GetEdit(1042).GetText()
dbconf['DBUserName'] = mc.GetWindow(14004).GetEdit(1043).GetText()
dbconf['DBPassword'] = mc.GetWindow(14004).GetEdit(1044).GetText()
dbconf['DBName'] = mc.GetWindow(14004).GetEdit(1045).GetText()
self.config.SetValue("dbconn", str(dbconf))
## Notify the user that the changes have been saved.
mc.ShowDialogNotification("Database Settings Saved")
self.log("def(SaveDbSettings): End ===========================================================")
"""
TestDbSettings - Test Database Settings
"""
def TestDbSettings(self):
self.log("def(TestDbSettings): Start =========================================================")
self.config.SetValue("loadingsettings", "true")
mc.GetWindow(14004).GetLabel(9002).SetLabel("Attempting Database Connection ...")
dbconf = {}
dbconf['DBHostName'] = mc.GetWindow(14004).GetEdit(1042).GetText()
dbconf['DBUserName'] = mc.GetWindow(14004).GetEdit(1043).GetText()
dbconf['DBPassword'] = mc.GetWindow(14004).GetEdit(1044).GetText()
dbconf['DBName'] = mc.GetWindow(14004).GetEdit(1045).GetText()
try:
self.log("def(TestDbSettings): Attempting Database Connection ...")
mythtv.MythDB(**dbconf)
except MythError, e:
self.log("def(TestDbSettings): Error: " + e.message)
mc.ShowDialogNotification("Failed to connect to the MythTV Backend")
else:
self.SaveDbSettings()
mc.ShowDialogNotification("Connection to MythTV Backend Success. Settings Saved.")
self.config.Reset("loadingsettings")
self.log("def(TestDbSettings): End ===========================================================")
"""
SaveSMBSettings - Saves the SMB settings the user inputted.
"""
def SaveSMBSettings(self):
self.log("def(SetStreamMethod): Start =========================================================")
## Save SMB settings the user inputted
self.config.SetValue("smb.share", mc.GetWindow(14004).GetEdit(1032).GetText())
self.config.SetValue("smb.username", mc.GetWindow(14004).GetEdit(1033).GetText())
self.config.SetValue("smb.password", mc.GetWindow(14004).GetEdit(1034).GetText())
## Notify the user that the changes have been saved.
mc.ShowDialogNotification("SMB Share Settings Saved")
self.log("def(SetStreamMethod): End ===========================================================")
"""
SettingsInit - Init function for Settings Window
"""
def SettingsInit(self):
self.log("def(SettingsInit): Start =========================================================")
self.config.SetValue("loadingsettings", "true")
if not self.config.GetValue("dbconn"):
mc.ShowDialogOk("MythBoxee", "Welcome to MythBoxee! Looks like this is the first time you have run this app. Please fill out all the settings and you'll be on your way to using this app.")
response = mc.ShowDialogConfirm("MythBoxee", "Do you know what your Security Pin is for your MythTV Backend? If not, we'll try the default, if that fails, you'll need to fill your database information in manually.", "No", "Yes")
if response:
pin = mc.ShowDialogKeyboard("Security Pin", "", True)
self.config.SetValue("pin", str(pin))
else:
pin = 0000
self.config.SetValue("pin", str(pin))
mc.GetWindow(14004).GetLabel(9002).SetLabel("Connecting to Backend ...")
if self.DiscoverBackend() == False:
mc.ShowDialogOk("MythBoxee", "Unfortunately MythBoxee wasn't able to auto-discover your MythTV backend and database credentials. Please enter them in manually.")
mc.GetWindow(14004).GetLabel(9002).SetLabel("LOADING...")
self.LoadSettings()
else:
mc.ShowDialogOk("MythBoxee", "MythBoxee auto-discovered your MythTV backend. Enjoy your recordings!")
self.config.Reset("app.lastruntime")
mc.CloseWindow()
else:
self.LoadSettings()
self.config.Reset("loadingsettings")
self.log("def(SettingsInit): End ===========================================================")
"""
StatusInit -- Function called when Status Window is opened.
This function pulls status information from the MythTV backend and displays it to the user.
Status information includes load, uptime, free space, upcoming recordings, guide data, etc ...
"""
def StatusInit(self):
self.log("def(StatusInit): Start =========================================================")
self.config.SetValue("loadingstatus", "true")
uptime = self.be.getUptime()
load = self.be.getLoad()
freespace = self.be.getFreeSpace()
guidedata = self.be.getLastGuideData()
isRecording = self.be.isRecording(1)
freespacesummary = self.be.getFreeSpaceSummary()
recorders = self.be.getRecorderList()
upcoming = self.be.getUpcomingRecordings()
self.log("def(StatusInit): Recorders: " + str(recorders))
self.log("def(StatusInit): Uptime: " + str(uptime))
self.log("def(StatusInit): Load: " + str(load))
self.log("def(StatusInit): Free Space: " + str(freespace))
self.log("def(StatusInit): Guide Data: " + str(guidedata))
self.log("def(StatusInit): Summary: " + str(freespacesummary))
self.log("def(StatusInit): Upcoming: " + str(upcoming))
self.log("def(StatusInit): Recording: " + str(isRecording))
self.config.Reset("loadingstatus")
self.log("def(StatusInit): End ===========================================================")
"""
log - logging function mainly for debugging
"""
def log(self, message):
if self.logLevel == 3:
mc.ShowDialogNotification(message)
if self.logLevel >= 2:
mc.LogDebug(">>> MythBoxee: " + message)
if self.logLevel == 1:
mc.LogInfo(">>> MythBoxee: " + message)
print ">>> MythBoxee: " + message

1872
mythtv/MythBase.py Executable file

File diff suppressed because it is too large Load Diff

1502
mythtv/MythData.py Executable file

File diff suppressed because it is too large Load Diff

1170
mythtv/MythFunc.py Executable file

File diff suppressed because it is too large Load Diff

13
mythtv/MythStatic.py Executable file
View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
"""
Contains any static and global variables for MythTV Python Bindings
"""
OWN_VERSION = (0,23,1,0)
SCHEMA_VERSION = 1254
MVSCHEMA_VERSION = 1032
NVSCHEMA_VERSION = 1004
PROTO_VERSION = 56
PROGRAM_FIELDS = 47
BACKEND_SEP = '[]:[]'

54
mythtv/__init__.py Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
__all__ = ['MythStatic', \
\
'DictData', 'DBData', 'DBDataWrite', 'DBDataCRef', 'MythDBConn', \
'MythBEConn', 'MythXMLConn', 'MythLog', 'MythError', \
'StorageGroup', 'Grabber', \
\
'ftopen', 'FileTransfer', 'FreeSpace', 'Program', 'Record', \
'Recorded', 'RecordedProgram', 'OldRecorded', 'Job', 'Channel', \
'Guide', 'Video', 'VideoGrabber', 'NetVisionRSSItem', \
'NetVisionTreeItem', 'NetVisionSite', 'NetVisionGrabber', \
\
'MythBE', 'Frontend', 'MythDB', 'MythVideo', 'MythXML']
import26 = """
import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from MythStatic import *
from MythBase import *
from MythData import *
from MythFunc import *
"""
import25 = """
from MythStatic import *
from MythBase import *
from MythData import *
from MythFunc import *
"""
from sys import version_info
if version_info >= (2, 6): # 2.6 or newer
exec(import26)
else:
exec(import25)
__version__ = OWN_VERSION
#MythStatic.mysqldb = MySQLdb.__version__
if __name__ == '__main__':
banner = 'MythTV Python interactive shell.'
import code
try:
import readline, rlcompleter
except:
pass
else:
readline.parse_and_bind("tab: complete")
banner += ' TAB completion available.'
namespace = globals().copy()
namespace.update(locals())
code.InteractiveConsole(namespace).interact(banner)

0
mythtv/tmdb/__init__.py Normal file
View File

1005
mythtv/tmdb/tmdb_api.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# ----------------------
# Name: tmdb_exceptions - Custom exceptions used or raised by tmdb_api
# Python Script
# Author: dbr/Ben modified by R.D. Vaughan
# Purpose: Custom exceptions used or raised by tmdb_api
#
# License:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
#-------------------------------------
__title__ ="tmdb_exceptions - Custom exceptions used or raised by tmdb_api";
__author__="dbr/Ben modified by R.D. Vaughan"
__version__="v0.1.4"
# 0.1.0 Initial development
# 0.1.1 Alpha Release
# 0.1.2 Release bump - no changes to this code
# 0.1.3 Release bump - no changes to this code
# 0.1.4 Release bump - no changes to this code
__all__ = ["TmdBaseError", "TmdHttpError", "TmdXmlError", "TmdbUiAbort", "TmdbMovieOrPersonNotFound", ]
# Start of code used to access themoviedb.org api
class TmdBaseError(Exception):
pass
class TmdHttpError(TmdBaseError):
def __repr__(self): # Display the type of error
return None
# end __repr__
class TmdXmlError(TmdBaseError):
def __repr__(self): # Display the type of error
return None
# end __repr__
class TmdbMovieOrPersonNotFound(TmdBaseError):
def __repr__(self):
return None
# end __repr__
class TmdbUiAbort(TmdBaseError):
def __repr__(self):
return None
# end __repr__

266
mythtv/tmdb/tmdb_ui.py Normal file
View File

@ -0,0 +1,266 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# ----------------------
# Name: tmdb_ui.py Is a simple console user interface for the tmdb_api. The interface is used selection
# of a movie from themoviesdb.org.
# Python Script
# Author: dbr/Ben modified by R.D. Vaughan
# Purpose: Console interface for selecting a movie from themoviedb.org. This interface would be invoked when
# an exact match is not found and the invoking script has specified the "interactive = True" when
# creating an instance of MovieDb().
#
# License:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
#-------------------------------------
__title__ ="tmdb_ui - Is a simple console user interface for the tmdb_api. The interface is used selection of a movie oe person from themoviesdb.org.";
__author__="dbr/Ben modified by R.D. Vaughan"
__purpose__='''Console interface for selecting a movie from themoviedb.org. This interface would be invoked when an exact match is not found and the invoking script has specified the "interactive = True" when creating an instance of MovieDb().
'''
__version__="v0.1.5"
# 0.1.0 Initial development
# 0.1.1 Alpha Release
# 0.1.2 Release bump - no changes to this code
# 0.1.3 Release bump - no changes to this code
# 0.1.4 Release bump - no changes to this code
# 0.1.5 Modified automated selection when there was only one search result. It was causing
# too many incorrect selections.
# Also removed a duplicate initialization of the version number.
"""Contains included user interfaces for tmdb movie/person selection.
A UI is a callback. A class, it's __init__ function takes two arguments:
- config, which is the tmdb config dict, setup in tmdb_api.py
- log, which is tmdb's logger instance (which uses the logging module). You can
call log.info() log.warning() etc
It pass a dictionary "allElements", this is passed a list of dicts, each dict
contains at least the keys "name" (human readable movie name), and "id" (the movies/person)
ID as on themoviedb.org). for movies only if the key 'released' is included the year will be added
to the movie title like 'Avatar (2009)' all other keys will be ignored.
For example:
[{'name': u'Avatar', 'id': u'19995', 'released': '2009-12-25'},
{'name': u'Avatar - Sequel', 'id': u'73181'}]
OR
[{'name': u'Tom Cruise', 'id': u'500'},
{'name': u'Cruise Moylan', 'id': u'77716'}]
The "selectMovieOrPerson" method must return the appropriate dict, or it can raise
TmdbUiAbort (if the selection is aborted), TmdbMovieOrPersonNotFound (if the movie
or person cannot be found).
A simple example callback, which returns a random movie:
>>> import random
>>> from tmdb_api_ui import BaseUI
>>> class RandomUI(BaseUI):
... def selectMovieOrPerson(self, allElements):
... import random
... return random.choice(allElements)
Then to use it..
>>> from tmdb_api import MovieDb
>>> t = MovieDb(custom_ui = RandomUI)
>>> random_matching_movie = t.searchTitle('Avatar')
>>> type(random_matching_movie)
[{"Avatar",'19995'}]
"""
import os, struct, sys, string
from tmdb_exceptions import TmdbUiAbort
class OutStreamEncoder(object):
"""Wraps a stream with an encoder"""
def __init__(self, outstream, encoding=None):
self.out = outstream
if not encoding:
self.encoding = sys.getfilesystemencoding()
else:
self.encoding = encoding
def write(self, obj):
"""Wraps the output stream, encoding Unicode strings with the specified encoding"""
if isinstance(obj, unicode):
try:
self.out.write(obj.encode(self.encoding))
except IOError:
pass
else:
try:
self.out.write(obj)
except IOError:
pass
def __getattr__(self, attr):
"""Delegate everything but write to the stream"""
return getattr(self.out, attr)
sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
# Two routines used for movie title search and matching
def is_punct_char(char):
'''check if char is punctuation char
return True if char is punctuation
return False if char is not punctuation
'''
return char in string.punctuation
def is_not_punct_char(char):
'''check if char is not punctuation char
return True if char is not punctuation
return False if chaar is punctuation
'''
return not is_punct_char(char)
def makeDict(movieORperson):
'''Make a dictionary out of the chosen movie data
return a dictionary of the movie and ancillary data
'''
selection = {}
for key in movieORperson.keys():
selection[key] = movieORperson[key]
return selection
# end makeDict()
class BaseUI:
"""Default non-interactive UI, which auto-selects first results
"""
def __init__(self, config, log, searchTerm=None):
self.config = config
self.log = log
self.searchTerm = searchTerm
def selectMovieOrPerson(self, allElements):
return makeDict([allElements[0]])
class ConsoleUI(BaseUI):
"""Interactively allows the user to select a movie or person from a console based UI
"""
def removeCommonWords(self, title):
'''Remove common words from a title
return title striped of common words
'''
if not title:
return u' '
wordList = [u'the ', u'a ', u' '] # common word list. Leave double space as the last value.
title = title.lower()
for word in wordList:
title = title.replace(word, u'')
if not title:
return u' '
title = title.strip()
return filter(is_not_punct_char, title)
# end removeCommonWords()
def _displayMovie(self, allElements):
"""Helper function, lists movies or people with corresponding ID
"""
print u"themoviedb.org Search Results:"
for i in range(len(allElements[:15])): # list first 15 search results
i_show = i + 1 # Start at more human readable number 1 (not 0)
self.log.debug('Showing allElements[%s] = %s)' % (i_show, allElements[i]))
if allElements[i]['name'] == u'User choses to ignore video':
print u"% 2s -> %s # %s" % (
i_show,
'99999999', "Set this video to be ignored by Jamu with a reference number of '99999999'"
)
continue
if not allElements[i].has_key('released'):
title = allElements[i]['name']
elif len(allElements[i]['released']) > 3:
title = u"%s (%s)" % (allElements[i]['name'], allElements[i]['released'][:4])
else:
title = allElements[i]['name']
if allElements[i]['url'].find('/person/') > -1:
format = u"%2s -> %-30s # http://www.themoviedb.org/person/%s"
else:
format = u"%2s -> %-50s # http://www.themoviedb.org/movie/%s"
print format % (
i_show,
title,
allElements[i]['id']
)
print u"Direct search of themoviedb.org # http://themoviedb.org/"
# end _displayMovie()
def selectMovieOrPerson(self, allElements):
if allElements[0]['url'].find('/movie/') > -1:
morp = u'movie'
else:
morp = u'person'
# Add the ability to select the skip inetref of '99999999' for movies only
if morp == u'movie':
allElements.append( {'id': '99999999', 'name': u'User choses to ignore video'} )
self._displayMovie(allElements)
refsize = 5 # The number of digits required in a TMDB number is directly entered
if len(allElements) == 2 and morp == u'movie':
data = makeDict(allElements[0])
if self.removeCommonWords(data['name']) == self.removeCommonWords(self.searchTerm) and data.has_key('released'):
# Single result, return it!
print u"Automatically selecting only result"
return [data]
if len(allElements) == 1 and morp == u'person':
data = makeDict(allElements[0])
if self.removeCommonWords(data['name']) == self.removeCommonWords(self.searchTerm):
# Single result, return it!
print u"Automatically selecting only result"
return [data]
if self.config['select_first'] is True:
print u"Automatically returning first search result"
return [makeDict(allElements[0])]
while True: # return breaks this loop
try:
print u'Enter choice:\n("Enter" key equals first selection (1)) or input a zero padded 5 digit %s TMDB id number, ? for help):' % morp
ans = raw_input()
except KeyboardInterrupt:
raise TmdbUiAbort("User aborted (^c keyboard interupt)")
except EOFError:
raise TmdbUiAbort("User aborted (EOF received)")
self.log.debug(u'Got choice of: %s' % (ans))
try:
if ans == '': # Enter pressed which equates to the first selection
selected_id = 0
else:
if int(ans) == 0:
raise ValueError
selected_id = int(ans) - 1 # The human entered 1 as first result, not zero
except ValueError: # Input was not number
if ans == "q":
self.log.debug(u'Got quit command (q)')
raise TmdbUiAbort("User aborted ('q' quit command)")
elif ans == "?":
print u"## Help"
print u"# Enter the number that corresponds to the correct movie."
print u"# Paste a TMDB %s ID number (pad with leading zeros to make 5 digits) from themoviedb.org and hit 'Enter'" % morp
print u"# ? - this help"
print u"# q - abort/skip movie selection"
else:
print '! Unknown/Invalid keypress "%s"\n' % (ans)
self.log.debug('Unknown keypress %s' % (ans))
else:
self.log.debug('Trying to return ID: %d' % (selected_id))
try:
data = makeDict(allElements[selected_id])
data['userResponse'] = u'User selected'
return [data]
except IndexError:
if len(ans) == refsize:
return [{'userResponse': u'User input', 'id': u'%d' % int(ans)}]
#end try
#end while not valid_input
# end selectMovieOrPerson()

0
mythtv/ttvdb/__init__.py Normal file
View File

232
mythtv/ttvdb/cache.py Normal file
View File

@ -0,0 +1,232 @@
#!/usr/bin/env python
#encoding:utf-8
#author:dbr/Ben
#project:tvdb_api
#repository:http://github.com/dbr/tvdb_api
#license:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
"""
urllib2 caching handler
Modified from http://code.activestate.com/recipes/491261/
"""
from __future__ import with_statement
__author__ = "dbr/Ben"
__version__ = "1.2.1"
import os
import time
import errno
import httplib
import urllib2
import StringIO
from hashlib import md5
from threading import RLock
cache_lock = RLock()
def locked_function(origfunc):
"""Decorator to execute function under lock"""
def wrapped(*args, **kwargs):
cache_lock.acquire()
try:
return origfunc(*args, **kwargs)
finally:
cache_lock.release()
return wrapped
def calculate_cache_path(cache_location, url):
"""Checks if [cache_location]/[hash_of_url].headers and .body exist
"""
thumb = md5(url).hexdigest()
header = os.path.join(cache_location, thumb + ".headers")
body = os.path.join(cache_location, thumb + ".body")
return header, body
def check_cache_time(path, max_age):
"""Checks if a file has been created/modified in the [last max_age] seconds.
False means the file is too old (or doesn't exist), True means it is
up-to-date and valid"""
if not os.path.isfile(path):
return False
cache_modified_time = os.stat(path).st_mtime
time_now = time.time()
if cache_modified_time < time_now - max_age:
# Cache is old
return False
else:
return True
@locked_function
def exists_in_cache(cache_location, url, max_age):
"""Returns if header AND body cache file exist (and are up-to-date)"""
hpath, bpath = calculate_cache_path(cache_location, url)
if os.path.exists(hpath) and os.path.exists(bpath):
return(
check_cache_time(hpath, max_age)
and check_cache_time(bpath, max_age)
)
else:
# File does not exist
return False
@locked_function
def store_in_cache(cache_location, url, response):
"""Tries to store response in cache."""
hpath, bpath = calculate_cache_path(cache_location, url)
try:
outf = open(hpath, "w")
headers = str(response.info())
outf.write(headers)
outf.close()
outf = open(bpath, "w")
outf.write(response.read())
outf.close()
except IOError:
return True
else:
return False
class CacheHandler(urllib2.BaseHandler):
"""Stores responses in a persistant on-disk cache.
If a subsequent GET request is made for the same URL, the stored
response is returned, saving time, resources and bandwidth
"""
@locked_function
def __init__(self, cache_location, max_age = 21600):
"""The location of the cache directory"""
self.max_age = max_age
self.cache_location = cache_location
if not os.path.exists(self.cache_location):
try:
os.mkdir(self.cache_location)
except OSError, e:
if e.errno == errno.EEXIST and os.path.isdir(self.cache_location):
# File exists, and it's a directory,
# another process beat us to creating this dir, that's OK.
pass
else:
# Our target dir is already a file, or different error,
# relay the error!
raise OSError(e)
def default_open(self, request):
"""Handles GET requests, if the response is cached it returns it
"""
if request.get_method() is not "GET":
return None # let the next handler try to handle the request
if exists_in_cache(
self.cache_location, request.get_full_url(), self.max_age
):
return CachedResponse(
self.cache_location,
request.get_full_url(),
set_cache_header = True
)
else:
return None
def http_response(self, request, response):
"""Gets a HTTP response, if it was a GET request and the status code
starts with 2 (200 OK etc) it caches it and returns a CachedResponse
"""
if (request.get_method() == "GET"
and str(response.code).startswith("2")
):
if 'x-local-cache' not in response.info():
# Response is not cached
set_cache_header = store_in_cache(
self.cache_location,
request.get_full_url(),
response
)
else:
set_cache_header = True
#end if x-cache in response
return CachedResponse(
self.cache_location,
request.get_full_url(),
set_cache_header = set_cache_header
)
else:
return response
class CachedResponse(StringIO.StringIO):
"""An urllib2.response-like object for cached responses.
To determine if a response is cached or coming directly from
the network, check the x-local-cache header rather than the object type.
"""
@locked_function
def __init__(self, cache_location, url, set_cache_header=True):
self.cache_location = cache_location
hpath, bpath = calculate_cache_path(cache_location, url)
StringIO.StringIO.__init__(self, file(bpath).read())
self.url = url
self.code = 200
self.msg = "OK"
headerbuf = file(hpath).read()
if set_cache_header:
headerbuf += "x-local-cache: %s\r\n" % (bpath)
self.headers = httplib.HTTPMessage(StringIO.StringIO(headerbuf))
def info(self):
"""Returns headers
"""
return self.headers
def geturl(self):
"""Returns original URL
"""
return self.url
@locked_function
def recache(self):
new_request = urllib2.urlopen(self.url)
set_cache_header = store_in_cache(
self.cache_location,
new_request.url,
new_request
)
CachedResponse.__init__(self, self.cache_location, self.url, True)
if __name__ == "__main__":
def main():
"""Quick test/example of CacheHandler"""
opener = urllib2.build_opener(CacheHandler("/tmp/"))
response = opener.open("http://google.com")
print response.headers
print "Response:", response.read()
response.recache()
print response.headers
print "After recache:", response.read()
# Test usage in threads
from threading import Thread
class CacheThreadTest(Thread):
lastdata = None
def run(self):
req = opener.open("http://google.com")
newdata = req.read()
if self.lastdata is None:
self.lastdata = newdata
assert self.lastdata == newdata, "Data was not consistent, uhoh"
req.recache()
threads = [CacheThreadTest() for x in range(50)]
print "Starting threads"
[t.start() for t in threads]
print "..done"
print "Joining threads"
[t.join() for t in threads]
print "..done"
main()

View File

@ -0,0 +1,109 @@
[File ttvdb-example.conf]
#-------------------------------------
# Name: ttvdb-example.conf
# Project: ttvdb
# Configuration file
# Author: R.D. Vaughan
# Version: 0.1.0 - Initial alpha release
# Version: 0.8.9 - version changed to match the current ttvdb release number
# Version: 0.9.5 - Changed regex pattern strings to support multi-langiage file names
#
# License:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
#-------------------------------------
#
# START season name overrides section --------------------------------------------------------------
#####
# PURPOSE: TV program sources such as Schedule Direct series names do not always match the series
# names on thetvdb.com. This section allow you to override series names to reduce the
# need for manual interaction and more accurate matching with thetvdb.com TV series wiki.
#
# FORMAT:
# Any line starting with "#" is treated as a comment
# Any blank line is ignored
# All other lines must have two fields in this specific order:
# 1st) The series name or the %TITLE% field as passed by MythTV and a trailing ':' character
# 2nd) The thetvdb.com series id (SID) as specified for the series on thetvdb.com wiki. This will
# override the series name to eliminate the need for manual interaction.
# NOTE: Included here. but disabled are examples of current (2009) TV shows that Schedule Direct
# has different TV series names then what is on thetvdb.com wiki. If you are searching for
# series based on the Schedule Direct names as MythTV does then the wrong TV series will be
# identified. You can pick the correct series by using interactive mode but this does not
# help with unattended processing. This section allows you to specify the correct TV series
# on thetvbd.com despite the name in Schedule Direct. Luckily there are not many TV series
# that require overrides.
#####
[series_name_override]
# Specify recorded "Life On Mars" shows as the US version
#Life on Mars:82289
# For overnight episode updates when a filename is used
#Life on Mars (US):82289
# Specify recorded "Eleventh Hour" shows as the US version
#Eleventh Hour:83066
# For overnight episode updates when a filename is used
#Eleventh Hour (US):83066
# Specify recorded "Frontline" or "Frontline/World" shows as the "Frontline PBS" version
#Frontline/World:80646
#Frontline:80646
# Specify recorded "The Beast" shows are the 2009 version
#The Beast:83805
# For overnight episode updates when a filename is used
#The Beast (2009):83805
# Specify recorded "Castle" shows are the 2009 version
#Castle:83462
# For overnight episode updates when a filename is used
#Castle (2009):83462
# Specify recorded "Battlestar Galactica" shows are the 2003 version
#Battlestar Galactica:73545
# For overnight episode updates when a filename is used
#Battlestar Galactica (2003):73545
# END season name overrides section --------------------------------------------------------------
# START episode name massaging section --------------------------------------------------------------
#####
# PURPOSE: TV program sources such as Schedule Direct episode names do not always match the episode
# names on thetvdb.com. This section allow you to massage episode names to reduce the
# need for manual interaction and more accurate matching with thetvdb.com TV series wiki.
# Alter the episode names for a series to reduce the need for manual interaction
# and more accurate matching with thetvdb.com TV series wiki. See example below.
#
# FORMAT:
# All lines must in the following format:
# 1st) The series name or the %TITLE% field as passed by MythTV and a trailing ':' character
# 2nd) Pairs of values separated by commas. The first value is the search text to match to text within
# the episode name such as the %SUBTITLE% field passed by MythTV and the text to replace the
# matched searched text. If the replacement text contains a space then surround that text with
# the '"' double quotation characters.
# E.g. "24": PM," PM", AM, " AM" will turn the episode name
# "Day 7: 11:00AM to 12:00PM" into "Day 7: 11:00 AM to 12:00 PM"
#
#####
[ep_name_massage]
#24: PM," PM", AM, " AM",M-,"M - "
# END episode name massaging section --------------------------------------------------------------
# START regex section------------------------------------------------------------------------------
#####
# NOTE: If you do not understand regex expressions DO NOT USE this section.
# PURPOSE: This section adds regex strings used to parse video file names when extracting
# the following: series name, season number, episode number. Essential when downloading
# metadata from mythtvfrontend->mythvideo when using ttvdb. You only need to add a regex
# string if ttvdb cannot extract the required information from your video file names.
# NOTE: ANY % percent sign in the expression must be doubled (e.g. a % must be changed to %% )
# NOTE: The key value (e.g. "regex##") must be unique for each value.
# NOTE: DO NOT surround the regex pattern string with the traditional ''' three single quotes
#
# "regex01" is an example video file name "foo_S01_12" where:
# series name is "foo", season number starts at "S" and episode number starts after '_'
# foo_[s01]_[e01]
#####
[regex]
# foo_S01_12
regex01: ^(.+?)[ \._\-][Ss]([0-9]+)_([0-9]+)[^\\/]*$
# END regex section------------------------------------------------------------------------------

805
mythtv/ttvdb/tvdb_api.py Normal file
View File

@ -0,0 +1,805 @@
#!/usr/bin/env python
#encoding:utf-8
#author:dbr/Ben
#project:tvdb_api
#repository:http://github.com/dbr/tvdb_api
#license:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
"""Simple-to-use Python interface to The TVDB's API (www.thetvdb.com)
Example usage:
>>> from tvdb_api import Tvdb
>>> t = Tvdb()
>>> t['Lost'][4][11]['episodename']
u'Cabin Fever'
"""
__author__ = "dbr/Ben"
__version__ = "1.2.1"
import os
import sys
import urllib
import urllib2
import tempfile
import logging
try:
import xml.etree.cElementTree as ElementTree
except ImportError:
import xml.etree.ElementTree as ElementTree
from cache import CacheHandler
from tvdb_ui import BaseUI, ConsoleUI
from tvdb_exceptions import (tvdb_error, tvdb_userabort, tvdb_shownotfound,
tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound)
class ShowContainer(dict):
"""Simple dict that holds a series of Show instances
"""
pass
class Show(dict):
"""Holds a dict of seasons, and show data.
"""
def __init__(self):
dict.__init__(self)
self.data = {}
def __repr__(self):
return "<Show %s (containing %s seasons)>" % (
self.data.get(u'seriesname', 'instance'),
len(self)
)
def __getitem__(self, key):
if key in self:
# Key is an episode, return it
return dict.__getitem__(self, key)
if key in self.data:
# Non-numeric request is for show-data
return dict.__getitem__(self.data, key)
# Data wasn't found, raise appropriate error
if isinstance(key, int) or key.isdigit():
# Episode number x was not found
raise tvdb_seasonnotfound("Could not find season %s" % (repr(key)))
else:
# If it's not numeric, it must be an attribute name, which
# doesn't exist, so attribute error.
raise tvdb_attributenotfound("Cannot find attribute %s" % (repr(key)))
def search(self, term = None, key = None):
"""
Search all episodes in show. Can search all data, or a specific key (for
example, episodename)
Always returns an array (can be empty). First index contains the first
match, and so on.
Each array index is an Episode() instance, so doing
search_results[0]['episodename'] will retrieve the episode name of the
first match.
Search terms are converted to lower case (unicode) strings.
# Examples
These examples assume t is an instance of Tvdb():
>>> t = Tvdb()
>>>
To search for all episodes of Scrubs with a bit of data
containing "my first day":
>>> t['Scrubs'].search("my first day")
[<Episode 01x01 - My First Day>]
>>>
Search for "My Name Is Earl" episode named "Faked His Own Death":
>>> t['My Name Is Earl'].search('Faked His Own Death', key = 'episodename')
[<Episode 01x04 - Faked His Own Death>]
>>>
To search Scrubs for all episodes with "mentor" in the episode name:
>>> t['scrubs'].search('mentor', key = 'episodename')
[<Episode 01x02 - My Mentor>, <Episode 03x15 - My Tormented Mentor>]
>>>
# Using search results
>>> results = t['Scrubs'].search("my first")
>>> print results[0]['episodename']
My First Day
>>> for x in results: print x['episodename']
My First Day
My First Step
My First Kill
>>>
"""
results = []
for cur_season in self.values():
searchresult = cur_season.search(term = term, key = key)
if len(searchresult) != 0:
results.extend(searchresult)
#end for cur_season
return results
class Season(dict):
def __repr__(self):
return "<Season instance (containing %s episodes)>" % (
len(self.keys())
)
def __getitem__(self, episode_number):
if episode_number not in self:
raise tvdb_episodenotfound("Could not find episode %s" % (repr(episode_number)))
else:
return dict.__getitem__(self, episode_number)
def search(self, term = None, key = None):
"""Search all episodes in season, returns a list of matching Episode
instances.
>>> t = Tvdb()
>>> t['scrubs'][1].search('first day')
[<Episode 01x01 - My First Day>]
>>>
See Show.search documentation for further information on search
"""
results = []
for ep in self.values():
searchresult = ep.search(term = term, key = key)
if searchresult is not None:
results.append(
searchresult
)
return results
class Episode(dict):
def __repr__(self):
seasno = int(self.get(u'seasonnumber', 0))
epno = int(self.get(u'episodenumber', 0))
epname = self.get(u'episodename')
if epname is not None:
return "<Episode %02dx%02d - %s>" % (seasno, epno, epname)
else:
return "<Episode %02dx%02d>" % (seasno, epno)
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
raise tvdb_attributenotfound("Cannot find attribute %s" % (repr(key)))
def search(self, term = None, key = None):
"""Search episode data for term, if it matches, return the Episode (self).
The key parameter can be used to limit the search to a specific element,
for example, episodename.
This primarily for use use by Show.search and Season.search. See
Show.search for further information on search
Simple example:
>>> e = Episode()
>>> e['episodename'] = "An Example"
>>> e.search("examp")
<Episode 00x00 - An Example>
>>>
Limiting by key:
>>> e.search("examp", key = "episodename")
<Episode 00x00 - An Example>
>>>
"""
if term == None:
raise TypeError("must supply string to search for (contents)")
term = unicode(term).lower()
for cur_key, cur_value in self.items():
cur_key, cur_value = unicode(cur_key).lower(), unicode(cur_value).lower()
if key is not None and cur_key != key:
# Do not search this key
continue
if cur_value.find( unicode(term).lower() ) > -1:
return self
#end if cur_value.find()
#end for cur_key, cur_value
class Actors(list):
"""Holds all Actor instances for a show
"""
pass
class Actor(dict):
"""Represents a single actor. Should contain..
id,
image,
name,
role,
sortorder
"""
def __repr__(self):
return "<Actor \"%s\">" % (self.get("name"))
class Tvdb:
"""Create easy-to-use interface to name of season/episode name
>>> t = Tvdb()
>>> t['Scrubs'][1][24]['episodename']
u'My Last Day'
"""
def __init__(self,
interactive = False,
select_first = False,
debug = False,
cache = True,
banners = False,
actors = False,
custom_ui = None,
language = None,
search_all_languages = False,
apikey = None):
"""interactive (True/False):
When True, uses built-in console UI is used to select the correct show.
When False, the first search result is used.
select_first (True/False):
Automatically selects the first series search result (rather
than showing the user a list of more than one series).
Is overridden by interactive = False, or specifying a custom_ui
debug (True/False):
shows verbose debugging information
cache (True/False/str/unicode):
Retrieved XML are persisted to to disc. If true, stores in tvdb_api
folder under your systems TEMP_DIR, if set to str/unicode instance it
will use this as the cache location. If False, disables caching.
banners (True/False):
Retrieves the banners for a show. These are accessed
via the _banners key of a Show(), for example:
>>> Tvdb(banners=True)['scrubs']['_banners'].keys()
['fanart', 'poster', 'series', 'season']
actors (True/False):
Retrieves a list of the actors for a show. These are accessed
via the _actors key of a Show(), for example:
>>> t = Tvdb(actors=True)
>>> t['scrubs']['_actors'][0]['name']
u'Zach Braff'
custom_ui (tvdb_ui.BaseUI subclass):
A callable subclass of tvdb_ui.BaseUI (overrides interactive option)
language (2 character language abbreviation):
The language of the returned data. Is also the language search
uses. Default is "en" (English). For full list, run..
>>> Tvdb().config['valid_languages'] #doctest: +ELLIPSIS
['da', 'fi', 'nl', ...]
search_all_languages (True/False):
By default, Tvdb will only search in the language specified using
the language option. When this is True, it will search for the
show in and language
apikey (str/unicode):
Override the default thetvdb.com API key. By default it will use
tvdb_api's own key (fine for small scripts), but you can use your
own key if desired - this is recommended if you are embedding
tvdb_api in a larger application)
See http://thetvdb.com/?tab=apiregister to get your own key
"""
self.shows = ShowContainer() # Holds all Show classes
self.corrections = {} # Holds show-name to show_id mapping
self.config = {}
if apikey is not None:
self.config['apikey'] = apikey
else:
self.config['apikey'] = "0629B785CE550C8D" # tvdb_api's API key
self.config['debug_enabled'] = debug # show debugging messages
self.config['custom_ui'] = custom_ui
self.config['interactive'] = interactive # prompt for correct series?
self.config['select_first'] = select_first
self.config['search_all_languages'] = search_all_languages
if cache is True:
self.config['cache_enabled'] = True
self.config['cache_location'] = self._getTempDir()
elif isinstance(cache, basestring):
self.config['cache_enabled'] = True
self.config['cache_location'] = cache
else:
self.config['cache_enabled'] = False
if self.config['cache_enabled']:
self.urlopener = urllib2.build_opener(
CacheHandler(self.config['cache_location'])
)
else:
self.urlopener = urllib2.build_opener()
self.config['banners_enabled'] = banners
self.config['actors_enabled'] = actors
self.log = self._initLogger() # Setups the logger (self.log.debug() etc)
# List of language from http://www.thetvdb.com/api/0629B785CE550C8D/languages.xml
# Hard-coded here as it is realtively static, and saves another HTTP request, as
# recommended on http://thetvdb.com/wiki/index.php/API:languages.xml
self.config['valid_languages'] = [
"da", "fi", "nl", "de", "it", "es", "fr","pl", "hu","el","tr",
"ru","he","ja","pt","zh","cs","sl", "hr","ko","en","sv","no"
]
if language is None:
self.config['language'] = "en"
elif language not in self.config['valid_languages']:
raise ValueError("Invalid language %s, options are: %s" % (
language, self.config['valid_languages']
))
else:
self.config['language'] = language
# The following url_ configs are based of the
# http://thetvdb.com/wiki/index.php/Programmers_API
self.config['base_url'] = "http://www.thetvdb.com"
if self.config['search_all_languages']:
self.config['url_getSeries'] = "%(base_url)s/api/GetSeries.php?seriesname=%%s&language=all" % self.config
else:
self.config['url_getSeries'] = "%(base_url)s/api/GetSeries.php?seriesname=%%s&language=%(language)s" % self.config
self.config['url_epInfo'] = "%(base_url)s/api/%(apikey)s/series/%%s/all/%(language)s.xml" % self.config
self.config['url_seriesInfo'] = "%(base_url)s/api/%(apikey)s/series/%%s/%(language)s.xml" % self.config
self.config['url_actorsInfo'] = "%(base_url)s/api/%(apikey)s/series/%%s/actors.xml" % self.config
self.config['url_seriesBanner'] = "%(base_url)s/api/%(apikey)s/series/%%s/banners.xml" % self.config
self.config['url_artworkPrefix'] = "%(base_url)s/banners/%%s" % self.config
#end __init__
def _initLogger(self):
"""Setups a logger using the logging module, returns a log object
"""
logger = logging.getLogger("tvdb")
formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s')
hdlr = logging.StreamHandler(sys.stdout)
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
if self.config['debug_enabled']:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.WARNING)
return logger
#end initLogger
def _getTempDir(self):
"""Returns the [system temp dir]/tvdb_api
"""
return os.path.join(tempfile.gettempdir(), "tvdb_api")
def _loadUrl(self, url, recache = False):
try:
self.log.debug("Retrieving URL %s" % url)
resp = self.urlopener.open(url)
if 'x-local-cache' in resp.headers:
self.log.debug("URL %s was cached in %s" % (
url,
resp.headers['x-local-cache'])
)
if recache:
self.log.debug("Attempting to recache %s" % url)
resp.recache()
except urllib2.URLError, errormsg:
raise tvdb_error("Could not connect to server: %s" % (errormsg))
#end try
return resp.read()
def _getetsrc(self, url):
"""Loads a URL sing caching, returns an ElementTree of the source
"""
src = self._loadUrl(url)
try:
return ElementTree.fromstring(src)
except SyntaxError:
src = self._loadUrl(url, recache=True)
try:
return ElementTree.fromstring(src)
except SyntaxError, exceptionmsg:
errormsg = "There was an error with the XML retrieved from thetvdb.com:\n%s" % (
exceptionmsg
)
if self.config['cache_enabled']:
errormsg += "\nFirst try emptying the cache folder at..\n%s" % (
self.config['cache_location']
)
errormsg += "\nIf this does not resolve the issue, please try again later. If the error persists, report a bug on"
errormsg += "\nhttp://dbr.lighthouseapp.com/projects/13342-tvdb_api/overview\n"
raise tvdb_error(errormsg)
#end _getetsrc
def _setItem(self, sid, seas, ep, attrib, value):
"""Creates a new episode, creating Show(), Season() and
Episode()s as required. Called by _getShowData to populute
Since the nice-to-use tvdb[1][24]['name] interface
makes it impossible to do tvdb[1][24]['name] = "name"
and still be capable of checking if an episode exists
so we can raise tvdb_shownotfound, we have a slightly
less pretty method of setting items.. but since the API
is supposed to be read-only, this is the best way to
do it!
The problem is that calling tvdb[1][24]['episodename'] = "name"
calls __getitem__ on tvdb[1], there is no way to check if
tvdb.__dict__ should have a key "1" before we auto-create it
"""
if sid not in self.shows:
self.shows[sid] = Show()
if seas not in self.shows[sid]:
self.shows[sid][seas] = Season()
if ep not in self.shows[sid][seas]:
self.shows[sid][seas][ep] = Episode()
self.shows[sid][seas][ep][attrib] = value
#end _set_item
def _setShowData(self, sid, key, value):
"""Sets self.shows[sid] to a new Show instance, or sets the data
"""
if sid not in self.shows:
self.shows[sid] = Show()
self.shows[sid].data[key] = value
def _cleanData(self, data):
"""Cleans up strings returned by TheTVDB.com
Issues corrected:
- Replaces &amp; with &
- Trailing whitespace
"""
data = data.replace(u"&amp;", u"&")
data = data.strip()
return data
#end _cleanData
def _getSeries(self, series):
"""This searches TheTVDB.com for the series name,
If a custom_ui UI is configured, it uses this to select the correct
series. If not, and interactive == True, ConsoleUI is used, if not
BaseUI is used to select the first result.
"""
series = urllib.quote(series.encode("utf-8"))
self.log.debug("Searching for show %s" % series)
seriesEt = self._getetsrc(self.config['url_getSeries'] % (series))
allSeries = []
for series in seriesEt:
sn = series.find('SeriesName')
value = self._cleanData(sn.text)
cur_sid = series.find('id').text
self.log.debug('Found series %s (id: %s)' % (value, cur_sid))
allSeries.append( {'sid':cur_sid, 'name':value} )
#end for series
if len(allSeries) == 0:
self.log.debug('Series result returned zero')
raise tvdb_shownotfound("Show-name search returned zero results (cannot find show on TVDB)")
if self.config['custom_ui'] is not None:
self.log.debug("Using custom UI %s" % (repr(self.config['custom_ui'])))
ui = self.config['custom_ui'](config = self.config, log = self.log)
else:
if not self.config['interactive']:
self.log.debug('Auto-selecting first search result using BaseUI')
ui = BaseUI(config = self.config, log = self.log)
else:
self.log.debug('Interactivily selecting show using ConsoleUI')
ui = ConsoleUI(config = self.config, log = self.log)
#end if config['interactive]
#end if custom_ui != None
return ui.selectSeries(allSeries)
#end _getSeries
def _parseBanners(self, sid):
"""Parses banners XML, from
http://www.thetvdb.com/api/[APIKEY]/series/[SERIES ID]/banners.xml
Banners are retrieved using t['show name]['_banners'], for example:
>>> t = Tvdb(banners = True)
>>> t['scrubs']['_banners'].keys()
['fanart', 'poster', 'series', 'season']
>>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath']
'http://www.thetvdb.com/banners/posters/76156-2.jpg'
>>>
Any key starting with an underscore has been processed (not the raw
data from the XML)
This interface will be improved in future versions.
"""
self.log.debug('Getting season banners for %s' % (sid))
bannersEt = self._getetsrc( self.config['url_seriesBanner'] % (sid) )
banners = {}
for cur_banner in bannersEt.findall('Banner'):
bid = cur_banner.find('id').text
btype = cur_banner.find('BannerType')
btype2 = cur_banner.find('BannerType2')
if btype is None or btype2 is None:
continue
btype, btype2 = btype.text, btype2.text
if not btype in banners:
banners[btype] = {}
if not btype2 in banners[btype]:
banners[btype][btype2] = {}
if not bid in banners[btype][btype2]:
banners[btype][btype2][bid] = {}
self.log.debug("Banner: %s", bid)
for cur_element in cur_banner.getchildren():
tag = cur_element.tag.lower()
value = cur_element.text
if tag is None or value is None:
continue
tag, value = tag.lower(), value.lower()
self.log.debug("Banner info: %s = %s" % (tag, value))
banners[btype][btype2][bid][tag] = value
for k, v in banners[btype][btype2][bid].items():
if k.endswith("path"):
new_key = "_%s" % (k)
self.log.debug("Transforming %s to %s" % (k, new_key))
new_url = self.config['url_artworkPrefix'] % (v)
self.log.debug("New banner URL: %s" % (new_url))
banners[btype][btype2][bid][new_key] = new_url
self._setShowData(sid, "_banners", banners)
# Alternate tvdb_api's method for retrieving graphics URLs but returned as a list that preserves
# the user rating order highest rated to lowest rated
def ttvdb_parseBanners(self, sid):
"""Parses banners XML, from
http://www.thetvdb.com/api/[APIKEY]/series/[SERIES ID]/banners.xml
Banners are retrieved using t['show name]['_banners'], for example:
>>> t = Tvdb(banners = True)
>>> t['scrubs']['_banners'].keys()
['fanart', 'poster', 'series', 'season']
>>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath']
'http://www.thetvdb.com/banners/posters/76156-2.jpg'
>>>
Any key starting with an underscore has been processed (not the raw
data from the XML)
This interface will be improved in future versions.
Changed in this interface is that a list or URLs is created to preserve the user rating order from
top rated to lowest rated.
"""
self.log.debug('Getting season banners for %s' % (sid))
bannersEt = self._getetsrc( self.config['url_seriesBanner'] % (sid) )
banners = {}
bid_order = {'fanart': [], 'poster': [], 'series': [], 'season': []}
for cur_banner in bannersEt.findall('Banner'):
bid = cur_banner.find('id').text
btype = cur_banner.find('BannerType')
btype2 = cur_banner.find('BannerType2')
if btype is None or btype2 is None:
continue
btype, btype2 = btype.text, btype2.text
if not btype in banners:
banners[btype] = {}
if not btype2 in banners[btype]:
banners[btype][btype2] = {}
if not bid in banners[btype][btype2]:
banners[btype][btype2][bid] = {}
if btype in bid_order.keys():
if btype2 != u'blank':
bid_order[btype].append([bid, btype2])
self.log.debug("Banner: %s", bid)
for cur_element in cur_banner.getchildren():
tag = cur_element.tag.lower()
value = cur_element.text
if tag is None or value is None:
continue
tag, value = tag.lower(), value.lower()
self.log.debug("Banner info: %s = %s" % (tag, value))
banners[btype][btype2][bid][tag] = value
for k, v in banners[btype][btype2][bid].items():
if k.endswith("path"):
new_key = "_%s" % (k)
self.log.debug("Transforming %s to %s" % (k, new_key))
new_url = self.config['url_artworkPrefix'] % (v)
self.log.debug("New banner URL: %s" % (new_url))
banners[btype][btype2][bid][new_key] = new_url
graphics_in_order = {'fanart': [], 'poster': [], 'series': [], 'season': []}
for key in bid_order.keys():
for bid in bid_order[key]:
graphics_in_order[key].append(banners[key][bid[1]][bid[0]])
return graphics_in_order
# end ttvdb_parseBanners()
def _parseActors(self, sid):
"""Parsers actors XML, from
http://www.thetvdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml
Actors are retrieved using t['show name]['_actors'], for example:
>>> t = Tvdb(actors = True)
>>> actors = t['scrubs']['_actors']
>>> type(actors)
<class 'tvdb_api.Actors'>
>>> type(actors[0])
<class 'tvdb_api.Actor'>
>>> actors[0]
<Actor "Zach Braff">
>>> sorted(actors[0].keys())
['id', 'image', 'name', 'role', 'sortorder']
>>> actors[0]['name']
u'Zach Braff'
>>> actors[0]['image']
'http://www.thetvdb.com/banners/actors/43640.jpg'
Any key starting with an underscore has been processed (not the raw
data from the XML)
"""
self.log.debug("Getting actors for %s" % (sid))
actorsEt = self._getetsrc(self.config['url_actorsInfo'] % (sid))
cur_actors = Actors()
for curActorItem in actorsEt.findall("Actor"):
curActor = Actor()
for curInfo in curActorItem:
tag = curInfo.tag.lower()
value = curInfo.text
if value is not None:
if tag == "image":
value = self.config['url_artworkPrefix'] % (value)
else:
value = self._cleanData(value)
curActor[tag] = value
cur_actors.append(curActor)
self._setShowData(sid, '_actors', cur_actors)
def _getShowData(self, sid):
"""Takes a series ID, gets the epInfo URL and parses the TVDB
XML file into the shows dict in layout:
shows[series_id][season_number][episode_number]
"""
# Parse show information
self.log.debug('Getting all series data for %s' % (sid))
seriesInfoEt = self._getetsrc(self.config['url_seriesInfo'] % (sid))
for curInfo in seriesInfoEt.findall("Series")[0]:
tag = curInfo.tag.lower()
value = curInfo.text
if value is not None:
if tag in ['banner', 'fanart', 'poster']:
value = self.config['url_artworkPrefix'] % (value)
else:
value = self._cleanData(value)
self._setShowData(sid, tag, value)
self.log.debug("Got info: %s = %s" % (tag, value))
#end for series
# Parse banners
if self.config['banners_enabled']:
self._parseBanners(sid)
# Parse actors
if self.config['actors_enabled']:
self._parseActors(sid)
# Parse episode data
self.log.debug('Getting all episodes of %s' % (sid))
epsEt = self._getetsrc( self.config['url_epInfo'] % (sid) )
for cur_ep in epsEt.findall("Episode"):
seas_no = int(cur_ep.find('SeasonNumber').text)
ep_no = int(cur_ep.find('EpisodeNumber').text)
for cur_item in cur_ep.getchildren():
tag = cur_item.tag.lower()
value = cur_item.text
if value is not None:
if tag == 'filename':
value = self.config['url_artworkPrefix'] % (value)
else:
value = self._cleanData(value)
self._setItem(sid, seas_no, ep_no, tag, value)
#end for cur_ep
#end _geEps
def _nameToSid(self, name):
"""Takes show name, returns the correct series ID (if the show has
already been grabbed), or grabs all episodes and returns
the correct SID.
"""
if name in self.corrections:
self.log.debug('Correcting %s to %s' % (name, self.corrections[name]) )
sid = self.corrections[name]
else:
self.log.debug('Getting show %s' % (name))
selected_series = self._getSeries( name )
sname, sid = selected_series['name'], selected_series['sid']
self.log.debug('Got %s, sid %s' % (sname, sid))
self.corrections[name] = sid
self._getShowData(sid)
#end if name in self.corrections
return sid
#end _nameToSid
def __getitem__(self, key):
"""Handles tvdb_instance['seriesname'] calls.
The dict index should be the show id
"""
if isinstance(key, (int, long)):
# Item is integer, treat as show id
if key not in self.shows:
self._getShowData(key)
return self.shows[key]
key = key.lower() # make key lower case
sid = self._nameToSid(key)
self.log.debug('Got series id %s' % (sid))
return self.shows[sid]
#end __getitem__
def __repr__(self):
return str(self.shows)
#end __repr__
#end Tvdb
def main():
"""Simple example of using tvdb_api - it just
grabs an episode name interactively.
"""
tvdb_instance = Tvdb(interactive=True, debug=True, cache=False)
print tvdb_instance['Lost']['seriesname']
print tvdb_instance['Lost'][1][4]['episodename']
if __name__ == '__main__':
main()

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
#encoding:utf-8
#author:dbr/Ben
#project:tvdb_api
#repository:http://github.com/dbr/tvdb_api
#license:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
"""Custom exceptions used or raised by tvdb_api
"""
__author__ = "dbr/Ben"
__version__ = "1.2.1"
__all__ = ["tvdb_error", "tvdb_userabort", "tvdb_shownotfound",
"tvdb_seasonnotfound", "tvdb_episodenotfound", "tvdb_attributenotfound"]
class tvdb_error(Exception):
"""An error with www.thetvdb.com (Cannot connect, for example)
"""
pass
class tvdb_userabort(Exception):
"""User aborted the interactive selection (via
the q command, ^c etc)
"""
pass
class tvdb_shownotfound(Exception):
"""Show cannot be found on www.thetvdb.com (non-existant show)
"""
pass
class tvdb_seasonnotfound(Exception):
"""Season cannot be found on www.thetvdb.com
"""
pass
class tvdb_episodenotfound(Exception):
"""Episode cannot be found on www.thetvdb.com
"""
pass
class tvdb_attributenotfound(Exception):
"""Raised if an episode does not have the requested
attribute (such as a episode name)
"""
pass

124
mythtv/ttvdb/tvdb_ui.py Normal file
View File

@ -0,0 +1,124 @@
#!/usr/bin/env python
#encoding:utf-8
#author:dbr/Ben
#project:tvdb_api
#repository:http://github.com/dbr/tvdb_api
#license:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
"""Contains included user interfaces for Tvdb show selection.
A UI is a callback. A class, it's __init__ function takes two arguments:
- config, which is the Tvdb config dict, setup in tvdb_api.py
- log, which is Tvdb's logger instance (which uses the logging module). You can
call log.info() log.warning() etc
It must have a method "selectSeries", this is passed a list of dicts, each dict
contains the the keys "name" (human readable show name), and "sid" (the shows
ID as on thetvdb.com). For example:
[{'name': u'Lost', 'sid': u'73739'},
{'name': u'Lost Universe', 'sid': u'73181'}]
The "selectSeries" method must return the appropriate dict, or it can raise
tvdb_userabort (if the selection is aborted), tvdb_shownotfound (if the show
cannot be found).
A simple example callback, which returns a random series:
>>> import random
>>> from tvdb_ui import BaseUI
>>> class RandomUI(BaseUI):
... def selectSeries(self, allSeries):
... import random
... return random.choice(allSeries)
Then to use it..
>>> from tvdb_api import Tvdb
>>> t = Tvdb(custom_ui = RandomUI)
>>> random_matching_series = t['Lost']
>>> type(random_matching_series)
<class 'tvdb_api.Show'>
"""
__author__ = "dbr/Ben"
__version__ = "1.2.1"
from tvdb_exceptions import tvdb_userabort
class BaseUI:
"""Default non-interactive UI, which auto-selects first results
"""
def __init__(self, config, log):
self.config = config
self.log = log
def selectSeries(self, allSeries):
return allSeries[0]
class ConsoleUI(BaseUI):
"""Interactively allows the user to select a show from a console based UI
"""
def _displaySeries(self, allSeries):
"""Helper function, lists series with corresponding ID
"""
print "TVDB Search Results:"
for i in range(len(allSeries[:6])): # list first 6 search results
i_show = i + 1 # Start at more human readable number 1 (not 0)
self.log.debug('Showing allSeries[%s] = %s)' % (i_show, allSeries[i]))
print "%s -> %s # http://thetvdb.com/?tab=series&id=%s" % (
i_show,
allSeries[i]['name'].encode("UTF-8","ignore"),
allSeries[i]['sid'].encode("UTF-8","ignore")
)
def selectSeries(self, allSeries):
self._displaySeries(allSeries)
if len(allSeries) == 1:
# Single result, return it!
print "Automatically selecting only result"
return allSeries[0]
if self.config['select_first'] is True:
print "Automatically returning first search result"
return allSeries[0]
while True: # return breaks this loop
try:
print "Enter choice (first number, ? for help):"
ans = raw_input()
except KeyboardInterrupt:
raise tvdb_userabort("User aborted (^c keyboard interupt)")
except EOFError:
raise tvdb_userabort("User aborted (EOF received)")
self.log.debug('Got choice of: %s' % (ans))
try:
selected_id = int(ans) - 1 # The human entered 1 as first result, not zero
except ValueError: # Input was not number
if ans == "q":
self.log.debug('Got quit command (q)')
raise tvdb_userabort("User aborted ('q' quit command)")
elif ans == "?":
print "## Help"
print "# Enter the number that corresponds to the correct show."
print "# ? - this help"
print "# q - abort tvnamer"
else:
self.log.debug('Unknown keypress %s' % (ans))
else:
self.log.debug('Trying to return ID: %d' % (selected_id))
try:
return allSeries[ selected_id ]
except IndexError:
self.log.debug('Invalid show number entered!')
print "Invalid number (%s) selected!"
self._displaySeries(allSeries)
#end try
#end while not valid_input

1868
patches/MythBase.py.orig Executable file

File diff suppressed because it is too large Load Diff

1504
patches/MythData.py.orig Executable file

File diff suppressed because it is too large Load Diff

131
patches/mythbase.patch Normal file
View File

@ -0,0 +1,131 @@
--- mythtv_old/MythBase.py 2010-07-31 08:46:35.000000000 -0400
+++ mythtv/MythBase.py 2010-08-17 21:49:43.000000000 -0400
@@ -7,18 +7,15 @@
from MythStatic import *
import os, re, socket, sys, locale, weakref
-from xml import *
+import xml.etree.cElementTree as etree
from datetime import datetime
from time import sleep, time
from urllib import urlopen
from subprocess import Popen
from sys import version_info
-#import MySQLdb, MySQLdb.cursors
-#MySQLdb.__version__ = tuple([v for v in MySQLdb.__version__.split('.')])
-
-import mysql.connector
-import mysql.connector.cursor
+import MySQLdb, MySQLdb.cursors
+MySQLdb.__version__ = tuple([v for v in MySQLdb.__version__.split('.')])
class DictData( object ):
"""
@@ -742,19 +739,19 @@
del self.hash[index]
del self.data[index]
-class MythDBCursor( mysql.connector.cursor.MySQLCursor ):
+class MythDBCursor( MySQLdb.cursors.Cursor ):
"""
Custom cursor, offering logging and error handling
"""
def __init__(self, connection):
self.log = None
- mysql.connector.cursor.MySQLCursor.__init__(self, connection)
+ MySQLdb.cursors.Cursor.__init__(self, connection)
def execute(self, query, args=None):
- #if MySQLdb.__version__ >= ('1','2','2'):
- # self.connection.ping(True)
- #else:
- # self.connection.ping()
+ if MySQLdb.__version__ >= ('1','2','2'):
+ self.connection.ping(True)
+ else:
+ self.connection.ping()
if self.log == None:
self.log = MythLog('Python Database Connection')
if args:
@@ -762,21 +759,21 @@
else:
self.log(self.log.DATABASE, ' '.join(query.split()))
try:
- return mysql.connector.cursor.MySQLCursor.execute(self, query, args)
+ return MySQLdb.cursors.Cursor.execute(self, query, args)
except Exception, e:
raise MythDBError(MythDBError.DB_RAW, e.args)
def executemany(self, query, args):
- #if MySQLdb.__version__ >= ('1','2','2'):
- # self.connection.ping(True)
- #else:
- # self.connection.ping()
+ if MySQLdb.__version__ >= ('1','2','2'):
+ self.connection.ping(True)
+ else:
+ self.connection.ping()
if self.log == None:
self.log = MythLog('Python Database Connection')
for arg in args:
self.log(self.log.DATABASE, ' '.join(query.split()), str(arg))
try:
- return mysql.connector.cursor.MySQLCursor.executemany(self, query, args)
+ return MySQLdb.cursors.Cursor.executemany(self, query, args)
except Exception, e:
raise MythDBError(MythDBError.DB_RAW, e.args)
@@ -793,23 +790,22 @@
try:
self.log(MythLog.DATABASE, "Attempting connection",
str(dbconn))
- self.db = mysql.connector.connect(user=dbconn['DBUserName'],
- host=dbconn['DBHostName'],
- password=dbconn['DBPassword'],
- db=dbconn['DBName'],
- port=dbconn['DBPort'],
- use_unicode=True,
- charset='utf8')
+ self.db = MySQLdb.connect( user= dbconn['DBUserName'],
+ host= dbconn['DBHostName'],
+ passwd= dbconn['DBPassword'],
+ db= dbconn['DBName'],
+ port= dbconn['DBPort'],
+ use_unicode=True,
+ charset='utf8')
except:
raise MythDBError(MythError.DB_CONNECTION, dbconn)
def cursor(self, log=None, type=MythDBCursor):
- #if MySQLdb.__version__ >= ('1','2','2'):
- # self.db.ping(True)
- #else:
- # self.db.ping()
- #c = self.db.cursor(type)
- c = self.db.cursor()
+ if MySQLdb.__version__ >= ('1','2','2'):
+ self.db.ping(True)
+ else:
+ self.db.ping()
+ c = self.db.cursor(type)
if log:
c.log = log
else:
@@ -1000,6 +996,7 @@
else:
# fall back to UPnP
dbconn = self._listenUPNP(dbconn['SecurityPin'], 5.0)
+
# push data to new settings file
settings = [dbconn[key] for key in \
('SecurityPin','DBHostName','DBUserName',
@@ -1449,7 +1446,7 @@
ind1 = xmlstr.find('xmlns')
ind2 = xmlstr.find('"', ind1+7) + 1
xmlstr = xmlstr[:ind1] + xmlstr[ind2:]
- return etree.ElementTree.fromstring(xmlstr)
+ return etree.fromstring(xmlstr)
def getConnectionInfo(self, pin=0):
"""Return dbconn dict from backend connection info."""

28
patches/mythdata.patch Normal file
View File

@ -0,0 +1,28 @@
--- mythtv_old/MythData.py 2010-08-07 21:55:58.000000000 -0400
+++ mythtv/MythData.py 2010-08-17 21:49:43.000000000 -0400
@@ -175,7 +175,7 @@
if not self.open:
return
self.control.backendCommand('QUERY_FILETRANSFER '\
- +BACKEND_SEP.join([str(self.sockno), 'JOIN']))
+ +BACKEND_SEP.join([str(self.sockno), 'DONE']))
self.socket.shutdown(1)
self.socket.close()
self.open = False
@@ -521,12 +521,14 @@
"""Program.getRecorded() -> Recorded object"""
return Recorded((self.chanid,self.recstartts), db=self.db)
- def open(self, type='r', dba=None):
+ def open(self, type='r'):
"""Program.open(type='r') -> file or FileTransfer object"""
if type != 'r':
raise MythFileError(MythError.FILE_FAILED_WRITE, self.filename,
'Program () objects cannot be opened for writing')
- return ftopen(self.filename, 'r', forceremote=False, nooverwrite=False, db=dba)
+ if not self.filename.startswith('myth://'):
+ self.filename = 'myth://%s/%s' % (self.hostname, self.filename)
+ return ftopen(self.filename, 'r')
class Record( DBDataWrite ):
"""

View File

@ -0,0 +1,199 @@
<?xml version="1.0"?>
<window type="window" id="14001">
<defaultcontrol always="true">1030</defaultcontrol>
<allowoverlay>yes</allowoverlay>
<onload lang="python"><![CDATA[
from mythboxee import MythBoxee
mb = MythBoxee()
mb.LoadMain()
]]></onload>
<controls>
<control type="group" id="1020">
<control type="image" id="1021">
<width>1280</width>
<height>720</height>
<texture>mb_bg.png</texture>
</control>
<control type="image" id="1022">
<posx>10</posx>
<posy>10</posy>
<width>244</width>
<height>65</height>
<texture>logo.png</texture>
</control>
</control>
<control type="grouplist" id="1040">
<posy>28</posy>
<posx>780</posx>
<width>500</width>
<itemgap>10</itemgap>
<orientation>horizontal</orientation>
<ondown>1030</ondown>
<control type="button" id="1041">
<label>Refresh</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<height>30</height>
<align>center</align>
<onclick lang="python"><![CDATA[mb.RefreshMain()]]></onclick>
</control>
<control type="button" id="1042">
<label>Settings</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<height>30</height>
<align>center</align>
<onclick lang="python"><![CDATA[mc.ActivateWindow(14004)]]></onclick>
</control>
<control type="button" id="1043">
<label>Status</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<height>29</height>
<align>center</align>
<onclick lang="python"><![CDATA[mc.ActivateWindow(14003)]]></onclick>
</control>
</control>
<control type="panel" id="1030">
<posx>22</posx>
<posy>82</posy>
<width>1280</width>
<height>692</height>
<onleft>-</onleft>
<onright>-</onright>
<onup>1040</onup>
<ondown>-</ondown>
<itemlayout width="420" height="120">
<control type="image">
<description>background</description>
<posx>0</posx>
<posy>0</posy>
<width>400</width>
<height>100</height>
<texture border="5">mb_item_big.png</texture>
</control>
<control type="image">
<description>thumbnail</description>
<posx>10</posx>
<posy>10</posy>
<width>380</width>
<height>62</height>
<info>Listitem.Thumb</info>
<aspectratio>scale</aspectratio>
<bordersize>3</bordersize>
<bordertexture>mb_thumb_bg.png</bordertexture>
</control>
<control type="label">
<description>show title</description>
<posx>10</posx>
<posy>75</posy>
<width>250</width>
<height>60</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16b</font>
<textcolor>FFFEFEFE</textcolor>
</control>
<control type="label">
<description>videos</description>
<posx>385</posx>
<posy>76</posy>
<width>152</width>
<height>60</height>
<align>right</align>
<aligny>top</aligny>
<label>Recordings: $INFO[ListItem.property(custom:videos)]</label>
<font>font14</font>
<textcolor>FFCCCCCC</textcolor>
</control>
</itemlayout>
<focusedlayout width="420" height="120">
<control type="togglebutton">
<description>background</description>
<posx>0</posx>
<posy>0</posy>
<width>400</width>
<height>100</height>
<texturefocus border="5">mb_item_big_hover.png</texturefocus>
<texturenofocus border="5">mb_item_big_hover.png</texturenofocus>
<alttexturefocus border="5">mb_item_big.png</alttexturefocus>
<alttexturenofocus border="5">mb_item_big.png</alttexturenofocus>
<usealttexture>!Control.HasFocus(1030)</usealttexture>
</control>
<control type="image">
<description>thumbnail</description>
<posx>10</posx>
<posy>10</posy>
<width>106</width>
<height>62</height>
<info>Listitem.Thumb</info>
<aspectratio>scale</aspectratio>
<bordertexture>mb_thumb_bg.png</bordertexture>
<bordersize>3</bordersize>
</control>
<control type="image">
<description>thumbnail hover</description>
<posx>10</posx>
<posy>10</posy>
<width>380</width>
<height>62</height>
<info>Listitem.Thumb</info>
<aspectratio>scale</aspectratio>
<bordertexture>mb_thumb_hover_bg.png</bordertexture>
<bordersize>3</bordersize>
<visible>Control.HasFocus(1030)</visible>
</control>
<control type="label">
<description>show title</description>
<posx>10</posx>
<posy>75</posy>
<width>250</width>
<height>60</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16b</font>
<textcolor>FFFEFEFE</textcolor>
<scroll>true</scroll>
</control>
<control type="label">
<description>videos</description>
<posx>385</posx>
<posy>76</posy>
<width>152</width>
<height>60</height>
<align>right</align>
<aligny>top</aligny>
<label>Recordings: $INFO[ListItem.property(custom:videos)]</label>
<font>font14</font>
<textcolor>FFCCCCCC</textcolor>
<scroll>true</scroll>
</control>
</focusedlayout>
<content type="action">
<onclick lang="python"><![CDATA[mb.DisplayShow()]]></onclick>
</content>
</control>
<control type="group">
<visible>App.HasSetting(loadingmain)</visible>
<animation effect="fade" start="0" end="100" time="150">VisibleChange</animation>
<control type="image">
<width>1280</width>
<height>720</height>
<texture>black.png</texture>
<animation effect="fade" start="80" end="80" time="0" condition="true">Conditional</animation>
</control>
<control type="label">
<posx>530</posx>
<width>1280</width>
<height>720</height>
<align>cetner</align>
<aligny>center</aligny>
<font>sans40b</font>
<label>LOADING...</label>
<textcolor>ffffffff</textcolor>
</control>
</control>
</controls>
</window>

View File

@ -0,0 +1,310 @@
<window type="window" id="14004">
<defaultcontrol always="true">1030</defaultcontrol>
<allowoverlay>yes</allowoverlay>
<onload lang="python"><![CDATA[
import mc
import mythboxee
mb.SettingsInit()
]]></onload>
<controls>
<control type="group" id="1010">
<control type="image" id="1011">
<width>1280</width>
<height>720</height>
<texture>mb_bg_settings.png</texture>
</control>
</control>
<!-- START: BACKEND INFORMATION -->
<control type="group" id="1040">
<control type="label" id="1041">
<posx>820</posx>
<posy>150</posy>
<width>250</width>
<label>Backend Information</label>
<font>font23</font>
</control>
<control type="edit" id="1042">
<posx>820</posx>
<posy>175</posy>
<width>300</width>
<label>Hostname/IP:</label>
<align>left</align>
<font>font18</font>
<ondown>1043</ondown>
</control>
<control type="edit" id="1043">
<posx>820</posx>
<posy>205</posy>
<width>300</width>
<label>Username:</label>
<align>left</align>
<font>font18</font>
<onup>1042</onup>
<ondown>1044</ondown>
</control>
<control type="edit" id="1044">
<posx>820</posx>
<posy>235</posy>
<width>300</width>
<align>left</align>
<label>Password:</label>
<password>true</password>
<font>font18</font>
<onup>1043</onup>
<ondown>1045</ondown>
</control>
<control type="edit" id="1045">
<posx>820</posx>
<posy>265</posy>
<width>300</width>
<align>left</align>
<label>Database:</label>
<font>font18</font>
<onup>1044</onup>
<ondown>1047</ondown>
</control>
</control>
<control type="grouplist" id="1046">
<posx>820</posx>
<posy>295</posy>
<width>300</width>
<itemgap>0</itemgap>
<orientation>horizontal</orientation>
<ondown>1022</ondown>
<control type="button" id="1047">
<label>Test Connection</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<font>font14</font>
<height>30</height>
<align>center</align>
<aligny>center</aligny>
<onclick lang="python"><![CDATA[mb.TestDbSettings()]]></onclick>
</control>
<control type="button" id="1048">
<label>Save DB Settings</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<font>font14</font>
<height>30</height>
<align>center</align>
<aligny>center</aligny>
<onclick lang="python"><![CDATA[mb.SaveDbSettings()]]></onclick>
</control>
</control>
<!-- END: BACKEND INFORMATION -->
<!-- START: STREAM METHOD -->
<control type="group" id="1020">
<control type="label" id="1021">
<posx>820</posx>
<posy>360</posy>
<width>250</width>
<align>left</align>
<label>Stream Method</label>
<font>font23</font>
</control>
<control type="list" id="1022">
<posx>830</posx>
<posy>395</posy>
<width>164</width>
<height>100</height>
<onup>1044</onup>
<ondown>1032</ondown>
<itemlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font18</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</itemlayout>
<focusedlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font18</font>
<textcolor>green</textcolor>
<visible>Control.HasFocus(1022)</visible>
<selectedcolor>green</selectedcolor>
</control>
<control type="label">
<visible>!Control.HasFocus(1022)</visible>
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font18</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</focusedlayout>
<content type="action">
<onclick lang="python"><![CDATA[mb.SetStreamMethod()]]></onclick>
</content>
</control>
</control>
<!-- END: STREAM METHOD -->
<!-- START: SMB SETTINGS -->
<control type="group" id="1030">
<control type="label" id="1031">
<posx>820</posx>
<posy>480</posy>
<width>250</width>
<align>left</align>
<label>SMB Share</label>
<font>font23</font>
</control>
<control type="edit" id="1032">
<posx>820</posx>
<posy>505</posy>
<width>300</width>
<label>Share:</label>
<align>left</align>
<font>font18</font>
<onup>1022</onup>
<ondown>1033</ondown>
</control>
<control type="edit" id="1033">
<posx>820</posx>
<posy>535</posy>
<width>300</width>
<label>Username:</label>
<align>left</align>
<font>font18</font>
<onup>1032</onup>
<ondown>1034</ondown>
</control>
<control type="edit" id="1034">
<posx>820</posx>
<posy>565</posy>
<width>300</width>
<label>Password:</label>
<align>left</align>
<font>font18</font>
<password>true</password>
<onup>1033</onup>
<ondown>1071</ondown>
</control>
</control>
<control type="grouplist" id="1070">
<posx>895</posx>
<posy>600</posy>
<width>300</width>
<itemgap>0</itemgap>
<orientation>horizontal</orientation>
<onup>1034</onup>
<control type="button" id="1071">
<label>Save SMB Settings</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<font>font14</font>
<height>30</height>
<align>center</align>
<aligny>center</aligny>
<onclick lang="python"><![CDATA[mb.SaveSMBSettings()]]></onclick>
</control>
</control>
<!-- END: SMB SETTINGS -->
<!-- START: CACHE SETTINGS -->
<!--
<control type="group" id="1050">
<control type="label" id="1051">
<posx>820</posx>
<posy>560</posy>
<width>250</width>
<align>left</align>
<label>Cache Settings</label>
<font>font23</font>
</control>
<control type="togglebutton" id="1052">
<posx>820</posx>
<posy>565</posy>
<width>300</width>
<label>Share:</label>
<align>left</align>
<font>font18</font>
<onup>1022</onup>
<ondown>1033</ondown>
<ontextchange lang="python"><![CDATA[mb.SaveCacheSettings()]]></ontextchange>
</control>
<control type="edit" id="1053">
<posx>820</posx>
<posy>590</posy>
<width>300</width>
<label>Username:</label>
<align>left</align>
<font>font18</font>
<onup>1032</onup>
<ondown>1034</ondown>
<ontextchange lang="python"><![CDATA[mb.SaveCacheSettings()]]></ontextchange>
</control>
<control type="edit" id="1054">
<posx>820</posx>
<posy>615</posy>
<width>300</width>
<label>Password:</label>
<align>left</align>
<font>font18</font>
<password>true</password>
<onup>1033</onup>
<ontextchange lang="python"><![CDATA[mb.SaveCacheSettings()]]></ontextchange>
</control>
</control>
-->
<!-- END: CACHE SETTINGS -->
<control type="group" id="9000">
<visible>App.HasSetting(loadingsettings)</visible>
<animation effect="fade" start="0" end="100" time="150">VisibleChange</animation>
<control type="image" id="9001">
<width>1280</width>
<height>720</height>
<texture>black.png</texture>
<animation effect="fade" start="80" end="80" time="0" condition="true">Conditional</animation>
</control>
<control type="label" id="9002">
<posy>0</posy>
<posx>0</posx>
<width>1280</width>
<height>720</height>
<align>center</align>
<aligny>center</aligny>
<font>sans40b</font>
<label>LOADING...</label>
<textcolor>ffffffff</textcolor>
</control>
</control>
</controls>
</window>

View File

@ -0,0 +1,526 @@
<?xml version="1.0"?>
<window type="window" id="14002">
<defaultcontrol always="true">2040</defaultcontrol>
<allowoverlay>true</allowoverlay>
<onload lang="python"><![CDATA[
import mythboxee
mb.LoadShow()
]]></onload>
<controls>
<control type="group" id="2020">
<control type="image" id="2021">
<width>1280</width>
<height>720</height>
<texture>mb_bg.png</texture>
</control>
<control type="image" id="2022">
<posx>10</posx>
<posy>10</posy>
<width>244</width>
<height>65</height>
<texture>logo.png</texture>
</control>
</control>
<!-- START: TOP MENU -->
<control type="grouplist" id="1040">
<posy>28</posy>
<posx>1106</posx>
<width>166</width>
<itemgap>10</itemgap>
<orientation>horizontal</orientation>
<ondown>2040</ondown>
<!--
940/334
<control type="button" id="1041">
<label>Refresh</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<height>30</height>
<align>center</align>
<onclick lang="python"><![CDATA[mb.RefreshMain()]]></onclick>
</control>
-->
<control type="button" id="1042">
<label>Settings</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<height>30</height>
<align>center</align>
<onclick lang="python"><![CDATA[mc.ActivateWindow(14004)]]></onclick>
</control>
<!--
780/500
<control type="button" id="1043">
<label>Send Logs</label>
<texturefocus>bg_btn.png</texturefocus>
<texturenofocus></texturenofocus>
<height>29</height>
<align>center</align>
<onclick lang="python"><![CDATA[mb.SendLogs()]]></onclick>
</control>
-->
</control>
<!-- END: TOP MENU -->
<control type="group" id="2030">
<description>details box</description>
<posx>28</posx>
<posy>81</posy>
<onleft>-</onleft>
<onright>2040</onright>
<onup>1040</onup>
<ondown>-</ondown>
<control type="image">
<description>details box's background</description>
<posx>0</posx>
<posy>0</posy>
<width>283</width>
<height>630</height>
<texture>mb_show.png</texture>
</control>
<control type="image">
<description>thumbnail</description>
<posx>15</posx>
<posy>10</posy>
<width>253</width>
<height>350</height>
<texture>$INFO[Container(2070).ListItem.Thumb]</texture>
<aspectratio>scale</aspectratio>
<fadetime>500</fadetime>
</control>
<control type="label">
<description>show title</description>
<posx>22</posx>
<posy>370</posy>
<width>228</width>
<height>30</height>
<align>left</align>
<aligny>top</aligny>
<scroll>true</scroll>
<label>$INFO[Container(2070).ListItem.Label]</label>
<font>font18</font>
<textcolor>FFFEFEFE</textcolor>
<wrapmultiline>true</wrapmultiline>
</control>
<control type="textbox">
<description>description</description>
<posx>22</posx>
<posy>395</posy>
<width>228</width>
<height>215</height>
<align>left</align>
<aligny>top</aligny>
<scroll>true</scroll>
<label>$INFO[Container(2070).ListItem.property(description)]</label>
<font>font16</font>
<textcolor>FFCCCCCC</textcolor>
<autoscroll delay="3000" time="2000" repeat="10000">Control.HasFocus(2040)</autoscroll>
</control>
</control>
<!-- end SHOW DETAILS -->
<!-- begin EPISODES LIST -->
<control type="list" id="2040">
<posx>345</posx>
<posy>82</posy>
<width>700</width>
<height>630</height>
<onleft>-</onleft>
<onright>2051</onright>
<onup>1040</onup>
<ondown>-</ondown>
<itemlayout width="700" height="105">
<control type="image">
<description>background</description>
<posx>0</posx>
<posy>0</posy>
<width>700</width>
<height>102</height>
<texture border="5">mb_item_big.png</texture>
</control>
<control type="image">
<description>thumbnail</description>
<posx>10</posx>
<posy>10</posy>
<width>126</width>
<height>82</height>
<info>ListItem.Thumb</info>
<aspectratio>scale</aspectratio>
<bordersize>3</bordersize>
<bordertexture>mb_thumb_bg.png</bordertexture>
</control>
<control type="label">
<description>episode title</description>
<posx>148</posx>
<posy>6</posy>
<width>440</width>
<height>30</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font18b</font>
<textcolor>FFFEFEFE</textcolor>
<wrapmultiline>true</wrapmultiline>
</control>
<control type="label">
<description>original air date</description>
<posx>690</posx>
<posy>5</posy>
<width>240</width>
<height>10</height>
<align>right</align>
<aligny>top</aligny>
<label>Air Date: $INFO[ListItem.Date]</label>
<font>font16</font>
<textcolor>FFCCCCCC</textcolor>
</control>
<control type="label">
<description>description</description>
<posx>148</posx>
<posy>31</posy>
<width>560</width>
<height>60</height>
<align>left</align>
<aligny>top</aligny>
<label>$INFO[ListItem.property(description)]</label>
<font>font16</font>
<textcolor>FFCCCCCC</textcolor>
<wrapmultiline>true</wrapmultiline>
</control>
</itemlayout>
<focusedlayout width="700" height="105">
<control type="togglebutton">
<description>background</description>
<posx>0</posx>
<posy>0</posy>
<width>700</width>
<height>102</height>
<texturefocus border="5">mb_item_big_hover.png</texturefocus>
<texturenofocus border="5">mb_item_big_hover.png</texturenofocus>
<alttexturefocus border="5">mb_item_big.png</alttexturefocus>
<alttexturenofocus border="5">mb_item_big.png</alttexturenofocus>
<usealttexture>!Control.HasFocus(2040)</usealttexture>
</control>
<control type="image">
<description>thumbnail</description>
<posx>10</posx>
<posy>10</posy>
<width>126</width>
<height>82</height>
<info>Listitem.Thumb</info>
<aspectratio>scale</aspectratio>
<bordertexture>mb_thumb_bg.png</bordertexture>
<bordersize>3</bordersize>
<visible>!Control.HasFocus(2040)</visible>
</control>
<control type="image">
<description>thumbnail hover</description>
<posx>10</posx>
<posy>10</posy>
<width>126</width>
<height>82</height>
<info>Listitem.Thumb</info>
<aspectratio>scale</aspectratio>
<bordertexture>mb_thumb_hover_bg.png</bordertexture>
<bordersize>3</bordersize>
<visible>Control.HasFocus(2040)</visible>
</control>
<control type="label">
<description>episode title</description>
<posx>148</posx>
<posy>6</posy>
<width>440</width>
<height>30</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font18b</font>
<textcolor>FFFEFEFE</textcolor>
</control>
<control type="label">
<description>original air date</description>
<posx>690</posx>
<posy>5</posy>
<width>240</width>
<height>10</height>
<align>right</align>
<aligny>top</aligny>
<label>Air Date: $INFO[ListItem.Date]</label>
<font>font16</font>
<textcolor>FFCCCCCC</textcolor>
</control>
<control type="label">
<description>description</description>
<posx>148</posx>
<posy>31</posy>
<width>560</width>
<height>60</height>
<align>left</align>
<aligny>top</aligny>
<label>$INFO[ListItem.property(description)]</label>
<font>font16</font>
<textcolor>FFCCCCCC</textcolor>
<wrapmultiline>true</wrapmultiline>
</control>
</focusedlayout>
</control>
<!-- end EPISODES LIST -->
<!-- begin SORT LIST -->
<control type="group" id="2050">
<control type="label">
<posx>1079</posx>
<posy>75</posy>
<label>SORT:</label>
<font>font14b</font>
<aligny>top</aligny>
<textcolor>FF999999</textcolor>
</control>
<control type="list" id="2051">
<posx>1070</posx>
<posy>100</posy>
<width>164</width>
<height>100</height>
<onleft>2040</onleft>
<onright>-</onright>
<onup>-</onup>
<ondown>2061</ondown>
<itemlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</itemlayout>
<focusedlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FFFFFFFF</textcolor>
<visible>Control.HasFocus(2051)</visible>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
<control type="label">
<visible>!Control.HasFocus(2051)</visible>
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</focusedlayout>
<content type="action">
<onclick lang="python"><![CDATA[mb.SortBy()]]></onclick>
</content>
</control>
</control>
<!-- end SORT LIST -->
<control type="group" id="2060">
<control type="label">
<posx>1079</posx>
<posy>200</posy>
<label>SORT DIRECTION:</label>
<font>font14b</font>
<aligny>top</aligny>
<textcolor>FF999999</textcolor>
</control>
<control type="list" id="2061">
<posx>1070</posx>
<posy>225</posy>
<width>164</width>
<height>100</height>
<onleft>2040</onleft>
<onright>-</onright>
<onup>2051</onup>
<ondown>2082</ondown>
<itemlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</itemlayout>
<focusedlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FFFFFFFF</textcolor>
<visible>Control.HasFocus(2061)</visible>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
<control type="label">
<visible>!Control.HasFocus(2061)</visible>
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</focusedlayout>
<content type="action">
<onclick lang="python"><![CDATA[mb.SortDir()]]></onclick>
</content>
</control>
</control>
<control type="group" id="2080">
<control type="label" id="2081">
<posx>1079</posx>
<posy>300</posy>
<label>FILTERS:</label>
<font>font14b</font>
<aligny>top</aligny>
<textcolor>FF999999</textcolor>
</control>
<control type="list" id="2082">
<posx>1070</posx>
<posy>325</posy>
<width>164</width>
<height>100</height>
<onleft>2040</onleft>
<onright>-</onright>
<onup>2061</onup>
<ondown>-</ondown>
<itemlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</itemlayout>
<focusedlayout width="164" height="27">
<control type="label">
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FFFFFFFF</textcolor>
<visible>Control.HasFocus(2082)</visible>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
<control type="label">
<visible>!Control.HasFocus(2082)</visible>
<description>title</description>
<posx>9</posx>
<posy>0</posy>
<width>146</width>
<height>25</height>
<align>left</align>
<aligny>top</aligny>
<info>ListItem.Label</info>
<font>font16</font>
<textcolor>FF999999</textcolor>
<selectedcolor>FFFFFFFF</selectedcolor>
</control>
</focusedlayout>
<content type="action">
<onclick lang="python"><![CDATA[mb.FilterEpisodes()]]></onclick>
</content>
</control>
</control>
<!-- begin SHOW DETAILS CONTAINER -->
<control type="list" id="2070">
<posx>1</posx>
<posy>1</posy>
<width>1</width>
<height>1</height>
<itemlayout width="1" height="1">
</itemlayout>
<focusedlayout width="1" height="1">
</focusedlayout>
</control>
<!-- end SHOW DETAILS CONTAINER -->
<control type="group">
<visible>App.HasSetting(loading)</visible>
<animation effect="fade" start="0" end="100" time="150">VisibleChange</animation>
<control type="image">
<width>1280</width>
<height>720</height>
<texture>black.png</texture>
<animation effect="fade" start="80" end="80" time="0" condition="true">Conditional</animation>
</control>
<control type="label">
<posx>530</posx>
<width>1280</width>
<height>720</height>
<align>cetner</align>
<aligny>center</aligny>
<font>sans40b</font>
<label>LOADING...</label>
<textcolor>ffffffff</textcolor>
</control>
</control>
</controls>
</window>

View File

@ -0,0 +1,132 @@
<window type="window" id="14003">
<allowoverlay>yes</allowoverlay>
<onload lang="python"><![CDATA[
import mc
import mythboxee
mb.StatusInit()
]]></onload>
<controls>
<control type="group" id="1010">
<control type="image" id="1011">
<width>1280</width>
<height>720</height>
<texture>mb_bg.png</texture>
</control>
<control type="image" id="1012">
<posx>10</posx>
<posy>10</posy>
<width>244</width>
<height>65</height>
<texture>logo.png</texture>
</control>
</control>
<!-- BEGIN ENCODER STATUS -->
<control type="group" id="1020">
<control type="image" id="1021">
<posx>20</posx>
<posy>120</posy>
<height>255</height>
<width>610</width>
<texture>bg_box.png</texture>
</control>
<control type="label" id="1022">
<posx>30</posx>
<posy>90</posy>
<label>Encoder Status</label>
<font>font28b</font>
</control>
</control>
<!-- END ENCODE STATUS -->
<!-- BEGIN SYSTEM INFORMATION -->
<control type="group" id="1030">
<control type="image" id="1031">
<posx>650</posx>
<posy>120</posy>
<height>255</height>
<width>610</width>
<texture>bg_box.png</texture>
</control>
<control type="label" id="1032">
<posx>660</posx>
<posy>90</posy>
<label>System Information</label>
<font>font28b</font>
</control>
</control>
<!-- END SYSTEM INFORMATION -->
<!-- BEGIN SCHEDULE -->
<control type="group" id="1040">
<control type="image" id="1041">
<posx>20</posx>
<posy>460</posy>
<height>240</height>
<width>610</width>
<texture>bg_box.png</texture>
</control>
<control type="label" id="1042">
<posx>30</posx>
<posy>430</posy>
<label>Upcoming Schedule</label>
<font>font28b</font>
</control>
</control>
<!-- END SCHEDULE -->
<!-- BEGIN JOB QUEUE -->
<control type="group" id="1050">
<control type="image" id="1051">
<posx>650</posx>
<posy>460</posy>
<height>240</height>
<width>610</width>
<texture>bg_box.png</texture>
</control>
<control type="label" id="1052">
<posx>660</posx>
<posy>430</posy>
<label>Job Queue</label>
<font>font28b</font>
</control>
</control>
<!-- END JOB QUEUE -->
<!-- BEGIN LOADING OVERLAY -->
<control type="group" id="9000">
<visible>App.HasSetting(loadingstatus)</visible>
<animation effect="fade" start="0" end="100" time="150">VisibleChange</animation>
<control type="image" id="9001">
<width>1280</width>
<height>720</height>
<texture>black.png</texture>
<animation effect="fade" start="80" end="80" time="0" condition="true">Conditional</animation>
</control>
<control type="label" id="9002">
<posy>0</posy>
<posx>0</posx>
<width>1280</width>
<height>720</height>
<align>center</align>
<aligny>center</aligny>
<font>sans40b</font>
<label>LOADING...</label>
<textcolor>ffffffff</textcolor>
</control>
</control>
<!-- END LOADING OVERLAY -->
</controls>
</window>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

51
xml/__init__.py Normal file
View File

@ -0,0 +1,51 @@
"""Core XML support for Python.
This package contains four sub-packages:
dom -- The W3C Document Object Model. This supports DOM Level 1 +
Namespaces.
parsers -- Python wrappers for XML parsers (currently only supports Expat).
sax -- The Simple API for XML, developed by XML-Dev, led by David
Megginson and ported to Python by Lars Marius Garshol. This
supports the SAX 2 API.
etree -- The ElementTree XML library. This is a subset of the full
ElementTree XML release.
"""
__all__ = ["dom", "parsers", "sax", "etree"]
# When being checked-out without options, this has the form
# "<dollar>Revision: x.y </dollar>"
# When exported using -kv, it is "x.y".
__version__ = "$Revision: 41660 $".split()[-2:][0]
_MINIMUM_XMLPLUS_VERSION = (0, 8, 4)
import os
# only prefer _xmlplus if the environment variable PY_USE_XMLPLUS is defined
if 'PY_USE_XMLPLUS' in os.environ:
try:
import _xmlplus
except ImportError:
pass
else:
try:
v = _xmlplus.version_info
except AttributeError:
# _xmlplus is too old; ignore it
pass
else:
if v >= _MINIMUM_XMLPLUS_VERSION:
import sys
_xmlplus.__path__.extend(__path__)
sys.modules[__name__] = _xmlplus
else:
del v

BIN
xml/__init__.pyc Normal file

Binary file not shown.

27
xml/dom/NodeFilter.py Normal file
View File

@ -0,0 +1,27 @@
# This is the Python mapping for interface NodeFilter from
# DOM2-Traversal-Range. It contains only constants.
class NodeFilter:
"""
This is the DOM2 NodeFilter interface. It contains only constants.
"""
FILTER_ACCEPT = 1
FILTER_REJECT = 2
FILTER_SKIP = 3
SHOW_ALL = 0xFFFFFFFFL
SHOW_ELEMENT = 0x00000001
SHOW_ATTRIBUTE = 0x00000002
SHOW_TEXT = 0x00000004
SHOW_CDATA_SECTION = 0x00000008
SHOW_ENTITY_REFERENCE = 0x00000010
SHOW_ENTITY = 0x00000020
SHOW_PROCESSING_INSTRUCTION = 0x00000040
SHOW_COMMENT = 0x00000080
SHOW_DOCUMENT = 0x00000100
SHOW_DOCUMENT_TYPE = 0x00000200
SHOW_DOCUMENT_FRAGMENT = 0x00000400
SHOW_NOTATION = 0x00000800
def acceptNode(self, node):
raise NotImplementedError

BIN
xml/dom/NodeFilter.pyc Normal file

Binary file not shown.

139
xml/dom/__init__.py Normal file
View File

@ -0,0 +1,139 @@
"""W3C Document Object Model implementation for Python.
The Python mapping of the Document Object Model is documented in the
Python Library Reference in the section on the xml.dom package.
This package contains the following modules:
minidom -- A simple implementation of the Level 1 DOM with namespace
support added (based on the Level 2 specification) and other
minor Level 2 functionality.
pulldom -- DOM builder supporting on-demand tree-building for selected
subtrees of the document.
"""
class Node:
"""Class giving the NodeType constants."""
# DOM implementations may use this as a base class for their own
# Node implementations. If they don't, the constants defined here
# should still be used as the canonical definitions as they match
# the values given in the W3C recommendation. Client code can
# safely refer to these values in all tests of Node.nodeType
# values.
ELEMENT_NODE = 1
ATTRIBUTE_NODE = 2
TEXT_NODE = 3
CDATA_SECTION_NODE = 4
ENTITY_REFERENCE_NODE = 5
ENTITY_NODE = 6
PROCESSING_INSTRUCTION_NODE = 7
COMMENT_NODE = 8
DOCUMENT_NODE = 9
DOCUMENT_TYPE_NODE = 10
DOCUMENT_FRAGMENT_NODE = 11
NOTATION_NODE = 12
#ExceptionCode
INDEX_SIZE_ERR = 1
DOMSTRING_SIZE_ERR = 2
HIERARCHY_REQUEST_ERR = 3
WRONG_DOCUMENT_ERR = 4
INVALID_CHARACTER_ERR = 5
NO_DATA_ALLOWED_ERR = 6
NO_MODIFICATION_ALLOWED_ERR = 7
NOT_FOUND_ERR = 8
NOT_SUPPORTED_ERR = 9
INUSE_ATTRIBUTE_ERR = 10
INVALID_STATE_ERR = 11
SYNTAX_ERR = 12
INVALID_MODIFICATION_ERR = 13
NAMESPACE_ERR = 14
INVALID_ACCESS_ERR = 15
VALIDATION_ERR = 16
class DOMException(Exception):
"""Abstract base class for DOM exceptions.
Exceptions with specific codes are specializations of this class."""
def __init__(self, *args, **kw):
if self.__class__ is DOMException:
raise RuntimeError(
"DOMException should not be instantiated directly")
Exception.__init__(self, *args, **kw)
def _get_code(self):
return self.code
class IndexSizeErr(DOMException):
code = INDEX_SIZE_ERR
class DomstringSizeErr(DOMException):
code = DOMSTRING_SIZE_ERR
class HierarchyRequestErr(DOMException):
code = HIERARCHY_REQUEST_ERR
class WrongDocumentErr(DOMException):
code = WRONG_DOCUMENT_ERR
class InvalidCharacterErr(DOMException):
code = INVALID_CHARACTER_ERR
class NoDataAllowedErr(DOMException):
code = NO_DATA_ALLOWED_ERR
class NoModificationAllowedErr(DOMException):
code = NO_MODIFICATION_ALLOWED_ERR
class NotFoundErr(DOMException):
code = NOT_FOUND_ERR
class NotSupportedErr(DOMException):
code = NOT_SUPPORTED_ERR
class InuseAttributeErr(DOMException):
code = INUSE_ATTRIBUTE_ERR
class InvalidStateErr(DOMException):
code = INVALID_STATE_ERR
class SyntaxErr(DOMException):
code = SYNTAX_ERR
class InvalidModificationErr(DOMException):
code = INVALID_MODIFICATION_ERR
class NamespaceErr(DOMException):
code = NAMESPACE_ERR
class InvalidAccessErr(DOMException):
code = INVALID_ACCESS_ERR
class ValidationErr(DOMException):
code = VALIDATION_ERR
class UserDataHandler:
"""Class giving the operation constants for UserDataHandler.handle()."""
# Based on DOM Level 3 (WD 9 April 2002)
NODE_CLONED = 1
NODE_IMPORTED = 2
NODE_DELETED = 3
NODE_RENAMED = 4
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/"
XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
EMPTY_NAMESPACE = None
EMPTY_PREFIX = None
from domreg import getDOMImplementation,registerDOMImplementation

BIN
xml/dom/__init__.pyc Normal file

Binary file not shown.

99
xml/dom/domreg.py Normal file
View File

@ -0,0 +1,99 @@
"""Registration facilities for DOM. This module should not be used
directly. Instead, the functions getDOMImplementation and
registerDOMImplementation should be imported from xml.dom."""
from xml.dom.minicompat import * # isinstance, StringTypes
# This is a list of well-known implementations. Well-known names
# should be published by posting to xml-sig@python.org, and are
# subsequently recorded in this file.
well_known_implementations = {
'minidom':'xml.dom.minidom',
'4DOM': 'xml.dom.DOMImplementation',
}
# DOM implementations not officially registered should register
# themselves with their
registered = {}
def registerDOMImplementation(name, factory):
"""registerDOMImplementation(name, factory)
Register the factory function with the name. The factory function
should return an object which implements the DOMImplementation
interface. The factory function can either return the same object,
or a new one (e.g. if that implementation supports some
customization)."""
registered[name] = factory
def _good_enough(dom, features):
"_good_enough(dom, features) -> Return 1 if the dom offers the features"
for f,v in features:
if not dom.hasFeature(f,v):
return 0
return 1
def getDOMImplementation(name = None, features = ()):
"""getDOMImplementation(name = None, features = ()) -> DOM implementation.
Return a suitable DOM implementation. The name is either
well-known, the module name of a DOM implementation, or None. If
it is not None, imports the corresponding module and returns
DOMImplementation object if the import succeeds.
If name is not given, consider the available implementations to
find one with the required feature set. If no implementation can
be found, raise an ImportError. The features list must be a sequence
of (feature, version) pairs which are passed to hasFeature."""
import os
creator = None
mod = well_known_implementations.get(name)
if mod:
mod = __import__(mod, {}, {}, ['getDOMImplementation'])
return mod.getDOMImplementation()
elif name:
return registered[name]()
elif os.environ.has_key("PYTHON_DOM"):
return getDOMImplementation(name = os.environ["PYTHON_DOM"])
# User did not specify a name, try implementations in arbitrary
# order, returning the one that has the required features
if isinstance(features, StringTypes):
features = _parse_feature_string(features)
for creator in registered.values():
dom = creator()
if _good_enough(dom, features):
return dom
for creator in well_known_implementations.keys():
try:
dom = getDOMImplementation(name = creator)
except StandardError: # typically ImportError, or AttributeError
continue
if _good_enough(dom, features):
return dom
raise ImportError,"no suitable DOM implementation found"
def _parse_feature_string(s):
features = []
parts = s.split()
i = 0
length = len(parts)
while i < length:
feature = parts[i]
if feature[0] in "0123456789":
raise ValueError, "bad feature name: %r" % (feature,)
i = i + 1
version = None
if i < length:
v = parts[i]
if v[0] in "0123456789":
i = i + 1
version = v
features.append((feature, version))
return tuple(features)

BIN
xml/dom/domreg.pyc Normal file

Binary file not shown.

983
xml/dom/expatbuilder.py Normal file
View File

@ -0,0 +1,983 @@
"""Facility to use the Expat parser to load a minidom instance
from a string or file.
This avoids all the overhead of SAX and pulldom to gain performance.
"""
# Warning!
#
# This module is tightly bound to the implementation details of the
# minidom DOM and can't be used with other DOM implementations. This
# is due, in part, to a lack of appropriate methods in the DOM (there is
# no way to create Entity and Notation nodes via the DOM Level 2
# interface), and for performance. The later is the cause of some fairly
# cryptic code.
#
# Performance hacks:
#
# - .character_data_handler() has an extra case in which continuing
# data is appended to an existing Text node; this can be a
# speedup since pyexpat can break up character data into multiple
# callbacks even though we set the buffer_text attribute on the
# parser. This also gives us the advantage that we don't need a
# separate normalization pass.
#
# - Determining that a node exists is done using an identity comparison
# with None rather than a truth test; this avoids searching for and
# calling any methods on the node object if it exists. (A rather
# nice speedup is achieved this way as well!)
from xml.dom import xmlbuilder, minidom, Node
from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE
from xml.parsers import expat
from xml.dom.minidom import _append_child, _set_attribute_node
from xml.dom.NodeFilter import NodeFilter
from xml.dom.minicompat import *
TEXT_NODE = Node.TEXT_NODE
CDATA_SECTION_NODE = Node.CDATA_SECTION_NODE
DOCUMENT_NODE = Node.DOCUMENT_NODE
FILTER_ACCEPT = xmlbuilder.DOMBuilderFilter.FILTER_ACCEPT
FILTER_REJECT = xmlbuilder.DOMBuilderFilter.FILTER_REJECT
FILTER_SKIP = xmlbuilder.DOMBuilderFilter.FILTER_SKIP
FILTER_INTERRUPT = xmlbuilder.DOMBuilderFilter.FILTER_INTERRUPT
theDOMImplementation = minidom.getDOMImplementation()
# Expat typename -> TypeInfo
_typeinfo_map = {
"CDATA": minidom.TypeInfo(None, "cdata"),
"ENUM": minidom.TypeInfo(None, "enumeration"),
"ENTITY": minidom.TypeInfo(None, "entity"),
"ENTITIES": minidom.TypeInfo(None, "entities"),
"ID": minidom.TypeInfo(None, "id"),
"IDREF": minidom.TypeInfo(None, "idref"),
"IDREFS": minidom.TypeInfo(None, "idrefs"),
"NMTOKEN": minidom.TypeInfo(None, "nmtoken"),
"NMTOKENS": minidom.TypeInfo(None, "nmtokens"),
}
class ElementInfo(object):
__slots__ = '_attr_info', '_model', 'tagName'
def __init__(self, tagName, model=None):
self.tagName = tagName
self._attr_info = []
self._model = model
def __getstate__(self):
return self._attr_info, self._model, self.tagName
def __setstate__(self, state):
self._attr_info, self._model, self.tagName = state
def getAttributeType(self, aname):
for info in self._attr_info:
if info[1] == aname:
t = info[-2]
if t[0] == "(":
return _typeinfo_map["ENUM"]
else:
return _typeinfo_map[info[-2]]
return minidom._no_type
def getAttributeTypeNS(self, namespaceURI, localName):
return minidom._no_type
def isElementContent(self):
if self._model:
type = self._model[0]
return type not in (expat.model.XML_CTYPE_ANY,
expat.model.XML_CTYPE_MIXED)
else:
return False
def isEmpty(self):
if self._model:
return self._model[0] == expat.model.XML_CTYPE_EMPTY
else:
return False
def isId(self, aname):
for info in self._attr_info:
if info[1] == aname:
return info[-2] == "ID"
return False
def isIdNS(self, euri, ename, auri, aname):
# not sure this is meaningful
return self.isId((auri, aname))
def _intern(builder, s):
return builder._intern_setdefault(s, s)
def _parse_ns_name(builder, name):
assert ' ' in name
parts = name.split(' ')
intern = builder._intern_setdefault
if len(parts) == 3:
uri, localname, prefix = parts
prefix = intern(prefix, prefix)
qname = "%s:%s" % (prefix, localname)
qname = intern(qname, qname)
localname = intern(localname, localname)
else:
uri, localname = parts
prefix = EMPTY_PREFIX
qname = localname = intern(localname, localname)
return intern(uri, uri), localname, prefix, qname
class ExpatBuilder:
"""Document builder that uses Expat to build a ParsedXML.DOM document
instance."""
def __init__(self, options=None):
if options is None:
options = xmlbuilder.Options()
self._options = options
if self._options.filter is not None:
self._filter = FilterVisibilityController(self._options.filter)
else:
self._filter = None
# This *really* doesn't do anything in this case, so
# override it with something fast & minimal.
self._finish_start_element = id
self._parser = None
self.reset()
def createParser(self):
"""Create a new parser object."""
return expat.ParserCreate()
def getParser(self):
"""Return the parser object, creating a new one if needed."""
if not self._parser:
self._parser = self.createParser()
self._intern_setdefault = self._parser.intern.setdefault
self._parser.buffer_text = True
self._parser.ordered_attributes = True
self._parser.specified_attributes = True
self.install(self._parser)
return self._parser
def reset(self):
"""Free all data structures used during DOM construction."""
self.document = theDOMImplementation.createDocument(
EMPTY_NAMESPACE, None, None)
self.curNode = self.document
self._elem_info = self.document._elem_info
self._cdata = False
def install(self, parser):
"""Install the callbacks needed to build the DOM into the parser."""
# This creates circular references!
parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler
parser.StartElementHandler = self.first_element_handler
parser.EndElementHandler = self.end_element_handler
parser.ProcessingInstructionHandler = self.pi_handler
if self._options.entities:
parser.EntityDeclHandler = self.entity_decl_handler
parser.NotationDeclHandler = self.notation_decl_handler
if self._options.comments:
parser.CommentHandler = self.comment_handler
if self._options.cdata_sections:
parser.StartCdataSectionHandler = self.start_cdata_section_handler
parser.EndCdataSectionHandler = self.end_cdata_section_handler
parser.CharacterDataHandler = self.character_data_handler_cdata
else:
parser.CharacterDataHandler = self.character_data_handler
parser.ExternalEntityRefHandler = self.external_entity_ref_handler
parser.XmlDeclHandler = self.xml_decl_handler
parser.ElementDeclHandler = self.element_decl_handler
parser.AttlistDeclHandler = self.attlist_decl_handler
def parseFile(self, file):
"""Parse a document from a file object, returning the document
node."""
parser = self.getParser()
first_buffer = True
try:
while 1:
buffer = file.read(16*1024)
if not buffer:
break
parser.Parse(buffer, 0)
if first_buffer and self.document.documentElement:
self._setup_subset(buffer)
first_buffer = False
parser.Parse("", True)
except ParseEscape:
pass
doc = self.document
self.reset()
self._parser = None
return doc
def parseString(self, string):
"""Parse a document from a string, returning the document node."""
parser = self.getParser()
try:
parser.Parse(string, True)
self._setup_subset(string)
except ParseEscape:
pass
doc = self.document
self.reset()
self._parser = None
return doc
def _setup_subset(self, buffer):
"""Load the internal subset if there might be one."""
if self.document.doctype:
extractor = InternalSubsetExtractor()
extractor.parseString(buffer)
subset = extractor.getSubset()
self.document.doctype.internalSubset = subset
def start_doctype_decl_handler(self, doctypeName, systemId, publicId,
has_internal_subset):
doctype = self.document.implementation.createDocumentType(
doctypeName, publicId, systemId)
doctype.ownerDocument = self.document
self.document.childNodes.append(doctype)
self.document.doctype = doctype
if self._filter and self._filter.acceptNode(doctype) == FILTER_REJECT:
self.document.doctype = None
del self.document.childNodes[-1]
doctype = None
self._parser.EntityDeclHandler = None
self._parser.NotationDeclHandler = None
if has_internal_subset:
if doctype is not None:
doctype.entities._seq = []
doctype.notations._seq = []
self._parser.CommentHandler = None
self._parser.ProcessingInstructionHandler = None
self._parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler
def end_doctype_decl_handler(self):
if self._options.comments:
self._parser.CommentHandler = self.comment_handler
self._parser.ProcessingInstructionHandler = self.pi_handler
if not (self._elem_info or self._filter):
self._finish_end_element = id
def pi_handler(self, target, data):
node = self.document.createProcessingInstruction(target, data)
_append_child(self.curNode, node)
if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:
self.curNode.removeChild(node)
def character_data_handler_cdata(self, data):
childNodes = self.curNode.childNodes
if self._cdata:
if ( self._cdata_continue
and childNodes[-1].nodeType == CDATA_SECTION_NODE):
childNodes[-1].appendData(data)
return
node = self.document.createCDATASection(data)
self._cdata_continue = True
elif childNodes and childNodes[-1].nodeType == TEXT_NODE:
node = childNodes[-1]
value = node.data + data
d = node.__dict__
d['data'] = d['nodeValue'] = value
return
else:
node = minidom.Text()
d = node.__dict__
d['data'] = d['nodeValue'] = data
d['ownerDocument'] = self.document
_append_child(self.curNode, node)
def character_data_handler(self, data):
childNodes = self.curNode.childNodes
if childNodes and childNodes[-1].nodeType == TEXT_NODE:
node = childNodes[-1]
d = node.__dict__
d['data'] = d['nodeValue'] = node.data + data
return
node = minidom.Text()
d = node.__dict__
d['data'] = d['nodeValue'] = node.data + data
d['ownerDocument'] = self.document
_append_child(self.curNode, node)
def entity_decl_handler(self, entityName, is_parameter_entity, value,
base, systemId, publicId, notationName):
if is_parameter_entity:
# we don't care about parameter entities for the DOM
return
if not self._options.entities:
return
node = self.document._create_entity(entityName, publicId,
systemId, notationName)
if value is not None:
# internal entity
# node *should* be readonly, but we'll cheat
child = self.document.createTextNode(value)
node.childNodes.append(child)
self.document.doctype.entities._seq.append(node)
if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:
del self.document.doctype.entities._seq[-1]
def notation_decl_handler(self, notationName, base, systemId, publicId):
node = self.document._create_notation(notationName, publicId, systemId)
self.document.doctype.notations._seq.append(node)
if self._filter and self._filter.acceptNode(node) == FILTER_ACCEPT:
del self.document.doctype.notations._seq[-1]
def comment_handler(self, data):
node = self.document.createComment(data)
_append_child(self.curNode, node)
if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:
self.curNode.removeChild(node)
def start_cdata_section_handler(self):
self._cdata = True
self._cdata_continue = False
def end_cdata_section_handler(self):
self._cdata = False
self._cdata_continue = False
def external_entity_ref_handler(self, context, base, systemId, publicId):
return 1
def first_element_handler(self, name, attributes):
if self._filter is None and not self._elem_info:
self._finish_end_element = id
self.getParser().StartElementHandler = self.start_element_handler
self.start_element_handler(name, attributes)
def start_element_handler(self, name, attributes):
node = self.document.createElement(name)
_append_child(self.curNode, node)
self.curNode = node
if attributes:
for i in range(0, len(attributes), 2):
a = minidom.Attr(attributes[i], EMPTY_NAMESPACE,
None, EMPTY_PREFIX)
value = attributes[i+1]
d = a.childNodes[0].__dict__
d['data'] = d['nodeValue'] = value
d = a.__dict__
d['value'] = d['nodeValue'] = value
d['ownerDocument'] = self.document
_set_attribute_node(node, a)
if node is not self.document.documentElement:
self._finish_start_element(node)
def _finish_start_element(self, node):
if self._filter:
# To be general, we'd have to call isSameNode(), but this
# is sufficient for minidom:
if node is self.document.documentElement:
return
filt = self._filter.startContainer(node)
if filt == FILTER_REJECT:
# ignore this node & all descendents
Rejecter(self)
elif filt == FILTER_SKIP:
# ignore this node, but make it's children become
# children of the parent node
Skipper(self)
else:
return
self.curNode = node.parentNode
node.parentNode.removeChild(node)
node.unlink()
# If this ever changes, Namespaces.end_element_handler() needs to
# be changed to match.
#
def end_element_handler(self, name):
curNode = self.curNode
self.curNode = curNode.parentNode
self._finish_end_element(curNode)
def _finish_end_element(self, curNode):
info = self._elem_info.get(curNode.tagName)
if info:
self._handle_white_text_nodes(curNode, info)
if self._filter:
if curNode is self.document.documentElement:
return
if self._filter.acceptNode(curNode) == FILTER_REJECT:
self.curNode.removeChild(curNode)
curNode.unlink()
def _handle_white_text_nodes(self, node, info):
if (self._options.whitespace_in_element_content
or not info.isElementContent()):
return
# We have element type information and should remove ignorable
# whitespace; identify for text nodes which contain only
# whitespace.
L = []
for child in node.childNodes:
if child.nodeType == TEXT_NODE and not child.data.strip():
L.append(child)
# Remove ignorable whitespace from the tree.
for child in L:
node.removeChild(child)
def element_decl_handler(self, name, model):
info = self._elem_info.get(name)
if info is None:
self._elem_info[name] = ElementInfo(name, model)
else:
assert info._model is None
info._model = model
def attlist_decl_handler(self, elem, name, type, default, required):
info = self._elem_info.get(elem)
if info is None:
info = ElementInfo(elem)
self._elem_info[elem] = info
info._attr_info.append(
[None, name, None, None, default, 0, type, required])
def xml_decl_handler(self, version, encoding, standalone):
self.document.version = version
self.document.encoding = encoding
# This is still a little ugly, thanks to the pyexpat API. ;-(
if standalone >= 0:
if standalone:
self.document.standalone = True
else:
self.document.standalone = False
# Don't include FILTER_INTERRUPT, since that's checked separately
# where allowed.
_ALLOWED_FILTER_RETURNS = (FILTER_ACCEPT, FILTER_REJECT, FILTER_SKIP)
class FilterVisibilityController(object):
"""Wrapper around a DOMBuilderFilter which implements the checks
to make the whatToShow filter attribute work."""
__slots__ = 'filter',
def __init__(self, filter):
self.filter = filter
def startContainer(self, node):
mask = self._nodetype_mask[node.nodeType]
if self.filter.whatToShow & mask:
val = self.filter.startContainer(node)
if val == FILTER_INTERRUPT:
raise ParseEscape
if val not in _ALLOWED_FILTER_RETURNS:
raise ValueError, \
"startContainer() returned illegal value: " + repr(val)
return val
else:
return FILTER_ACCEPT
def acceptNode(self, node):
mask = self._nodetype_mask[node.nodeType]
if self.filter.whatToShow & mask:
val = self.filter.acceptNode(node)
if val == FILTER_INTERRUPT:
raise ParseEscape
if val == FILTER_SKIP:
# move all child nodes to the parent, and remove this node
parent = node.parentNode
for child in node.childNodes[:]:
parent.appendChild(child)
# node is handled by the caller
return FILTER_REJECT
if val not in _ALLOWED_FILTER_RETURNS:
raise ValueError, \
"acceptNode() returned illegal value: " + repr(val)
return val
else:
return FILTER_ACCEPT
_nodetype_mask = {
Node.ELEMENT_NODE: NodeFilter.SHOW_ELEMENT,
Node.ATTRIBUTE_NODE: NodeFilter.SHOW_ATTRIBUTE,
Node.TEXT_NODE: NodeFilter.SHOW_TEXT,
Node.CDATA_SECTION_NODE: NodeFilter.SHOW_CDATA_SECTION,
Node.ENTITY_REFERENCE_NODE: NodeFilter.SHOW_ENTITY_REFERENCE,
Node.ENTITY_NODE: NodeFilter.SHOW_ENTITY,
Node.PROCESSING_INSTRUCTION_NODE: NodeFilter.SHOW_PROCESSING_INSTRUCTION,
Node.COMMENT_NODE: NodeFilter.SHOW_COMMENT,
Node.DOCUMENT_NODE: NodeFilter.SHOW_DOCUMENT,
Node.DOCUMENT_TYPE_NODE: NodeFilter.SHOW_DOCUMENT_TYPE,
Node.DOCUMENT_FRAGMENT_NODE: NodeFilter.SHOW_DOCUMENT_FRAGMENT,
Node.NOTATION_NODE: NodeFilter.SHOW_NOTATION,
}
class FilterCrutch(object):
__slots__ = '_builder', '_level', '_old_start', '_old_end'
def __init__(self, builder):
self._level = 0
self._builder = builder
parser = builder._parser
self._old_start = parser.StartElementHandler
self._old_end = parser.EndElementHandler
parser.StartElementHandler = self.start_element_handler
parser.EndElementHandler = self.end_element_handler
class Rejecter(FilterCrutch):
__slots__ = ()
def __init__(self, builder):
FilterCrutch.__init__(self, builder)
parser = builder._parser
for name in ("ProcessingInstructionHandler",
"CommentHandler",
"CharacterDataHandler",
"StartCdataSectionHandler",
"EndCdataSectionHandler",
"ExternalEntityRefHandler",
):
setattr(parser, name, None)
def start_element_handler(self, *args):
self._level = self._level + 1
def end_element_handler(self, *args):
if self._level == 0:
# restore the old handlers
parser = self._builder._parser
self._builder.install(parser)
parser.StartElementHandler = self._old_start
parser.EndElementHandler = self._old_end
else:
self._level = self._level - 1
class Skipper(FilterCrutch):
__slots__ = ()
def start_element_handler(self, *args):
node = self._builder.curNode
self._old_start(*args)
if self._builder.curNode is not node:
self._level = self._level + 1
def end_element_handler(self, *args):
if self._level == 0:
# We're popping back out of the node we're skipping, so we
# shouldn't need to do anything but reset the handlers.
self._builder._parser.StartElementHandler = self._old_start
self._builder._parser.EndElementHandler = self._old_end
self._builder = None
else:
self._level = self._level - 1
self._old_end(*args)
# framework document used by the fragment builder.
# Takes a string for the doctype, subset string, and namespace attrs string.
_FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID = \
"http://xml.python.org/entities/fragment-builder/internal"
_FRAGMENT_BUILDER_TEMPLATE = (
'''\
<!DOCTYPE wrapper
%%s [
<!ENTITY fragment-builder-internal
SYSTEM "%s">
%%s
]>
<wrapper %%s
>&fragment-builder-internal;</wrapper>'''
% _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID)
class FragmentBuilder(ExpatBuilder):
"""Builder which constructs document fragments given XML source
text and a context node.
The context node is expected to provide information about the
namespace declarations which are in scope at the start of the
fragment.
"""
def __init__(self, context, options=None):
if context.nodeType == DOCUMENT_NODE:
self.originalDocument = context
self.context = context
else:
self.originalDocument = context.ownerDocument
self.context = context
ExpatBuilder.__init__(self, options)
def reset(self):
ExpatBuilder.reset(self)
self.fragment = None
def parseFile(self, file):
"""Parse a document fragment from a file object, returning the
fragment node."""
return self.parseString(file.read())
def parseString(self, string):
"""Parse a document fragment from a string, returning the
fragment node."""
self._source = string
parser = self.getParser()
doctype = self.originalDocument.doctype
ident = ""
if doctype:
subset = doctype.internalSubset or self._getDeclarations()
if doctype.publicId:
ident = ('PUBLIC "%s" "%s"'
% (doctype.publicId, doctype.systemId))
elif doctype.systemId:
ident = 'SYSTEM "%s"' % doctype.systemId
else:
subset = ""
nsattrs = self._getNSattrs() # get ns decls from node's ancestors
document = _FRAGMENT_BUILDER_TEMPLATE % (ident, subset, nsattrs)
try:
parser.Parse(document, 1)
except:
self.reset()
raise
fragment = self.fragment
self.reset()
## self._parser = None
return fragment
def _getDeclarations(self):
"""Re-create the internal subset from the DocumentType node.
This is only needed if we don't already have the
internalSubset as a string.
"""
doctype = self.context.ownerDocument.doctype
s = ""
if doctype:
for i in range(doctype.notations.length):
notation = doctype.notations.item(i)
if s:
s = s + "\n "
s = "%s<!NOTATION %s" % (s, notation.nodeName)
if notation.publicId:
s = '%s PUBLIC "%s"\n "%s">' \
% (s, notation.publicId, notation.systemId)
else:
s = '%s SYSTEM "%s">' % (s, notation.systemId)
for i in range(doctype.entities.length):
entity = doctype.entities.item(i)
if s:
s = s + "\n "
s = "%s<!ENTITY %s" % (s, entity.nodeName)
if entity.publicId:
s = '%s PUBLIC "%s"\n "%s"' \
% (s, entity.publicId, entity.systemId)
elif entity.systemId:
s = '%s SYSTEM "%s"' % (s, entity.systemId)
else:
s = '%s "%s"' % (s, entity.firstChild.data)
if entity.notationName:
s = "%s NOTATION %s" % (s, entity.notationName)
s = s + ">"
return s
def _getNSattrs(self):
return ""
def external_entity_ref_handler(self, context, base, systemId, publicId):
if systemId == _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID:
# this entref is the one that we made to put the subtree
# in; all of our given input is parsed in here.
old_document = self.document
old_cur_node = self.curNode
parser = self._parser.ExternalEntityParserCreate(context)
# put the real document back, parse into the fragment to return
self.document = self.originalDocument
self.fragment = self.document.createDocumentFragment()
self.curNode = self.fragment
try:
parser.Parse(self._source, 1)
finally:
self.curNode = old_cur_node
self.document = old_document
self._source = None
return -1
else:
return ExpatBuilder.external_entity_ref_handler(
self, context, base, systemId, publicId)
class Namespaces:
"""Mix-in class for builders; adds support for namespaces."""
def _initNamespaces(self):
# list of (prefix, uri) ns declarations. Namespace attrs are
# constructed from this and added to the element's attrs.
self._ns_ordered_prefixes = []
def createParser(self):
"""Create a new namespace-handling parser."""
parser = expat.ParserCreate(namespace_separator=" ")
parser.namespace_prefixes = True
return parser
def install(self, parser):
"""Insert the namespace-handlers onto the parser."""
ExpatBuilder.install(self, parser)
if self._options.namespace_declarations:
parser.StartNamespaceDeclHandler = (
self.start_namespace_decl_handler)
def start_namespace_decl_handler(self, prefix, uri):
"""Push this namespace declaration on our storage."""
self._ns_ordered_prefixes.append((prefix, uri))
def start_element_handler(self, name, attributes):
if ' ' in name:
uri, localname, prefix, qname = _parse_ns_name(self, name)
else:
uri = EMPTY_NAMESPACE
qname = name
localname = None
prefix = EMPTY_PREFIX
node = minidom.Element(qname, uri, prefix, localname)
node.ownerDocument = self.document
_append_child(self.curNode, node)
self.curNode = node
if self._ns_ordered_prefixes:
for prefix, uri in self._ns_ordered_prefixes:
if prefix:
a = minidom.Attr(_intern(self, 'xmlns:' + prefix),
XMLNS_NAMESPACE, prefix, "xmlns")
else:
a = minidom.Attr("xmlns", XMLNS_NAMESPACE,
"xmlns", EMPTY_PREFIX)
d = a.childNodes[0].__dict__
d['data'] = d['nodeValue'] = uri
d = a.__dict__
d['value'] = d['nodeValue'] = uri
d['ownerDocument'] = self.document
_set_attribute_node(node, a)
del self._ns_ordered_prefixes[:]
if attributes:
_attrs = node._attrs
_attrsNS = node._attrsNS
for i in range(0, len(attributes), 2):
aname = attributes[i]
value = attributes[i+1]
if ' ' in aname:
uri, localname, prefix, qname = _parse_ns_name(self, aname)
a = minidom.Attr(qname, uri, localname, prefix)
_attrs[qname] = a
_attrsNS[(uri, localname)] = a
else:
a = minidom.Attr(aname, EMPTY_NAMESPACE,
aname, EMPTY_PREFIX)
_attrs[aname] = a
_attrsNS[(EMPTY_NAMESPACE, aname)] = a
d = a.childNodes[0].__dict__
d['data'] = d['nodeValue'] = value
d = a.__dict__
d['ownerDocument'] = self.document
d['value'] = d['nodeValue'] = value
d['ownerElement'] = node
if __debug__:
# This only adds some asserts to the original
# end_element_handler(), so we only define this when -O is not
# used. If changing one, be sure to check the other to see if
# it needs to be changed as well.
#
def end_element_handler(self, name):
curNode = self.curNode
if ' ' in name:
uri, localname, prefix, qname = _parse_ns_name(self, name)
assert (curNode.namespaceURI == uri
and curNode.localName == localname
and curNode.prefix == prefix), \
"element stack messed up! (namespace)"
else:
assert curNode.nodeName == name, \
"element stack messed up - bad nodeName"
assert curNode.namespaceURI == EMPTY_NAMESPACE, \
"element stack messed up - bad namespaceURI"
self.curNode = curNode.parentNode
self._finish_end_element(curNode)
class ExpatBuilderNS(Namespaces, ExpatBuilder):
"""Document builder that supports namespaces."""
def reset(self):
ExpatBuilder.reset(self)
self._initNamespaces()
class FragmentBuilderNS(Namespaces, FragmentBuilder):
"""Fragment builder that supports namespaces."""
def reset(self):
FragmentBuilder.reset(self)
self._initNamespaces()
def _getNSattrs(self):
"""Return string of namespace attributes from this element and
ancestors."""
# XXX This needs to be re-written to walk the ancestors of the
# context to build up the namespace information from
# declarations, elements, and attributes found in context.
# Otherwise we have to store a bunch more data on the DOM
# (though that *might* be more reliable -- not clear).
attrs = ""
context = self.context
L = []
while context:
if hasattr(context, '_ns_prefix_uri'):
for prefix, uri in context._ns_prefix_uri.items():
# add every new NS decl from context to L and attrs string
if prefix in L:
continue
L.append(prefix)
if prefix:
declname = "xmlns:" + prefix
else:
declname = "xmlns"
if attrs:
attrs = "%s\n %s='%s'" % (attrs, declname, uri)
else:
attrs = " %s='%s'" % (declname, uri)
context = context.parentNode
return attrs
class ParseEscape(Exception):
"""Exception raised to short-circuit parsing in InternalSubsetExtractor."""
pass
class InternalSubsetExtractor(ExpatBuilder):
"""XML processor which can rip out the internal document type subset."""
subset = None
def getSubset(self):
"""Return the internal subset as a string."""
return self.subset
def parseFile(self, file):
try:
ExpatBuilder.parseFile(self, file)
except ParseEscape:
pass
def parseString(self, string):
try:
ExpatBuilder.parseString(self, string)
except ParseEscape:
pass
def install(self, parser):
parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler
parser.StartElementHandler = self.start_element_handler
def start_doctype_decl_handler(self, name, publicId, systemId,
has_internal_subset):
if has_internal_subset:
parser = self.getParser()
self.subset = []
parser.DefaultHandler = self.subset.append
parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler
else:
raise ParseEscape()
def end_doctype_decl_handler(self):
s = ''.join(self.subset).replace('\r\n', '\n').replace('\r', '\n')
self.subset = s
raise ParseEscape()
def start_element_handler(self, name, attrs):
raise ParseEscape()
def parse(file, namespaces=True):
"""Parse a document, returning the resulting Document node.
'file' may be either a file name or an open file object.
"""
if namespaces:
builder = ExpatBuilderNS()
else:
builder = ExpatBuilder()
if isinstance(file, StringTypes):
fp = open(file, 'rb')
try:
result = builder.parseFile(fp)
finally:
fp.close()
else:
result = builder.parseFile(file)
return result
def parseString(string, namespaces=True):
"""Parse a document from a string, returning the resulting
Document node.
"""
if namespaces:
builder = ExpatBuilderNS()
else:
builder = ExpatBuilder()
return builder.parseString(string)
def parseFragment(file, context, namespaces=True):
"""Parse a fragment of a document, given the context from which it
was originally extracted. context should be the parent of the
node(s) which are in the fragment.
'file' may be either a file name or an open file object.
"""
if namespaces:
builder = FragmentBuilderNS(context)
else:
builder = FragmentBuilder(context)
if isinstance(file, StringTypes):
fp = open(file, 'rb')
try:
result = builder.parseFile(fp)
finally:
fp.close()
else:
result = builder.parseFile(file)
return result
def parseFragmentString(string, context, namespaces=True):
"""Parse a fragment of a document from a string, given the context
from which it was originally extracted. context should be the
parent of the node(s) which are in the fragment.
"""
if namespaces:
builder = FragmentBuilderNS(context)
else:
builder = FragmentBuilder(context)
return builder.parseString(string)
def makeBuilder(options):
"""Create a builder based on an Options object."""
if options.namespaces:
return ExpatBuilderNS(options)
else:
return ExpatBuilder(options)

BIN
xml/dom/expatbuilder.pyc Normal file

Binary file not shown.

110
xml/dom/minicompat.py Normal file
View File

@ -0,0 +1,110 @@
"""Python version compatibility support for minidom."""
# This module should only be imported using "import *".
#
# The following names are defined:
#
# NodeList -- lightest possible NodeList implementation
#
# EmptyNodeList -- lightest possible NodeList that is guarateed to
# remain empty (immutable)
#
# StringTypes -- tuple of defined string types
#
# defproperty -- function used in conjunction with GetattrMagic;
# using these together is needed to make them work
# as efficiently as possible in both Python 2.2+
# and older versions. For example:
#
# class MyClass(GetattrMagic):
# def _get_myattr(self):
# return something
#
# defproperty(MyClass, "myattr",
# "return some value")
#
# For Python 2.2 and newer, this will construct a
# property object on the class, which avoids
# needing to override __getattr__(). It will only
# work for read-only attributes.
#
# For older versions of Python, inheriting from
# GetattrMagic will use the traditional
# __getattr__() hackery to achieve the same effect,
# but less efficiently.
#
# defproperty() should be used for each version of
# the relevant _get_<property>() function.
__all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty"]
import xml.dom
try:
unicode
except NameError:
StringTypes = type(''),
else:
StringTypes = type(''), type(unicode(''))
class NodeList(list):
__slots__ = ()
def item(self, index):
if 0 <= index < len(self):
return self[index]
def _get_length(self):
return len(self)
def _set_length(self, value):
raise xml.dom.NoModificationAllowedErr(
"attempt to modify read-only attribute 'length'")
length = property(_get_length, _set_length,
doc="The number of nodes in the NodeList.")
def __getstate__(self):
return list(self)
def __setstate__(self, state):
self[:] = state
class EmptyNodeList(tuple):
__slots__ = ()
def __add__(self, other):
NL = NodeList()
NL.extend(other)
return NL
def __radd__(self, other):
NL = NodeList()
NL.extend(other)
return NL
def item(self, index):
return None
def _get_length(self):
return 0
def _set_length(self, value):
raise xml.dom.NoModificationAllowedErr(
"attempt to modify read-only attribute 'length'")
length = property(_get_length, _set_length,
doc="The number of nodes in the NodeList.")
def defproperty(klass, name, doc):
get = getattr(klass, ("_get_" + name)).im_func
def set(self, value, name=name):
raise xml.dom.NoModificationAllowedErr(
"attempt to modify read-only attribute " + repr(name))
assert not hasattr(klass, "_set_" + name), \
"expected not to find _set_" + name
prop = property(get, set, doc=doc)
setattr(klass, name, prop)

BIN
xml/dom/minicompat.pyc Normal file

Binary file not shown.

1941
xml/dom/minidom.py Normal file

File diff suppressed because it is too large Load Diff

BIN
xml/dom/minidom.pyc Normal file

Binary file not shown.

351
xml/dom/pulldom.py Normal file
View File

@ -0,0 +1,351 @@
import xml.sax
import xml.sax.handler
import types
try:
_StringTypes = [types.StringType, types.UnicodeType]
except AttributeError:
_StringTypes = [types.StringType]
START_ELEMENT = "START_ELEMENT"
END_ELEMENT = "END_ELEMENT"
COMMENT = "COMMENT"
START_DOCUMENT = "START_DOCUMENT"
END_DOCUMENT = "END_DOCUMENT"
PROCESSING_INSTRUCTION = "PROCESSING_INSTRUCTION"
IGNORABLE_WHITESPACE = "IGNORABLE_WHITESPACE"
CHARACTERS = "CHARACTERS"
class PullDOM(xml.sax.ContentHandler):
_locator = None
document = None
def __init__(self, documentFactory=None):
from xml.dom import XML_NAMESPACE
self.documentFactory = documentFactory
self.firstEvent = [None, None]
self.lastEvent = self.firstEvent
self.elementStack = []
self.push = self.elementStack.append
try:
self.pop = self.elementStack.pop
except AttributeError:
# use class' pop instead
pass
self._ns_contexts = [{XML_NAMESPACE:'xml'}] # contains uri -> prefix dicts
self._current_context = self._ns_contexts[-1]
self.pending_events = []
def pop(self):
result = self.elementStack[-1]
del self.elementStack[-1]
return result
def setDocumentLocator(self, locator):
self._locator = locator
def startPrefixMapping(self, prefix, uri):
if not hasattr(self, '_xmlns_attrs'):
self._xmlns_attrs = []
self._xmlns_attrs.append((prefix or 'xmlns', uri))
self._ns_contexts.append(self._current_context.copy())
self._current_context[uri] = prefix or None
def endPrefixMapping(self, prefix):
self._current_context = self._ns_contexts.pop()
def startElementNS(self, name, tagName , attrs):
# Retrieve xml namespace declaration attributes.
xmlns_uri = 'http://www.w3.org/2000/xmlns/'
xmlns_attrs = getattr(self, '_xmlns_attrs', None)
if xmlns_attrs is not None:
for aname, value in xmlns_attrs:
attrs._attrs[(xmlns_uri, aname)] = value
self._xmlns_attrs = []
uri, localname = name
if uri:
# When using namespaces, the reader may or may not
# provide us with the original name. If not, create
# *a* valid tagName from the current context.
if tagName is None:
prefix = self._current_context[uri]
if prefix:
tagName = prefix + ":" + localname
else:
tagName = localname
if self.document:
node = self.document.createElementNS(uri, tagName)
else:
node = self.buildDocument(uri, tagName)
else:
# When the tagname is not prefixed, it just appears as
# localname
if self.document:
node = self.document.createElement(localname)
else:
node = self.buildDocument(None, localname)
for aname,value in attrs.items():
a_uri, a_localname = aname
if a_uri == xmlns_uri:
if a_localname == 'xmlns':
qname = a_localname
else:
qname = 'xmlns:' + a_localname
attr = self.document.createAttributeNS(a_uri, qname)
node.setAttributeNodeNS(attr)
elif a_uri:
prefix = self._current_context[a_uri]
if prefix:
qname = prefix + ":" + a_localname
else:
qname = a_localname
attr = self.document.createAttributeNS(a_uri, qname)
node.setAttributeNodeNS(attr)
else:
attr = self.document.createAttribute(a_localname)
node.setAttributeNode(attr)
attr.value = value
self.lastEvent[1] = [(START_ELEMENT, node), None]
self.lastEvent = self.lastEvent[1]
self.push(node)
def endElementNS(self, name, tagName):
self.lastEvent[1] = [(END_ELEMENT, self.pop()), None]
self.lastEvent = self.lastEvent[1]
def startElement(self, name, attrs):
if self.document:
node = self.document.createElement(name)
else:
node = self.buildDocument(None, name)
for aname,value in attrs.items():
attr = self.document.createAttribute(aname)
attr.value = value
node.setAttributeNode(attr)
self.lastEvent[1] = [(START_ELEMENT, node), None]
self.lastEvent = self.lastEvent[1]
self.push(node)
def endElement(self, name):
self.lastEvent[1] = [(END_ELEMENT, self.pop()), None]
self.lastEvent = self.lastEvent[1]
def comment(self, s):
if self.document:
node = self.document.createComment(s)
self.lastEvent[1] = [(COMMENT, node), None]
self.lastEvent = self.lastEvent[1]
else:
event = [(COMMENT, s), None]
self.pending_events.append(event)
def processingInstruction(self, target, data):
if self.document:
node = self.document.createProcessingInstruction(target, data)
self.lastEvent[1] = [(PROCESSING_INSTRUCTION, node), None]
self.lastEvent = self.lastEvent[1]
else:
event = [(PROCESSING_INSTRUCTION, target, data), None]
self.pending_events.append(event)
def ignorableWhitespace(self, chars):
node = self.document.createTextNode(chars)
self.lastEvent[1] = [(IGNORABLE_WHITESPACE, node), None]
self.lastEvent = self.lastEvent[1]
def characters(self, chars):
node = self.document.createTextNode(chars)
self.lastEvent[1] = [(CHARACTERS, node), None]
self.lastEvent = self.lastEvent[1]
def startDocument(self):
if self.documentFactory is None:
import xml.dom.minidom
self.documentFactory = xml.dom.minidom.Document.implementation
def buildDocument(self, uri, tagname):
# Can't do that in startDocument, since we need the tagname
# XXX: obtain DocumentType
node = self.documentFactory.createDocument(uri, tagname, None)
self.document = node
self.lastEvent[1] = [(START_DOCUMENT, node), None]
self.lastEvent = self.lastEvent[1]
self.push(node)
# Put everything we have seen so far into the document
for e in self.pending_events:
if e[0][0] == PROCESSING_INSTRUCTION:
_,target,data = e[0]
n = self.document.createProcessingInstruction(target, data)
e[0] = (PROCESSING_INSTRUCTION, n)
elif e[0][0] == COMMENT:
n = self.document.createComment(e[0][1])
e[0] = (COMMENT, n)
else:
raise AssertionError("Unknown pending event ",e[0][0])
self.lastEvent[1] = e
self.lastEvent = e
self.pending_events = None
return node.firstChild
def endDocument(self):
self.lastEvent[1] = [(END_DOCUMENT, self.document), None]
self.pop()
def clear(self):
"clear(): Explicitly release parsing structures"
self.document = None
class ErrorHandler:
def warning(self, exception):
print exception
def error(self, exception):
raise exception
def fatalError(self, exception):
raise exception
class DOMEventStream:
def __init__(self, stream, parser, bufsize):
self.stream = stream
self.parser = parser
self.bufsize = bufsize
if not hasattr(self.parser, 'feed'):
self.getEvent = self._slurp
self.reset()
def reset(self):
self.pulldom = PullDOM()
# This content handler relies on namespace support
self.parser.setFeature(xml.sax.handler.feature_namespaces, 1)
self.parser.setContentHandler(self.pulldom)
def __getitem__(self, pos):
rc = self.getEvent()
if rc:
return rc
raise IndexError
def next(self):
rc = self.getEvent()
if rc:
return rc
raise StopIteration
def __iter__(self):
return self
def expandNode(self, node):
event = self.getEvent()
parents = [node]
while event:
token, cur_node = event
if cur_node is node:
return
if token != END_ELEMENT:
parents[-1].appendChild(cur_node)
if token == START_ELEMENT:
parents.append(cur_node)
elif token == END_ELEMENT:
del parents[-1]
event = self.getEvent()
def getEvent(self):
# use IncrementalParser interface, so we get the desired
# pull effect
if not self.pulldom.firstEvent[1]:
self.pulldom.lastEvent = self.pulldom.firstEvent
while not self.pulldom.firstEvent[1]:
buf = self.stream.read(self.bufsize)
if not buf:
self.parser.close()
return None
self.parser.feed(buf)
rc = self.pulldom.firstEvent[1][0]
self.pulldom.firstEvent[1] = self.pulldom.firstEvent[1][1]
return rc
def _slurp(self):
""" Fallback replacement for getEvent() using the
standard SAX2 interface, which means we slurp the
SAX events into memory (no performance gain, but
we are compatible to all SAX parsers).
"""
self.parser.parse(self.stream)
self.getEvent = self._emit
return self._emit()
def _emit(self):
""" Fallback replacement for getEvent() that emits
the events that _slurp() read previously.
"""
rc = self.pulldom.firstEvent[1][0]
self.pulldom.firstEvent[1] = self.pulldom.firstEvent[1][1]
return rc
def clear(self):
"""clear(): Explicitly release parsing objects"""
self.pulldom.clear()
del self.pulldom
self.parser = None
self.stream = None
class SAX2DOM(PullDOM):
def startElementNS(self, name, tagName , attrs):
PullDOM.startElementNS(self, name, tagName, attrs)
curNode = self.elementStack[-1]
parentNode = self.elementStack[-2]
parentNode.appendChild(curNode)
def startElement(self, name, attrs):
PullDOM.startElement(self, name, attrs)
curNode = self.elementStack[-1]
parentNode = self.elementStack[-2]
parentNode.appendChild(curNode)
def processingInstruction(self, target, data):
PullDOM.processingInstruction(self, target, data)
node = self.lastEvent[0][1]
parentNode = self.elementStack[-1]
parentNode.appendChild(node)
def ignorableWhitespace(self, chars):
PullDOM.ignorableWhitespace(self, chars)
node = self.lastEvent[0][1]
parentNode = self.elementStack[-1]
parentNode.appendChild(node)
def characters(self, chars):
PullDOM.characters(self, chars)
node = self.lastEvent[0][1]
parentNode = self.elementStack[-1]
parentNode.appendChild(node)
default_bufsize = (2 ** 14) - 20
def parse(stream_or_string, parser=None, bufsize=None):
if bufsize is None:
bufsize = default_bufsize
if type(stream_or_string) in _StringTypes:
stream = open(stream_or_string)
else:
stream = stream_or_string
if not parser:
parser = xml.sax.make_parser()
return DOMEventStream(stream, parser, bufsize)
def parseString(string, parser=None):
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
bufsize = len(string)
buf = StringIO(string)
if not parser:
parser = xml.sax.make_parser()
return DOMEventStream(buf, parser, bufsize)

BIN
xml/dom/pulldom.pyc Normal file

Binary file not shown.

386
xml/dom/xmlbuilder.py Normal file
View File

@ -0,0 +1,386 @@
"""Implementation of the DOM Level 3 'LS-Load' feature."""
import copy
import xml.dom
from xml.dom.NodeFilter import NodeFilter
__all__ = ["DOMBuilder", "DOMEntityResolver", "DOMInputSource"]
class Options:
"""Features object that has variables set for each DOMBuilder feature.
The DOMBuilder class uses an instance of this class to pass settings to
the ExpatBuilder class.
"""
# Note that the DOMBuilder class in LoadSave constrains which of these
# values can be set using the DOM Level 3 LoadSave feature.
namespaces = 1
namespace_declarations = True
validation = False
external_parameter_entities = True
external_general_entities = True
external_dtd_subset = True
validate_if_schema = False
validate = False
datatype_normalization = False
create_entity_ref_nodes = True
entities = True
whitespace_in_element_content = True
cdata_sections = True
comments = True
charset_overrides_xml_encoding = True
infoset = False
supported_mediatypes_only = False
errorHandler = None
filter = None
class DOMBuilder:
entityResolver = None
errorHandler = None
filter = None
ACTION_REPLACE = 1
ACTION_APPEND_AS_CHILDREN = 2
ACTION_INSERT_AFTER = 3
ACTION_INSERT_BEFORE = 4
_legal_actions = (ACTION_REPLACE, ACTION_APPEND_AS_CHILDREN,
ACTION_INSERT_AFTER, ACTION_INSERT_BEFORE)
def __init__(self):
self._options = Options()
def _get_entityResolver(self):
return self.entityResolver
def _set_entityResolver(self, entityResolver):
self.entityResolver = entityResolver
def _get_errorHandler(self):
return self.errorHandler
def _set_errorHandler(self, errorHandler):
self.errorHandler = errorHandler
def _get_filter(self):
return self.filter
def _set_filter(self, filter):
self.filter = filter
def setFeature(self, name, state):
if self.supportsFeature(name):
state = state and 1 or 0
try:
settings = self._settings[(_name_xform(name), state)]
except KeyError:
raise xml.dom.NotSupportedErr(
"unsupported feature: %r" % (name,))
else:
for name, value in settings:
setattr(self._options, name, value)
else:
raise xml.dom.NotFoundErr("unknown feature: " + repr(name))
def supportsFeature(self, name):
return hasattr(self._options, _name_xform(name))
def canSetFeature(self, name, state):
key = (_name_xform(name), state and 1 or 0)
return self._settings.has_key(key)
# This dictionary maps from (feature,value) to a list of
# (option,value) pairs that should be set on the Options object.
# If a (feature,value) setting is not in this dictionary, it is
# not supported by the DOMBuilder.
#
_settings = {
("namespace_declarations", 0): [
("namespace_declarations", 0)],
("namespace_declarations", 1): [
("namespace_declarations", 1)],
("validation", 0): [
("validation", 0)],
("external_general_entities", 0): [
("external_general_entities", 0)],
("external_general_entities", 1): [
("external_general_entities", 1)],
("external_parameter_entities", 0): [
("external_parameter_entities", 0)],
("external_parameter_entities", 1): [
("external_parameter_entities", 1)],
("validate_if_schema", 0): [
("validate_if_schema", 0)],
("create_entity_ref_nodes", 0): [
("create_entity_ref_nodes", 0)],
("create_entity_ref_nodes", 1): [
("create_entity_ref_nodes", 1)],
("entities", 0): [
("create_entity_ref_nodes", 0),
("entities", 0)],
("entities", 1): [
("entities", 1)],
("whitespace_in_element_content", 0): [
("whitespace_in_element_content", 0)],
("whitespace_in_element_content", 1): [
("whitespace_in_element_content", 1)],
("cdata_sections", 0): [
("cdata_sections", 0)],
("cdata_sections", 1): [
("cdata_sections", 1)],
("comments", 0): [
("comments", 0)],
("comments", 1): [
("comments", 1)],
("charset_overrides_xml_encoding", 0): [
("charset_overrides_xml_encoding", 0)],
("charset_overrides_xml_encoding", 1): [
("charset_overrides_xml_encoding", 1)],
("infoset", 0): [],
("infoset", 1): [
("namespace_declarations", 0),
("validate_if_schema", 0),
("create_entity_ref_nodes", 0),
("entities", 0),
("cdata_sections", 0),
("datatype_normalization", 1),
("whitespace_in_element_content", 1),
("comments", 1),
("charset_overrides_xml_encoding", 1)],
("supported_mediatypes_only", 0): [
("supported_mediatypes_only", 0)],
("namespaces", 0): [
("namespaces", 0)],
("namespaces", 1): [
("namespaces", 1)],
}
def getFeature(self, name):
xname = _name_xform(name)
try:
return getattr(self._options, xname)
except AttributeError:
if name == "infoset":
options = self._options
return (options.datatype_normalization
and options.whitespace_in_element_content
and options.comments
and options.charset_overrides_xml_encoding
and not (options.namespace_declarations
or options.validate_if_schema
or options.create_entity_ref_nodes
or options.entities
or options.cdata_sections))
raise xml.dom.NotFoundErr("feature %s not known" % repr(name))
def parseURI(self, uri):
if self.entityResolver:
input = self.entityResolver.resolveEntity(None, uri)
else:
input = DOMEntityResolver().resolveEntity(None, uri)
return self.parse(input)
def parse(self, input):
options = copy.copy(self._options)
options.filter = self.filter
options.errorHandler = self.errorHandler
fp = input.byteStream
if fp is None and options.systemId:
import urllib2
fp = urllib2.urlopen(input.systemId)
return self._parse_bytestream(fp, options)
def parseWithContext(self, input, cnode, action):
if action not in self._legal_actions:
raise ValueError("not a legal action")
raise NotImplementedError("Haven't written this yet...")
def _parse_bytestream(self, stream, options):
import xml.dom.expatbuilder
builder = xml.dom.expatbuilder.makeBuilder(options)
return builder.parseFile(stream)
def _name_xform(name):
return name.lower().replace('-', '_')
class DOMEntityResolver(object):
__slots__ = '_opener',
def resolveEntity(self, publicId, systemId):
assert systemId is not None
source = DOMInputSource()
source.publicId = publicId
source.systemId = systemId
source.byteStream = self._get_opener().open(systemId)
# determine the encoding if the transport provided it
source.encoding = self._guess_media_encoding(source)
# determine the base URI is we can
import posixpath, urlparse
parts = urlparse.urlparse(systemId)
scheme, netloc, path, params, query, fragment = parts
# XXX should we check the scheme here as well?
if path and not path.endswith("/"):
path = posixpath.dirname(path) + "/"
parts = scheme, netloc, path, params, query, fragment
source.baseURI = urlparse.urlunparse(parts)
return source
def _get_opener(self):
try:
return self._opener
except AttributeError:
self._opener = self._create_opener()
return self._opener
def _create_opener(self):
import urllib2
return urllib2.build_opener()
def _guess_media_encoding(self, source):
info = source.byteStream.info()
if info.has_key("Content-Type"):
for param in info.getplist():
if param.startswith("charset="):
return param.split("=", 1)[1].lower()
class DOMInputSource(object):
__slots__ = ('byteStream', 'characterStream', 'stringData',
'encoding', 'publicId', 'systemId', 'baseURI')
def __init__(self):
self.byteStream = None
self.characterStream = None
self.stringData = None
self.encoding = None
self.publicId = None
self.systemId = None
self.baseURI = None
def _get_byteStream(self):
return self.byteStream
def _set_byteStream(self, byteStream):
self.byteStream = byteStream
def _get_characterStream(self):
return self.characterStream
def _set_characterStream(self, characterStream):
self.characterStream = characterStream
def _get_stringData(self):
return self.stringData
def _set_stringData(self, data):
self.stringData = data
def _get_encoding(self):
return self.encoding
def _set_encoding(self, encoding):
self.encoding = encoding
def _get_publicId(self):
return self.publicId
def _set_publicId(self, publicId):
self.publicId = publicId
def _get_systemId(self):
return self.systemId
def _set_systemId(self, systemId):
self.systemId = systemId
def _get_baseURI(self):
return self.baseURI
def _set_baseURI(self, uri):
self.baseURI = uri
class DOMBuilderFilter:
"""Element filter which can be used to tailor construction of
a DOM instance.
"""
# There's really no need for this class; concrete implementations
# should just implement the endElement() and startElement()
# methods as appropriate. Using this makes it easy to only
# implement one of them.
FILTER_ACCEPT = 1
FILTER_REJECT = 2
FILTER_SKIP = 3
FILTER_INTERRUPT = 4
whatToShow = NodeFilter.SHOW_ALL
def _get_whatToShow(self):
return self.whatToShow
def acceptNode(self, element):
return self.FILTER_ACCEPT
def startContainer(self, element):
return self.FILTER_ACCEPT
del NodeFilter
class DocumentLS:
"""Mixin to create documents that conform to the load/save spec."""
async = False
def _get_async(self):
return False
def _set_async(self, async):
if async:
raise xml.dom.NotSupportedErr(
"asynchronous document loading is not supported")
def abort(self):
# What does it mean to "clear" a document? Does the
# documentElement disappear?
raise NotImplementedError(
"haven't figured out what this means yet")
def load(self, uri):
raise NotImplementedError("haven't written this yet")
def loadXML(self, source):
raise NotImplementedError("haven't written this yet")
def saveXML(self, snode):
if snode is None:
snode = self
elif snode.ownerDocument is not self:
raise xml.dom.WrongDocumentErr()
return snode.toxml()
class DOMImplementationLS:
MODE_SYNCHRONOUS = 1
MODE_ASYNCHRONOUS = 2
def createDOMBuilder(self, mode, schemaType):
if schemaType is not None:
raise xml.dom.NotSupportedErr(
"schemaType not yet supported")
if mode == self.MODE_SYNCHRONOUS:
return DOMBuilder()
if mode == self.MODE_ASYNCHRONOUS:
raise xml.dom.NotSupportedErr(
"asynchronous builders are not supported")
raise ValueError("unknown value for mode")
def createDOMWriter(self):
raise NotImplementedError(
"the writer interface hasn't been written yet!")
def createDOMInputSource(self):
return DOMInputSource()

BIN
xml/dom/xmlbuilder.pyc Normal file

Binary file not shown.

143
xml/etree/ElementInclude.py Normal file
View File

@ -0,0 +1,143 @@
#
# ElementTree
# $Id: ElementInclude.py 1862 2004-06-18 07:31:02Z Fredrik $
#
# limited xinclude support for element trees
#
# history:
# 2003-08-15 fl created
# 2003-11-14 fl fixed default loader
#
# Copyright (c) 2003-2004 by Fredrik Lundh. All rights reserved.
#
# fredrik@pythonware.com
# http://www.pythonware.com
#
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
# Copyright (c) 1999-2004 by Fredrik Lundh
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------
# Licensed to PSF under a Contributor Agreement.
# See http://www.python.org/2.4/license for licensing details.
##
# Limited XInclude support for the ElementTree package.
##
import copy
import ElementTree
XINCLUDE = "{http://www.w3.org/2001/XInclude}"
XINCLUDE_INCLUDE = XINCLUDE + "include"
XINCLUDE_FALLBACK = XINCLUDE + "fallback"
##
# Fatal include error.
class FatalIncludeError(SyntaxError):
pass
##
# Default loader. This loader reads an included resource from disk.
#
# @param href Resource reference.
# @param parse Parse mode. Either "xml" or "text".
# @param encoding Optional text encoding.
# @return The expanded resource. If the parse mode is "xml", this
# is an ElementTree instance. If the parse mode is "text", this
# is a Unicode string. If the loader fails, it can return None
# or raise an IOError exception.
# @throws IOError If the loader fails to load the resource.
def default_loader(href, parse, encoding=None):
file = open(href)
if parse == "xml":
data = ElementTree.parse(file).getroot()
else:
data = file.read()
if encoding:
data = data.decode(encoding)
file.close()
return data
##
# Expand XInclude directives.
#
# @param elem Root element.
# @param loader Optional resource loader. If omitted, it defaults
# to {@link default_loader}. If given, it should be a callable
# that implements the same interface as <b>default_loader</b>.
# @throws FatalIncludeError If the function fails to include a given
# resource, or if the tree contains malformed XInclude elements.
# @throws IOError If the function fails to load a given resource.
def include(elem, loader=None):
if loader is None:
loader = default_loader
# look for xinclude elements
i = 0
while i < len(elem):
e = elem[i]
if e.tag == XINCLUDE_INCLUDE:
# process xinclude directive
href = e.get("href")
parse = e.get("parse", "xml")
if parse == "xml":
node = loader(href, parse)
if node is None:
raise FatalIncludeError(
"cannot load %r as %r" % (href, parse)
)
node = copy.copy(node)
if e.tail:
node.tail = (node.tail or "") + e.tail
elem[i] = node
elif parse == "text":
text = loader(href, parse, e.get("encoding"))
if text is None:
raise FatalIncludeError(
"cannot load %r as %r" % (href, parse)
)
if i:
node = elem[i-1]
node.tail = (node.tail or "") + text
else:
elem.text = (elem.text or "") + text + (e.tail or "")
del elem[i]
continue
else:
raise FatalIncludeError(
"unknown parse type in xi:include tag (%r)" % parse
)
elif e.tag == XINCLUDE_FALLBACK:
raise FatalIncludeError(
"xi:fallback tag must be child of xi:include (%r)" % e.tag
)
else:
include(e, loader)
i = i + 1

Binary file not shown.

198
xml/etree/ElementPath.py Normal file
View File

@ -0,0 +1,198 @@
#
# ElementTree
# $Id: ElementPath.py 1858 2004-06-17 21:31:41Z Fredrik $
#
# limited xpath support for element trees
#
# history:
# 2003-05-23 fl created
# 2003-05-28 fl added support for // etc
# 2003-08-27 fl fixed parsing of periods in element names
#
# Copyright (c) 2003-2004 by Fredrik Lundh. All rights reserved.
#
# fredrik@pythonware.com
# http://www.pythonware.com
#
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
# Copyright (c) 1999-2004 by Fredrik Lundh
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------
# Licensed to PSF under a Contributor Agreement.
# See http://www.python.org/2.4/license for licensing details.
##
# Implementation module for XPath support. There's usually no reason
# to import this module directly; the <b>ElementTree</b> does this for
# you, if needed.
##
import re
xpath_tokenizer = re.compile(
"(::|\.\.|\(\)|[/.*:\[\]\(\)@=])|((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|\s+"
).findall
class xpath_descendant_or_self:
pass
##
# Wrapper for a compiled XPath.
class Path:
##
# Create an Path instance from an XPath expression.
def __init__(self, path):
tokens = xpath_tokenizer(path)
# the current version supports 'path/path'-style expressions only
self.path = []
self.tag = None
if tokens and tokens[0][0] == "/":
raise SyntaxError("cannot use absolute path on element")
while tokens:
op, tag = tokens.pop(0)
if tag or op == "*":
self.path.append(tag or op)
elif op == ".":
pass
elif op == "/":
self.path.append(xpath_descendant_or_self())
continue
else:
raise SyntaxError("unsupported path syntax (%s)" % op)
if tokens:
op, tag = tokens.pop(0)
if op != "/":
raise SyntaxError(
"expected path separator (%s)" % (op or tag)
)
if self.path and isinstance(self.path[-1], xpath_descendant_or_self):
raise SyntaxError("path cannot end with //")
if len(self.path) == 1 and isinstance(self.path[0], type("")):
self.tag = self.path[0]
##
# Find first matching object.
def find(self, element):
tag = self.tag
if tag is None:
nodeset = self.findall(element)
if not nodeset:
return None
return nodeset[0]
for elem in element:
if elem.tag == tag:
return elem
return None
##
# Find text for first matching object.
def findtext(self, element, default=None):
tag = self.tag
if tag is None:
nodeset = self.findall(element)
if not nodeset:
return default
return nodeset[0].text or ""
for elem in element:
if elem.tag == tag:
return elem.text or ""
return default
##
# Find all matching objects.
def findall(self, element):
nodeset = [element]
index = 0
while 1:
try:
path = self.path[index]
index = index + 1
except IndexError:
return nodeset
set = []
if isinstance(path, xpath_descendant_or_self):
try:
tag = self.path[index]
if not isinstance(tag, type("")):
tag = None
else:
index = index + 1
except IndexError:
tag = None # invalid path
for node in nodeset:
new = list(node.getiterator(tag))
if new and new[0] is node:
set.extend(new[1:])
else:
set.extend(new)
else:
for node in nodeset:
for node in node:
if path == "*" or node.tag == path:
set.append(node)
if not set:
return []
nodeset = set
_cache = {}
##
# (Internal) Compile path.
def _compile(path):
p = _cache.get(path)
if p is not None:
return p
p = Path(path)
if len(_cache) >= 100:
_cache.clear()
_cache[path] = p
return p
##
# Find first matching object.
def find(element, path):
return _compile(path).find(element)
##
# Find text for first matching object.
def findtext(element, path, default=None):
return _compile(path).findtext(element, default)
##
# Find all matching objects.
def findall(element, path):
return _compile(path).findall(element)

BIN
xml/etree/ElementPath.pyc Normal file

Binary file not shown.

1260
xml/etree/ElementTree.py Normal file

File diff suppressed because it is too large Load Diff

BIN
xml/etree/ElementTree.pyc Normal file

Binary file not shown.

33
xml/etree/__init__.py Normal file
View File

@ -0,0 +1,33 @@
# $Id: __init__.py 1821 2004-06-03 16:57:49Z fredrik $
# elementtree package
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
# Copyright (c) 1999-2004 by Fredrik Lundh
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------
# Licensed to PSF under a Contributor Agreement.
# See http://www.python.org/2.4/license for licensing details.

BIN
xml/etree/__init__.pyc Normal file

Binary file not shown.

View File

@ -0,0 +1,3 @@
# Wrapper module for _elementtree
from ElementTree import *

BIN
xml/etree/cElementTree.pyc Normal file

Binary file not shown.

8
xml/parsers/__init__.py Normal file
View File

@ -0,0 +1,8 @@
"""Python interfaces to XML parsers.
This package contains one module:
expat -- Python wrapper for James Clark's Expat parser, with namespace
support.
"""

BIN
xml/parsers/__init__.pyc Normal file

Binary file not shown.

4
xml/parsers/expat.py Normal file
View File

@ -0,0 +1,4 @@
"""Interface to the Expat non-validating XML parser."""
__version__ = '$Revision: 17640 $'
from pyexpat import *

BIN
xml/parsers/expat.pyc Normal file

Binary file not shown.

108
xml/sax/__init__.py Normal file
View File

@ -0,0 +1,108 @@
"""Simple API for XML (SAX) implementation for Python.
This module provides an implementation of the SAX 2 interface;
information about the Java version of the interface can be found at
http://www.megginson.com/SAX/. The Python version of the interface is
documented at <...>.
This package contains the following modules:
handler -- Base classes and constants which define the SAX 2 API for
the 'client-side' of SAX for Python.
saxutils -- Implementation of the convenience classes commonly used to
work with SAX.
xmlreader -- Base classes and constants which define the SAX 2 API for
the parsers used with SAX for Python.
expatreader -- Driver that allows use of the Expat parser with SAX.
"""
from xmlreader import InputSource
from handler import ContentHandler, ErrorHandler
from _exceptions import SAXException, SAXNotRecognizedException, \
SAXParseException, SAXNotSupportedException, \
SAXReaderNotAvailable
def parse(source, handler, errorHandler=ErrorHandler()):
parser = make_parser()
parser.setContentHandler(handler)
parser.setErrorHandler(errorHandler)
parser.parse(source)
def parseString(string, handler, errorHandler=ErrorHandler()):
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
if errorHandler is None:
errorHandler = ErrorHandler()
parser = make_parser()
parser.setContentHandler(handler)
parser.setErrorHandler(errorHandler)
inpsrc = InputSource()
inpsrc.setByteStream(StringIO(string))
parser.parse(inpsrc)
# this is the parser list used by the make_parser function if no
# alternatives are given as parameters to the function
default_parser_list = ["xml.sax.expatreader"]
# tell modulefinder that importing sax potentially imports expatreader
_false = 0
if _false:
import xml.sax.expatreader
import os, sys
if os.environ.has_key("PY_SAX_PARSER"):
default_parser_list = os.environ["PY_SAX_PARSER"].split(",")
del os
_key = "python.xml.sax.parser"
if sys.platform[:4] == "java" and sys.registry.containsKey(_key):
default_parser_list = sys.registry.getProperty(_key).split(",")
def make_parser(parser_list = []):
"""Creates and returns a SAX parser.
Creates the first parser it is able to instantiate of the ones
given in the list created by doing parser_list +
default_parser_list. The lists must contain the names of Python
modules containing both a SAX parser and a create_parser function."""
for parser_name in parser_list + default_parser_list:
try:
return _create_parser(parser_name)
except ImportError,e:
import sys
if parser_name in sys.modules:
# The parser module was found, but importing it
# failed unexpectedly, pass this exception through
raise
except SAXReaderNotAvailable:
# The parser module detected that it won't work properly,
# so try the next one
pass
raise SAXReaderNotAvailable("No parsers found", None)
# --- Internal utility methods used by make_parser
if sys.platform[ : 4] == "java":
def _create_parser(parser_name):
from org.python.core import imp
drv_module = imp.importName(parser_name, 0, globals())
return drv_module.create_parser()
else:
def _create_parser(parser_name):
drv_module = __import__(parser_name,{},{},['create_parser'])
return drv_module.create_parser()
del sys

BIN
xml/sax/__init__.pyc Normal file

Binary file not shown.

131
xml/sax/_exceptions.py Normal file
View File

@ -0,0 +1,131 @@
"""Different kinds of SAX Exceptions"""
import sys
if sys.platform[:4] == "java":
from java.lang import Exception
del sys
# ===== SAXEXCEPTION =====
class SAXException(Exception):
"""Encapsulate an XML error or warning. This class can contain
basic error or warning information from either the XML parser or
the application: you can subclass it to provide additional
functionality, or to add localization. Note that although you will
receive a SAXException as the argument to the handlers in the
ErrorHandler interface, you are not actually required to throw
the exception; instead, you can simply read the information in
it."""
def __init__(self, msg, exception=None):
"""Creates an exception. The message is required, but the exception
is optional."""
self._msg = msg
self._exception = exception
Exception.__init__(self, msg)
def getMessage(self):
"Return a message for this exception."
return self._msg
def getException(self):
"Return the embedded exception, or None if there was none."
return self._exception
def __str__(self):
"Create a string representation of the exception."
return self._msg
def __getitem__(self, ix):
"""Avoids weird error messages if someone does exception[ix] by
mistake, since Exception has __getitem__ defined."""
raise AttributeError("__getitem__")
# ===== SAXPARSEEXCEPTION =====
class SAXParseException(SAXException):
"""Encapsulate an XML parse error or warning.
This exception will include information for locating the error in
the original XML document. Note that although the application will
receive a SAXParseException as the argument to the handlers in the
ErrorHandler interface, the application is not actually required
to throw the exception; instead, it can simply read the
information in it and take a different action.
Since this exception is a subclass of SAXException, it inherits
the ability to wrap another exception."""
def __init__(self, msg, exception, locator):
"Creates the exception. The exception parameter is allowed to be None."
SAXException.__init__(self, msg, exception)
self._locator = locator
# We need to cache this stuff at construction time.
# If this exception is thrown, the objects through which we must
# traverse to get this information may be deleted by the time
# it gets caught.
self._systemId = self._locator.getSystemId()
self._colnum = self._locator.getColumnNumber()
self._linenum = self._locator.getLineNumber()
def getColumnNumber(self):
"""The column number of the end of the text where the exception
occurred."""
return self._colnum
def getLineNumber(self):
"The line number of the end of the text where the exception occurred."
return self._linenum
def getPublicId(self):
"Get the public identifier of the entity where the exception occurred."
return self._locator.getPublicId()
def getSystemId(self):
"Get the system identifier of the entity where the exception occurred."
return self._systemId
def __str__(self):
"Create a string representation of the exception."
sysid = self.getSystemId()
if sysid is None:
sysid = "<unknown>"
linenum = self.getLineNumber()
if linenum is None:
linenum = "?"
colnum = self.getColumnNumber()
if colnum is None:
colnum = "?"
return "%s:%s:%s: %s" % (sysid, linenum, colnum, self._msg)
# ===== SAXNOTRECOGNIZEDEXCEPTION =====
class SAXNotRecognizedException(SAXException):
"""Exception class for an unrecognized identifier.
An XMLReader will raise this exception when it is confronted with an
unrecognized feature or property. SAX applications and extensions may
use this class for similar purposes."""
# ===== SAXNOTSUPPORTEDEXCEPTION =====
class SAXNotSupportedException(SAXException):
"""Exception class for an unsupported operation.
An XMLReader will raise this exception when a service it cannot
perform is requested (specifically setting a state or value). SAX
applications and extensions may use this class for similar
purposes."""
# ===== SAXNOTSUPPORTEDEXCEPTION =====
class SAXReaderNotAvailable(SAXNotSupportedException):
"""Exception class for a missing driver.
An XMLReader module (driver) should raise this exception when it
is first imported, e.g. when a support module cannot be imported.
It also may be raised during parsing, e.g. if executing an external
program is not permitted."""

BIN
xml/sax/_exceptions.pyc Normal file

Binary file not shown.

414
xml/sax/expatreader.py Normal file
View File

@ -0,0 +1,414 @@
"""
SAX driver for the pyexpat C module. This driver works with
pyexpat.__version__ == '2.22'.
"""
version = "0.20"
from xml.sax._exceptions import *
from xml.sax.handler import feature_validation, feature_namespaces
from xml.sax.handler import feature_namespace_prefixes
from xml.sax.handler import feature_external_ges, feature_external_pes
from xml.sax.handler import feature_string_interning
from xml.sax.handler import property_xml_string, property_interning_dict
# xml.parsers.expat does not raise ImportError in Jython
import sys
if sys.platform[:4] == "java":
raise SAXReaderNotAvailable("expat not available in Java", None)
del sys
try:
from xml.parsers import expat
except ImportError:
raise SAXReaderNotAvailable("expat not supported", None)
else:
if not hasattr(expat, "ParserCreate"):
raise SAXReaderNotAvailable("expat not supported", None)
from xml.sax import xmlreader, saxutils, handler
AttributesImpl = xmlreader.AttributesImpl
AttributesNSImpl = xmlreader.AttributesNSImpl
# If we're using a sufficiently recent version of Python, we can use
# weak references to avoid cycles between the parser and content
# handler, otherwise we'll just have to pretend.
try:
import _weakref
except ImportError:
def _mkproxy(o):
return o
else:
import weakref
_mkproxy = weakref.proxy
del weakref, _weakref
# --- ExpatLocator
class ExpatLocator(xmlreader.Locator):
"""Locator for use with the ExpatParser class.
This uses a weak reference to the parser object to avoid creating
a circular reference between the parser and the content handler.
"""
def __init__(self, parser):
self._ref = _mkproxy(parser)
def getColumnNumber(self):
parser = self._ref
if parser._parser is None:
return None
return parser._parser.ErrorColumnNumber
def getLineNumber(self):
parser = self._ref
if parser._parser is None:
return 1
return parser._parser.ErrorLineNumber
def getPublicId(self):
parser = self._ref
if parser is None:
return None
return parser._source.getPublicId()
def getSystemId(self):
parser = self._ref
if parser is None:
return None
return parser._source.getSystemId()
# --- ExpatParser
class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator):
"""SAX driver for the pyexpat C module."""
def __init__(self, namespaceHandling=0, bufsize=2**16-20):
xmlreader.IncrementalParser.__init__(self, bufsize)
self._source = xmlreader.InputSource()
self._parser = None
self._namespaces = namespaceHandling
self._lex_handler_prop = None
self._parsing = 0
self._entity_stack = []
self._external_ges = 1
self._interning = None
# XMLReader methods
def parse(self, source):
"Parse an XML document from a URL or an InputSource."
source = saxutils.prepare_input_source(source)
self._source = source
self.reset()
self._cont_handler.setDocumentLocator(ExpatLocator(self))
xmlreader.IncrementalParser.parse(self, source)
def prepareParser(self, source):
if source.getSystemId() is not None:
self._parser.SetBase(source.getSystemId())
# Redefined setContentHandler to allow changing handlers during parsing
def setContentHandler(self, handler):
xmlreader.IncrementalParser.setContentHandler(self, handler)
if self._parsing:
self._reset_cont_handler()
def getFeature(self, name):
if name == feature_namespaces:
return self._namespaces
elif name == feature_string_interning:
return self._interning is not None
elif name in (feature_validation, feature_external_pes,
feature_namespace_prefixes):
return 0
elif name == feature_external_ges:
return self._external_ges
raise SAXNotRecognizedException("Feature '%s' not recognized" % name)
def setFeature(self, name, state):
if self._parsing:
raise SAXNotSupportedException("Cannot set features while parsing")
if name == feature_namespaces:
self._namespaces = state
elif name == feature_external_ges:
self._external_ges = state
elif name == feature_string_interning:
if state:
if self._interning is None:
self._interning = {}
else:
self._interning = None
elif name == feature_validation:
if state:
raise SAXNotSupportedException(
"expat does not support validation")
elif name == feature_external_pes:
if state:
raise SAXNotSupportedException(
"expat does not read external parameter entities")
elif name == feature_namespace_prefixes:
if state:
raise SAXNotSupportedException(
"expat does not report namespace prefixes")
else:
raise SAXNotRecognizedException(
"Feature '%s' not recognized" % name)
def getProperty(self, name):
if name == handler.property_lexical_handler:
return self._lex_handler_prop
elif name == property_interning_dict:
return self._interning
elif name == property_xml_string:
if self._parser:
if hasattr(self._parser, "GetInputContext"):
return self._parser.GetInputContext()
else:
raise SAXNotRecognizedException(
"This version of expat does not support getting"
" the XML string")
else:
raise SAXNotSupportedException(
"XML string cannot be returned when not parsing")
raise SAXNotRecognizedException("Property '%s' not recognized" % name)
def setProperty(self, name, value):
if name == handler.property_lexical_handler:
self._lex_handler_prop = value
if self._parsing:
self._reset_lex_handler_prop()
elif name == property_interning_dict:
self._interning = value
elif name == property_xml_string:
raise SAXNotSupportedException("Property '%s' cannot be set" %
name)
else:
raise SAXNotRecognizedException("Property '%s' not recognized" %
name)
# IncrementalParser methods
def feed(self, data, isFinal = 0):
if not self._parsing:
self.reset()
self._parsing = 1
self._cont_handler.startDocument()
try:
# The isFinal parameter is internal to the expat reader.
# If it is set to true, expat will check validity of the entire
# document. When feeding chunks, they are not normally final -
# except when invoked from close.
self._parser.Parse(data, isFinal)
except expat.error, e:
exc = SAXParseException(expat.ErrorString(e.code), e, self)
# FIXME: when to invoke error()?
self._err_handler.fatalError(exc)
def close(self):
if self._entity_stack:
# If we are completing an external entity, do nothing here
return
self.feed("", isFinal = 1)
self._cont_handler.endDocument()
self._parsing = 0
# break cycle created by expat handlers pointing to our methods
self._parser = None
def _reset_cont_handler(self):
self._parser.ProcessingInstructionHandler = \
self._cont_handler.processingInstruction
self._parser.CharacterDataHandler = self._cont_handler.characters
def _reset_lex_handler_prop(self):
lex = self._lex_handler_prop
parser = self._parser
if lex is None:
parser.CommentHandler = None
parser.StartCdataSectionHandler = None
parser.EndCdataSectionHandler = None
parser.StartDoctypeDeclHandler = None
parser.EndDoctypeDeclHandler = None
else:
parser.CommentHandler = lex.comment
parser.StartCdataSectionHandler = lex.startCDATA
parser.EndCdataSectionHandler = lex.endCDATA
parser.StartDoctypeDeclHandler = self.start_doctype_decl
parser.EndDoctypeDeclHandler = lex.endDTD
def reset(self):
if self._namespaces:
self._parser = expat.ParserCreate(self._source.getEncoding(), " ",
intern=self._interning)
self._parser.namespace_prefixes = 1
self._parser.StartElementHandler = self.start_element_ns
self._parser.EndElementHandler = self.end_element_ns
else:
self._parser = expat.ParserCreate(self._source.getEncoding(),
intern = self._interning)
self._parser.StartElementHandler = self.start_element
self._parser.EndElementHandler = self.end_element
self._reset_cont_handler()
self._parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl
self._parser.NotationDeclHandler = self.notation_decl
self._parser.StartNamespaceDeclHandler = self.start_namespace_decl
self._parser.EndNamespaceDeclHandler = self.end_namespace_decl
self._decl_handler_prop = None
if self._lex_handler_prop:
self._reset_lex_handler_prop()
# self._parser.DefaultHandler =
# self._parser.DefaultHandlerExpand =
# self._parser.NotStandaloneHandler =
self._parser.ExternalEntityRefHandler = self.external_entity_ref
try:
self._parser.SkippedEntityHandler = self.skipped_entity_handler
except AttributeError:
# This pyexpat does not support SkippedEntity
pass
self._parser.SetParamEntityParsing(
expat.XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE)
self._parsing = 0
self._entity_stack = []
# Locator methods
def getColumnNumber(self):
if self._parser is None:
return None
return self._parser.ErrorColumnNumber
def getLineNumber(self):
if self._parser is None:
return 1
return self._parser.ErrorLineNumber
def getPublicId(self):
return self._source.getPublicId()
def getSystemId(self):
return self._source.getSystemId()
# event handlers
def start_element(self, name, attrs):
self._cont_handler.startElement(name, AttributesImpl(attrs))
def end_element(self, name):
self._cont_handler.endElement(name)
def start_element_ns(self, name, attrs):
pair = name.split()
if len(pair) == 1:
# no namespace
pair = (None, name)
elif len(pair) == 3:
pair = pair[0], pair[1]
else:
# default namespace
pair = tuple(pair)
newattrs = {}
qnames = {}
for (aname, value) in attrs.items():
parts = aname.split()
length = len(parts)
if length == 1:
# no namespace
qname = aname
apair = (None, aname)
elif length == 3:
qname = "%s:%s" % (parts[2], parts[1])
apair = parts[0], parts[1]
else:
# default namespace
qname = parts[1]
apair = tuple(parts)
newattrs[apair] = value
qnames[apair] = qname
self._cont_handler.startElementNS(pair, None,
AttributesNSImpl(newattrs, qnames))
def end_element_ns(self, name):
pair = name.split()
if len(pair) == 1:
pair = (None, name)
elif len(pair) == 3:
pair = pair[0], pair[1]
else:
pair = tuple(pair)
self._cont_handler.endElementNS(pair, None)
# this is not used (call directly to ContentHandler)
def processing_instruction(self, target, data):
self._cont_handler.processingInstruction(target, data)
# this is not used (call directly to ContentHandler)
def character_data(self, data):
self._cont_handler.characters(data)
def start_namespace_decl(self, prefix, uri):
self._cont_handler.startPrefixMapping(prefix, uri)
def end_namespace_decl(self, prefix):
self._cont_handler.endPrefixMapping(prefix)
def start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
self._lex_handler_prop.startDTD(name, pubid, sysid)
def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
self._dtd_handler.unparsedEntityDecl(name, pubid, sysid, notation_name)
def notation_decl(self, name, base, sysid, pubid):
self._dtd_handler.notationDecl(name, pubid, sysid)
def external_entity_ref(self, context, base, sysid, pubid):
if not self._external_ges:
return 1
source = self._ent_handler.resolveEntity(pubid, sysid)
source = saxutils.prepare_input_source(source,
self._source.getSystemId() or
"")
self._entity_stack.append((self._parser, self._source))
self._parser = self._parser.ExternalEntityParserCreate(context)
self._source = source
try:
xmlreader.IncrementalParser.parse(self, source)
except:
return 0 # FIXME: save error info here?
(self._parser, self._source) = self._entity_stack[-1]
del self._entity_stack[-1]
return 1
def skipped_entity_handler(self, name, is_pe):
if is_pe:
# The SAX spec requires to report skipped PEs with a '%'
name = '%'+name
self._cont_handler.skippedEntity(name)
# ---
def create_parser(*args, **kwargs):
return ExpatParser(*args, **kwargs)
# ---
if __name__ == "__main__":
import xml.sax
p = create_parser()
p.setContentHandler(xml.sax.XMLGenerator())
p.setErrorHandler(xml.sax.ErrorHandler())
p.parse("../../../hamlet.xml")

BIN
xml/sax/expatreader.pyc Normal file

Binary file not shown.

342
xml/sax/handler.py Normal file
View File

@ -0,0 +1,342 @@
"""
This module contains the core classes of version 2.0 of SAX for Python.
This file provides only default classes with absolutely minimum
functionality, from which drivers and applications can be subclassed.
Many of these classes are empty and are included only as documentation
of the interfaces.
$Id: handler.py 35816 2004-05-06 03:47:48Z fdrake $
"""
version = '2.0beta'
#============================================================================
#
# HANDLER INTERFACES
#
#============================================================================
# ===== ERRORHANDLER =====
class ErrorHandler:
"""Basic interface for SAX error handlers.
If you create an object that implements this interface, then
register the object with your XMLReader, the parser will call the
methods in your object to report all warnings and errors. There
are three levels of errors available: warnings, (possibly)
recoverable errors, and unrecoverable errors. All methods take a
SAXParseException as the only parameter."""
def error(self, exception):
"Handle a recoverable error."
raise exception
def fatalError(self, exception):
"Handle a non-recoverable error."
raise exception
def warning(self, exception):
"Handle a warning."
print exception
# ===== CONTENTHANDLER =====
class ContentHandler:
"""Interface for receiving logical document content events.
This is the main callback interface in SAX, and the one most
important to applications. The order of events in this interface
mirrors the order of the information in the document."""
def __init__(self):
self._locator = None
def setDocumentLocator(self, locator):
"""Called by the parser to give the application a locator for
locating the origin of document events.
SAX parsers are strongly encouraged (though not absolutely
required) to supply a locator: if it does so, it must supply
the locator to the application by invoking this method before
invoking any of the other methods in the DocumentHandler
interface.
The locator allows the application to determine the end
position of any document-related event, even if the parser is
not reporting an error. Typically, the application will use
this information for reporting its own errors (such as
character content that does not match an application's
business rules). The information returned by the locator is
probably not sufficient for use with a search engine.
Note that the locator will return correct information only
during the invocation of the events in this interface. The
application should not attempt to use it at any other time."""
self._locator = locator
def startDocument(self):
"""Receive notification of the beginning of a document.
The SAX parser will invoke this method only once, before any
other methods in this interface or in DTDHandler (except for
setDocumentLocator)."""
def endDocument(self):
"""Receive notification of the end of a document.
The SAX parser will invoke this method only once, and it will
be the last method invoked during the parse. The parser shall
not invoke this method until it has either abandoned parsing
(because of an unrecoverable error) or reached the end of
input."""
def startPrefixMapping(self, prefix, uri):
"""Begin the scope of a prefix-URI Namespace mapping.
The information from this event is not necessary for normal
Namespace processing: the SAX XML reader will automatically
replace prefixes for element and attribute names when the
http://xml.org/sax/features/namespaces feature is true (the
default).
There are cases, however, when applications need to use
prefixes in character data or in attribute values, where they
cannot safely be expanded automatically; the
start/endPrefixMapping event supplies the information to the
application to expand prefixes in those contexts itself, if
necessary.
Note that start/endPrefixMapping events are not guaranteed to
be properly nested relative to each-other: all
startPrefixMapping events will occur before the corresponding
startElement event, and all endPrefixMapping events will occur
after the corresponding endElement event, but their order is
not guaranteed."""
def endPrefixMapping(self, prefix):
"""End the scope of a prefix-URI mapping.
See startPrefixMapping for details. This event will always
occur after the corresponding endElement event, but the order
of endPrefixMapping events is not otherwise guaranteed."""
def startElement(self, name, attrs):
"""Signals the start of an element in non-namespace mode.
The name parameter contains the raw XML 1.0 name of the
element type as a string and the attrs parameter holds an
instance of the Attributes class containing the attributes of
the element."""
def endElement(self, name):
"""Signals the end of an element in non-namespace mode.
The name parameter contains the name of the element type, just
as with the startElement event."""
def startElementNS(self, name, qname, attrs):
"""Signals the start of an element in namespace mode.
The name parameter contains the name of the element type as a
(uri, localname) tuple, the qname parameter the raw XML 1.0
name used in the source document, and the attrs parameter
holds an instance of the Attributes class containing the
attributes of the element.
The uri part of the name tuple is None for elements which have
no namespace."""
def endElementNS(self, name, qname):
"""Signals the end of an element in namespace mode.
The name parameter contains the name of the element type, just
as with the startElementNS event."""
def characters(self, content):
"""Receive notification of character data.
The Parser will call this method to report each chunk of
character data. SAX parsers may return all contiguous
character data in a single chunk, or they may split it into
several chunks; however, all of the characters in any single
event must come from the same external entity so that the
Locator provides useful information."""
def ignorableWhitespace(self, whitespace):
"""Receive notification of ignorable whitespace in element content.
Validating Parsers must use this method to report each chunk
of ignorable whitespace (see the W3C XML 1.0 recommendation,
section 2.10): non-validating parsers may also use this method
if they are capable of parsing and using content models.
SAX parsers may return all contiguous whitespace in a single
chunk, or they may split it into several chunks; however, all
of the characters in any single event must come from the same
external entity, so that the Locator provides useful
information."""
def processingInstruction(self, target, data):
"""Receive notification of a processing instruction.
The Parser will invoke this method once for each processing
instruction found: note that processing instructions may occur
before or after the main document element.
A SAX parser should never report an XML declaration (XML 1.0,
section 2.8) or a text declaration (XML 1.0, section 4.3.1)
using this method."""
def skippedEntity(self, name):
"""Receive notification of a skipped entity.
The Parser will invoke this method once for each entity
skipped. Non-validating processors may skip entities if they
have not seen the declarations (because, for example, the
entity was declared in an external DTD subset). All processors
may skip external entities, depending on the values of the
http://xml.org/sax/features/external-general-entities and the
http://xml.org/sax/features/external-parameter-entities
properties."""
# ===== DTDHandler =====
class DTDHandler:
"""Handle DTD events.
This interface specifies only those DTD events required for basic
parsing (unparsed entities and attributes)."""
def notationDecl(self, name, publicId, systemId):
"Handle a notation declaration event."
def unparsedEntityDecl(self, name, publicId, systemId, ndata):
"Handle an unparsed entity declaration event."
# ===== ENTITYRESOLVER =====
class EntityResolver:
"""Basic interface for resolving entities. If you create an object
implementing this interface, then register the object with your
Parser, the parser will call the method in your object to
resolve all external entities. Note that DefaultHandler implements
this interface with the default behaviour."""
def resolveEntity(self, publicId, systemId):
"""Resolve the system identifier of an entity and return either
the system identifier to read from as a string, or an InputSource
to read from."""
return systemId
#============================================================================
#
# CORE FEATURES
#
#============================================================================
feature_namespaces = "http://xml.org/sax/features/namespaces"
# true: Perform Namespace processing (default).
# false: Optionally do not perform Namespace processing
# (implies namespace-prefixes).
# access: (parsing) read-only; (not parsing) read/write
feature_namespace_prefixes = "http://xml.org/sax/features/namespace-prefixes"
# true: Report the original prefixed names and attributes used for Namespace
# declarations.
# false: Do not report attributes used for Namespace declarations, and
# optionally do not report original prefixed names (default).
# access: (parsing) read-only; (not parsing) read/write
feature_string_interning = "http://xml.org/sax/features/string-interning"
# true: All element names, prefixes, attribute names, Namespace URIs, and
# local names are interned using the built-in intern function.
# false: Names are not necessarily interned, although they may be (default).
# access: (parsing) read-only; (not parsing) read/write
feature_validation = "http://xml.org/sax/features/validation"
# true: Report all validation errors (implies external-general-entities and
# external-parameter-entities).
# false: Do not report validation errors.
# access: (parsing) read-only; (not parsing) read/write
feature_external_ges = "http://xml.org/sax/features/external-general-entities"
# true: Include all external general (text) entities.
# false: Do not include external general entities.
# access: (parsing) read-only; (not parsing) read/write
feature_external_pes = "http://xml.org/sax/features/external-parameter-entities"
# true: Include all external parameter entities, including the external
# DTD subset.
# false: Do not include any external parameter entities, even the external
# DTD subset.
# access: (parsing) read-only; (not parsing) read/write
all_features = [feature_namespaces,
feature_namespace_prefixes,
feature_string_interning,
feature_validation,
feature_external_ges,
feature_external_pes]
#============================================================================
#
# CORE PROPERTIES
#
#============================================================================
property_lexical_handler = "http://xml.org/sax/properties/lexical-handler"
# data type: xml.sax.sax2lib.LexicalHandler
# description: An optional extension handler for lexical events like comments.
# access: read/write
property_declaration_handler = "http://xml.org/sax/properties/declaration-handler"
# data type: xml.sax.sax2lib.DeclHandler
# description: An optional extension handler for DTD-related events other
# than notations and unparsed entities.
# access: read/write
property_dom_node = "http://xml.org/sax/properties/dom-node"
# data type: org.w3c.dom.Node
# description: When parsing, the current DOM node being visited if this is
# a DOM iterator; when not parsing, the root DOM node for
# iteration.
# access: (parsing) read-only; (not parsing) read/write
property_xml_string = "http://xml.org/sax/properties/xml-string"
# data type: String
# description: The literal string of characters that was the source for
# the current event.
# access: read-only
property_encoding = "http://www.python.org/sax/properties/encoding"
# data type: String
# description: The name of the encoding to assume for input data.
# access: write: set the encoding, e.g. established by a higher-level
# protocol. May change during parsing (e.g. after
# processing a META tag)
# read: return the current encoding (possibly established through
# auto-detection.
# initial value: UTF-8
#
property_interning_dict = "http://www.python.org/sax/properties/interning-dict"
# data type: Dictionary
# description: The dictionary used to intern common strings in the document
# access: write: Request that the parser uses a specific dictionary, to
# allow interning across different documents
# read: return the current interning dictionary, or None
#
all_properties = [property_lexical_handler,
property_dom_node,
property_declaration_handler,
property_xml_string,
property_encoding,
property_interning_dict]

BIN
xml/sax/handler.pyc Normal file

Binary file not shown.

302
xml/sax/saxutils.py Normal file
View File

@ -0,0 +1,302 @@
"""\
A library of useful helper classes to the SAX classes, for the
convenience of application and driver writers.
"""
import os, urlparse, urllib, types
import handler
import xmlreader
try:
_StringTypes = [types.StringType, types.UnicodeType]
except AttributeError:
_StringTypes = [types.StringType]
# See whether the xmlcharrefreplace error handler is
# supported
try:
from codecs import xmlcharrefreplace_errors
_error_handling = "xmlcharrefreplace"
del xmlcharrefreplace_errors
except ImportError:
_error_handling = "strict"
def __dict_replace(s, d):
"""Replace substrings of a string using a dictionary."""
for key, value in d.items():
s = s.replace(key, value)
return s
def escape(data, entities={}):
"""Escape &, <, and > in a string of data.
You can escape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
# must do ampersand first
data = data.replace("&", "&amp;")
data = data.replace(">", "&gt;")
data = data.replace("<", "&lt;")
if entities:
data = __dict_replace(data, entities)
return data
def unescape(data, entities={}):
"""Unescape &amp;, &lt;, and &gt; in a string of data.
You can unescape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
data = data.replace("&lt;", "<")
data = data.replace("&gt;", ">")
if entities:
data = __dict_replace(data, entities)
# must do ampersand last
return data.replace("&amp;", "&")
def quoteattr(data, entities={}):
"""Escape and quote an attribute value.
Escape &, <, and > in a string of data, then quote it for use as
an attribute value. The \" character will be escaped as well, if
necessary.
You can escape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
entities = entities.copy()
entities.update({'\n': '&#10;', '\r': '&#13;', '\t':'&#9;'})
data = escape(data, entities)
if '"' in data:
if "'" in data:
data = '"%s"' % data.replace('"', "&quot;")
else:
data = "'%s'" % data
else:
data = '"%s"' % data
return data
class XMLGenerator(handler.ContentHandler):
def __init__(self, out=None, encoding="iso-8859-1"):
if out is None:
import sys
out = sys.stdout
handler.ContentHandler.__init__(self)
self._out = out
self._ns_contexts = [{}] # contains uri -> prefix dicts
self._current_context = self._ns_contexts[-1]
self._undeclared_ns_maps = []
self._encoding = encoding
def _write(self, text):
if isinstance(text, str):
self._out.write(text)
else:
self._out.write(text.encode(self._encoding, _error_handling))
def _qname(self, name):
"""Builds a qualified name from a (ns_url, localname) pair"""
if name[0]:
# The name is in a non-empty namespace
prefix = self._current_context[name[0]]
if prefix:
# If it is not the default namespace, prepend the prefix
return prefix + ":" + name[1]
# Return the unqualified name
return name[1]
# ContentHandler methods
def startDocument(self):
self._write('<?xml version="1.0" encoding="%s"?>\n' %
self._encoding)
def startPrefixMapping(self, prefix, uri):
self._ns_contexts.append(self._current_context.copy())
self._current_context[uri] = prefix
self._undeclared_ns_maps.append((prefix, uri))
def endPrefixMapping(self, prefix):
self._current_context = self._ns_contexts[-1]
del self._ns_contexts[-1]
def startElement(self, name, attrs):
self._write('<' + name)
for (name, value) in attrs.items():
self._write(' %s=%s' % (name, quoteattr(value)))
self._write('>')
def endElement(self, name):
self._write('</%s>' % name)
def startElementNS(self, name, qname, attrs):
self._write('<' + self._qname(name))
for prefix, uri in self._undeclared_ns_maps:
if prefix:
self._out.write(' xmlns:%s="%s"' % (prefix, uri))
else:
self._out.write(' xmlns="%s"' % uri)
self._undeclared_ns_maps = []
for (name, value) in attrs.items():
self._write(' %s=%s' % (self._qname(name), quoteattr(value)))
self._write('>')
def endElementNS(self, name, qname):
self._write('</%s>' % self._qname(name))
def characters(self, content):
self._write(escape(content))
def ignorableWhitespace(self, content):
self._write(content)
def processingInstruction(self, target, data):
self._write('<?%s %s?>' % (target, data))
class XMLFilterBase(xmlreader.XMLReader):
"""This class is designed to sit between an XMLReader and the
client application's event handlers. By default, it does nothing
but pass requests up to the reader and events on to the handlers
unmodified, but subclasses can override specific methods to modify
the event stream or the configuration requests as they pass
through."""
def __init__(self, parent = None):
xmlreader.XMLReader.__init__(self)
self._parent = parent
# ErrorHandler methods
def error(self, exception):
self._err_handler.error(exception)
def fatalError(self, exception):
self._err_handler.fatalError(exception)
def warning(self, exception):
self._err_handler.warning(exception)
# ContentHandler methods
def setDocumentLocator(self, locator):
self._cont_handler.setDocumentLocator(locator)
def startDocument(self):
self._cont_handler.startDocument()
def endDocument(self):
self._cont_handler.endDocument()
def startPrefixMapping(self, prefix, uri):
self._cont_handler.startPrefixMapping(prefix, uri)
def endPrefixMapping(self, prefix):
self._cont_handler.endPrefixMapping(prefix)
def startElement(self, name, attrs):
self._cont_handler.startElement(name, attrs)
def endElement(self, name):
self._cont_handler.endElement(name)
def startElementNS(self, name, qname, attrs):
self._cont_handler.startElementNS(name, qname, attrs)
def endElementNS(self, name, qname):
self._cont_handler.endElementNS(name, qname)
def characters(self, content):
self._cont_handler.characters(content)
def ignorableWhitespace(self, chars):
self._cont_handler.ignorableWhitespace(chars)
def processingInstruction(self, target, data):
self._cont_handler.processingInstruction(target, data)
def skippedEntity(self, name):
self._cont_handler.skippedEntity(name)
# DTDHandler methods
def notationDecl(self, name, publicId, systemId):
self._dtd_handler.notationDecl(name, publicId, systemId)
def unparsedEntityDecl(self, name, publicId, systemId, ndata):
self._dtd_handler.unparsedEntityDecl(name, publicId, systemId, ndata)
# EntityResolver methods
def resolveEntity(self, publicId, systemId):
return self._ent_handler.resolveEntity(publicId, systemId)
# XMLReader methods
def parse(self, source):
self._parent.setContentHandler(self)
self._parent.setErrorHandler(self)
self._parent.setEntityResolver(self)
self._parent.setDTDHandler(self)
self._parent.parse(source)
def setLocale(self, locale):
self._parent.setLocale(locale)
def getFeature(self, name):
return self._parent.getFeature(name)
def setFeature(self, name, state):
self._parent.setFeature(name, state)
def getProperty(self, name):
return self._parent.getProperty(name)
def setProperty(self, name, value):
self._parent.setProperty(name, value)
# XMLFilter methods
def getParent(self):
return self._parent
def setParent(self, parent):
self._parent = parent
# --- Utility functions
def prepare_input_source(source, base = ""):
"""This function takes an InputSource and an optional base URL and
returns a fully resolved InputSource object ready for reading."""
if type(source) in _StringTypes:
source = xmlreader.InputSource(source)
elif hasattr(source, "read"):
f = source
source = xmlreader.InputSource()
source.setByteStream(f)
if hasattr(f, "name"):
source.setSystemId(f.name)
if source.getByteStream() is None:
sysid = source.getSystemId()
basehead = os.path.dirname(os.path.normpath(base))
sysidfilename = os.path.join(basehead, sysid)
if os.path.isfile(sysidfilename):
source.setSystemId(sysidfilename)
f = open(sysidfilename, "rb")
else:
source.setSystemId(urlparse.urljoin(base, sysid))
f = urllib.urlopen(source.getSystemId())
source.setByteStream(f)
return source

BIN
xml/sax/saxutils.pyc Normal file

Binary file not shown.

381
xml/sax/xmlreader.py Normal file
View File

@ -0,0 +1,381 @@
"""An XML Reader is the SAX 2 name for an XML parser. XML Parsers
should be based on this code. """
import handler
from _exceptions import SAXNotSupportedException, SAXNotRecognizedException
# ===== XMLREADER =====
class XMLReader:
"""Interface for reading an XML document using callbacks.
XMLReader is the interface that an XML parser's SAX2 driver must
implement. This interface allows an application to set and query
features and properties in the parser, to register event handlers
for document processing, and to initiate a document parse.
All SAX interfaces are assumed to be synchronous: the parse
methods must not return until parsing is complete, and readers
must wait for an event-handler callback to return before reporting
the next event."""
def __init__(self):
self._cont_handler = handler.ContentHandler()
self._dtd_handler = handler.DTDHandler()
self._ent_handler = handler.EntityResolver()
self._err_handler = handler.ErrorHandler()
def parse(self, source):
"Parse an XML document from a system identifier or an InputSource."
raise NotImplementedError("This method must be implemented!")
def getContentHandler(self):
"Returns the current ContentHandler."
return self._cont_handler
def setContentHandler(self, handler):
"Registers a new object to receive document content events."
self._cont_handler = handler
def getDTDHandler(self):
"Returns the current DTD handler."
return self._dtd_handler
def setDTDHandler(self, handler):
"Register an object to receive basic DTD-related events."
self._dtd_handler = handler
def getEntityResolver(self):
"Returns the current EntityResolver."
return self._ent_handler
def setEntityResolver(self, resolver):
"Register an object to resolve external entities."
self._ent_handler = resolver
def getErrorHandler(self):
"Returns the current ErrorHandler."
return self._err_handler
def setErrorHandler(self, handler):
"Register an object to receive error-message events."
self._err_handler = handler
def setLocale(self, locale):
"""Allow an application to set the locale for errors and warnings.
SAX parsers are not required to provide localization for errors
and warnings; if they cannot support the requested locale,
however, they must throw a SAX exception. Applications may
request a locale change in the middle of a parse."""
raise SAXNotSupportedException("Locale support not implemented")
def getFeature(self, name):
"Looks up and returns the state of a SAX2 feature."
raise SAXNotRecognizedException("Feature '%s' not recognized" % name)
def setFeature(self, name, state):
"Sets the state of a SAX2 feature."
raise SAXNotRecognizedException("Feature '%s' not recognized" % name)
def getProperty(self, name):
"Looks up and returns the value of a SAX2 property."
raise SAXNotRecognizedException("Property '%s' not recognized" % name)
def setProperty(self, name, value):
"Sets the value of a SAX2 property."
raise SAXNotRecognizedException("Property '%s' not recognized" % name)
class IncrementalParser(XMLReader):
"""This interface adds three extra methods to the XMLReader
interface that allow XML parsers to support incremental
parsing. Support for this interface is optional, since not all
underlying XML parsers support this functionality.
When the parser is instantiated it is ready to begin accepting
data from the feed method immediately. After parsing has been
finished with a call to close the reset method must be called to
make the parser ready to accept new data, either from feed or
using the parse method.
Note that these methods must _not_ be called during parsing, that
is, after parse has been called and before it returns.
By default, the class also implements the parse method of the XMLReader
interface using the feed, close and reset methods of the
IncrementalParser interface as a convenience to SAX 2.0 driver
writers."""
def __init__(self, bufsize=2**16):
self._bufsize = bufsize
XMLReader.__init__(self)
def parse(self, source):
import saxutils
source = saxutils.prepare_input_source(source)
self.prepareParser(source)
file = source.getByteStream()
buffer = file.read(self._bufsize)
while buffer != "":
self.feed(buffer)
buffer = file.read(self._bufsize)
self.close()
def feed(self, data):
"""This method gives the raw XML data in the data parameter to
the parser and makes it parse the data, emitting the
corresponding events. It is allowed for XML constructs to be
split across several calls to feed.
feed may raise SAXException."""
raise NotImplementedError("This method must be implemented!")
def prepareParser(self, source):
"""This method is called by the parse implementation to allow
the SAX 2.0 driver to prepare itself for parsing."""
raise NotImplementedError("prepareParser must be overridden!")
def close(self):
"""This method is called when the entire XML document has been
passed to the parser through the feed method, to notify the
parser that there are no more data. This allows the parser to
do the final checks on the document and empty the internal
data buffer.
The parser will not be ready to parse another document until
the reset method has been called.
close may raise SAXException."""
raise NotImplementedError("This method must be implemented!")
def reset(self):
"""This method is called after close has been called to reset
the parser so that it is ready to parse new documents. The
results of calling parse or feed after close without calling
reset are undefined."""
raise NotImplementedError("This method must be implemented!")
# ===== LOCATOR =====
class Locator:
"""Interface for associating a SAX event with a document
location. A locator object will return valid results only during
calls to DocumentHandler methods; at any other time, the
results are unpredictable."""
def getColumnNumber(self):
"Return the column number where the current event ends."
return -1
def getLineNumber(self):
"Return the line number where the current event ends."
return -1
def getPublicId(self):
"Return the public identifier for the current event."
return None
def getSystemId(self):
"Return the system identifier for the current event."
return None
# ===== INPUTSOURCE =====
class InputSource:
"""Encapsulation of the information needed by the XMLReader to
read entities.
This class may include information about the public identifier,
system identifier, byte stream (possibly with character encoding
information) and/or the character stream of an entity.
Applications will create objects of this class for use in the
XMLReader.parse method and for returning from
EntityResolver.resolveEntity.
An InputSource belongs to the application, the XMLReader is not
allowed to modify InputSource objects passed to it from the
application, although it may make copies and modify those."""
def __init__(self, system_id = None):
self.__system_id = system_id
self.__public_id = None
self.__encoding = None
self.__bytefile = None
self.__charfile = None
def setPublicId(self, public_id):
"Sets the public identifier of this InputSource."
self.__public_id = public_id
def getPublicId(self):
"Returns the public identifier of this InputSource."
return self.__public_id
def setSystemId(self, system_id):
"Sets the system identifier of this InputSource."
self.__system_id = system_id
def getSystemId(self):
"Returns the system identifier of this InputSource."
return self.__system_id
def setEncoding(self, encoding):
"""Sets the character encoding of this InputSource.
The encoding must be a string acceptable for an XML encoding
declaration (see section 4.3.3 of the XML recommendation).
The encoding attribute of the InputSource is ignored if the
InputSource also contains a character stream."""
self.__encoding = encoding
def getEncoding(self):
"Get the character encoding of this InputSource."
return self.__encoding
def setByteStream(self, bytefile):
"""Set the byte stream (a Python file-like object which does
not perform byte-to-character conversion) for this input
source.
The SAX parser will ignore this if there is also a character
stream specified, but it will use a byte stream in preference
to opening a URI connection itself.
If the application knows the character encoding of the byte
stream, it should set it with the setEncoding method."""
self.__bytefile = bytefile
def getByteStream(self):
"""Get the byte stream for this input source.
The getEncoding method will return the character encoding for
this byte stream, or None if unknown."""
return self.__bytefile
def setCharacterStream(self, charfile):
"""Set the character stream for this input source. (The stream
must be a Python 2.0 Unicode-wrapped file-like that performs
conversion to Unicode strings.)
If there is a character stream specified, the SAX parser will
ignore any byte stream and will not attempt to open a URI
connection to the system identifier."""
self.__charfile = charfile
def getCharacterStream(self):
"Get the character stream for this input source."
return self.__charfile
# ===== ATTRIBUTESIMPL =====
class AttributesImpl:
def __init__(self, attrs):
"""Non-NS-aware implementation.
attrs should be of the form {name : value}."""
self._attrs = attrs
def getLength(self):
return len(self._attrs)
def getType(self, name):
return "CDATA"
def getValue(self, name):
return self._attrs[name]
def getValueByQName(self, name):
return self._attrs[name]
def getNameByQName(self, name):
if not name in self._attrs:
raise KeyError, name
return name
def getQNameByName(self, name):
if not name in self._attrs:
raise KeyError, name
return name
def getNames(self):
return self._attrs.keys()
def getQNames(self):
return self._attrs.keys()
def __len__(self):
return len(self._attrs)
def __getitem__(self, name):
return self._attrs[name]
def keys(self):
return self._attrs.keys()
def has_key(self, name):
return name in self._attrs
def __contains__(self, name):
return self._attrs.has_key(name)
def get(self, name, alternative=None):
return self._attrs.get(name, alternative)
def copy(self):
return self.__class__(self._attrs)
def items(self):
return self._attrs.items()
def values(self):
return self._attrs.values()
# ===== ATTRIBUTESNSIMPL =====
class AttributesNSImpl(AttributesImpl):
def __init__(self, attrs, qnames):
"""NS-aware implementation.
attrs should be of the form {(ns_uri, lname): value, ...}.
qnames of the form {(ns_uri, lname): qname, ...}."""
self._attrs = attrs
self._qnames = qnames
def getValueByQName(self, name):
for (nsname, qname) in self._qnames.items():
if qname == name:
return self._attrs[nsname]
raise KeyError, name
def getNameByQName(self, name):
for (nsname, qname) in self._qnames.items():
if qname == name:
return nsname
raise KeyError, name
def getQNameByName(self, name):
return self._qnames[name]
def getQNames(self):
return self._qnames.values()
def copy(self):
return self.__class__(self._attrs, self._qnames)
def _test():
XMLReader()
IncrementalParser()
Locator()
if __name__ == "__main__":
_test()

BIN
xml/sax/xmlreader.pyc Normal file

Binary file not shown.