/[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 1375 - (show annotations)
Tue Jul 8 10:53:05 2003 UTC (21 years, 8 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 12634 byte(s)
* Resources/XML/thuban-0.9.dtd: New. This will become the DTD for
0.9.

* Thuban/Model/transientdb.py (TransientJoinedTable.JoinType):
New. Return the join type

* Thuban/Model/save.py (SessionSaver.write_session): Use new 0.9
DTD
(SessionSaver.write_data_containers): Save the join type for
joined tables

* Thuban/Model/load.py (SessionLoader.__init__): Add the new 0.9
namespace
(SessionLoader.start_jointable): Handle the jointype attribute

* test/test_load_0_8.py: New. Effectively a copy of test_load.py
as of Thuban 0.8. These are now tests to determine whether Thuban
can still read files generated by Thuban 0.8

* test/test_load.py (LoadSessionTest.dtd)
(TestSingleLayer.file_contents)
(TestLayerVisibility.file_contents, TestLabels.file_contents)
(TestLayerProjection.file_contents)
(TestRasterLayer.file_contents, TestJoinedTable.file_contents)
(TestJoinedTable.file_contents)
(TestLoadError.file_contents): Update for new DTD
(TestJoinedTable.file_contents, TestJoinedTable.setUp): Add test
for new join type attribute

* test/test_save.py (SaveSessionTest.dtd)
(SaveSessionTest.testEmptySession)
(SaveSessionTest.testSingleLayer)
(SaveSessionTest.testLayerProjection)
(SaveSessionTest.testRasterLayer)
(SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.test_dbf_table)
(SaveSessionTest.test_joined_table): Update for new DTD
(SaveSessionTest.test_joined_table): Add test for new join type
attribute

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