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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1664 - (show annotations)
Wed Aug 27 15:20:54 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 13929 byte(s)
As preparation for the 0.9 release, switch thuban files to a
non-dev namespace

* Thuban/Model/save.py (SessionSaver.write_session): Write files
with the http://thuban.intevation.org/dtds/thuban-0.9.dtd
namespace

* Thuban/Model/load.py (SessionLoader.__init__): Accept the
http://thuban.intevation.org/dtds/thuban-0.9.dtd namespace too

* test/test_save.py (SaveSessionTest.dtd)
(SaveSessionTest.testEmptySession)
(SaveSessionTest.testSingleLayer)
(SaveSessionTest.testLayerProjection)
(SaveSessionTest.testRasterLayer)
(SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.test_dbf_table)
(SaveSessionTest.test_joined_table)
(SaveSessionTest.test_save_postgis): Update for new namespace

* test/test_load.py (LoadSessionTest.dtd, TestSingleLayer)
(TestLayerVisibility.file_contents, TestLabels.file_contents)
(TestLayerProjection.file_contents)
(TestRasterLayer.file_contents, TestJoinedTable.file_contents)
(TestPostGISLayer.file_contents)
(TestPostGISLayerPassword.file_contents)
(TestLoadError.file_contents, TestLoadError.test): Update for new
namespace

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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26