Libraries for Subliminal
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The sax module contains a collection of classes that provide a
|
||||
(D)ocument (O)bject (M)odel representation of an XML document.
|
||||
The goal is to provide an easy, intuative interface for managing XML
|
||||
documents. Although, the term, DOM, is used above, this model is
|
||||
B{far} better.
|
||||
|
||||
XML namespaces in suds are represented using a (2) element tuple
|
||||
containing the prefix and the URI. Eg: I{('tns', 'http://myns')}
|
||||
|
||||
@var encoder: A I{pluggable} XML special character processor used to
|
||||
encode/decode strings.
|
||||
@type encoder: L{Encoder}
|
||||
"""
|
||||
|
||||
from suds.sax.enc import Encoder
|
||||
|
||||
#
|
||||
# pluggable XML special character encoder.
|
||||
#
|
||||
encoder = Encoder()
|
||||
|
||||
|
||||
def splitPrefix(name):
|
||||
"""
|
||||
Split the name into a tuple (I{prefix}, I{name}). The first element in
|
||||
the tuple is I{None} when the name does't have a prefix.
|
||||
@param name: A node name containing an optional prefix.
|
||||
@type name: basestring
|
||||
@return: A tuple containing the (2) parts of I{name}
|
||||
@rtype: (I{prefix}, I{name})
|
||||
"""
|
||||
if isinstance(name, basestring) \
|
||||
and ':' in name:
|
||||
return tuple(name.split(':', 1))
|
||||
else:
|
||||
return (None, name)
|
||||
|
||||
|
||||
class Namespace:
|
||||
"""
|
||||
The namespace class represents XML namespaces.
|
||||
"""
|
||||
|
||||
default = (None, None)
|
||||
xmlns = ('xml', 'http://www.w3.org/XML/1998/namespace')
|
||||
xsdns = ('xs', 'http://www.w3.org/2001/XMLSchema')
|
||||
xsins = ('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
|
||||
all = (xsdns, xsins)
|
||||
|
||||
@classmethod
|
||||
def create(cls, p=None, u=None):
|
||||
return (p, u)
|
||||
|
||||
@classmethod
|
||||
def none(cls, ns):
|
||||
return ( ns == cls.default )
|
||||
|
||||
@classmethod
|
||||
def xsd(cls, ns):
|
||||
try:
|
||||
return cls.w3(ns) and ns[1].endswith('XMLSchema')
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def xsi(cls, ns):
|
||||
try:
|
||||
return cls.w3(ns) and ns[1].endswith('XMLSchema-instance')
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def xs(cls, ns):
|
||||
return ( cls.xsd(ns) or cls.xsi(ns) )
|
||||
|
||||
@classmethod
|
||||
def w3(cls, ns):
|
||||
try:
|
||||
return ns[1].startswith('http://www.w3.org')
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def isns(cls, ns):
|
||||
try:
|
||||
return isinstance(ns, tuple) and len(ns) == len(cls.default)
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
@@ -0,0 +1,181 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XML I{attribute} classes.
|
||||
"""
|
||||
|
||||
import suds.sax
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
from suds.sax.text import Text
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
class Attribute:
|
||||
"""
|
||||
An XML attribute object.
|
||||
@ivar parent: The node containing this attribute
|
||||
@type parent: L{element.Element}
|
||||
@ivar prefix: The I{optional} namespace prefix.
|
||||
@type prefix: basestring
|
||||
@ivar name: The I{unqualified} name of the attribute
|
||||
@type name: basestring
|
||||
@ivar value: The attribute's value
|
||||
@type value: basestring
|
||||
"""
|
||||
def __init__(self, name, value=None):
|
||||
"""
|
||||
@param name: The attribute's name with I{optional} namespace prefix.
|
||||
@type name: basestring
|
||||
@param value: The attribute's value
|
||||
@type value: basestring
|
||||
"""
|
||||
self.parent = None
|
||||
self.prefix, self.name = splitPrefix(name)
|
||||
self.setValue(value)
|
||||
|
||||
def clone(self, parent=None):
|
||||
"""
|
||||
Clone this object.
|
||||
@param parent: The parent for the clone.
|
||||
@type parent: L{element.Element}
|
||||
@return: A copy of this object assigned to the new parent.
|
||||
@rtype: L{Attribute}
|
||||
"""
|
||||
a = Attribute(self.qname(), self.value)
|
||||
a.parent = parent
|
||||
return a
|
||||
|
||||
def qname(self):
|
||||
"""
|
||||
Get the B{fully} qualified name of this attribute
|
||||
@return: The fully qualified name.
|
||||
@rtype: basestring
|
||||
"""
|
||||
if self.prefix is None:
|
||||
return self.name
|
||||
else:
|
||||
return ':'.join((self.prefix, self.name))
|
||||
|
||||
def setValue(self, value):
|
||||
"""
|
||||
Set the attributes value
|
||||
@param value: The new value (may be None)
|
||||
@type value: basestring
|
||||
@return: self
|
||||
@rtype: L{Attribute}
|
||||
"""
|
||||
if isinstance(value, Text):
|
||||
self.value = value
|
||||
else:
|
||||
self.value = Text(value)
|
||||
return self
|
||||
|
||||
def getValue(self, default=Text('')):
|
||||
"""
|
||||
Get the attributes value with optional default.
|
||||
@param default: An optional value to be return when the
|
||||
attribute's has not been set.
|
||||
@type default: basestring
|
||||
@return: The attribute's value, or I{default}
|
||||
@rtype: L{Text}
|
||||
"""
|
||||
if self.hasText():
|
||||
return self.value
|
||||
else:
|
||||
return default
|
||||
|
||||
def hasText(self):
|
||||
"""
|
||||
Get whether the attribute has I{text} and that it is not an empty
|
||||
(zero length) string.
|
||||
@return: True when has I{text}.
|
||||
@rtype: boolean
|
||||
"""
|
||||
return ( self.value is not None and len(self.value) )
|
||||
|
||||
def namespace(self):
|
||||
"""
|
||||
Get the attributes namespace. This may either be the namespace
|
||||
defined by an optional prefix, or its parent's namespace.
|
||||
@return: The attribute's namespace
|
||||
@rtype: (I{prefix}, I{name})
|
||||
"""
|
||||
if self.prefix is None:
|
||||
return Namespace.default
|
||||
else:
|
||||
return self.resolvePrefix(self.prefix)
|
||||
|
||||
def resolvePrefix(self, prefix):
|
||||
"""
|
||||
Resolve the specified prefix to a known namespace.
|
||||
@param prefix: A declared prefix
|
||||
@type prefix: basestring
|
||||
@return: The namespace that has been mapped to I{prefix}
|
||||
@rtype: (I{prefix}, I{name})
|
||||
"""
|
||||
ns = Namespace.default
|
||||
if self.parent is not None:
|
||||
ns = self.parent.resolvePrefix(prefix)
|
||||
return ns
|
||||
|
||||
def match(self, name=None, ns=None):
|
||||
"""
|
||||
Match by (optional) name and/or (optional) namespace.
|
||||
@param name: The optional attribute tag name.
|
||||
@type name: str
|
||||
@param ns: An optional namespace.
|
||||
@type ns: (I{prefix}, I{name})
|
||||
@return: True if matched.
|
||||
@rtype: boolean
|
||||
"""
|
||||
if name is None:
|
||||
byname = True
|
||||
else:
|
||||
byname = ( self.name == name )
|
||||
if ns is None:
|
||||
byns = True
|
||||
else:
|
||||
byns = ( self.namespace()[1] == ns[1] )
|
||||
return ( byname and byns )
|
||||
|
||||
def __eq__(self, rhs):
|
||||
""" equals operator """
|
||||
return rhs is not None and \
|
||||
isinstance(rhs, Attribute) and \
|
||||
self.prefix == rhs.name and \
|
||||
self.name == rhs.name
|
||||
|
||||
def __repr__(self):
|
||||
""" get a string representation """
|
||||
return \
|
||||
'attr (prefix=%s, name=%s, value=(%s))' %\
|
||||
(self.prefix, self.name, self.value)
|
||||
|
||||
def __str__(self):
|
||||
""" get an xml string representation """
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
""" get an xml string representation """
|
||||
n = self.qname()
|
||||
if self.hasText():
|
||||
v = self.value.escape()
|
||||
else:
|
||||
v = self.value
|
||||
return u'%s="%s"' % (n, v)
|
||||
@@ -0,0 +1,378 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Nathan Van Gheem (vangheem@gmail.com)
|
||||
|
||||
"""
|
||||
The I{xdate} module provides classes for converstion
|
||||
between XML dates and python objects.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
import time
|
||||
import datetime as dt
|
||||
import re
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Date:
|
||||
"""
|
||||
An XML date object.
|
||||
Supported formats:
|
||||
- YYYY-MM-DD
|
||||
- YYYY-MM-DD(z|Z)
|
||||
- YYYY-MM-DD+06:00
|
||||
- YYYY-MM-DD-06:00
|
||||
@ivar date: The object value.
|
||||
@type date: B{datetime}.I{date}
|
||||
"""
|
||||
def __init__(self, date):
|
||||
"""
|
||||
@param date: The value of the object.
|
||||
@type date: (date|str)
|
||||
@raise ValueError: When I{date} is invalid.
|
||||
"""
|
||||
if isinstance(date, dt.date):
|
||||
self.date = date
|
||||
return
|
||||
if isinstance(date, basestring):
|
||||
self.date = self.__parse(date)
|
||||
return
|
||||
raise ValueError, type(date)
|
||||
|
||||
def year(self):
|
||||
"""
|
||||
Get the I{year} component.
|
||||
@return: The year.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.date.year
|
||||
|
||||
def month(self):
|
||||
"""
|
||||
Get the I{month} component.
|
||||
@return: The month.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.date.month
|
||||
|
||||
def day(self):
|
||||
"""
|
||||
Get the I{day} component.
|
||||
@return: The day.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.date.day
|
||||
|
||||
def __parse(self, s):
|
||||
"""
|
||||
Parse the string date.
|
||||
Supported formats:
|
||||
- YYYY-MM-DD
|
||||
- YYYY-MM-DD(z|Z)
|
||||
- YYYY-MM-DD+06:00
|
||||
- YYYY-MM-DD-06:00
|
||||
Although, the TZ is ignored because it's meaningless
|
||||
without the time, right?
|
||||
@param s: A date string.
|
||||
@type s: str
|
||||
@return: A date object.
|
||||
@rtype: I{date}
|
||||
"""
|
||||
try:
|
||||
year, month, day = s[:10].split('-', 2)
|
||||
year = int(year)
|
||||
month = int(month)
|
||||
day = int(day)
|
||||
return dt.date(year, month, day)
|
||||
except:
|
||||
log.debug(s, exec_info=True)
|
||||
raise ValueError, 'Invalid format "%s"' % s
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.date.isoformat()
|
||||
|
||||
|
||||
class Time:
|
||||
"""
|
||||
An XML time object.
|
||||
Supported formats:
|
||||
- HH:MI:SS
|
||||
- HH:MI:SS(z|Z)
|
||||
- HH:MI:SS.ms
|
||||
- HH:MI:SS.ms(z|Z)
|
||||
- HH:MI:SS(+|-)06:00
|
||||
- HH:MI:SS.ms(+|-)06:00
|
||||
@ivar tz: The timezone
|
||||
@type tz: L{Timezone}
|
||||
@ivar date: The object value.
|
||||
@type date: B{datetime}.I{time}
|
||||
"""
|
||||
|
||||
def __init__(self, time, adjusted=True):
|
||||
"""
|
||||
@param time: The value of the object.
|
||||
@type time: (time|str)
|
||||
@param adjusted: Adjust for I{local} Timezone.
|
||||
@type adjusted: boolean
|
||||
@raise ValueError: When I{time} is invalid.
|
||||
"""
|
||||
self.tz = Timezone()
|
||||
if isinstance(time, dt.time):
|
||||
self.time = time
|
||||
return
|
||||
if isinstance(time, basestring):
|
||||
self.time = self.__parse(time)
|
||||
if adjusted:
|
||||
self.__adjust()
|
||||
return
|
||||
raise ValueError, type(time)
|
||||
|
||||
def hour(self):
|
||||
"""
|
||||
Get the I{hour} component.
|
||||
@return: The hour.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.time.hour
|
||||
|
||||
def minute(self):
|
||||
"""
|
||||
Get the I{minute} component.
|
||||
@return: The minute.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.time.minute
|
||||
|
||||
def second(self):
|
||||
"""
|
||||
Get the I{seconds} component.
|
||||
@return: The seconds.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.time.second
|
||||
|
||||
def microsecond(self):
|
||||
"""
|
||||
Get the I{microsecond} component.
|
||||
@return: The microsecond.
|
||||
@rtype: int
|
||||
"""
|
||||
return self.time.microsecond
|
||||
|
||||
def __adjust(self):
|
||||
"""
|
||||
Adjust for TZ offset.
|
||||
"""
|
||||
if hasattr(self, 'offset'):
|
||||
today = dt.date.today()
|
||||
delta = self.tz.adjustment(self.offset)
|
||||
d = dt.datetime.combine(today, self.time)
|
||||
d = ( d + delta )
|
||||
self.time = d.time()
|
||||
|
||||
def __parse(self, s):
|
||||
"""
|
||||
Parse the string date.
|
||||
Patterns:
|
||||
- HH:MI:SS
|
||||
- HH:MI:SS(z|Z)
|
||||
- HH:MI:SS.ms
|
||||
- HH:MI:SS.ms(z|Z)
|
||||
- HH:MI:SS(+|-)06:00
|
||||
- HH:MI:SS.ms(+|-)06:00
|
||||
@param s: A time string.
|
||||
@type s: str
|
||||
@return: A time object.
|
||||
@rtype: B{datetime}.I{time}
|
||||
"""
|
||||
try:
|
||||
offset = None
|
||||
part = Timezone.split(s)
|
||||
hour, minute, second = part[0].split(':', 2)
|
||||
hour = int(hour)
|
||||
minute = int(minute)
|
||||
second, ms = self.__second(second)
|
||||
if len(part) == 2:
|
||||
self.offset = self.__offset(part[1])
|
||||
if ms is None:
|
||||
return dt.time(hour, minute, second)
|
||||
else:
|
||||
return dt.time(hour, minute, second, ms)
|
||||
except:
|
||||
log.debug(s, exec_info=True)
|
||||
raise ValueError, 'Invalid format "%s"' % s
|
||||
|
||||
def __second(self, s):
|
||||
"""
|
||||
Parse the seconds and microseconds.
|
||||
The microseconds are truncated to 999999 due to a restriction in
|
||||
the python datetime.datetime object.
|
||||
@param s: A string representation of the seconds.
|
||||
@type s: str
|
||||
@return: Tuple of (sec,ms)
|
||||
@rtype: tuple.
|
||||
"""
|
||||
part = s.split('.')
|
||||
if len(part) > 1:
|
||||
return (int(part[0]), int(part[1][:6]))
|
||||
else:
|
||||
return (int(part[0]), None)
|
||||
|
||||
def __offset(self, s):
|
||||
"""
|
||||
Parse the TZ offset.
|
||||
@param s: A string representation of the TZ offset.
|
||||
@type s: str
|
||||
@return: The signed offset in hours.
|
||||
@rtype: str
|
||||
"""
|
||||
if len(s) == len('-00:00'):
|
||||
return int(s[:3])
|
||||
if len(s) == 0:
|
||||
return self.tz.local
|
||||
if len(s) == 1:
|
||||
return 0
|
||||
raise Exception()
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self)
|
||||
|
||||
def __unicode__(self):
|
||||
time = self.time.isoformat()
|
||||
if self.tz.local:
|
||||
return '%s%+.2d:00' % (time, self.tz.local)
|
||||
else:
|
||||
return '%sZ' % time
|
||||
|
||||
|
||||
class DateTime(Date,Time):
|
||||
"""
|
||||
An XML time object.
|
||||
Supported formats:
|
||||
- YYYY-MM-DDB{T}HH:MI:SS
|
||||
- YYYY-MM-DDB{T}HH:MI:SS(z|Z)
|
||||
- YYYY-MM-DDB{T}HH:MI:SS.ms
|
||||
- YYYY-MM-DDB{T}HH:MI:SS.ms(z|Z)
|
||||
- YYYY-MM-DDB{T}HH:MI:SS(+|-)06:00
|
||||
- YYYY-MM-DDB{T}HH:MI:SS.ms(+|-)06:00
|
||||
@ivar datetime: The object value.
|
||||
@type datetime: B{datetime}.I{datedate}
|
||||
"""
|
||||
def __init__(self, date):
|
||||
"""
|
||||
@param date: The value of the object.
|
||||
@type date: (datetime|str)
|
||||
@raise ValueError: When I{tm} is invalid.
|
||||
"""
|
||||
if isinstance(date, dt.datetime):
|
||||
Date.__init__(self, date.date())
|
||||
Time.__init__(self, date.time())
|
||||
self.datetime = \
|
||||
dt.datetime.combine(self.date, self.time)
|
||||
return
|
||||
if isinstance(date, basestring):
|
||||
part = date.split('T')
|
||||
Date.__init__(self, part[0])
|
||||
Time.__init__(self, part[1], 0)
|
||||
self.datetime = \
|
||||
dt.datetime.combine(self.date, self.time)
|
||||
self.__adjust()
|
||||
return
|
||||
raise ValueError, type(date)
|
||||
|
||||
def __adjust(self):
|
||||
"""
|
||||
Adjust for TZ offset.
|
||||
"""
|
||||
if not hasattr(self, 'offset'):
|
||||
return
|
||||
delta = self.tz.adjustment(self.offset)
|
||||
try:
|
||||
d = ( self.datetime + delta )
|
||||
self.datetime = d
|
||||
self.date = d.date()
|
||||
self.time = d.time()
|
||||
except OverflowError:
|
||||
log.warn('"%s" caused overflow, not-adjusted', self.datetime)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self)
|
||||
|
||||
def __unicode__(self):
|
||||
s = []
|
||||
s.append(Date.__unicode__(self))
|
||||
s.append(Time.__unicode__(self))
|
||||
return 'T'.join(s)
|
||||
|
||||
|
||||
class UTC(DateTime):
|
||||
"""
|
||||
Represents current UTC time.
|
||||
"""
|
||||
|
||||
def __init__(self, date=None):
|
||||
if date is None:
|
||||
date = dt.datetime.utcnow()
|
||||
DateTime.__init__(self, date)
|
||||
self.tz.local = 0
|
||||
|
||||
|
||||
class Timezone:
|
||||
"""
|
||||
Timezone object used to do TZ conversions
|
||||
@cvar local: The (A) local TZ offset.
|
||||
@type local: int
|
||||
@cvar patten: The regex patten to match TZ.
|
||||
@type patten: re.Pattern
|
||||
"""
|
||||
|
||||
pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})')
|
||||
|
||||
LOCAL = ( 0-time.timezone/60/60 )
|
||||
|
||||
def __init__(self, offset=None):
|
||||
if offset is None:
|
||||
offset = self.LOCAL
|
||||
self.local = offset
|
||||
|
||||
@classmethod
|
||||
def split(cls, s):
|
||||
"""
|
||||
Split the TZ from string.
|
||||
@param s: A string containing a timezone
|
||||
@type s: basestring
|
||||
@return: The split parts.
|
||||
@rtype: tuple
|
||||
"""
|
||||
m = cls.pattern.search(s)
|
||||
if m is None:
|
||||
return (s,)
|
||||
x = m.start(0)
|
||||
return (s[:x], s[x:])
|
||||
|
||||
def adjustment(self, offset):
|
||||
"""
|
||||
Get the adjustment to the I{local} TZ.
|
||||
@return: The delta between I{offset} and local TZ.
|
||||
@rtype: B{datetime}.I{timedelta}
|
||||
"""
|
||||
delta = ( self.local - offset )
|
||||
return dt.timedelta(hours=delta)
|
||||
@@ -0,0 +1,61 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XML I{document} classes.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
from suds.sax.element import Element
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
class Document(Element):
|
||||
""" simple document """
|
||||
|
||||
DECL = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
|
||||
def __init__(self, root=None):
|
||||
Element.__init__(self, 'document')
|
||||
if root is not None:
|
||||
self.append(root)
|
||||
|
||||
def root(self):
|
||||
if len(self.children):
|
||||
return self.children[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def str(self):
|
||||
s = []
|
||||
s.append(self.DECL)
|
||||
s.append('\n')
|
||||
s.append(self.root().str())
|
||||
return ''.join(s)
|
||||
|
||||
def plain(self):
|
||||
s = []
|
||||
s.append(self.DECL)
|
||||
s.append(self.root().plain())
|
||||
return ''.join(s)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.str()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XML I{special character} encoder classes.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
class Encoder:
|
||||
"""
|
||||
An XML special character encoder/decoder.
|
||||
@cvar encodings: A mapping of special characters encoding.
|
||||
@type encodings: [(str,str)]
|
||||
@cvar decodings: A mapping of special characters decoding.
|
||||
@type decodings: [(str,str)]
|
||||
@cvar special: A list of special characters
|
||||
@type special: [char]
|
||||
"""
|
||||
|
||||
encodings = \
|
||||
(( '&(?!(amp|lt|gt|quot|apos);)', '&' ),( '<', '<' ),( '>', '>' ),( '"', '"' ),("'", ''' ))
|
||||
decodings = \
|
||||
(( '<', '<' ),( '>', '>' ),( '"', '"' ),( ''', "'" ),( '&', '&' ))
|
||||
special = \
|
||||
('&', '<', '>', '"', "'")
|
||||
|
||||
def needsEncoding(self, s):
|
||||
"""
|
||||
Get whether string I{s} contains special characters.
|
||||
@param s: A string to check.
|
||||
@type s: str
|
||||
@return: True if needs encoding.
|
||||
@rtype: boolean
|
||||
"""
|
||||
if isinstance(s, basestring):
|
||||
for c in self.special:
|
||||
if c in s:
|
||||
return True
|
||||
return False
|
||||
|
||||
def encode(self, s):
|
||||
"""
|
||||
Encode special characters found in string I{s}.
|
||||
@param s: A string to encode.
|
||||
@type s: str
|
||||
@return: The encoded string.
|
||||
@rtype: str
|
||||
"""
|
||||
if isinstance(s, basestring) and self.needsEncoding(s):
|
||||
for x in self.encodings:
|
||||
s = re.sub(x[0], x[1], s)
|
||||
return s
|
||||
|
||||
def decode(self, s):
|
||||
"""
|
||||
Decode special characters encodings found in string I{s}.
|
||||
@param s: A string to decode.
|
||||
@type s: str
|
||||
@return: The decoded string.
|
||||
@rtype: str
|
||||
"""
|
||||
if isinstance(s, basestring) and '&' in s:
|
||||
for x in self.decodings:
|
||||
s = s.replace(x[0], x[1])
|
||||
return s
|
||||
@@ -0,0 +1,139 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The sax module contains a collection of classes that provide a
|
||||
(D)ocument (O)bject (M)odel representation of an XML document.
|
||||
The goal is to provide an easy, intuative interface for managing XML
|
||||
documents. Although, the term, DOM, is used above, this model is
|
||||
B{far} better.
|
||||
|
||||
XML namespaces in suds are represented using a (2) element tuple
|
||||
containing the prefix and the URI. Eg: I{('tns', 'http://myns')}
|
||||
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
import suds.metrics
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
from suds.sax.document import Document
|
||||
from suds.sax.element import Element
|
||||
from suds.sax.text import Text
|
||||
from suds.sax.attribute import Attribute
|
||||
from xml.sax import make_parser, InputSource, ContentHandler
|
||||
from xml.sax.handler import feature_external_ges
|
||||
from cStringIO import StringIO
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Handler(ContentHandler):
|
||||
""" sax hanlder """
|
||||
|
||||
def __init__(self):
|
||||
self.nodes = [Document()]
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
top = self.top()
|
||||
node = Element(unicode(name), parent=top)
|
||||
for a in attrs.getNames():
|
||||
n = unicode(a)
|
||||
v = unicode(attrs.getValue(a))
|
||||
attribute = Attribute(n,v)
|
||||
if self.mapPrefix(node, attribute):
|
||||
continue
|
||||
node.append(attribute)
|
||||
node.charbuffer = []
|
||||
top.append(node)
|
||||
self.push(node)
|
||||
|
||||
def mapPrefix(self, node, attribute):
|
||||
skip = False
|
||||
if attribute.name == 'xmlns':
|
||||
if len(attribute.value):
|
||||
node.expns = unicode(attribute.value)
|
||||
skip = True
|
||||
elif attribute.prefix == 'xmlns':
|
||||
prefix = attribute.name
|
||||
node.nsprefixes[prefix] = unicode(attribute.value)
|
||||
skip = True
|
||||
return skip
|
||||
|
||||
def endElement(self, name):
|
||||
name = unicode(name)
|
||||
current = self.top()
|
||||
if len(current.charbuffer):
|
||||
current.text = Text(u''.join(current.charbuffer))
|
||||
del current.charbuffer
|
||||
if len(current):
|
||||
current.trim()
|
||||
currentqname = current.qname()
|
||||
if name == currentqname:
|
||||
self.pop()
|
||||
else:
|
||||
raise Exception('malformed document')
|
||||
|
||||
def characters(self, content):
|
||||
text = unicode(content)
|
||||
node = self.top()
|
||||
node.charbuffer.append(text)
|
||||
|
||||
def push(self, node):
|
||||
self.nodes.append(node)
|
||||
return node
|
||||
|
||||
def pop(self):
|
||||
return self.nodes.pop()
|
||||
|
||||
def top(self):
|
||||
return self.nodes[len(self.nodes)-1]
|
||||
|
||||
|
||||
class Parser:
|
||||
""" SAX Parser """
|
||||
|
||||
@classmethod
|
||||
def saxparser(cls):
|
||||
p = make_parser()
|
||||
p.setFeature(feature_external_ges, 0)
|
||||
h = Handler()
|
||||
p.setContentHandler(h)
|
||||
return (p, h)
|
||||
|
||||
def parse(self, file=None, string=None):
|
||||
"""
|
||||
SAX parse XML text.
|
||||
@param file: Parse a python I{file-like} object.
|
||||
@type file: I{file-like} object.
|
||||
@param string: Parse string XML.
|
||||
@type string: str
|
||||
"""
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
sax, handler = self.saxparser()
|
||||
if file is not None:
|
||||
sax.parse(file)
|
||||
timer.stop()
|
||||
metrics.log.debug('sax (%s) duration: %s', file, timer)
|
||||
return handler.nodes[0]
|
||||
if string is not None:
|
||||
source = InputSource(None)
|
||||
source.setByteStream(StringIO(string))
|
||||
sax.parse(source)
|
||||
timer.stop()
|
||||
metrics.log.debug('%s\nsax duration: %s', string, timer)
|
||||
return handler.nodes[0]
|
||||
@@ -0,0 +1,116 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser 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.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Contains XML text classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
|
||||
|
||||
class Text(unicode):
|
||||
"""
|
||||
An XML text object used to represent text content.
|
||||
@ivar lang: The (optional) language flag.
|
||||
@type lang: bool
|
||||
@ivar escaped: The (optional) XML special character escaped flag.
|
||||
@type escaped: bool
|
||||
"""
|
||||
__slots__ = ('lang', 'escaped',)
|
||||
|
||||
@classmethod
|
||||
def __valid(cls, *args):
|
||||
return ( len(args) and args[0] is not None )
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls.__valid(*args):
|
||||
lang = kwargs.pop('lang', None)
|
||||
escaped = kwargs.pop('escaped', False)
|
||||
result = super(Text, cls).__new__(cls, *args, **kwargs)
|
||||
result.lang = lang
|
||||
result.escaped = escaped
|
||||
else:
|
||||
result = None
|
||||
return result
|
||||
|
||||
def escape(self):
|
||||
"""
|
||||
Encode (escape) special XML characters.
|
||||
@return: The text with XML special characters escaped.
|
||||
@rtype: L{Text}
|
||||
"""
|
||||
if not self.escaped:
|
||||
post = sax.encoder.encode(self)
|
||||
escaped = ( post != self )
|
||||
return Text(post, lang=self.lang, escaped=escaped)
|
||||
return self
|
||||
|
||||
def unescape(self):
|
||||
"""
|
||||
Decode (unescape) special XML characters.
|
||||
@return: The text with escaped XML special characters decoded.
|
||||
@rtype: L{Text}
|
||||
"""
|
||||
if self.escaped:
|
||||
post = sax.encoder.decode(self)
|
||||
return Text(post, lang=self.lang)
|
||||
return self
|
||||
|
||||
def trim(self):
|
||||
post = self.strip()
|
||||
return Text(post, lang=self.lang, escaped=self.escaped)
|
||||
|
||||
def __add__(self, other):
|
||||
joined = u''.join((self, other))
|
||||
result = Text(joined, lang=self.lang, escaped=self.escaped)
|
||||
if isinstance(other, Text):
|
||||
result.escaped = ( self.escaped or other.escaped )
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
s = [self]
|
||||
if self.lang is not None:
|
||||
s.append(' [%s]' % self.lang)
|
||||
if self.escaped:
|
||||
s.append(' <escaped>')
|
||||
return ''.join(s)
|
||||
|
||||
def __getstate__(self):
|
||||
state = {}
|
||||
for k in self.__slots__:
|
||||
state[k] = getattr(self, k)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
for k in self.__slots__:
|
||||
setattr(self, k, state[k])
|
||||
|
||||
|
||||
class Raw(Text):
|
||||
"""
|
||||
Raw text which is not XML escaped.
|
||||
This may include I{string} XML.
|
||||
"""
|
||||
def escape(self):
|
||||
return self
|
||||
|
||||
def unescape(self):
|
||||
return self
|
||||
|
||||
def __add__(self, other):
|
||||
joined = u''.join((self, other))
|
||||
return Raw(joined, lang=self.lang)
|
||||
Reference in New Issue
Block a user