Compare commits

...

23 Commits

Author SHA1 Message Date
Kenneth Reitz 26b6faa88d Merge branch 'release/0.9.3' 2011-01-31 01:35:52 -05:00
Kenneth Reitz 140736ff33 fabfile typo. 2011-01-31 01:34:40 -05:00
Kenneth Reitz 5379c5683d Markup license notice.
PD? Really?
2011-01-31 01:33:12 -05:00
Kenneth Reitz e8b44b5777 Version bump. 2011-01-31 01:33:00 -05:00
Kenneth Reitz a0822bc9b0 sorting update. 2011-01-31 01:29:41 -05:00
Kenneth Reitz 89b431213b Sorting update for headerless datasets. 2011-01-31 01:28:10 -05:00
Kenneth Reitz 695e8c5af7 Merge branch 'feature/sorting' into release/0.9.3 2011-01-31 00:58:27 -05:00
Kenneth Reitz 0797ec67d4 Prepping for new release (0.9.3) 2011-01-31 00:58:16 -05:00
Kenneth Reitz 1852624a7e Merge pull request #28 from cswegger/tablib
---

This patch provides simple column-based sorting to tablib. A (passing) unit-test is also included.
2011-01-22 18:35:34 -05:00
Luca Beltrame f81dc41a57 Support for sorting. Unit-tested. 2011-01-11 20:53:59 +01:00
Kenneth Reitz 34415b89b8 New Year! 2011-01-10 19:28:12 -05:00
Kenneth Reitz d25655588b TODO update. 2010-12-13 17:08:11 -05:00
Kenneth Reitz 22c4d185e1 Export HTML for Databooks. 2010-11-21 21:33:01 -05:00
Kenneth Reitz e3b3659ea4 whitespace fix 2010-11-21 21:32:00 -05:00
Kenneth Reitz 22d337790a small changes to html output 2010-11-21 18:58:30 -05:00
Kenneth Reitz 0784d4b32c Updated todo w/ new html output feature 2010-11-21 18:55:45 -05:00
Kenneth Reitz 332c5bccd9 Merge branch 'feature/html_out' into develop 2010-11-21 18:53:21 -05:00
Kenneth Reitz 7055d18a2e History update. 2010-11-21 18:53:18 -05:00
Kenneth Reitz 6a7c685111 Import path fix. 2010-11-21 18:49:02 -05:00
Kenneth Reitz 0e5b8f7058 Merge branch 'fix_databooks' into develop 2010-11-21 18:44:24 -05:00
Luca Beltrame e3e6b656e3 Fix the stupid mistake. 2010-11-21 13:17:36 +01:00
Luca Beltrame 99896a5f28 Fix Databook data leaks. 2010-11-21 13:14:47 +01:00
Luca Beltrame 25da44f569 Support for HTML (export only). Unit-tested. Depends on the "markup.py"
package(http://markup.sourceforge.net) which is included in packages/
Notice that the tests now depend on the presence of markup.py.
2010-11-21 13:00:56 +01:00
14 changed files with 670 additions and 22 deletions
+13 -5
View File
@@ -1,19 +1,27 @@
History
-------
0.9.3 (2011-01-31)
++++++++++++++++++
* Databook duplication leak fix.
* HTML Table output.
* Added column sorting.
0.9.2 (2010-11-17)
++++++++++++++++++
* Tanspose method added to Datasets
* New frozen top row in Excel output
* Pickling support for Datasets and Rows
* Support for row/column stacking
* Tanspose method added to Datasets.
* New frozen top row in Excel output.
* Pickling support for Datasets and Rows.
* Support for row/column stacking.
0.9.1 (2010-11-04)
++++++++++++++++++
* Minor reference shadowing bugfix
* Minor reference shadowing bugfix.
0.9.0 (2010-11-04)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2010 Kenneth Reitz.
Copyright (c) 2011 Kenneth Reitz.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+6
View File
@@ -1,6 +1,12 @@
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
simplejson, and xlwt.
Markup License
==============
Markup is in the public domain.
OrderedDict License
===================
+1
View File
@@ -18,6 +18,7 @@ Output formats supported:
- Excel (Sets + Books)
- JSON (Sets + Books)
- YAML (Sets + Books)
- HTML (Sets)
- TSV (Sets)
- CSV (Sets)
+7 -2
View File
@@ -1,8 +1,13 @@
* Add seperator support to HTML out
* Hooks System
- pre/post-append
- pre/post-import
- pre/post-export
* Big Data
* Backwards-compatible OrderedDict support
* Write more exhausive unit-tests.
* Write stress tests.
* Make CSV write customizable.
* HTML Table exports.
* Integrate django-tablib
* Mention django-tablib in Documention
* Dataset title usage in documentation (#17)
* Dataset title usage in documentation (#17)
+1 -1
View File
@@ -1,6 +1,6 @@
Modifications:
Copyright (c) 2010 Kenneth Reitz.
Copyright (c) 2011 Kenneth Reitz.
Original Project:
+1 -1
View File
@@ -42,7 +42,7 @@ master_doc = 'index'
# General information about the project.
project = u'Tablib'
copyright = u'2010, Kenneth Reitz. Styles (modified) © Armin Ronacher'
copyright = u'2011, Kenneth Reitz. Styles (modified) © Armin Ronacher'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
+1 -1
View File
@@ -19,7 +19,7 @@ required = []
setup(
name='tablib',
version='0.9.2',
version='0.9.3',
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
+58 -7
View File
@@ -5,21 +5,22 @@
This module implements the central tablib objects.
:copyright: (c) 2010 by Kenneth Reitz.
:copyright: (c) 2011 by Kenneth Reitz.
:license: MIT, see LICENSE for more details.
"""
from copy import copy
from operator import itemgetter
from tablib import formats
__title__ = 'tablib'
__version__ = '0.9.2'
__build__ = 0x000902
__version__ = '0.9.3'
__build__ = 0x000903
__author__ = 'Kenneth Reitz'
__license__ = 'MIT'
__copyright__ = 'Copyright 2010 Kenneth Reitz'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
class Row(object):
@@ -425,6 +426,14 @@ class Dataset(object):
Import assumes (for now) that headers exist.
"""
@property
def html():
"""A HTML table representation of the :class:`Dataset` object. If
headers have been set, they will be used as table headers.
..notice:: This method can be used for export only.
"""
pass
def append(self, row=None, col=None, header=None, tags=list()):
"""Adds a row or column to the :class:`Dataset`.
@@ -511,6 +520,7 @@ class Dataset(object):
else:
self._data = [Row([row]) for row in col]
def filter(self, tag):
"""Returns a new instance of the :class:`Dataset`, excluding any rows
that do not contain the given :ref:`tags <tags>`.
@@ -520,6 +530,43 @@ class Dataset(object):
return _dset
def sort(self, col, reverse=False):
"""Sort a :class:`Dataset` by a specific column, given string (for
header) or integer (for column index). The order can be reversed by
setting ``reverse`` to ``True``.
Returns a new :class:`Dataset` instance where columns have been
sorted."""
if isinstance(col, basestring):
if not self.headers:
raise HeadersNeeded
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
_dset = Dataset(headers=self.headers)
for item in _sorted:
row = [item[key] for key in self.headers]
_dset.append(row=row)
else:
if self.headers:
col = self.headers[col]
_sorted = sorted(self.dict, key=itemgetter(col), reverse=reverse)
_dset = Dataset(headers=self.headers)
for item in _sorted:
if self.headers:
row = [item[key] for key in self.headers]
else:
row = item
_dset.append(row=row)
return _dset
def transpose(self):
"""Transpose a :class:`Dataset`, turning rows into columns and vice
versa, returning a new ``Dataset`` instance. The first row of the
@@ -615,10 +662,14 @@ class Databook(object):
"""A book of :class:`Dataset` objects.
"""
def __init__(self, sets=[]):
self._datasets = sets
self._register_formats()
def __init__(self, sets=None):
if sets is None:
self._datasets = list()
else:
self._datasets = sets
self._register_formats()
def __repr__(self):
try:
+2 -1
View File
@@ -8,5 +8,6 @@ import _json as json
import _xls as xls
import _yaml as yaml
import _tsv as tsv
import _html as html
available = (json, xls, yaml, csv, tsv)
available = (json, xls, yaml, csv, tsv, html)
+53
View File
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
""" Tablib - HTML export support.
"""
from StringIO import StringIO
from tablib.packages import markup
import tablib
BOOK_ENDINGS = 'h3'
title = 'html'
extentions = ('html', )
def export_set(dataset):
"""HTML representation of a Dataset."""
stream = StringIO()
page = markup.page()
page.table.open()
if dataset.headers is not None:
page.thead.open()
headers = markup.oneliner.th(dataset.headers)
page.tr(headers)
page.thead.close()
for row in dataset:
html_row = markup.oneliner.td(row)
page.tr(html_row)
page.table.close()
stream.writelines(str(page))
return stream.getvalue()
def export_book(databook):
"""HTML representation of a Databook."""
stream = StringIO()
for i, dset in enumerate(databook._datasets):
title = (dset.title if dset.title else 'Set %s' % (i))
stream.write('<%s>%s</%s>\n' % (BOOK_ENDINGS, title, BOOK_ENDINGS))
stream.write(dset.html)
stream.write('\n')
return stream.getvalue()
+3 -3
View File
@@ -26,11 +26,11 @@ def export_set(dataset):
def export_book(databook):
"""Returns JSON representation of Databook."""
return json.dumps(databook._package())
def import_set(dset, in_stream):
"""Returns dataset from JSON stream."""
dset.wipe()
dset.dict = json.loads(in_stream)
@@ -52,4 +52,4 @@ def detect(stream):
json.loads(stream)
return True
except ValueError:
return False
return False
+484
View File
@@ -0,0 +1,484 @@
# This code is in the public domain, it comes
# with absolutely no warranty and you can do
# absolutely whatever you want with it.
__date__ = '17 May 2007'
__version__ = '1.7'
__doc__= """
This is markup.py - a Python module that attempts to
make it easier to generate HTML/XML from a Python program
in an intuitive, lightweight, customizable and pythonic way.
The code is in the public domain.
Version: %s as of %s.
Documentation and further info is at http://markup.sourceforge.net/
Please send bug reports, feature requests, enhancement
ideas or questions to nogradi at gmail dot com.
Installation: drop markup.py somewhere into your Python path.
""" % ( __version__, __date__ )
import string
class element:
"""This class handles the addition of a new element."""
def __init__( self, tag, case='lower', parent=None ):
self.parent = parent
if case == 'lower':
self.tag = tag.lower( )
else:
self.tag = tag.upper( )
def __call__( self, *args, **kwargs ):
if len( args ) > 1:
raise ArgumentError( self.tag )
# if class_ was defined in parent it should be added to every element
if self.parent is not None and self.parent.class_ is not None:
if 'class_' not in kwargs:
kwargs['class_'] = self.parent.class_
if self.parent is None and len( args ) == 1:
x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
return '\n'.join( x )
elif self.parent is None and len( args ) == 0:
x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
return '\n'.join( x )
if self.tag in self.parent.twotags:
for myarg, mydict in _argsdicts( args, kwargs ):
self.render( self.tag, False, myarg, mydict )
elif self.tag in self.parent.onetags:
if len( args ) == 0:
for myarg, mydict in _argsdicts( args, kwargs ):
self.render( self.tag, True, myarg, mydict ) # here myarg is always None, because len( args ) = 0
else:
raise ClosingError( self.tag )
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
else:
raise InvalidElementError( self.tag, self.parent.mode )
def render( self, tag, single, between, kwargs ):
"""Append the actual tags to content."""
out = "<%s" % tag
for key, value in kwargs.iteritems( ):
if value is not None: # when value is None that means stuff like <... checked>
key = key.strip('_') # strip this so class_ will mean class, etc.
if key == 'http_equiv': # special cases, maybe change _ to - overall?
key = 'http-equiv'
elif key == 'accept_charset':
key = 'accept-charset'
out = "%s %s=\"%s\"" % ( out, key, escape( value ) )
else:
out = "%s %s" % ( out, key )
if between is not None:
out = "%s>%s</%s>" % ( out, between, tag )
else:
if single:
out = "%s />" % out
else:
out = "%s>" % out
if self.parent is not None:
self.parent.content.append( out )
else:
return out
def close( self ):
"""Append a closing tag unless element has only opening tag."""
if self.tag in self.parent.twotags:
self.parent.content.append( "</%s>" % self.tag )
elif self.tag in self.parent.onetags:
raise ClosingError( self.tag )
elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
def open( self, **kwargs ):
"""Append an opening tag."""
if self.tag in self.parent.twotags or self.tag in self.parent.onetags:
self.render( self.tag, False, None, kwargs )
elif self.mode == 'strict_html' and self.tag in self.parent.deptags:
raise DeprecationError( self.tag )
class page:
"""This is our main class representing a document. Elements are added
as attributes of an instance of this class."""
def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ):
"""Stuff that effects the whole document.
mode -- 'strict_html' for HTML 4.01 (default)
'html' alias for 'strict_html'
'loose_html' to allow some deprecated elements
'xml' to allow arbitrary elements
case -- 'lower' element names will be printed in lower case (default)
'upper' they will be printed in upper case
onetags -- list or tuple of valid elements with opening tags only
twotags -- list or tuple of valid elements with both opening and closing tags
these two keyword arguments may be used to select
the set of valid elements in 'xml' mode
invalid elements will raise appropriate exceptions
separator -- string to place between added elements, defaults to newline
class_ -- a class that will be added to every element if defined"""
valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ]
valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON",
"CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET",
"FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS",
"KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP",
"OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE",
"SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR",
"TT", "UL", "VAR" ]
deprecated_onetags = [ "BASEFONT", "ISINDEX" ]
deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ]
self.header = [ ]
self.content = [ ]
self.footer = [ ]
self.case = case
self.separator = separator
# init( ) sets it to True so we know that </body></html> has to be printed at the end
self._full = False
self.class_= class_
if mode == 'strict_html' or mode == 'html':
self.onetags = valid_onetags
self.onetags += map( string.lower, self.onetags )
self.twotags = valid_twotags
self.twotags += map( string.lower, self.twotags )
self.deptags = deprecated_onetags + deprecated_twotags
self.deptags += map( string.lower, self.deptags )
self.mode = 'strict_html'
elif mode == 'loose_html':
self.onetags = valid_onetags + deprecated_onetags
self.onetags += map( string.lower, self.onetags )
self.twotags = valid_twotags + deprecated_twotags
self.twotags += map( string.lower, self.twotags )
self.mode = mode
elif mode == 'xml':
if onetags and twotags:
self.onetags = onetags
self.twotags = twotags
elif ( onetags and not twotags ) or ( twotags and not onetags ):
raise CustomizationError( )
else:
self.onetags = russell( )
self.twotags = russell( )
self.mode = mode
else:
raise ModeError( mode )
def __getattr__( self, attr ):
if attr.startswith("__") and attr.endswith("__"):
raise AttributeError, attr
return element( attr, case=self.case, parent=self )
def __str__( self ):
if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ):
end = [ '</body>', '</html>' ]
else:
end = [ ]
return self.separator.join( self.header + self.content + self.footer + end )
def __call__( self, escape=False ):
"""Return the document as a string.
escape -- False print normally
True replace < and > by &lt; and &gt;
the default escape sequences in most browsers"""
if escape:
return _escape( self.__str__( ) )
else:
return self.__str__( )
def add( self, text ):
"""This is an alias to addcontent."""
self.addcontent( text )
def addfooter( self, text ):
"""Add some text to the bottom of the document"""
self.footer.append( text )
def addheader( self, text ):
"""Add some text to the top of the document"""
self.header.append( text )
def addcontent( self, text ):
"""Add some text to the main part of the document"""
self.content.append( text )
def init( self, lang='en', css=None, metainfo=None, title=None, header=None,
footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None ):
"""This method is used for complete documents with appropriate
doctype, encoding, title, etc information. For an HTML/XML snippet
omit this method.
lang -- language, usually a two character string, will appear
as <html lang='en'> in html mode (ignored in xml mode)
css -- Cascading Style Sheet filename as a string or a list of
strings for multiple css files (ignored in xml mode)
metainfo -- a dictionary in the form { 'name':'content' } to be inserted
into meta element(s) as <meta name='name' content='content'>
(ignored in xml mode)
bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added
as attributes of the <body> element as <body key='value' ... >
(ignored in xml mode)
script -- dictionary containing src:type pairs, <script type='text/type' src=src></script>
title -- the title of the document as a string to be inserted into
a title element as <title>my title</title> (ignored in xml mode)
header -- some text to be inserted right after the <body> element
(ignored in xml mode)
footer -- some text to be inserted right before the </body> element
(ignored in xml mode)
charset -- a string defining the character set, will be inserted into a
<meta http-equiv='Content-Type' content='text/html; charset=myset'>
element (ignored in xml mode)
encoding -- a string defining the encoding, will be put into to first line of
the document as <?xml version='1.0' encoding='myencoding' ?> in
xml mode (ignored in html mode)
doctype -- the document type string, defaults to
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
in html mode (ignored in xml mode)"""
self._full = True
if self.mode == 'strict_html' or self.mode == 'loose_html':
if doctype is None:
doctype = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>"
self.header.append( doctype )
self.html( lang=lang )
self.head( )
if charset is not None:
self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset )
if metainfo is not None:
self.metainfo( metainfo )
if css is not None:
self.css( css )
if title is not None:
self.title( title )
if script is not None:
self.scripts( script )
self.head.close()
if bodyattrs is not None:
self.body( **bodyattrs )
else:
self.body( )
if header is not None:
self.content.append( header )
if footer is not None:
self.footer.append( footer )
elif self.mode == 'xml':
if doctype is None:
if encoding is not None:
doctype = "<?xml version='1.0' encoding='%s' ?>" % encoding
else:
doctype = "<?xml version='1.0' ?>"
self.header.append( doctype )
def css( self, filelist ):
"""This convenience function is only useful for html.
It adds css stylesheet(s) to the document via the <link> element."""
if isinstance( filelist, basestring ):
self.link( href=filelist, rel='stylesheet', type='text/css', media='all' )
else:
for file in filelist:
self.link( href=file, rel='stylesheet', type='text/css', media='all' )
def metainfo( self, mydict ):
"""This convenience function is only useful for html.
It adds meta information via the <meta> element, the argument is
a dictionary of the form { 'name':'content' }."""
if isinstance( mydict, dict ):
for name, content in mydict.iteritems( ):
self.meta( name=name, content=content )
else:
raise TypeError, "Metainfo should be called with a dictionary argument of name:content pairs."
def scripts( self, mydict ):
"""Only useful in html, mydict is dictionary of src:type pairs will
be rendered as <script type='text/type' src=src></script>"""
if isinstance( mydict, dict ):
for src, type in mydict.iteritems( ):
self.script( '', src=src, type='text/%s' % type )
else:
raise TypeError, "Script should be given a dictionary of src:type pairs."
class _oneliner:
"""An instance of oneliner returns a string corresponding to one element.
This class can be used to write 'oneliners' that return a string
immediately so there is no need to instantiate the page class."""
def __init__( self, case='lower' ):
self.case = case
def __getattr__( self, attr ):
if attr.startswith("__") and attr.endswith("__"):
raise AttributeError, attr
return element( attr, case=self.case, parent=None )
oneliner = _oneliner( case='lower' )
upper_oneliner = _oneliner( case='upper' )
def _argsdicts( args, mydict ):
"""A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1."""
if len( args ) == 0:
args = None,
elif len( args ) == 1:
args = _totuple( args[0] )
else:
raise Exception, "We should have never gotten here."
mykeys = mydict.keys( )
myvalues = map( _totuple, mydict.values( ) )
maxlength = max( map( len, [ args ] + myvalues ) )
for i in xrange( maxlength ):
thisdict = { }
for key, value in zip( mykeys, myvalues ):
try:
thisdict[ key ] = value[i]
except IndexError:
thisdict[ key ] = value[-1]
try:
thisarg = args[i]
except IndexError:
thisarg = args[-1]
yield thisarg, thisdict
def _totuple( x ):
"""Utility stuff to convert string, int, float, None or anything to a usable tuple."""
if isinstance( x, basestring ):
out = x,
elif isinstance( x, ( int, float ) ):
out = str( x ),
elif x is None:
out = None,
else:
out = tuple( x )
return out
def escape( text, newline=False ):
"""Escape special html characters."""
if isinstance( text, basestring ):
if '&' in text:
text = text.replace( '&', '&amp;' )
if '>' in text:
text = text.replace( '>', '&gt;' )
if '<' in text:
text = text.replace( '<', '&lt;' )
if '\"' in text:
text = text.replace( '\"', '&quot;' )
if '\'' in text:
text = text.replace( '\'', '&quot;' )
if newline:
if '\n' in text:
text = text.replace( '\n', '<br>' )
return text
_escape = escape
def unescape( text ):
"""Inverse of escape."""
if isinstance( text, basestring ):
if '&amp;' in text:
text = text.replace( '&amp;', '&' )
if '&gt;' in text:
text = text.replace( '&gt;', '>' )
if '&lt;' in text:
text = text.replace( '&lt;', '<' )
if '&quot;' in text:
text = text.replace( '&quot;', '\"' )
return text
class dummy:
"""A dummy class for attaching attributes."""
pass
doctype = dummy( )
doctype.frameset = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/html4/frameset.dtd'>"
doctype.strict = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"
doctype.loose = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>"
class russell:
"""A dummy class that contains anything."""
def __contains__( self, item ):
return True
class MarkupError( Exception ):
"""All our exceptions subclass this."""
def __str__( self ):
return self.message
class ClosingError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag
class OpeningError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' can not be opened." % tag
class ArgumentError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' was called with more than one non-keyword argument." % tag
class InvalidElementError( MarkupError ):
def __init__( self, tag, mode ):
self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode )
class DeprecationError( MarkupError ):
def __init__( self, tag ):
self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag
class ModeError( MarkupError ):
def __init__( self, mode ):
self.message = "Mode '%s' is invalid, possible values: strict_html, loose_html, xml." % mode
class CustomizationError( MarkupError ):
def __init__( self ):
self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'."
if __name__ == '__main__':
print __doc__
+39
View File
@@ -5,6 +5,8 @@
import unittest
from tablib.packages import markup
import tablib
@@ -182,6 +184,27 @@ class TablibTestCase(unittest.TestCase):
self.assertEqual(tsv, self.founders.tsv)
def test_html_export(self):
"""HTML export"""
html = markup.page()
html.table.open()
html.thead.open()
html.tr(markup.oneliner.th(self.founders.headers))
html.thead.close()
for founder in self.founders:
html.tr(markup.oneliner.td(founder))
html.table.close()
html = str(html)
self.assertEqual(html, self.founders.html)
def test_unicode_append(self):
"""Passes in a single unicode charecter and exports."""
@@ -402,6 +425,22 @@ class TablibTestCase(unittest.TestCase):
self.assertEqual(column_stacked[0],
("John", "Adams", 90, "John", "Adams", 90))
def test_sorting(self):
"""Sort columns."""
sorted_data = self.founders.sort(col="first_name")
first_row = sorted_data[0]
second_row = sorted_data[2]
third_row = sorted_data[1]
expected_first = self.founders[1]
expected_second = self.founders[2]
expected_third = self.founders[0]
self.assertEqual(first_row, expected_first)
self.assertEqual(second_row, expected_second)
self.assertEqual(third_row, expected_third)
def test_wipe(self):
"""Purge a dataset."""