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

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26