/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/save.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/Model/save.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

trunk/thuban/Thuban/Model/save.py revision 74 by bh, Mon Feb 4 19:22:08 2002 UTC branches/WIP-pyshapelib-bramz/Thuban/Model/save.py revision 2734 by bramz, Thu Mar 1 12:42:59 2007 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 by Intevation GmbH  # Copyright (c) 2001-2005 by Intevation GmbH
2  # Authors:  # Authors:
3  # Jan-Oliver Wagner <[email protected]>  # Jan-Oliver Wagner <[email protected]> (2004-2005)
4  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]> (2001-2004)
5    # Jonathan Coles <[email protected]> (2003)
6    # Frank Koormann <[email protected]> (2003)
7  #  #
8  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
9  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
# Line 11  Functions to save a session to a file Line 13  Functions to save a session to a file
13  """  """
14    
15  __version__ = "$Revision$"  __version__ = "$Revision$"
16    # $Source$
17    # $Id$
18    
19  import os  import os
 import string  
20    
21  from Thuban.Lib.fileutil import relative_filename  import Thuban.Lib.fileutil
22    
23  def escape(data):  from Thuban.Model.layer import Layer, RasterLayer
24      """Escape &, \", ', <, and > in a string of data.  
25    from Thuban.Model.classification import \
26        ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, \
27        ClassGroupPattern, ClassGroupMap
28    from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
29    from Thuban.Model.table import DBFTable, FIELDTYPE_STRING
30    from Thuban.Model.data import DerivedShapeStore, FileShapeStore, \
31                                  SHAPETYPE_POINT
32    
33    from Thuban.Model.xmlwriter import XMLWriter
34    from postgisdb import PostGISConnection, PostGISShapeStore
35    
36    def relative_filename(dir, filename):
37        """Return a filename relative to dir for the absolute file name absname.
38    
39        This is almost the same as the function in fileutil, except that dir
40        can be an empty string in which case filename will be returned
41        unchanged.
42      """      """
43      data = string.replace(data, "&", "&amp;")      if dir:
44      data = string.replace(data, "<", "&lt;")          return Thuban.Lib.fileutil.relative_filename(dir, filename)
45      data = string.replace(data, ">", "&gt;")      else:
46      data = string.replace(data, '"', "&quot;")          return filename
     data = string.replace(data, "'", "&apos;")  
     return data  
   
 def save_session(session, filename):  
     """Save the session session to the file given by filename"""  
     dir = os.path.dirname(filename)  
     file = open(filename, 'w')  
     write = file.write  
     write('<?xml version="1.0" encoding="UTF-8"?>\n')  
     write('<!DOCTYPE session SYSTEM "thuban.dtd">\n')  
     write('<session title="%s">\n' % escape(session.title))  
     for map in session.Maps():  
         write('\t<map title="%s">\n' % escape(map.title))  
         if map.projection and len(map.projection.params) > 0:  
             write('\t\t<projection>\n')  
             for param in map.projection.params:  
                 write('\t\t\t<parameter value="%s"/>\n' % escape(param))  
             write('\t\t</projection>\n')  
47    
48          for layer in map.Layers():  
49              fill = layer.fill  def unify_filename(filename):
50              if fill is None:      """Return a 'unified' version of filename
51                  fill = "None"  
52        The .thuban files should be as platform independent as possible.
53        Since they must contain filenames the filenames have to unified. We
54        unify on unix-like filenames for now, which means we do nothing on a
55        posix system and simply replace backslashes with slashes on windows
56        """
57        if os.name == "posix":
58            return filename
59        elif os.name == "nt":
60            return "/".join(filename.split("\\"))
61        else:
62            raise RuntimeError("Unsupported platform for unify_filename: %s"
63                               % os.name)
64    
65    def sort_data_stores(stores):
66        """Return a topologically sorted version of the sequence of data containers
67    
68        The list is sorted so that data containers that depend on other data
69        containers have higher indexes than the containers they depend on.
70        """
71        if not stores:
72            return []
73        processed = {}
74        result = []
75        todo = stores[:]
76        while todo:
77            # It doesn't really matter which if the items of todo is
78            # processed next, but if we take the first one, the order is
79            # preserved to some degree which makes writing some of the test
80            # cases easier.
81            container = todo.pop(0)
82            if id(container) in processed:
83                continue
84            deps = [dep for dep in container.Dependencies()
85                        if id(dep) not in processed]
86            if deps:
87                todo.append(container)
88                todo.extend(deps)
89            else:
90                result.append(container)
91                processed[id(container)] = 1
92        return result
93    
94    def bool2str(b):
95        if b: return "true"
96        else: return "false"
97    
98    class SessionSaver(XMLWriter):
99    
100        """Class to serialize a session into an XML file.
101    
102        Applications built on top of Thuban may derive from this class and
103        override or extend the methods to save additional information. This
104        additional information should take the form of additional attributes
105        or elements whose names are prefixed with a namespace. To define a
106        namespace derived classes should extend the write_session method to
107        pass the namespaces to the default implementation.
108        """
109    
110    
111        def __init__(self, session):
112            XMLWriter.__init__(self)
113            self.session = session
114            # Map object ids to the ids used in the thuban files
115            self.idmap = {}
116    
117        def get_id(self, obj):
118            """Return the id used in the thuban file for the object obj"""
119            return self.idmap.get(id(obj))
120    
121        def define_id(self, obj, value = None):
122            if value is None:
123                value = "D" + str(id(obj))
124            self.idmap[id(obj)] = value
125            return value
126    
127        def has_id(self, obj):
128            return self.idmap.has_key(id(obj))
129    
130        def prepare_filename(self, filename):
131            """Return the string to use when writing filename to the thuban file
132    
133            The returned string is a unified version (only slashes as
134            directory separators, see unify_filename) of filename expressed
135            relative to the directory the .thuban file is written to.
136            """
137            return unify_filename(relative_filename(self.dir, filename))
138    
139        def write(self, file_or_filename):
140            XMLWriter.write(self, file_or_filename)
141    
142            self.write_header("session", "thuban-1.1.dtd")
143            self.write_session(self.session)
144            self.close()
145    
146        def write_session(self, session, attrs = None, namespaces = ()):
147            """Write the session and its contents
148    
149            By default, write a session element with the title attribute and
150            call write_map for each map contained in the session.
151    
152            The optional argument attrs is for additional attributes and, if
153            given, should be a mapping from attribute names to attribute
154            values. The values should not be XML-escaped yet.
155    
156            The optional argument namespaces, if given, should be a sequence
157            of (name, URI) pairs. The namespaces are written as namespace
158            attributes into the session element. This is mainly useful for
159            derived classes that need to store additional information in a
160            thuban session file.
161            """
162            if attrs is None:
163                attrs = {}
164            attrs["title"] = session.title
165            for name, uri in namespaces:
166                attrs["xmlns:" + name] = uri
167            # default name space
168            attrs["xmlns"] = \
169                   "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"
170            self.open_element("session", attrs)
171            self.write_db_connections(session)
172            self.write_data_containers(session)
173            for map in session.Maps():
174                self.write_map(map)
175            self.close_element("session")
176    
177        def write_db_connections(self, session):
178            for conn in session.DBConnections():
179                if isinstance(conn, PostGISConnection):
180                    self.write_element("dbconnection",
181                                       {"id": self.define_id(conn),
182                                        "dbtype": "postgis",
183                                        "host": conn.host,
184                                        "port": conn.port,
185                                        "user": conn.user,
186                                        "dbname": conn.dbname})
187              else:              else:
188                  fill = fill.hex()                  raise ValueError("Can't handle db connection %r" % conn)
189              stroke = layer.stroke  
190              if stroke is None:      def write_data_containers(self, session):
191                  stroke = "None"          containers = sort_data_stores(session.DataContainers())
192            for container in containers:
193                if isinstance(container, AutoTransientTable):
194                    # AutoTransientTable instances are invisible in the
195                    # thuban files. They're only used internally. To make
196                    # sure that containers depending on AutoTransientTable
197                    # instances refer to the right real containers we give
198                    # the AutoTransientTable instances the same id as the
199                    # source they depend on.
200                    self.define_id(container,
201                                   self.get_id(container.Dependencies()[0]))
202                    continue
203    
204                idvalue = self.define_id(container)
205                if isinstance(container, FileShapeStore):
206                    self.define_id(container.Table(), idvalue)
207                    filename = self.prepare_filename(container.FileName())
208                    self.write_element("fileshapesource",
209                                       {"id": idvalue, "filename": filename,
210                                        "filetype": container.FileType()})
211                elif isinstance(container, DerivedShapeStore):
212                    shapesource, table = container.Dependencies()
213                    self.write_element("derivedshapesource",
214                                       {"id": idvalue,
215                                        "shapesource": self.get_id(shapesource),
216                                        "table": self.get_id(table)})
217                elif isinstance(container, PostGISShapeStore):
218                    conn = container.DBConnection()
219                    self.write_element("dbshapesource",
220                                       {"id": idvalue,
221                                        "dbconn": self.get_id(conn),
222                                        "tablename": container.TableName(),
223                                        "id_column": container.IDColumn().name,
224                                        "geometry_column":
225                                          container.GeometryColumn().name,
226                                        })
227                elif isinstance(container, DBFTable):
228                    filename = self.prepare_filename(container.FileName())
229                    self.write_element("filetable",
230                                       {"id": idvalue,
231                                        "title": container.Title(),
232                                        "filename": filename,
233                                        "filetype": "DBF"})
234                elif isinstance(container, TransientJoinedTable):
235                    left, right = container.Dependencies()
236                    left_field = container.left_field
237                    right_field = container.right_field
238                    self.write_element("jointable",
239                                       {"id": idvalue,
240                                        "title": container.Title(),
241                                        "right": self.get_id(right),
242                                        "rightcolumn": right_field,
243                                        "left": self.get_id(left),
244                                        "leftcolumn": left_field,
245                                        "jointype": container.JoinType()})
246              else:              else:
247                  stroke = stroke.hex()                  raise ValueError("Can't handle container %r" % container)
248              write(('\t\t<layer title="%s" filename="%s"'  
249                     ' fill="%s" stroke="%s" stroke_width="%d"/>\n') %  
250                    (escape(layer.title),      def write_map(self, map):
251                     escape(relative_filename(dir, layer.filename)),          """Write the map and its contents.
252                     fill, stroke, layer.stroke_width))  
253          labels = map.LabelLayer().Labels()          By default, write a map element element with the title
254            attribute, call write_projection to write the projection
255            element, call write_layer for each layer contained in the map
256            and finally call write_label_layer to write the label layer.
257            """
258            self.open_element('map title="%s"' % self.encode(map.title))
259            self.write_projection(map.projection)
260            for layer in map.Layers():
261                self.write_layer(layer)
262            self.write_label_layer(map.LabelLayer())
263            self.close_element('map')
264    
265        def write_projection(self, projection):
266            """Write the projection.
267            """
268            if projection and len(projection.params) > 0:
269                attrs = {"name": projection.GetName()}
270                epsg = projection.EPSGCode()
271                if epsg is not None:
272                    attrs["epsg"] = epsg
273                self.open_element("projection", attrs)
274                for param in projection.params:
275                    self.write_element('parameter value="%s"' %
276                                       self.encode(param))
277                self.close_element("projection")
278    
279        def write_layer(self, layer, attrs = None):
280            """Write the layer.
281    
282            The optional argument attrs is for additional attributes and, if
283            given, should be a mapping from attribute names to attribute
284            values. The values should not be XML-escaped yet.
285            """
286    
287            if attrs is None:
288                attrs = {}
289    
290            attrs["title"]   = layer.title
291            attrs["visible"] = bool2str(layer.Visible())
292    
293            if isinstance(layer, Layer):
294                attrs["shapestore"]   = self.get_id(layer.ShapeStore())
295                self.open_element("layer", attrs)
296                self.write_projection(layer.GetProjection())
297                self.write_classification(layer)
298                self.close_element("layer")
299            elif isinstance(layer, RasterLayer):
300                attrs["filename"] = self.prepare_filename(layer.filename)
301    
302                masknames = ["none", "bit", "alpha"]
303    
304                if layer.MaskType() != layer.MASK_BIT:
305                    attrs["masktype"] = masknames[layer.MaskType()]
306    
307                if layer.Opacity() != 1:
308                    attrs["opacity"] = str(layer.Opacity())
309    
310                self.open_element("rasterlayer", attrs)
311                self.write_projection(layer.GetProjection())
312                self.close_element("rasterlayer")
313    
314        def write_classification(self, layer, attrs = None):
315            """Write Classification information."""
316    
317            if attrs is None:
318                attrs = {}
319    
320            lc = layer.GetClassification()
321    
322            field = layer.GetClassificationColumn()
323    
324            if field is not None:
325                attrs["field"] = field
326                attrs["field_type"] = str(layer.GetFieldType(field))
327    
328            self.open_element("classification", attrs)
329    
330            for g in lc:
331                if isinstance(g, ClassGroupDefault):
332                    open_el  = 'clnull label="%s"' % self.encode(g.GetLabel())
333                    close_el = 'clnull'
334                elif isinstance(g, ClassGroupSingleton):
335                    if layer.GetFieldType(field) == FIELDTYPE_STRING:
336                        value = self.encode(g.GetValue())
337                    else:
338                        value = str(g.GetValue())
339                    open_el  = 'clpoint label="%s" value="%s"' \
340                               % (self.encode(g.GetLabel()), value)
341                    close_el = 'clpoint'
342                elif isinstance(g, ClassGroupRange):
343                    open_el  = 'clrange label="%s" range="%s"' \
344                              % (self.encode(g.GetLabel()), str(g.GetRange()))
345                    close_el = 'clrange'
346                elif isinstance(g, ClassGroupPattern):
347                    open_el  = 'clpattern label="%s" pattern="%s"' \
348                              % (self.encode(g.GetLabel()), str(g.GetPattern()))
349                    close_el = 'clpattern'
350    
351                else:
352                    assert False, _("Unsupported group type in classification")
353                    continue
354    
355                data = g.GetProperties()
356                dict = {'stroke'      : data.GetLineColor().hex(),
357                        'stroke_width': str(data.GetLineWidth()),
358                        'fill'        : data.GetFill().hex()}
359    
360                # only for point layers write the size attribute
361                if layer.ShapeType() == SHAPETYPE_POINT:
362                    dict['size'] =  str(data.GetSize())
363    
364                self.open_element(open_el)
365                self.write_element("cldata", dict)
366                self.close_element(close_el)
367    
368            self.close_element("classification")
369    
370        def write_label_layer(self, layer):
371            """Write the label layer.
372            """
373            labels = layer.Labels()
374          if labels:          if labels:
375              write('\t\t<labellayer>\n')              self.open_element('labellayer')
376              for label in labels:              for label in labels:
377                  write(('\t\t\t<label x="%g" y="%g" text="%s"'                  self.write_element(('label x="%g" y="%g" text="%s"'
378                         ' halign="%s" valign="%s"/>\n')                                      ' halign="%s" valign="%s"')
379                        % (label.x, label.y, label.text, label.halign,                                  % (label.x, label.y,
380                           label.valign))                                     self.encode(label.text),
381              write('\t\t</labellayer>\n')                                     label.halign,
382          write('\t</map>\n')                                     label.valign))
383      write('</session>\n')              self.close_element('labellayer')
384    
385    
386    
387    def save_session(session, file, saver_class = None):
388        """Save the session session to a file.
389    
390        The file argument may either be a filename or an open file object.
391    
392        The optional argument saver_class is the class to use to serialize
393        the session. By default or if it's None, the saver class will be
394        SessionSaver.
395    
396        If writing the session is successful call the session's
397        UnsetModified method
398        """
399        if saver_class is None:
400            saver_class = SessionSaver
401        saver = saver_class(session)
402        saver.write(file)
403    
404      # after a successful save consider the session unmodified.      # after a successful save consider the session unmodified.
405      session.UnsetModified()      session.UnsetModified()

Legend:
Removed from v.74  
changed lines
  Added in v.2734

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26