[Mpuls-commits] r4000 - in base/trunk: . mpulsweb/lib
scm-commit@wald.intevation.org
scm-commit at wald.intevation.org
Wed Oct 20 20:13:18 CEST 2010
Author: bh
Date: 2010-10-20 20:13:16 +0200 (Wed, 20 Oct 2010)
New Revision: 4000
Modified:
base/trunk/ChangeLog
base/trunk/mpulsweb/lib/export.py
Log:
* mpulsweb/lib/export.py (CSVTable, CSVContainer, CSVExport): New.
Classes to implement CSV export.
Modified: base/trunk/ChangeLog
===================================================================
--- base/trunk/ChangeLog 2010-10-20 12:01:22 UTC (rev 3999)
+++ base/trunk/ChangeLog 2010-10-20 18:13:16 UTC (rev 4000)
@@ -1,5 +1,10 @@
2010-10-20 Bernhard Herzog <bh at intevation.de>
+ * mpulsweb/lib/export.py (CSVTable, CSVContainer, CSVExport): New.
+ Classes to implement CSV export.
+
+2010-10-20 Bernhard Herzog <bh at intevation.de>
+
* mpulsweb/model/case.py (MPulsXMLImporter.import_allowed): The
uuid may be None now, in which case we cannot perform any checks
and always allow the import.
Modified: base/trunk/mpulsweb/lib/export.py
===================================================================
--- base/trunk/mpulsweb/lib/export.py 2010-10-20 12:01:22 UTC (rev 3999)
+++ base/trunk/mpulsweb/lib/export.py 2010-10-20 18:13:16 UTC (rev 4000)
@@ -2,10 +2,13 @@
from StringIO import StringIO
import logging
import itertools
+import zipfile
+import stat
import pyExcelerator
from formed.meta.data import PageNode, WidgetCollector, RepeatNode
+from formed.meta.structure import StructureWidgetCollector
from mpulsweb.lib.base import g
@@ -156,11 +159,151 @@
out.real_close()
+class CSVTable:
+
+ """A single Table that can be serialized as CSV"""
+
+ def __init__(self, columns):
+ """Initialize the table.
+ The parameter columns is a list defining the column names"""
+ self.columns = columns
+ self.rows = []
+
+ def append(self, row):
+ """Append a row to the table.
+ The parameter row should be a dictionary mapping column names to
+ values.
+ """
+ self.rows.append(row)
+
+ def serialize(self, sep):
+ """Return the table as a string in CSV format.
+
+ The parameter sep is the separator character. Rows are
+ separated by newlines. If the separator character occurs in
+ values, all occurrences are replaced by spaces or, if the
+ separator is the space character, by tabs.
+ """
+ lines = ["#" + sep.join(self.columns)]
+ for row in self.rows:
+ lines.append(sep.join(self.encode(row.get(col), sep)
+ for col in self.columns))
+ lines.append("")
+ return "\n".join(lines)
+
+ def encode(self, value, sep):
+ # first, convert non-strings to strings
+ if isinstance(value, int):
+ # Simply converting ints to unicode assumes that the
+ # separator char is neither a digit nor the minus sign.
+ value = unicode(value)
+ elif value is None:
+ value = ""
+
+ # convert strings if they contain the separator or CR or NL.
+ if isinstance(value, basestring):
+ rep = "\t" if sep == " " else " "
+ value = value.replace(sep, rep)
+ value = value.replace("\r\n", rep)
+ value = value.replace("\r", rep)
+ value = value.replace("\n", rep)
+ return value
+ else:
+ raise TypeError("Cannot handle type %r in CSV export" % type(value))
+
+
+
+class CSVContainer:
+
+ """A container of multiple CSVTable instances"""
+
+ def __init__(self, meta_tree, selection):
+ """Initialize the container with a FormEd meta tree and the selection.
+ """
+ self.meta_tree = meta_tree
+ self.tables = dict()
+ self.create_tables(selection)
+
+ def create_tables(self, selection):
+ table_columns = dict()
+ repeat_groups = set()
+ for page in self.meta_tree.findAllByClass(PageNode):
+ if page.getName() not in selection:
+ continue
+
+ collector = StructureWidgetCollector()
+ page.visit(collector.visitor)
+ for nc in collector.widgets:
+ table_name = "master"
+ for p in nc.pathToRoot():
+ if isinstance(p, RepeatNode):
+ table_name = p.getName()
+ repeat_groups.add(table_name)
+ break
+ table_columns.setdefault(table_name, []).append(nc.getName())
+
+ for name, columns in table_columns.iteritems():
+ extra_columns = ["id"]
+ if name in repeat_groups:
+ extra_columns.append("master_id")
+ self.tables[name] = CSVTable(extra_columns + columns)
+
+ def fill(self, tree):
+ """Fill the tables with the contents of the ElementTree tree."""
+ self.repeat_group_ids = itertools.count()
+ for case_id, case in enumerate(tree.getiterator('master')):
+ self.fill_table(case, case_id)
+
+ def fill_table(self, node, node_id, parent_id=None):
+ table = self.tables[node.tag]
+
+ row = dict(id=node_id)
+ if parent_id is not None:
+ row["master_id"] = parent_id
+
+ for child in node.getchildren():
+ name = child.tag
+ nc = self.meta_tree.findByName(name)
+ if isinstance(nc, RepeatNode):
+ self.fill_table(child, self.repeat_group_ids.next(), node_id)
+ else:
+ row[name] = child.text
+ table.append(row)
+
+ def write_zip_file(self, output, encoding="utf-8"):
+ """Write the contents of the container to output as a zip-File"""
+ def add(name, contents):
+ name = name.encode(encoding)
+ z.writestr(name, contents.encode(encoding))
+
+ # By default all permission bits are unset when writestr is
+ # used so that when unzipping the resulting zip-file, all
+ # files are unreadable. Set default permissions so that the
+ # owner may read and write and all other can read.
+ info = z.getinfo(name)
+ # expression to set unix permissions on external_attr taken
+ # from the Python 2.5 zipfile module
+ permissions = (stat.S_IRUSR | stat.S_IWUSR
+ | stat.S_IRGRP | stat.S_IROTH)
+ info.external_attr = (permissions & 0xFFFF) << 16L
+
+ z = zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED)
+ sep = "\t"
+ add("master", self.tables["master"].serialize(sep))
+ for name, table in self.tables.iteritems():
+ if name != "master":
+ add(name, table.serialize(sep))
+
+
+
class CSVExport(Export):
def __init__(self, tree, selection):
- self.csv = ""
+ self.tree = tree
+ self.csv = CSVContainer(g.formedTree, selection)
def export(self):
- return "Test CSV"
-
+ self.csv.fill(self.tree)
+ s = StringIO()
+ self.csv.write_zip_file(s)
+ return s.getvalue()
More information about the Mpuls-commits
mailing list