/[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 1375 - (hide 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 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     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 bh 1282 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
229     self.theSession.AddShapeStore(store)
230     self.idmap[attrs["id"]] = store
231 bh 1268
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 bh 1375 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 bh 1268 table = TransientJoinedTable(self.theSession.TransientDB(),
272     attrs["left"], attrs["leftcolumn"],
273 bh 1375 attrs["right"], attrs["rightcolumn"],
274     outer_join = outer_join)
275 bh 1268 table.SetTitle(attrs["title"])
276     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
277    
278 bh 267 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 jonathan 874 self.aMap = None
285 bh 267
286     def start_projection(self, name, qname, attrs):
287 jonathan 874 self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
288 bh 267 self.ProjectionParams = [ ]
289    
290     def end_projection(self, name, qname):
291 jonathan 874 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 jonathan 744 Projection(self.ProjectionParams, self.ProjectionName))
301 bh 267
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 jonathan 874 title = self.encode(attrs.get((None, 'title'), ""))
315 bh 267 filename = attrs.get((None, 'filename'), "")
316 jonathan 694 filename = os.path.join(self.GetDirectory(), filename)
317 jonathan 930 filename = self.encode(filename)
318     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
319 bh 267 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 bh 1268 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 bh 723 fill = fill, stroke = stroke,
328 jonathan 772 lineWidth = stroke_width,
329 jonathan 930 visible = visible)
330 bh 267
331     def end_layer(self, name, qname):
332     self.aMap.AddLayer(self.aLayer)
333 jonathan 874 self.aLayer = None
334 bh 267
335 jonathan 930 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 jonathan 365 def start_classification(self, name, qname, attrs):
349 jonathan 465 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 jonathan 1339 self.aLayer.GetClassification().SetFieldInfo(field, fieldType)
367 jonathan 465
368 jonathan 365 def end_classification(self, name, qname):
369     pass
370    
371     def start_clnull(self, name, qname, attrs):
372 jonathan 439 self.cl_group = ClassGroupDefault()
373 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
374 jonathan 439 self.cl_prop = ClassGroupProperties()
375 jonathan 365
376     def end_clnull(self, name, qname):
377 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
378     self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
379     del self.cl_group, self.cl_prop
380 jonathan 365
381     def start_clpoint(self, name, qname, attrs):
382     attrib_value = attrs.get((None, 'value'), "0")
383    
384 jonathan 465 value = self.conv(attrib_value)
385    
386 jonathan 439 self.cl_group = ClassGroupSingleton(value)
387 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
388 jonathan 439 self.cl_prop = ClassGroupProperties()
389 jonathan 413
390 jonathan 365
391     def end_clpoint(self, name, qname):
392 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
393 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
394 jonathan 439 del self.cl_group, self.cl_prop
395 jonathan 365
396     def start_clrange(self, name, qname, attrs):
397    
398 jonathan 874 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 jonathan 365 try:
404 jonathan 874 if range is not None:
405     self.cl_group = ClassGroupRange(Range(range))
406     elif min is not None and max is not None:
407 jonathan 1354 self.cl_group = ClassGroupRange((self.conv(min),
408     self.conv(max)))
409 jonathan 874 else:
410     self.cl_group = ClassGroupRange(Range(None))
411    
412 jonathan 365 except ValueError:
413 jan 374 raise ValueError(_("Classification range is not a number!"))
414 jonathan 365
415 jonathan 439 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
416     self.cl_prop = ClassGroupProperties()
417 jonathan 413
418 jonathan 365
419     def end_clrange(self, name, qname):
420 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
421 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
422 jonathan 439 del self.cl_group, self.cl_prop
423 jonathan 365
424     def start_cldata(self, name, qname, attrs):
425 jonathan 465 self.cl_prop.SetLineColor(
426     parse_color(attrs.get((None, 'stroke'), "None")))
427     self.cl_prop.SetLineWidth(
428 jonathan 390 int(attrs.get((None, 'stroke_width'), "0")))
429 jonathan 439 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
430 jonathan 365
431     def end_cldata(self, name, qname):
432     pass
433    
434 bh 267 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 jonathan 874 text = self.encode(attrs[(None, 'text')])
441 bh 267 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 jonathan 694 def load_session(filename):
450     """Load a Thuban session from the file object file"""
451    
452 jonathan 706 handler = SessionLoader()
453     handler.read(filename)
454 jonathan 694
455 bh 6 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