mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 23:10:17 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26b6faa88d | |||
| 140736ff33 | |||
| 5379c5683d | |||
| e8b44b5777 | |||
| a0822bc9b0 | |||
| 89b431213b | |||
| 695e8c5af7 | |||
| 0797ec67d4 | |||
| 1852624a7e | |||
| f81dc41a57 | |||
| 34415b89b8 | |||
| d25655588b | |||
| 22c4d185e1 | |||
| e3b3659ea4 | |||
| 22d337790a | |||
| 0784d4b32c | |||
| 332c5bccd9 | |||
| 7055d18a2e | |||
| 6a7c685111 | |||
| 0e5b8f7058 | |||
| e3e6b656e3 | |||
| 99896a5f28 | |||
| 25da44f569 |
+13
-5
@@ -1,19 +1,27 @@
|
|||||||
History
|
History
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
0.9.3 (2011-01-31)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Databook duplication leak fix.
|
||||||
|
* HTML Table output.
|
||||||
|
* Added column sorting.
|
||||||
|
|
||||||
|
|
||||||
0.9.2 (2010-11-17)
|
0.9.2 (2010-11-17)
|
||||||
++++++++++++++++++
|
++++++++++++++++++
|
||||||
|
|
||||||
* Tanspose method added to Datasets
|
* Tanspose method added to Datasets.
|
||||||
* New frozen top row in Excel output
|
* New frozen top row in Excel output.
|
||||||
* Pickling support for Datasets and Rows
|
* Pickling support for Datasets and Rows.
|
||||||
* Support for row/column stacking
|
* Support for row/column stacking.
|
||||||
|
|
||||||
|
|
||||||
0.9.1 (2010-11-04)
|
0.9.1 (2010-11-04)
|
||||||
++++++++++++++++++
|
++++++++++++++++++
|
||||||
|
|
||||||
* Minor reference shadowing bugfix
|
* Minor reference shadowing bugfix.
|
||||||
|
|
||||||
|
|
||||||
0.9.0 (2010-11-04)
|
0.9.0 (2010-11-04)
|
||||||
|
|||||||
@@ -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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
|
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
|
||||||
simplejson, and xlwt.
|
simplejson, and xlwt.
|
||||||
|
|
||||||
|
Markup License
|
||||||
|
==============
|
||||||
|
|
||||||
|
Markup is in the public domain.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
OrderedDict License
|
OrderedDict License
|
||||||
===================
|
===================
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Output formats supported:
|
|||||||
- Excel (Sets + Books)
|
- Excel (Sets + Books)
|
||||||
- JSON (Sets + Books)
|
- JSON (Sets + Books)
|
||||||
- YAML (Sets + Books)
|
- YAML (Sets + Books)
|
||||||
|
- HTML (Sets)
|
||||||
- TSV (Sets)
|
- TSV (Sets)
|
||||||
- CSV (Sets)
|
- CSV (Sets)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* Backwards-compatible OrderedDict support
|
||||||
* Write more exhausive unit-tests.
|
* Write more exhausive unit-tests.
|
||||||
* Write stress tests.
|
* Write stress tests.
|
||||||
* Make CSV write customizable.
|
* Make CSV write customizable.
|
||||||
* HTML Table exports.
|
|
||||||
* Integrate django-tablib
|
* Integrate django-tablib
|
||||||
* Mention django-tablib in Documention
|
* Mention django-tablib in Documention
|
||||||
* Dataset title usage in documentation (#17)
|
* Dataset title usage in documentation (#17)
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
Modifications:
|
Modifications:
|
||||||
|
|
||||||
Copyright (c) 2010 Kenneth Reitz.
|
Copyright (c) 2011 Kenneth Reitz.
|
||||||
|
|
||||||
|
|
||||||
Original Project:
|
Original Project:
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Tablib'
|
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
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ required = []
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='tablib',
|
name='tablib',
|
||||||
version='0.9.2',
|
version='0.9.3',
|
||||||
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
|
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
|
||||||
long_description=open('README.rst').read() + '\n\n' +
|
long_description=open('README.rst').read() + '\n\n' +
|
||||||
open('HISTORY.rst').read(),
|
open('HISTORY.rst').read(),
|
||||||
|
|||||||
+58
-7
@@ -5,21 +5,22 @@
|
|||||||
|
|
||||||
This module implements the central tablib objects.
|
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.
|
:license: MIT, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
from tablib import formats
|
from tablib import formats
|
||||||
|
|
||||||
|
|
||||||
__title__ = 'tablib'
|
__title__ = 'tablib'
|
||||||
__version__ = '0.9.2'
|
__version__ = '0.9.3'
|
||||||
__build__ = 0x000902
|
__build__ = 0x000903
|
||||||
__author__ = 'Kenneth Reitz'
|
__author__ = 'Kenneth Reitz'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
__copyright__ = 'Copyright 2010 Kenneth Reitz'
|
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||||
|
|
||||||
|
|
||||||
class Row(object):
|
class Row(object):
|
||||||
@@ -425,6 +426,14 @@ class Dataset(object):
|
|||||||
Import assumes (for now) that headers exist.
|
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()):
|
def append(self, row=None, col=None, header=None, tags=list()):
|
||||||
"""Adds a row or column to the :class:`Dataset`.
|
"""Adds a row or column to the :class:`Dataset`.
|
||||||
@@ -511,6 +520,7 @@ class Dataset(object):
|
|||||||
else:
|
else:
|
||||||
self._data = [Row([row]) for row in col]
|
self._data = [Row([row]) for row in col]
|
||||||
|
|
||||||
|
|
||||||
def filter(self, tag):
|
def filter(self, tag):
|
||||||
"""Returns a new instance of the :class:`Dataset`, excluding any rows
|
"""Returns a new instance of the :class:`Dataset`, excluding any rows
|
||||||
that do not contain the given :ref:`tags <tags>`.
|
that do not contain the given :ref:`tags <tags>`.
|
||||||
@@ -520,6 +530,43 @@ class Dataset(object):
|
|||||||
|
|
||||||
return _dset
|
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):
|
def transpose(self):
|
||||||
"""Transpose a :class:`Dataset`, turning rows into columns and vice
|
"""Transpose a :class:`Dataset`, turning rows into columns and vice
|
||||||
versa, returning a new ``Dataset`` instance. The first row of the
|
versa, returning a new ``Dataset`` instance. The first row of the
|
||||||
@@ -615,10 +662,14 @@ class Databook(object):
|
|||||||
"""A book of :class:`Dataset` objects.
|
"""A book of :class:`Dataset` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sets=[]):
|
def __init__(self, sets=None):
|
||||||
self._datasets = sets
|
|
||||||
self._register_formats()
|
|
||||||
|
|
||||||
|
if sets is None:
|
||||||
|
self._datasets = list()
|
||||||
|
else:
|
||||||
|
self._datasets = sets
|
||||||
|
|
||||||
|
self._register_formats()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ import _json as json
|
|||||||
import _xls as xls
|
import _xls as xls
|
||||||
import _yaml as yaml
|
import _yaml as yaml
|
||||||
import _tsv as tsv
|
import _tsv as tsv
|
||||||
|
import _html as html
|
||||||
|
|
||||||
available = (json, xls, yaml, csv, tsv)
|
available = (json, xls, yaml, csv, tsv, html)
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -26,11 +26,11 @@ def export_set(dataset):
|
|||||||
def export_book(databook):
|
def export_book(databook):
|
||||||
"""Returns JSON representation of Databook."""
|
"""Returns JSON representation of Databook."""
|
||||||
return json.dumps(databook._package())
|
return json.dumps(databook._package())
|
||||||
|
|
||||||
|
|
||||||
def import_set(dset, in_stream):
|
def import_set(dset, in_stream):
|
||||||
"""Returns dataset from JSON stream."""
|
"""Returns dataset from JSON stream."""
|
||||||
|
|
||||||
dset.wipe()
|
dset.wipe()
|
||||||
dset.dict = json.loads(in_stream)
|
dset.dict = json.loads(in_stream)
|
||||||
|
|
||||||
@@ -52,4 +52,4 @@ def detect(stream):
|
|||||||
json.loads(stream)
|
json.loads(stream)
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -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 < and >
|
||||||
|
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( '&', '&' )
|
||||||
|
if '>' in text:
|
||||||
|
text = text.replace( '>', '>' )
|
||||||
|
if '<' in text:
|
||||||
|
text = text.replace( '<', '<' )
|
||||||
|
if '\"' in text:
|
||||||
|
text = text.replace( '\"', '"' )
|
||||||
|
if '\'' in text:
|
||||||
|
text = text.replace( '\'', '"' )
|
||||||
|
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 '&' in text:
|
||||||
|
text = text.replace( '&', '&' )
|
||||||
|
if '>' in text:
|
||||||
|
text = text.replace( '>', '>' )
|
||||||
|
if '<' in text:
|
||||||
|
text = text.replace( '<', '<' )
|
||||||
|
if '"' in text:
|
||||||
|
text = text.replace( '"', '\"' )
|
||||||
|
|
||||||
|
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__
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from tablib.packages import markup
|
||||||
|
|
||||||
import tablib
|
import tablib
|
||||||
|
|
||||||
|
|
||||||
@@ -182,6 +184,27 @@ class TablibTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(tsv, self.founders.tsv)
|
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):
|
def test_unicode_append(self):
|
||||||
"""Passes in a single unicode charecter and exports."""
|
"""Passes in a single unicode charecter and exports."""
|
||||||
|
|
||||||
@@ -402,6 +425,22 @@ class TablibTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(column_stacked[0],
|
self.assertEqual(column_stacked[0],
|
||||||
("John", "Adams", 90, "John", "Adams", 90))
|
("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):
|
def test_wipe(self):
|
||||||
"""Purge a dataset."""
|
"""Purge a dataset."""
|
||||||
|
|||||||
Reference in New Issue
Block a user