/[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 2642 - (show annotations)
Fri Jul 1 20:49:04 2005 UTC (19 years, 8 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 28038 byte(s)
First step towards unicode.  With this roughly we're at step 1
string_representation.txt

* Doc/technotes/string_representation.txt: New.  Document how
strings are represented in Thuban and how to get to a Unicode
Thuban.

* Thuban/__init__.py (set_internal_encoding)
(unicode_from_internal, internal_from_unicode): New. The first few
functions for the internal string representation

* Thuban/UI/about.py (unicodeToLocale): Removed.  Use
internal_from_unicode instead.

* Thuban/UI/__init__.py (install_wx_translation): Determine the
encoding to use for the internal string representation.  Also,
change the translation function to return strings in internal
representation even on unicode builds of wxPython

* Thuban/Model/load.py (SessionLoader.check_attrs): Decode
filenames too.
(SessionLoader.start_clrange): Use check_attrs to decode and check
the attributes.

* Thuban/Model/xmlreader.py (XMLReader.encode): Use
internal_from_unicode to convert unicode strings.

* Thuban/Model/xmlwriter.py (XMLWriter.encode): Use
unicode_from_internal when applicable

* test/runtests.py (main): New command line option:
internal-encoding to specify the internal string encoding to use
in the tests.

* test/support.py (initthuban): Set the internal encoding to
latin-1

* test/test_load.py (TestSingleLayer.test, TestClassification.test)
(TestLabelLayer.test): Use the internal string representation when
dealing with non-ascii characters

* test/test_load_1_0.py (TestSingleLayer.test)
(TestClassification.test, TestLabelLayer.test): Use the internal
string representation when dealing with non-ascii characters

* test/test_load_0_9.py (TestSingleLayer.test)
(TestClassification.test): Use the internal string representation
when dealing with non-ascii characters

* test/test_load_0_8.py (TestUnicodeStrings.test): Use the
internal string representation when dealing with non-ascii
characters

* test/test_save.py (XMLWriterTest.testEncode)
(SaveSessionTest.testClassifiedLayer): Use the internal string
representation when dealing with non-ascii characters where
applicable

1 # Copyright (C) 2001, 2002, 2003, 2004, 2005 by Intevation GmbH
2 # Authors:
3 # Jan-Oliver Wagner <[email protected]>
4 # Bernhard Herzog <[email protected]>
5 # Jonathan Coles <[email protected]>
6 # Frank Koormann <[email protected]>
7 #
8 # This program is free software under the GPL (>=v2)
9 # Read the file COPYING coming with GRASS for details.
10
11 """
12 Parser for thuban session files.
13 """
14
15 __version__ = "$Revision$"
16
17 import string, os
18
19 import xml.sax
20 import xml.sax.handler
21 from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
22
23 from Thuban import _
24
25 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
26 FIELDTYPE_STRING
27
28 from Thuban.Model.color import Color, Transparent
29
30 from Thuban.Model.session import Session
31 from Thuban.Model.map import Map
32 from Thuban.Model.layer import Layer, RasterLayer
33 from Thuban.Model.proj import Projection
34 from Thuban.Model.range import Range
35 from Thuban.Model.classification import Classification, \
36 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
37 ClassGroupProperties
38 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
39 from Thuban.Model.table import DBFTable
40 from Thuban.Model.transientdb import TransientJoinedTable
41
42 from Thuban.Model.xmlreader import XMLReader
43 import resource
44
45 import postgisdb
46
47 class LoadError(Exception):
48
49 """Exception raised when the thuban file is corrupted
50
51 Not all cases of corrupted thuban files will lead to this exception
52 but those that are found by checks in the loading code itself are.
53 """
54
55
56 class LoadCancelled(Exception):
57
58 """Exception raised to indicate that loading was interrupted by the user"""
59
60
61 def parse_color(color):
62 """Return the color object for the string color.
63
64 Color may be either 'None' or of the form '#RRGGBB' in the usual
65 HTML color notation
66 """
67 color = string.strip(color)
68 if color == "None":
69 result = Transparent
70 elif color[0] == '#':
71 if len(color) == 7:
72 r = string.atoi(color[1:3], 16) / 255.0
73 g = string.atoi(color[3:5], 16) / 255.0
74 b = string.atoi(color[5:7], 16) / 255.0
75 result = Color(r, g, b)
76 else:
77 raise ValueError(_("Invalid hexadecimal color specification %s")
78 % color)
79 else:
80 raise ValueError(_("Invalid color specification %s") % color)
81 return result
82
83 class AttrDesc:
84
85 def __init__(self, name, required = False, default = "",
86 conversion = None):
87 if not isinstance(name, tuple):
88 fullname = (None, name)
89 else:
90 fullname = name
91 name = name[1]
92 self.name = name
93 self.fullname = fullname
94 self.required = required
95 self.default = default
96 self.conversion = conversion
97
98 # set by the SessionLoader's check_attrs method
99 self.value = None
100
101
102 class SessionLoader(XMLReader):
103
104 def __init__(self, db_connection_callback = None,
105 shapefile_callback = None):
106 """Inititialize the Sax handler."""
107 XMLReader.__init__(self)
108
109 self.db_connection_callback = db_connection_callback
110 self.shapefile_callback = shapefile_callback
111 self.theSession = None
112 self.aMap = None
113 self.aLayer = None
114
115 # Map ids used in the thuban file to the corresponding objects
116 # in the session
117 self.idmap = {}
118
119 dispatchers = {
120 'session' : ("start_session", "end_session"),
121
122 'dbconnection': ("start_dbconnection", None),
123
124 'dbshapesource': ("start_dbshapesource", None),
125 'fileshapesource': ("start_fileshapesource", None),
126 'derivedshapesource': ("start_derivedshapesource", None),
127 'filetable': ("start_filetable", None),
128 'jointable': ("start_jointable", None),
129
130 'map' : ("start_map", "end_map"),
131 'projection' : ("start_projection", "end_projection"),
132 'parameter' : ("start_parameter", None),
133 'layer' : ("start_layer", "end_layer"),
134 'rasterlayer' : ("start_rasterlayer", "end_rasterlayer"),
135 'classification': ("start_classification", "end_classification"),
136 'clnull' : ("start_clnull", "end_clnull"),
137 'clpoint' : ("start_clpoint", "end_clpoint"),
138 'clrange' : ("start_clrange", "end_clrange"),
139 'cldata' : ("start_cldata", "end_cldata"),
140 'table' : ("start_table", "end_table"),
141 'labellayer' : ("start_labellayer", None),
142 'label' : ("start_label", None)}
143
144 # all dispatchers should be used for the 0.8 and 0.9 namespaces too
145 for xmlns in ("http://thuban.intevation.org/dtds/thuban-0.8.dtd",
146 "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd",
147 "http://thuban.intevation.org/dtds/thuban-0.9.dtd",
148 "http://thuban.intevation.org/dtds/thuban-1.0-dev.dtd",
149 "http://thuban.intevation.org/dtds/thuban-1.0rc1.dtd",
150 "http://thuban.intevation.org/dtds/thuban-1.0.0.dtd",
151 "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"):
152 for key, value in dispatchers.items():
153 dispatchers[(xmlns, key)] = value
154
155 XMLReader.AddDispatchers(self, dispatchers)
156
157 def Destroy(self):
158 """Clear all instance variables to cut cyclic references.
159
160 The GC would have collected the loader eventually but it can
161 happen that it doesn't run at all until Thuban is closed (2.3
162 but not 2.2 tries a bit harder and forces a collection when the
163 interpreter terminates)
164 """
165 self.__dict__.clear()
166
167 def start_session(self, name, qname, attrs):
168 self.theSession = Session(self.encode(attrs.get((None, 'title'),
169 None)))
170
171 def end_session(self, name, qname):
172 pass
173
174 def check_attrs(self, element, attrs, descr):
175 """Check and convert some of the attributes of an element
176
177 Parameters:
178 element -- The element name
179 attrs -- The attrs mapping as passed to the start_* methods
180 descr -- Sequence of attribute descriptions (AttrDesc instances)
181
182 Return a dictionary containig normalized versions of the
183 attributes described in descr. The keys of that dictionary are
184 the name attributes of the attribute descriptions. The attrs
185 dictionary will not be modified.
186
187 If the attribute is required, i.e. the 'required' attribute of
188 the descrtiption is true, but it is not in attrs, raise a
189 LoadError.
190
191 If the attribute has a default value and it is not present in
192 attrs, use that default value as the value in the returned dict.
193
194 The value is converted before putting it into the returned dict.
195 The following conversions are available:
196
197 'filename' -- The attribute is a filename.
198
199 If the filename is a relative name, interpret
200 it relative to the directory containing the
201 .thuban file and make it an absolute name
202
203 'shapestore' -- The attribute is the ID of a shapestore
204 defined earlier in the .thuban file. Look it
205 up self.idmap
206
207 'table' -- The attribute is the ID of a table or shapestore
208 defined earlier in the .thuban file. Look it up
209 self.idmap. If it's the ID of a shapestore the
210 value will be the table of the shapestore.
211
212 'idref' -- The attribute is the id of an object defined
213 earlier in the .thuban file. Look it up self.idmap
214
215 'ascii' -- The attribute is converted to a bytestring with
216 ascii encoding.
217
218 a callable -- The attribute value is passed to the callable
219 and the return value is used as the converted
220 value
221
222 If no conversion is specified for an attribute it is converted
223 with self.encode.
224 """
225 normalized = {}
226
227 for d in descr:
228 if d.required and not attrs.has_key(d.fullname):
229 raise LoadError("Element %s requires an attribute %r"
230 % (element, d.name))
231 value = attrs.get(d.fullname, d.default)
232
233 if d.conversion in ("idref", "shapesource"):
234 if value in self.idmap:
235 value = self.idmap[value]
236 else:
237 raise LoadError("Element %s requires an already defined ID"
238 " in attribute %r"
239 % (element, d.name))
240 elif d.conversion == "table":
241 if value in self.idmap:
242 value = self.idmap[value]
243 if isinstance(value, ShapefileStore):
244 value = value.Table()
245 else:
246 raise LoadError("Element %s requires an already defined ID"
247 " in attribute %r"
248 % (element, d.name))
249 elif d.conversion == "filename":
250 value = os.path.abspath(os.path.join(self.GetDirectory(),
251 self.encode(value)))
252 elif d.conversion == "ascii":
253 value = value.encode("ascii")
254 elif d.conversion:
255 # Assume it's a callable
256 value = d.conversion(value)
257 else:
258 value = self.encode(value)
259
260 normalized[d.name] = value
261 return normalized
262
263 def open_shapefile(self, filename):
264 """Open shapefile, with alternative path handling.
265
266 If a shapefile cannot be opened and an IOError is raised, check for
267 an alternative. This alternative can be specified interactively by
268 the user or taken from a list of (potential) locations, depending on
269 the callback implementation.
270
271 The alternative is rechecked. If taken from a list the user
272 has to confirm the alternative.
273 """
274
275 # Flag if the alternative path was specified interactively / from list.
276 from_list = 0
277 while 1:
278 try:
279 store = self.theSession.OpenShapefile(filename)
280 if from_list:
281 # A valid path has been guessed from a list
282 # Let the user confirm - or select an alternative.
283 filename, from_list = self.shapefile_callback(
284 filename, "check")
285 if filename is None:
286 # Selection cancelled
287 raise LoadCancelled
288 elif store.FileName() == filename:
289 # Proposed file has been accepted
290 break
291 else:
292 # the filename has been changed, try the new file
293 pass
294 else:
295 break
296 except IOError:
297 if self.shapefile_callback is not None:
298 filename, from_list = self.shapefile_callback(
299 filename,
300 mode = "search",
301 second_try = from_list)
302 if filename is None:
303 raise LoadCancelled
304 else:
305 raise
306 return store
307
308 def start_dbconnection(self, name, qname, attrs):
309 attrs = self.check_attrs(name, attrs,
310 [AttrDesc("id", True),
311 AttrDesc("dbtype", True),
312 AttrDesc("host", False, ""),
313 AttrDesc("port", False, ""),
314 AttrDesc("user", False, ""),
315 AttrDesc("dbname", True)])
316 ID = attrs["id"]
317 dbtype = attrs["dbtype"]
318 if dbtype != "postgis":
319 raise LoadError("dbtype %r not supported" % filetype)
320
321 del attrs["id"]
322 del attrs["dbtype"]
323
324 # Try to open the connection and if it fails ask the user for
325 # the correct parameters repeatedly.
326 # FIXME: it would be better not to insist on getting a
327 # connection here. We should handle this more like the raster
328 # images where the layers etc still are created but are not
329 # drawn in case Thuban can't use the data for various reasons
330 while 1:
331 try:
332 conn = postgisdb.PostGISConnection(**attrs)
333 break
334 except postgisdb.ConnectionError, val:
335 if self.db_connection_callback is not None:
336 attrs = self.db_connection_callback(attrs, str(val))
337 if attrs is None:
338 raise LoadCancelled
339 else:
340 raise
341
342 self.idmap[ID] = conn
343 self.theSession.AddDBConnection(conn)
344
345 def start_dbshapesource(self, name, qname, attrs):
346 attrs = self.check_attrs(name, attrs,
347 [AttrDesc("id", True),
348 AttrDesc("dbconn", True,
349 conversion = "idref"),
350 AttrDesc("tablename", True,
351 conversion = "ascii"),
352 # id_column and geometry_column were
353 # newly introduced with thuban-1.1.dtd
354 # where they're required. Since we
355 # support the older formats too we
356 # have them optional here.
357 AttrDesc("id_column", False, "gid",
358 conversion = "ascii"),
359 AttrDesc("geometry_column", False,
360 conversion = "ascii")])
361 # The default value of geometry_column to use when instantiating
362 # the db shapestore is None which we currently can't easily use
363 # in check_attrs
364 geometry_column = attrs["geometry_column"]
365 if not geometry_column:
366 geometry_column = None
367 dbopen = self.theSession.OpenDBShapeStore
368 self.idmap[attrs["id"]] = dbopen(attrs["dbconn"], attrs["tablename"],
369 id_column = attrs["id_column"],
370 geometry_column=geometry_column)
371
372 def start_fileshapesource(self, name, qname, attrs):
373 attrs = self.check_attrs(name, attrs,
374 [AttrDesc("id", True),
375 AttrDesc("filename", True,
376 conversion = "filename"),
377 AttrDesc("filetype", True)])
378 ID = attrs["id"]
379 filename = attrs["filename"]
380 filetype = attrs["filetype"]
381 if filetype != "shapefile":
382 raise LoadError("shapesource filetype %r not supported" % filetype)
383 self.idmap[ID] = self.open_shapefile(filename)
384
385 def start_derivedshapesource(self, name, qname, attrs):
386 attrs = self.check_attrs(name, attrs,
387 [AttrDesc("id", True),
388 AttrDesc("shapesource", True,
389 conversion = "shapesource"),
390 AttrDesc("table", True, conversion="table")])
391 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
392 self.theSession.AddShapeStore(store)
393 self.idmap[attrs["id"]] = store
394
395 def start_filetable(self, name, qname, attrs):
396 attrs = self.check_attrs(name, attrs,
397 [AttrDesc("id", True),
398 AttrDesc("title", True),
399 AttrDesc("filename", True,
400 conversion = "filename"),
401 AttrDesc("filetype")])
402 filetype = attrs["filetype"]
403 if filetype != "DBF":
404 raise LoadError("shapesource filetype %r not supported" % filetype)
405 table = DBFTable(attrs["filename"])
406 table.SetTitle(attrs["title"])
407 self.idmap[attrs["id"]] = self.theSession.AddTable(table)
408
409 def start_jointable(self, name, qname, attrs):
410 attrs = self.check_attrs(name, attrs,
411 [AttrDesc("id", True),
412 AttrDesc("title", True),
413 AttrDesc("left", True, conversion="table"),
414 AttrDesc("leftcolumn", True),
415 AttrDesc("right", True, conversion="table"),
416 AttrDesc("rightcolumn", True),
417
418 # jointype is required for file
419 # version 0.9 but this attribute
420 # wasn't in the 0.8 version because of
421 # an oversight so we assume it's
422 # optional since we want to handle
423 # both file format versions here.
424 AttrDesc("jointype", False,
425 default="INNER")])
426
427 jointype = attrs["jointype"]
428 if jointype == "LEFT OUTER":
429 outer_join = True
430 elif jointype == "INNER":
431 outer_join = False
432 else:
433 raise LoadError("jointype %r not supported" % jointype )
434 table = TransientJoinedTable(self.theSession.TransientDB(),
435 attrs["left"], attrs["leftcolumn"],
436 attrs["right"], attrs["rightcolumn"],
437 outer_join = outer_join)
438 table.SetTitle(attrs["title"])
439 self.idmap[attrs["id"]] = self.theSession.AddTable(table)
440
441 def start_map(self, name, qname, attrs):
442 """Start a map."""
443 self.aMap = Map(self.encode(attrs.get((None, 'title'), None)))
444
445 def end_map(self, name, qname):
446 self.theSession.AddMap(self.aMap)
447 self.aMap = None
448
449 def start_projection(self, name, qname, attrs):
450 attrs = self.check_attrs(name, attrs,
451 [AttrDesc("name", conversion=self.encode),
452 AttrDesc("epsg", default=None,
453 conversion=self.encode)])
454 self.projection_name = attrs["name"]
455 self.projection_epsg = attrs["epsg"]
456 self.projection_params = [ ]
457
458 def end_projection(self, name, qname):
459 if self.aLayer is not None:
460 obj = self.aLayer
461 elif self.aMap is not None:
462 obj = self.aMap
463 else:
464 assert False, "projection tag out of context"
465 pass
466
467 obj.SetProjection(Projection(self.projection_params,
468 self.projection_name,
469 epsg = self.projection_epsg))
470
471 def start_parameter(self, name, qname, attrs):
472 s = attrs.get((None, 'value'))
473 s = str(s) # we can't handle unicode in proj
474 self.projection_params.append(s)
475
476 def start_layer(self, name, qname, attrs, layer_class = Layer):
477 """Start a layer
478
479 Instantiate a layer of class layer_class from the attributes in
480 attrs which may be a dictionary as well as the normal SAX attrs
481 object and bind it to self.aLayer.
482 """
483 title = self.encode(attrs.get((None, 'title'), ""))
484 filename = attrs.get((None, 'filename'), "")
485 filename = os.path.join(self.GetDirectory(), filename)
486 filename = self.encode(filename)
487 visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
488 fill = parse_color(attrs.get((None, 'fill'), "None"))
489 stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
490 stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
491 if attrs.has_key((None, "shapestore")):
492 store = self.idmap[attrs[(None, "shapestore")]]
493 else:
494 store = self.open_shapefile(filename)
495
496 self.aLayer = layer_class(title, store,
497 fill = fill, stroke = stroke,
498 lineWidth = stroke_width,
499 visible = visible)
500
501 def end_layer(self, name, qname):
502 self.aMap.AddLayer(self.aLayer)
503 self.aLayer = None
504
505 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
506 title = self.encode(attrs.get((None, 'title'), ""))
507 filename = attrs.get((None, 'filename'), "")
508 filename = os.path.join(self.GetDirectory(), filename)
509 filename = self.encode(filename)
510 visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
511 opacity = float(attrs.get((None, 'opacity'), "1"))
512 masktype = str(attrs.get((None, 'masktype'), "bit"))
513
514 masktypes = {"none": layer_class.MASK_NONE,
515 "bit": layer_class.MASK_BIT,
516 "alpha": layer_class.MASK_ALPHA}
517
518 self.aLayer = layer_class(title, filename,
519 visible = visible,
520 opacity = opacity,
521 masktype = masktypes[masktype])
522
523 def end_rasterlayer(self, name, qname):
524 self.aMap.AddLayer(self.aLayer)
525 self.aLayer = None
526
527 def start_classification(self, name, qname, attrs):
528 attrs = self.check_attrs(name, attrs,
529 [AttrDesc("field", True),
530 AttrDesc("field_type", True)])
531 field = attrs["field"]
532 fieldType = attrs["field_type"]
533
534 dbFieldType = self.aLayer.GetFieldType(field)
535
536 if fieldType != dbFieldType:
537 raise ValueError(_("xml field type differs from database!"))
538
539 # setup conversion routines depending on the kind of data
540 # we will be seeing later on
541 if fieldType == FIELDTYPE_STRING:
542 self.conv = str
543 elif fieldType == FIELDTYPE_INT:
544 self.conv = lambda p: int(float(p))
545 elif fieldType == FIELDTYPE_DOUBLE:
546 self.conv = float
547
548 self.aLayer.SetClassificationColumn(field)
549
550 def end_classification(self, name, qname):
551 pass
552
553 def start_clnull(self, name, qname, attrs):
554 self.cl_group = ClassGroupDefault()
555 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
556 self.cl_prop = ClassGroupProperties()
557
558 def end_clnull(self, name, qname):
559 self.cl_group.SetProperties(self.cl_prop)
560 self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
561 del self.cl_group, self.cl_prop
562
563 def start_clpoint(self, name, qname, attrs):
564 attrib_value = attrs.get((None, 'value'), "0")
565
566 field = self.aLayer.GetClassificationColumn()
567 if self.aLayer.GetFieldType(field) == FIELDTYPE_STRING:
568 value = self.encode(attrib_value)
569 else:
570 value = self.conv(attrib_value)
571 self.cl_group = ClassGroupSingleton(value)
572 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
573 self.cl_prop = ClassGroupProperties()
574
575
576 def end_clpoint(self, name, qname):
577 self.cl_group.SetProperties(self.cl_prop)
578 self.aLayer.GetClassification().AppendGroup(self.cl_group)
579 del self.cl_group, self.cl_prop
580
581 def start_clrange(self, name, qname, attrs):
582 attrs = self.check_attrs(name, attrs,
583 [AttrDesc("range", False, None),
584 AttrDesc("min", False, None),
585 AttrDesc("max", False, None)])
586
587 range = attrs['range']
588 # for backward compatibility (min/max are not saved)
589 min = attrs['min']
590 max = attrs['max']
591
592 try:
593 if range is not None:
594 self.cl_group = ClassGroupRange(Range(range))
595 elif min is not None and max is not None:
596 self.cl_group = ClassGroupRange((self.conv(min),
597 self.conv(max)))
598 else:
599 self.cl_group = ClassGroupRange(Range(None))
600
601 except ValueError:
602 raise ValueError(_("Classification range is not a number!"))
603
604 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
605 self.cl_prop = ClassGroupProperties()
606
607
608 def end_clrange(self, name, qname):
609 self.cl_group.SetProperties(self.cl_prop)
610 self.aLayer.GetClassification().AppendGroup(self.cl_group)
611 del self.cl_group, self.cl_prop
612
613 def start_cldata(self, name, qname, attrs):
614 self.cl_prop.SetLineColor(
615 parse_color(attrs.get((None, 'stroke'), "None")))
616 self.cl_prop.SetLineWidth(
617 int(attrs.get((None, 'stroke_width'), "0")))
618 self.cl_prop.SetSize(int(attrs.get((None, 'size'), "5")))
619 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
620
621 def end_cldata(self, name, qname):
622 pass
623
624 def start_labellayer(self, name, qname, attrs):
625 self.aLayer = self.aMap.LabelLayer()
626
627 def start_label(self, name, qname, attrs):
628 attrs = self.check_attrs(name, attrs,
629 [AttrDesc("x", True, conversion = float),
630 AttrDesc("y", True, conversion = float),
631 AttrDesc("text", True),
632 AttrDesc("halign", True,
633 conversion = "ascii"),
634 AttrDesc("valign", True,
635 conversion = "ascii")])
636 x = attrs['x']
637 y = attrs['y']
638 text = attrs['text']
639 halign = attrs['halign']
640 valign = attrs['valign']
641 if halign not in ("left", "center", "right"):
642 raise LoadError("Unsupported halign value %r" % halign)
643 if valign not in ("top", "center", "bottom"):
644 raise LoadError("Unsupported valign value %r" % valign)
645 self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
646
647 def characters(self, chars):
648 pass
649
650
651 def load_session(filename, db_connection_callback = None,
652 shapefile_callback = None):
653 """Load a Thuban session from the file object file
654
655 The db_connection_callback, if given should be a callable object
656 that can be called like this:
657 db_connection_callback(params, message)
658
659 where params is a dictionary containing the known connection
660 parameters and message is a string with a message why the connection
661 failed. db_connection_callback should return a new dictionary with
662 corrected and perhaps additional parameters like a password or None
663 to indicate that the user cancelled.
664 """
665 handler = SessionLoader(db_connection_callback, shapefile_callback)
666 handler.read(filename)
667
668 session = handler.theSession
669 # Newly loaded session aren't modified
670 session.UnsetModified()
671
672 handler.Destroy()
673
674 return session
675

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26