/[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 1268 - (show annotations)
Fri Jun 20 16:10:12 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: 12598 byte(s)
* Resources/XML/thuban-0.8.dtd: New DTD for the new file format
version.

* Thuban/Model/save.py (sort_data_stores): New. Make topological
sort of the data sources so they can be written with sources that
data sources that depend on other data sources come after the
sources they depend on.
(SessionSaver.__init__): Add idmap instance variable to map from
objects to the ids used in the file.
(SessionSaver.get_id, SessionSaver.define_id)
(SessionSaver.has_id): New. Methods to manage the idmap
(SessionSaver.write): Use thuban-0.8.dtd
(SessionSaver.write_session): Add a namespace on the session and
write out the data sources before the maps.
(SessionSaver.write_data_containers): New. Write the data
containers.
(SessionSaver.write_layer): Layer elements now refer to a
shapestore and don't contain filenames anymore.

* Thuban/Model/load.py (LoadError): Exception class to raise when
errors in the files are discovered
(SessionLoader.__init__): Define dispatchers for elements with a
thuban-0.8 namespace too.
(SessionLoader.check_attrs): New helper method to check and
convert attributes
(AttrDesc): New. Helper class for SessionLoader.check_attrs
(SessionLoader.start_fileshapesource)
(SessionLoader.start_derivedshapesource)
(SessionLoader.start_filetable, SessionLoader.start_jointable):
Handlers for the new elements in the new fileformat
(SessionLoader.start_layer): Handle the shapestore attribute in
addition to filename.
(SessionLoader.start_table, SessionLoader.end_table): Removed.
They were never used in the old formats and aren't needed for the
new.

* Thuban/Model/session.py (Session.DataContainers): New method to
return all "data containers", i.e. shapestores and tables

* test/xmlsupport.py (SaxEventLister.__init__)
(SaxEventLister.startElementNS, sax_eventlist): Add support to
normalize IDs.

* test/test_xmlsupport.py
(TestEventList.test_even_list_id_normalization): New test case for
id normalization

* test/test_load.py (LoadSessionTest.check_format): Use ID
normalization
(LoadSessionTest.thubanids, LoadSessionTest.thubanidrefs): New
class atrributes used for ID normalization
(TestSingleLayer, TestLayerVisibility, TestLabels.test)
(TestLayerProjection.test, TestRasterLayer.test): Adapt to new
file format
(TestJoinedTable): New test for loading sessions with joined
tables.
(TestLoadError): New. Test whether missing required attributes
cause a LoadError.

* test/test_save.py (SaveSessionTest.thubanids)
(SaveSessionTest.thubanidrefs): New class attributes for ID
normalization in .thuban files.
(SaveSessionTest.compare_xml): Use id-normalization.
(SaveSessionTest.testEmptySession)
(SaveSessionTest.testLayerProjection)
(SaveSessionTest.testRasterLayer)
(SaveSessionTest.testClassifiedLayer): Adapt to new file format.
(SaveSessionTest.testLayerProjection): The filename used was the
same as for testSingleLayer. Use a different one.
(SaveSessionTest.test_dbf_table)
(SaveSessionTest.test_joined_table): New test cases for saving the
new data sources structures.
(TestStoreSort, MockDataStore): Classes to test the sorting of the
data stores for writing.

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.color import Color
21 from Thuban.Model.layer import Layer, RasterLayer
22
23 from Thuban.Model.classification import \
24 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
25 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
26 from Thuban.Model.table import DBFTable
27 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
28
29 from Thuban.Model.xmlwriter import XMLWriter
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.8.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.8.dtd"
137 self.open_element("session", attrs)
138 self.write_data_containers(session)
139 for map in session.Maps():
140 self.write_map(map)
141 self.close_element("session")
142
143 def write_data_containers(self, session):
144 containers = sort_data_stores(session.DataContainers())
145 for container in containers:
146 if isinstance(container, AutoTransientTable):
147 # AutoTransientTable instances are invisible in the
148 # thuban files. They're only used internally. To make
149 # sure that containers depending on AutoTransientTable
150 # instances refer to the right real containers we give
151 # the AutoTransientTable instances the same id as the
152 # source they depend on.
153 self.define_id(container,
154 self.get_id(container.Dependencies()[0]))
155 continue
156
157 idvalue = self.define_id(container)
158 if isinstance(container, ShapefileStore):
159 self.define_id(container.Table(), idvalue)
160 filename = relative_filename(self.dir, container.FileName())
161 self.write_element("fileshapesource",
162 {"id": idvalue, "filename": filename,
163 "filetype": "shapefile"})
164 elif isinstance(container, DerivedShapeStore):
165 shapesource, table = container.Dependencies()
166 self.write_element("derivedshapesource",
167 {"id": idvalue,
168 "shapesource": self.get_id(shapesource),
169 "table": self.get_id(table)})
170 elif isinstance(container, DBFTable):
171 filename = relative_filename(self.dir, container.FileName())
172 self.write_element("filetable",
173 {"id": idvalue,
174 "title": container.Title(),
175 "filename": filename,
176 "filetype": "DBF"})
177 elif isinstance(container, TransientJoinedTable):
178 left, right = container.Dependencies()
179 left_field = container.left_field
180 right_field = container.right_field
181 self.write_element("jointable",
182 {"id": idvalue,
183 "title": container.Title(),
184 "right": self.get_id(right),
185 "rightcolumn": right_field,
186 "left": self.get_id(left),
187 "leftcolumn": left_field})
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