/[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 1642 - (hide annotations)
Mon Aug 25 10:54:31 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 18219 byte(s)
* Thuban/Model/load.py (SessionLoader.check_attrs): Raise a
LoadError when a required attribute is missing. The code used to
be commented out for some reason, but probably should have been
active.

* test/test_load.py (TestLoadError.test): Test the message in the
LoadError too to make sure it really is about the missing
attribute

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 jonathan 1339 from Thuban.Model.color import Color, Transparent
28    
29 bh 6 from Thuban.Model.session import Session
30     from Thuban.Model.map import Map
31 jonathan 930 from Thuban.Model.layer import Layer, RasterLayer
32 bh 6 from Thuban.Model.proj import Projection
33 jonathan 874 from Thuban.Model.range import Range
34 jonathan 413 from Thuban.Model.classification import Classification, \
35 jonathan 439 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
36     ClassGroupProperties
37 bh 1268 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
38     from Thuban.Model.table import DBFTable
39     from Thuban.Model.transientdb import TransientJoinedTable
40 bh 6
41 bh 1268 class LoadError(Exception):
42     pass
43    
44 jonathan 1159 from Thuban.Model.xmlreader import XMLReader
45     import resource
46 bh 6
47 bh 267 def parse_color(color):
48     """Return the color object for the string color.
49 bh 6
50 bh 267 Color may be either 'None' or of the form '#RRGGBB' in the usual
51     HTML color notation
52 bh 6 """
53     color = string.strip(color)
54     if color == "None":
55 jonathan 1339 result = Transparent
56 bh 6 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 jan 374 raise ValueError(_("Invalid hexadecimal color specification %s")
64 bh 6 % color)
65     else:
66 jan 374 raise ValueError(_("Invalid color specification %s") % color)
67 bh 6 return result
68    
69 bh 1268 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 jonathan 706 class SessionLoader(XMLReader):
89 jonathan 694
90 jonathan 706 def __init__(self):
91 jonathan 694 """Inititialize the Sax handler."""
92 jonathan 706 XMLReader.__init__(self)
93 jonathan 694
94     self.theSession = None
95     self.aMap = None
96     self.aLayer = None
97    
98 bh 1268 # Map ids used in the thuban file to the corresponding objects
99     # in the session
100     self.idmap = {}
101 jonathan 706
102 bh 1268 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 bh 1375 # 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 bh 1268
129     XMLReader.AddDispatchers(self, dispatchers)
130    
131 bh 267 def start_session(self, name, qname, attrs):
132 bh 1268 self.theSession = Session(self.encode(attrs.get((None, 'title'),
133     None)))
134 bh 6
135 bh 267 def end_session(self, name, qname):
136     pass
137 bh 6
138 bh 1268 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 bh 1642 raise LoadError("Element %s requires an attribute %r"
182     % (element, d.name))
183 bh 1268 value = attrs.get(d.fullname, d.default)
184    
185     if d.conversion == "shapesource":
186     if value in self.idmap:
187     value = self.idmap[value]
188     else:
189     raise LoadError("Element %s requires an already defined ID"
190     " in attribute %r"
191     % (element, d.name))
192     elif d.conversion == "table":
193     if value in self.idmap:
194     value = self.idmap[value]
195     if isinstance(value, ShapefileStore):
196     value = value.Table()
197     else:
198     raise LoadError("Element %s requires an already defined ID"
199     " in attribute %r"
200     % (element, d.name))
201     elif d.conversion == "filename":
202     value = os.path.abspath(os.path.join(self.GetDirectory(),
203     value))
204    
205     normalized[d.name] = value
206     return normalized
207    
208     def start_fileshapesource(self, name, qname, attrs):
209     attrs = self.check_attrs(name, attrs,
210     [AttrDesc("id", True),
211     AttrDesc("filename", True,
212     conversion = "filename"),
213     AttrDesc("filetype", True)])
214     ID = attrs["id"]
215     filename = attrs["filename"]
216     filetype = attrs["filetype"]
217     if filetype != "shapefile":
218     raise LoadError("shapesource filetype %r not supported" % filetype)
219     self.idmap[ID] = self.theSession.OpenShapefile(filename)
220    
221     def start_derivedshapesource(self, name, qname, attrs):
222     attrs = self.check_attrs(name, attrs,
223     [AttrDesc("id", True),
224     AttrDesc("shapesource", True,
225     conversion = "shapesource"),
226     AttrDesc("table", True, conversion="table")])
227 bh 1282 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
228     self.theSession.AddShapeStore(store)
229     self.idmap[attrs["id"]] = store
230 bh 1268
231     def start_filetable(self, name, qname, attrs):
232     attrs = self.check_attrs(name, attrs,
233     [AttrDesc("id", True),
234     AttrDesc("title", True),
235     AttrDesc("filename", True,
236     conversion = "filename"),
237     AttrDesc("filetype")])
238     filetype = attrs["filetype"]
239     if filetype != "DBF":
240     raise LoadError("shapesource filetype %r not supported" % filetype)
241     table = DBFTable(attrs["filename"])
242     table.SetTitle(attrs["title"])
243     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
244    
245     def start_jointable(self, name, qname, attrs):
246     attrs = self.check_attrs(name, attrs,
247     [AttrDesc("id", True),
248     AttrDesc("title", True),
249     AttrDesc("left", True, conversion="table"),
250     AttrDesc("leftcolumn", True),
251     AttrDesc("right", True, conversion="table"),
252 bh 1375 AttrDesc("rightcolumn", True),
253    
254     # jointype is required for file
255     # version 0.9 but this attribute
256     # wasn't in the 0.8 version because of
257     # an oversight so we assume it's
258     # optional since we want to handle
259     # both file format versions here.
260     AttrDesc("jointype", False,
261     default="INNER")])
262    
263     jointype = attrs["jointype"]
264     if jointype == "LEFT OUTER":
265     outer_join = True
266     elif jointype == "INNER":
267     outer_join = False
268     else:
269     raise LoadError("jointype %r not supported" % jointype )
270 bh 1268 table = TransientJoinedTable(self.theSession.TransientDB(),
271     attrs["left"], attrs["leftcolumn"],
272 bh 1375 attrs["right"], attrs["rightcolumn"],
273     outer_join = outer_join)
274 bh 1268 table.SetTitle(attrs["title"])
275     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
276    
277 bh 267 def start_map(self, name, qname, attrs):
278     """Start a map."""
279 frank 1408 self.aMap = Map(self.encode(attrs.get((None, 'title'), None)))
280 bh 267
281     def end_map(self, name, qname):
282     self.theSession.AddMap(self.aMap)
283 jonathan 874 self.aMap = None
284 bh 267
285     def start_projection(self, name, qname, attrs):
286 jonathan 874 self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
287 bh 267 self.ProjectionParams = [ ]
288    
289     def end_projection(self, name, qname):
290 jonathan 874 if self.aLayer is not None:
291     obj = self.aLayer
292     elif self.aMap is not None:
293     obj = self.aMap
294     else:
295     assert False, "projection tag out of context"
296     pass
297    
298     obj.SetProjection(
299 jonathan 744 Projection(self.ProjectionParams, self.ProjectionName))
300 bh 267
301     def start_parameter(self, name, qname, attrs):
302     s = attrs.get((None, 'value'))
303     s = str(s) # we can't handle unicode in proj
304     self.ProjectionParams.append(s)
305    
306     def start_layer(self, name, qname, attrs, layer_class = Layer):
307     """Start a layer
308    
309     Instantiate a layer of class layer_class from the attributes in
310     attrs which may be a dictionary as well as the normal SAX attrs
311     object and bind it to self.aLayer.
312     """
313 jonathan 874 title = self.encode(attrs.get((None, 'title'), ""))
314 bh 267 filename = attrs.get((None, 'filename'), "")
315 jonathan 694 filename = os.path.join(self.GetDirectory(), filename)
316 jonathan 930 filename = self.encode(filename)
317     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
318 bh 267 fill = parse_color(attrs.get((None, 'fill'), "None"))
319     stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
320     stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
321 bh 1268 if attrs.has_key((None, "shapestore")):
322     store = self.idmap[attrs[(None, "shapestore")]]
323     else:
324     store = self.theSession.OpenShapefile(filename)
325     self.aLayer = layer_class(title, store,
326 bh 723 fill = fill, stroke = stroke,
327 jonathan 772 lineWidth = stroke_width,
328 jonathan 930 visible = visible)
329 bh 267
330     def end_layer(self, name, qname):
331     self.aMap.AddLayer(self.aLayer)
332 jonathan 874 self.aLayer = None
333 bh 267
334 jonathan 930 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
335     title = self.encode(attrs.get((None, 'title'), ""))
336     filename = attrs.get((None, 'filename'), "")
337     filename = os.path.join(self.GetDirectory(), filename)
338     filename = self.encode(filename)
339     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
340    
341     self.aLayer = layer_class(title, filename, visible = visible)
342    
343     def end_rasterlayer(self, name, qname):
344     self.aMap.AddLayer(self.aLayer)
345     self.aLayer = None
346    
347 jonathan 365 def start_classification(self, name, qname, attrs):
348 jonathan 465 field = attrs.get((None, 'field'), None)
349    
350     fieldType = attrs.get((None, 'field_type'), None)
351     dbFieldType = self.aLayer.GetFieldType(field)
352    
353     if fieldType != dbFieldType:
354     raise ValueError(_("xml field type differs from database!"))
355    
356     # setup conversion routines depending on the kind of data
357     # we will be seeing later on
358     if fieldType == FIELDTYPE_STRING:
359     self.conv = str
360     elif fieldType == FIELDTYPE_INT:
361     self.conv = lambda p: int(float(p))
362     elif fieldType == FIELDTYPE_DOUBLE:
363     self.conv = float
364    
365 bh 1452 self.aLayer.SetClassificationColumn(field)
366 jonathan 465
367 jonathan 365 def end_classification(self, name, qname):
368     pass
369    
370     def start_clnull(self, name, qname, attrs):
371 jonathan 439 self.cl_group = ClassGroupDefault()
372 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
373 jonathan 439 self.cl_prop = ClassGroupProperties()
374 jonathan 365
375     def end_clnull(self, name, qname):
376 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
377     self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
378     del self.cl_group, self.cl_prop
379 jonathan 365
380     def start_clpoint(self, name, qname, attrs):
381     attrib_value = attrs.get((None, 'value'), "0")
382    
383 bh 1452 field = self.aLayer.GetClassificationColumn()
384 jonathan 1428 if self.aLayer.GetFieldType(field) == FIELDTYPE_STRING:
385 bh 1417 value = self.encode(attrib_value)
386     else:
387     value = self.conv(attrib_value)
388 jonathan 439 self.cl_group = ClassGroupSingleton(value)
389 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
390 jonathan 439 self.cl_prop = ClassGroupProperties()
391 jonathan 413
392 jonathan 365
393     def end_clpoint(self, name, qname):
394 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
395 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
396 jonathan 439 del self.cl_group, self.cl_prop
397 jonathan 365
398     def start_clrange(self, name, qname, attrs):
399    
400 jonathan 874 range = attrs.get((None, 'range'), None)
401     # for backward compatibility (min/max are not saved)
402     min = attrs.get((None, 'min'), None)
403     max = attrs.get((None, 'max'), None)
404    
405 jonathan 365 try:
406 jonathan 874 if range is not None:
407     self.cl_group = ClassGroupRange(Range(range))
408     elif min is not None and max is not None:
409 jonathan 1354 self.cl_group = ClassGroupRange((self.conv(min),
410     self.conv(max)))
411 jonathan 874 else:
412     self.cl_group = ClassGroupRange(Range(None))
413    
414 jonathan 365 except ValueError:
415 jan 374 raise ValueError(_("Classification range is not a number!"))
416 jonathan 365
417 jonathan 439 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
418     self.cl_prop = ClassGroupProperties()
419 jonathan 413
420 jonathan 365
421     def end_clrange(self, name, qname):
422 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
423 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
424 jonathan 439 del self.cl_group, self.cl_prop
425 jonathan 365
426     def start_cldata(self, name, qname, attrs):
427 jonathan 465 self.cl_prop.SetLineColor(
428     parse_color(attrs.get((None, 'stroke'), "None")))
429     self.cl_prop.SetLineWidth(
430 jonathan 390 int(attrs.get((None, 'stroke_width'), "0")))
431 jonathan 439 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
432 jonathan 365
433     def end_cldata(self, name, qname):
434     pass
435    
436 bh 267 def start_labellayer(self, name, qname, attrs):
437     self.aLayer = self.aMap.LabelLayer()
438    
439     def start_label(self, name, qname, attrs):
440     x = float(attrs[(None, 'x')])
441     y = float(attrs[(None, 'y')])
442 jonathan 874 text = self.encode(attrs[(None, 'text')])
443 bh 267 halign = attrs[(None, 'halign')]
444     valign = attrs[(None, 'valign')]
445     self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
446    
447     def characters(self, chars):
448     pass
449    
450    
451 jonathan 694 def load_session(filename):
452     """Load a Thuban session from the file object file"""
453    
454 jonathan 706 handler = SessionLoader()
455     handler.read(filename)
456 jonathan 694
457 bh 6 session = handler.theSession
458     # Newly loaded session aren't modified
459     session.UnsetModified()
460    
461     return session
462    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26