[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