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

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/load.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/load.py
File MIME type: text/x-python
File size: 17012 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 723 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4 bh 267 # Bernhard Herzog <[email protected]>
5 jonathan 413 # Jonathan Coles <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with GRASS for details.
9    
10     """
11     Parser for thuban session files.
12     """
13    
14     __version__ = "$Revision$"
15    
16 bh 723 import string, os
17 bh 267
18     import xml.sax
19     import xml.sax.handler
20 jonathan 526 from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
21 bh 267
22 jan 374 from Thuban import _
23 jonathan 413
24 jonathan 473 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
25     FIELDTYPE_STRING
26    
27 bh 6 from Thuban.Model.session import Session
28     from Thuban.Model.map import Map
29 jonathan 930 from Thuban.Model.layer import Layer, RasterLayer
30 bh 6 from Thuban.Model.color import Color
31     from Thuban.Model.proj import Projection
32 jonathan 874 from Thuban.Model.range import Range
33 jonathan 413 from Thuban.Model.classification import Classification, \
34 jonathan 439 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
35     ClassGroupProperties
36 bh 1268 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
37     from Thuban.Model.table import DBFTable
38     from Thuban.Model.transientdb import TransientJoinedTable
39 bh 6
40 bh 1268 class LoadError(Exception):
41     pass
42    
43 jonathan 1159 from Thuban.Model.xmlreader import XMLReader
44     import resource
45 bh 6
46 bh 267 def parse_color(color):
47     """Return the color object for the string color.
48 bh 6
49 bh 267 Color may be either 'None' or of the form '#RRGGBB' in the usual
50     HTML color notation
51 bh 6 """
52     color = string.strip(color)
53     if color == "None":
54 jonathan 610 result = Color.Transparent
55 bh 6 elif color[0] == '#':
56     if len(color) == 7:
57     r = string.atoi(color[1:3], 16) / 255.0
58     g = string.atoi(color[3:5], 16) / 255.0
59     b = string.atoi(color[5:7], 16) / 255.0
60     result = Color(r, g, b)
61     else:
62 jan 374 raise ValueError(_("Invalid hexadecimal color specification %s")
63 bh 6 % color)
64     else:
65 jan 374 raise ValueError(_("Invalid color specification %s") % color)
66 bh 6 return result
67    
68 bh 1268 class AttrDesc:
69    
70     def __init__(self, name, required = False, default = "",
71     conversion = None):
72     if not isinstance(name, tuple):
73     fullname = (None, name)
74     else:
75     fullname = name
76     name = name[1]
77     self.name = name
78     self.fullname = fullname
79     self.required = required
80     self.default = default
81     self.conversion = conversion
82    
83     # set by the SessionLoader's check_attrs method
84     self.value = None
85    
86    
87 jonathan 706 class SessionLoader(XMLReader):
88 jonathan 694
89 jonathan 706 def __init__(self):
90 jonathan 694 """Inititialize the Sax handler."""
91 jonathan 706 XMLReader.__init__(self)
92 jonathan 694
93     self.theSession = None
94     self.aMap = None
95     self.aLayer = None
96    
97 bh 1268 # Map ids used in the thuban file to the corresponding objects
98     # in the session
99     self.idmap = {}
100 jonathan 706
101 bh 1268 dispatchers = {
102     'session' : ("start_session", "end_session"),
103     'fileshapesource': ("start_fileshapesource", None),
104     'derivedshapesource': ("start_derivedshapesource", None),
105     'filetable': ("start_filetable", None),
106     'jointable': ("start_jointable", None),
107    
108     'map' : ("start_map", "end_map"),
109     'projection' : ("start_projection", "end_projection"),
110     'parameter' : ("start_parameter", None),
111     'layer' : ("start_layer", "end_layer"),
112     'rasterlayer' : ("start_rasterlayer", "end_rasterlayer"),
113     'classification': ("start_classification", "end_classification"),
114     'clnull' : ("start_clnull", "end_clnull"),
115     'clpoint' : ("start_clpoint", "end_clpoint"),
116     'clrange' : ("start_clrange", "end_clrange"),
117     'cldata' : ("start_cldata", "end_cldata"),
118     'table' : ("start_table", "end_table"),
119     'labellayer' : ("start_labellayer", None),
120     'label' : ("start_label", None)}
121    
122     # all dispatchers should be used for the 0.8 namespace
123     xmlns = "http://thuban.intevation.org/dtds/thuban-0.8.dtd"
124     for key, value in dispatchers.items():
125     dispatchers[(xmlns, key)] = value
126    
127     XMLReader.AddDispatchers(self, dispatchers)
128    
129 bh 267 def start_session(self, name, qname, attrs):
130 bh 1268 self.theSession = Session(self.encode(attrs.get((None, 'title'),
131     None)))
132 bh 6
133 bh 267 def end_session(self, name, qname):
134     pass
135 bh 6
136 bh 1268 def check_attrs(self, element, attrs, descr):
137     """Check and convert some of the attributes of an element
138    
139     Parameters:
140     element -- The element name
141     attrs -- The attrs mapping as passed to the start_* methods
142     descr -- Sequence of attribute descriptions (AttrDesc instances)
143    
144     Return a dictionary containig normalized versions of the
145     attributes described in descr. The keys of that dictionary are
146     the name attributes of the attribute descriptions. The attrs
147     dictionary will not be modified.
148    
149     If the attribute is required, i.e. the 'required' attribute of
150     the descrtiption is true, but it is not in attrs, raise a
151     LoadError.
152    
153     If the attribute has a default value and it is not present in
154     attrs, use that default value as the value in the returned dict.
155    
156     If a conversion is specified, convert the value before putting
157     it into the returned dict. The following conversions are
158     available:
159    
160     'filename' -- The attribute is a filename.
161    
162     If the filename is a relative name, interpret
163     it relative to the directory containing the
164     .thuban file and make it an absolute name
165    
166     'shapestore' -- The attribute is the ID of a shapestore
167     defined earlier in the .thuban file. Look it
168     up self.idmap
169    
170     'table' -- The attribute is the ID of a table or shapestore
171     defined earlier in the .thuban file. Look it up
172     self.idmap. If it's the ID of a shapestore the
173     value will be the table of the shapestore.
174     """
175     normalized = {}
176    
177     for d in descr:
178     if d.required and not attrs.has_key(d.fullname):
179     pass
180     #raise LoadError("Element %s requires an attribute %r"
181     # % (element, d.name))
182     value = attrs.get(d.fullname, d.default)
183    
184     if d.conversion == "shapesource":
185     if value in self.idmap:
186     value = self.idmap[value]
187     else:
188     raise LoadError("Element %s requires an already defined ID"
189     " in attribute %r"
190     % (element, d.name))
191     elif d.conversion == "table":
192     if value in self.idmap:
193     value = self.idmap[value]
194     if isinstance(value, ShapefileStore):
195     value = value.Table()
196     else:
197     raise LoadError("Element %s requires an already defined ID"
198     " in attribute %r"
199     % (element, d.name))
200     elif d.conversion == "filename":
201     value = os.path.abspath(os.path.join(self.GetDirectory(),
202     value))
203    
204     normalized[d.name] = value
205     return normalized
206    
207     def start_fileshapesource(self, name, qname, attrs):
208     attrs = self.check_attrs(name, attrs,
209     [AttrDesc("id", True),
210     AttrDesc("filename", True,
211     conversion = "filename"),
212     AttrDesc("filetype", True)])
213     ID = attrs["id"]
214     filename = attrs["filename"]
215     filetype = attrs["filetype"]
216     if filetype != "shapefile":
217     raise LoadError("shapesource filetype %r not supported" % filetype)
218     self.idmap[ID] = self.theSession.OpenShapefile(filename)
219    
220     def start_derivedshapesource(self, name, qname, attrs):
221     attrs = self.check_attrs(name, attrs,
222     [AttrDesc("id", True),
223     AttrDesc("shapesource", True,
224     conversion = "shapesource"),
225     AttrDesc("table", True, conversion="table")])
226     self.idmap[attrs["id"]] = DerivedShapeStore(attrs["shapesource"],
227     attrs["table"])
228    
229     def start_filetable(self, name, qname, attrs):
230     attrs = self.check_attrs(name, attrs,
231     [AttrDesc("id", True),
232     AttrDesc("title", True),
233     AttrDesc("filename", True,
234     conversion = "filename"),
235     AttrDesc("filetype")])
236     filetype = attrs["filetype"]
237     if filetype != "DBF":
238     raise LoadError("shapesource filetype %r not supported" % filetype)
239     table = DBFTable(attrs["filename"])
240     table.SetTitle(attrs["title"])
241     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
242    
243     def start_jointable(self, name, qname, attrs):
244     attrs = self.check_attrs(name, attrs,
245     [AttrDesc("id", True),
246     AttrDesc("title", True),
247     AttrDesc("left", True, conversion="table"),
248     AttrDesc("leftcolumn", True),
249     AttrDesc("right", True, conversion="table"),
250     AttrDesc("rightcolumn")])
251     table = TransientJoinedTable(self.theSession.TransientDB(),
252     attrs["left"], attrs["leftcolumn"],
253     attrs["right"], attrs["rightcolumn"])
254     table.SetTitle(attrs["title"])
255     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
256    
257 bh 267 def start_map(self, name, qname, attrs):
258     """Start a map."""
259     self.aMap = Map(attrs.get((None, 'title'), None))
260    
261     def end_map(self, name, qname):
262     self.theSession.AddMap(self.aMap)
263 jonathan 874 self.aMap = None
264 bh 267
265     def start_projection(self, name, qname, attrs):
266 jonathan 874 self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
267 bh 267 self.ProjectionParams = [ ]
268    
269     def end_projection(self, name, qname):
270 jonathan 874 if self.aLayer is not None:
271     obj = self.aLayer
272     elif self.aMap is not None:
273     obj = self.aMap
274     else:
275     assert False, "projection tag out of context"
276     pass
277    
278     obj.SetProjection(
279 jonathan 744 Projection(self.ProjectionParams, self.ProjectionName))
280 bh 267
281     def start_parameter(self, name, qname, attrs):
282     s = attrs.get((None, 'value'))
283     s = str(s) # we can't handle unicode in proj
284     self.ProjectionParams.append(s)
285    
286     def start_layer(self, name, qname, attrs, layer_class = Layer):
287     """Start a layer
288    
289     Instantiate a layer of class layer_class from the attributes in
290     attrs which may be a dictionary as well as the normal SAX attrs
291     object and bind it to self.aLayer.
292     """
293 jonathan 874 title = self.encode(attrs.get((None, 'title'), ""))
294 bh 267 filename = attrs.get((None, 'filename'), "")
295 jonathan 694 filename = os.path.join(self.GetDirectory(), filename)
296 jonathan 930 filename = self.encode(filename)
297     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
298 bh 267 fill = parse_color(attrs.get((None, 'fill'), "None"))
299     stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
300     stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
301 bh 1268 if attrs.has_key((None, "shapestore")):
302     store = self.idmap[attrs[(None, "shapestore")]]
303     else:
304     store = self.theSession.OpenShapefile(filename)
305     self.aLayer = layer_class(title, store,
306 bh 723 fill = fill, stroke = stroke,
307 jonathan 772 lineWidth = stroke_width,
308 jonathan 930 visible = visible)
309 bh 267
310     def end_layer(self, name, qname):
311     self.aMap.AddLayer(self.aLayer)
312 jonathan 874 self.aLayer = None
313 bh 267
314 jonathan 930 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
315     title = self.encode(attrs.get((None, 'title'), ""))
316     filename = attrs.get((None, 'filename'), "")
317     filename = os.path.join(self.GetDirectory(), filename)
318     filename = self.encode(filename)
319     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
320    
321     self.aLayer = layer_class(title, filename, visible = visible)
322    
323     def end_rasterlayer(self, name, qname):
324     self.aMap.AddLayer(self.aLayer)
325     self.aLayer = None
326    
327 jonathan 365 def start_classification(self, name, qname, attrs):
328 jonathan 465 field = attrs.get((None, 'field'), None)
329    
330     fieldType = attrs.get((None, 'field_type'), None)
331     dbFieldType = self.aLayer.GetFieldType(field)
332    
333     if fieldType != dbFieldType:
334     raise ValueError(_("xml field type differs from database!"))
335    
336     # setup conversion routines depending on the kind of data
337     # we will be seeing later on
338     if fieldType == FIELDTYPE_STRING:
339     self.conv = str
340     elif fieldType == FIELDTYPE_INT:
341     self.conv = lambda p: int(float(p))
342     elif fieldType == FIELDTYPE_DOUBLE:
343     self.conv = float
344    
345     self.aLayer.GetClassification().SetField(field)
346    
347 jonathan 365 def end_classification(self, name, qname):
348     pass
349    
350     def start_clnull(self, name, qname, attrs):
351 jonathan 439 self.cl_group = ClassGroupDefault()
352 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
353 jonathan 439 self.cl_prop = ClassGroupProperties()
354 jonathan 365
355     def end_clnull(self, name, qname):
356 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
357     self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
358     del self.cl_group, self.cl_prop
359 jonathan 365
360     def start_clpoint(self, name, qname, attrs):
361     attrib_value = attrs.get((None, 'value'), "0")
362    
363 jonathan 465 value = self.conv(attrib_value)
364    
365 jonathan 439 self.cl_group = ClassGroupSingleton(value)
366 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
367 jonathan 439 self.cl_prop = ClassGroupProperties()
368 jonathan 413
369 jonathan 365
370     def end_clpoint(self, name, qname):
371 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
372 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
373 jonathan 439 del self.cl_group, self.cl_prop
374 jonathan 365
375     def start_clrange(self, name, qname, attrs):
376    
377 jonathan 874 range = attrs.get((None, 'range'), None)
378     # for backward compatibility (min/max are not saved)
379     min = attrs.get((None, 'min'), None)
380     max = attrs.get((None, 'max'), None)
381    
382 jonathan 365 try:
383 jonathan 874 if range is not None:
384     self.cl_group = ClassGroupRange(Range(range))
385     elif min is not None and max is not None:
386     self.cl_group = ClassGroupRange(self.conv(min), self.conv(max))
387     else:
388     self.cl_group = ClassGroupRange(Range(None))
389    
390 jonathan 365 except ValueError:
391 jan 374 raise ValueError(_("Classification range is not a number!"))
392 jonathan 365
393 jonathan 439 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
394     self.cl_prop = ClassGroupProperties()
395 jonathan 413
396 jonathan 365
397     def end_clrange(self, name, qname):
398 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
399 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
400 jonathan 439 del self.cl_group, self.cl_prop
401 jonathan 365
402     def start_cldata(self, name, qname, attrs):
403 jonathan 465 self.cl_prop.SetLineColor(
404     parse_color(attrs.get((None, 'stroke'), "None")))
405     self.cl_prop.SetLineWidth(
406 jonathan 390 int(attrs.get((None, 'stroke_width'), "0")))
407 jonathan 439 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
408 jonathan 365
409     def end_cldata(self, name, qname):
410     pass
411    
412 bh 267 def start_labellayer(self, name, qname, attrs):
413     self.aLayer = self.aMap.LabelLayer()
414    
415     def start_label(self, name, qname, attrs):
416     x = float(attrs[(None, 'x')])
417     y = float(attrs[(None, 'y')])
418 jonathan 874 text = self.encode(attrs[(None, 'text')])
419 bh 267 halign = attrs[(None, 'halign')]
420     valign = attrs[(None, 'valign')]
421     self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
422    
423     def characters(self, chars):
424     pass
425    
426    
427 jonathan 694 def load_session(filename):
428     """Load a Thuban session from the file object file"""
429    
430 jonathan 706 handler = SessionLoader()
431     handler.read(filename)
432 jonathan 694
433 bh 6 session = handler.theSession
434     # Newly loaded session aren't modified
435     session.UnsetModified()
436    
437     return session
438    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26