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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1268 - (hide 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 bh 454 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4     # Bernhard Herzog <[email protected]>
5 jonathan 414 # Jonathan Coles <[email protected]>
6 bh 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 bh 201 import Thuban.Lib.fileutil
19 bh 6
20 jonathan 932 from Thuban.Model.color import Color
21     from Thuban.Model.layer import Layer, RasterLayer
22    
23 jonathan 876 from Thuban.Model.classification import \
24     ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
25 bh 1268 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
26     from Thuban.Model.table import DBFTable
27     from Thuban.Model.data import DerivedShapeStore, ShapefileStore
28 jonathan 429
29 jonathan 1160 from Thuban.Model.xmlwriter import XMLWriter
30 jonathan 366
31 bh 201 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 bh 1268
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 jonathan 710 class SessionSaver(XMLWriter):
75 bh 268
76 jonathan 697 """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 jonathan 710 XMLWriter.__init__(self)
89 jonathan 697 self.session = session
90 bh 1268 # Map object ids to the ids used in the thuban files
91     self.idmap = {}
92 jonathan 697
93 bh 1268 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 jonathan 697 def write(self, file_or_filename):
107 jonathan 710 XMLWriter.write(self, file_or_filename)
108 jonathan 697
109 bh 1268 self.write_header("session", "thuban-0.8.dtd")
110 jonathan 697 self.write_session(self.session)
111     self.close()
112    
113 bh 268 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 bh 1268 # default name space
135     attrs["xmlns"] = \
136     "http://thuban.intevation.org/dtds/thuban-0.8.dtd"
137 jonathan 366 self.open_element("session", attrs)
138 bh 1268 self.write_data_containers(session)
139 bh 268 for map in session.Maps():
140     self.write_map(map)
141 jonathan 366 self.close_element("session")
142 bh 268
143 bh 1268 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 bh 268 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 jonathan 876 self.open_element('map title="%s"' % self.encode(map.title))
201 bh 268 self.write_projection(map.projection)
202     for layer in map.Layers():
203     self.write_layer(layer)
204     self.write_label_layer(map.LabelLayer())
205 jonathan 366 self.close_element('map')
206 bh 6
207 bh 268 def write_projection(self, projection):
208     """Write the projection.
209     """
210     if projection and len(projection.params) > 0:
211 jonathan 876 self.open_element("projection", {"name": projection.GetName()})
212 bh 268 for param in projection.params:
213 jonathan 876 self.write_element('parameter value="%s"' %
214     self.encode(param))
215 jonathan 366 self.close_element("projection")
216 bh 268
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 jonathan 391
225 bh 268 if attrs is None:
226     attrs = {}
227 jonathan 429
228 jonathan 1251 attrs["title"] = layer.title
229     attrs["visible"] = ("false", "true")[int(layer.Visible())]
230 bh 268
231 jonathan 932 if isinstance(layer, Layer):
232 bh 1268 attrs["shapestore"] = self.get_id(layer.ShapeStore())
233 jonathan 740
234 jonathan 932 lc = layer.GetClassification()
235 bh 1268 attrs["stroke"] = lc.GetDefaultLineColor().hex()
236 jonathan 932 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
237 bh 1268 attrs["fill"] = lc.GetDefaultFill().hex()
238 jonathan 740
239 jonathan 932 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 bh 1219 attrs["filename"] = relative_filename(self.dir, layer.filename)
245 jonathan 932 self.open_element("rasterlayer", attrs)
246     self.write_projection(layer.GetProjection())
247     self.close_element("rasterlayer")
248    
249 jonathan 366 def write_classification(self, layer, attrs = None):
250 jonathan 1251 """Write Classification information."""
251    
252 jonathan 366 if attrs is None:
253     attrs = {}
254    
255 jonathan 414 lc = layer.GetClassification()
256 jonathan 366
257 jonathan 429 field = lc.GetField()
258 jonathan 366
259     #
260 jonathan 1251 # there isn't a classification of anything so do nothing
261 jonathan 366 #
262     if field is None: return
263    
264     attrs["field"] = field
265 jonathan 466 attrs["field_type"] = str(lc.GetFieldType())
266 jonathan 366 self.open_element("classification", attrs)
267    
268 jonathan 1251 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 jonathan 429
284 jonathan 1251 data = g.GetProperties()
285     dict = {'stroke' : data.GetLineColor().hex(),
286     'stroke_width': str(data.GetLineWidth()),
287     'fill' : data.GetFill().hex()}
288 jonathan 429
289 jonathan 1251 self.open_element(open_el)
290     self.write_element("cldata", dict)
291     self.close_element(close_el)
292 jonathan 366
293     self.close_element("classification")
294    
295 bh 268 def write_label_layer(self, layer):
296     """Write the label layer.
297     """
298     labels = layer.Labels()
299 bh 6 if labels:
300 jonathan 366 self.open_element('labellayer')
301 bh 6 for label in labels:
302 jonathan 366 self.write_element(('label x="%g" y="%g" text="%s"'
303     ' halign="%s" valign="%s"')
304 jonathan 876 % (label.x, label.y,
305     self.encode(label.text),
306     label.halign,
307 bh 268 label.valign))
308 jonathan 366 self.close_element('labellayer')
309 bh 6
310 bh 268
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 jonathan 697 SessionSaver.
320 bh 268
321     If writing the session is successful call the session's
322     UnsetModified method
323     """
324     if saver_class is None:
325 jonathan 697 saver_class = SessionSaver
326 bh 268 saver = saver_class(session)
327     saver.write(file)
328    
329 bh 6 # 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