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

Diff of /branches/WIP-pyshapelib-bramz/Thuban/Model/load.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 744 by jonathan, Fri Apr 25 10:26:35 2003 UTC revision 2446 by frank, Mon Dec 13 11:52:34 2004 UTC
# Line 1  Line 1 
1  # Copyright (C) 2001, 2002, 2003 by Intevation GmbH  # Copyright (C) 2001, 2002, 2003, 2004 by Intevation GmbH
2  # Authors:  # Authors:
3  # Jan-Oliver Wagner <[email protected]>  # Jan-Oliver Wagner <[email protected]>
4  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
5  # Jonathan Coles <[email protected]>  # Jonathan Coles <[email protected]>
6    # Frank Koormann <[email protected]>
7  #  #
8  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
9  # Read the file COPYING coming with GRASS for details.  # Read the file COPYING coming with GRASS for details.
# Line 20  import xml.sax.handler Line 21  import xml.sax.handler
21  from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException  from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
22    
23  from Thuban import _  from Thuban import _
 from Thuban.common import *  
24    
25  from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \  from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
26       FIELDTYPE_STRING       FIELDTYPE_STRING
27    
28    from Thuban.Model.color import Color, Transparent
29    
30  from Thuban.Model.session import Session  from Thuban.Model.session import Session
31  from Thuban.Model.map import Map  from Thuban.Model.map import Map
32  from Thuban.Model.layer import Layer  from Thuban.Model.layer import Layer, RasterLayer
 from Thuban.Model.color import Color  
33  from Thuban.Model.proj import Projection  from Thuban.Model.proj import Projection
34    from Thuban.Model.range import Range
35  from Thuban.Model.classification import Classification, \  from Thuban.Model.classification import Classification, \
36      ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \      ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
37      ClassGroupProperties      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):  def parse_color(color):
# Line 43  def parse_color(color): Line 66  def parse_color(color):
66      """      """
67      color = string.strip(color)      color = string.strip(color)
68      if color == "None":      if color == "None":
69          result = Color.Transparent          result = Transparent
70      elif color[0] == '#':      elif color[0] == '#':
71          if len(color) == 7:          if len(color) == 7:
72              r = string.atoi(color[1:3], 16) / 255.0              r = string.atoi(color[1:3], 16) / 255.0
# Line 57  def parse_color(color): Line 80  def parse_color(color):
80          raise ValueError(_("Invalid color specification %s") % color)          raise ValueError(_("Invalid color specification %s") % color)
81      return result      return result
82    
83    class AttrDesc:
84    
85  class XMLReader(xml.sax.handler.ContentHandler):      def __init__(self, name, required = False, default = "",
86                     conversion = None):
87      # Dictionary mapping element names (or (URI, element name) pairs for          if not isinstance(name, tuple):
88      # documents using namespaces) to method names. The methods should              fullname = (None, name)
     # accept the same parameters as the startElement (or startElementNS)  
     # methods. The start_dispatcher is used by the default startElement  
     # and startElementNS methods to call a method for the open tag of an  
     # element.  
     start_dispatcher = {}  
   
     # end_dispatcher works just like start_dispatcher but it's used by  
     # endElement and endElementNS. The method whose names it maps to  
     # should accept the same parameters as endElement and endElementNS.  
     end_dispatcher = {}  
   
   
     def __init__(self):  
         self.chars = ''  
         self.__parser = None  
         self.__directory = ""  
         self.__dispatchers = {}  
   
     def read(self, file_or_filename):  
   
         if hasattr(file_or_filename, "read"):  
             # it's a file object  
             self.__directory = ""  
             self.__file = file_or_filename  
89          else:          else:
90              filename = file_or_filename              fullname = name
91              self.__directory = os.path.dirname(filename)              name = name[1]
92              self.__file = open(filename)          self.name = name
93            self.fullname = fullname
94          if self.__parser is None:          self.required = required
95              self.__parser = make_parser()          self.default = default
96              self.__parser.setContentHandler(self)          self.conversion = conversion
             self.__parser.setErrorHandler(ErrorHandler())  
             self.__parser.setFeature(xml.sax.handler.feature_namespaces, 1)  
   
             #  
             # Well, this isn't pretty, but it appears that if you  
             # use Python 2.2 without the site-package _xmlplus then  
             # the following will fail, and without them it will work.  
             # However, if you do have the site-package and you don't  
             # call these functions, the reader raises an exception  
             #  
             # The reason we set these to 0 in the first place is  
             # because there is an unresolved issue with external  
             # entities causing an exception in the reader  
             #  
             try:  
                 self.__parser.setFeature(xml.sax.handler.feature_validation,0)  
                 self.__parser.setFeature(xml.sax.handler.feature_external_ges,0)  
                 self.__parser.setFeature(xml.sax.handler.feature_external_pes,0)  
             except SAXNotRecognizedException:  
                 pass  
   
         self.__parser.parse(self.__file)  
   
         self.close()  
   
     def close(self):  
         self.__file.close()  
           
     def GetFilename(self):  
         if hasattr(self.__file, "name"):  
             return self.__file.name  
   
         return ""  
   
     def GetDirectory(self):  
         return self.__directory  
   
   
     def AddDispatchers(self, dict):  
         """Add the function names that should be used to process XML tags.  
   
         dict -- a dictionary whose keys are XML tag strings and whose values  
                 are pairs of strings such that the first string is  
                 the name of the function that should be called when the  
                 XML tag opens and the second string is the name of the  
                 function that should be called when the XML tag closes.  
                 If a pair element is None, no function is called.  
         """  
97    
98          self.__dispatchers.update(dict)          # set by the SessionLoader's check_attrs method
99            self.value = None
100    
     def startElementNS(self, name, qname, attrs):  
         """Call the method given for name in self.start_dispatcher  
         """  
         if name[0] is None:  
             method_name = self.__dispatchers.get(name[1])  
         else:  
             # Dispatch with namespace  
             method_name = self.__dispatchers.get(name)  
         if method_name is not None and method_name[0] is not None:  
             getattr(self, method_name[0])(name, qname, attrs)  
   
     def endElementNS(self, name, qname):  
         """Call the method given for name in self.end_dispatcher  
         """  
         if name[0] is None:  
             method_name = self.__dispatchers.get(name[1])  
         else:  
             # Dispatch with namespace  
             method_name = self.__dispatchers.get(name)  
         if method_name is not None and method_name[1] is not None:  
             getattr(self, method_name[1])(name, qname)  
101    
102  class SessionLoader(XMLReader):  class SessionLoader(XMLReader):
103    
104      def __init__(self):      def __init__(self, db_connection_callback = None,
105                           shapefile_callback = None):
106          """Inititialize the Sax handler."""          """Inititialize the Sax handler."""
107          XMLReader.__init__(self)          XMLReader.__init__(self)
108    
109            self.db_connection_callback = db_connection_callback
110            self.shapefile_callback = shapefile_callback
111          self.theSession = None          self.theSession = None
112          self.aMap = None          self.aMap = None
113          self.aLayer = None          self.aLayer = None
114    
115          XMLReader.AddDispatchers(self,          # Map ids used in the thuban file to the corresponding objects
116              {'session'       : ("start_session",        "end_session"),          # in the session
117               'map'           : ("start_map",            "end_map"),          self.idmap = {}
118               'projection'    : ("start_projection",     "end_projection"),  
119               'parameter'     : ("start_parameter",      None),          dispatchers = {
120               'layer'         : ("start_layer",          "end_layer"),              'session'       : ("start_session",        "end_session"),
121               'classification': ("start_classification", "end_classification"),  
122               'clnull'        : ("start_clnull",         "end_clnull"),              'dbconnection': ("start_dbconnection", None),
123               'clpoint'       : ("start_clpoint",        "end_clpoint"),  
124               'clrange'       : ("start_clrange",        "end_clrange"),              'dbshapesource': ("start_dbshapesource", None),
125               'cldata'        : ("start_cldata",         "end_cldata"),              'fileshapesource': ("start_fileshapesource", None),
126               'table'         : ("start_table",          "end_table"),              'derivedshapesource': ("start_derivedshapesource", None),
127               'labellayer'    : ("start_labellayer",     None),              'filetable': ("start_filetable", None),
128               'label'         : ("start_label",          None)})              '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):      def start_session(self, name, qname, attrs):
168          self.theSession = Session(attrs.get((None, 'title'), None))          self.theSession = Session(self.encode(attrs.get((None, 'title'),
169                                                            None)))
170    
171      def end_session(self, name, qname):      def end_session(self, name, qname):
172          pass          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                                                         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, eventually with alternative path."""
265            from_list = 0
266            while 1:
267                try:
268                    store = self.theSession.OpenShapefile(filename)
269                    if from_list:
270                        # The correct? path has been guessed from a list
271                        # Let the user confirm - or select an alternative.
272                        filename, from_list = self.shapefile_callback(
273                                                filename, "check")
274                        if filename is None:
275                            # Selection cancelled
276                            raise LoadCancelled
277                        elif store.FileName() == filename:
278                            # Proposed file has been accepted
279                            break
280                        else:
281                            # the filename has been changed, try the new file
282                            pass
283                    else:
284                        break
285                except IOError:
286                    if self.shapefile_callback is not None:
287                        filename, from_list = self.shapefile_callback(
288                                                filename,
289                                                mode = "search",
290                                                second_try = from_list)
291                        if filename is None:
292                            raise LoadCancelled
293                        print filename
294                    else:
295                        raise
296            return store
297    
298        def start_dbconnection(self, name, qname, attrs):
299            attrs = self.check_attrs(name, attrs,
300                                     [AttrDesc("id", True),
301                                      AttrDesc("dbtype", True),
302                                      AttrDesc("host", False, ""),
303                                      AttrDesc("port", False, ""),
304                                      AttrDesc("user", False, ""),
305                                      AttrDesc("dbname", True)])
306            ID = attrs["id"]
307            dbtype = attrs["dbtype"]
308            if dbtype != "postgis":
309                raise LoadError("dbtype %r not supported" % filetype)
310    
311            del attrs["id"]
312            del attrs["dbtype"]
313    
314            # Try to open the connection and if it fails ask the user for
315            # the correct parameters repeatedly.
316            # FIXME: it would be better not to insist on getting a
317            # connection here. We should handle this more like the raster
318            # images where the layers etc still are created but are not
319            # drawn in case Thuban can't use the data for various reasons
320            while 1:
321                try:
322                    conn = postgisdb.PostGISConnection(**attrs)
323                    break
324                except postgisdb.ConnectionError, val:
325                    if self.db_connection_callback is not None:
326                        attrs = self.db_connection_callback(attrs, str(val))
327                        if attrs is None:
328                            raise LoadCancelled
329                    else:
330                        raise
331    
332            self.idmap[ID] = conn
333            self.theSession.AddDBConnection(conn)
334    
335        def start_dbshapesource(self, name, qname, attrs):
336            attrs = self.check_attrs(name, attrs,
337                                     [AttrDesc("id", True),
338                                      AttrDesc("dbconn", True,
339                                               conversion = "idref"),
340                                      AttrDesc("tablename", True,
341                                               conversion = "ascii"),
342                                      # id_column and geometry_column were
343                                      # newly introduced with thuban-1.1.dtd
344                                      # where they're required.  Since we
345                                      # support the older formats too we
346                                      # have them optional here.
347                                      AttrDesc("id_column", False, "gid",
348                                               conversion = "ascii"),
349                                      AttrDesc("geometry_column", False,
350                                               conversion = "ascii")])
351            # The default value of geometry_column to use when instantiating
352            # the db shapestore is None which we currently can't easily use
353            # in check_attrs
354            geometry_column = attrs["geometry_column"]
355            if not geometry_column:
356                geometry_column = None
357            dbopen = self.theSession.OpenDBShapeStore
358            self.idmap[attrs["id"]] = dbopen(attrs["dbconn"], attrs["tablename"],
359                                             id_column = attrs["id_column"],
360                                             geometry_column=geometry_column)
361    
362        def start_fileshapesource(self, name, qname, attrs):
363            attrs = self.check_attrs(name, attrs,
364                                      [AttrDesc("id", True),
365                                       AttrDesc("filename", True,
366                                                conversion = "filename"),
367                                       AttrDesc("filetype", True)])
368            ID = attrs["id"]
369            filename = attrs["filename"]
370            filetype = attrs["filetype"]
371            if filetype != "shapefile":
372                raise LoadError("shapesource filetype %r not supported" % filetype)
373            self.idmap[ID] = self.open_shapefile(filename)
374    
375        def start_derivedshapesource(self, name, qname, attrs):
376            attrs = self.check_attrs(name, attrs,
377                                     [AttrDesc("id", True),
378                                      AttrDesc("shapesource", True,
379                                               conversion = "shapesource"),
380                                      AttrDesc("table", True, conversion="table")])
381            store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
382            self.theSession.AddShapeStore(store)
383            self.idmap[attrs["id"]] = store
384    
385        def start_filetable(self, name, qname, attrs):
386            attrs = self.check_attrs(name, attrs,
387                                     [AttrDesc("id", True),
388                                      AttrDesc("title", True),
389                                      AttrDesc("filename", True,
390                                               conversion = "filename"),
391                                      AttrDesc("filetype")])
392            filetype = attrs["filetype"]
393            if filetype != "DBF":
394                raise LoadError("shapesource filetype %r not supported" % filetype)
395            table = DBFTable(attrs["filename"])
396            table.SetTitle(attrs["title"])
397            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
398    
399        def start_jointable(self, name, qname, attrs):
400            attrs = self.check_attrs(name, attrs,
401                                     [AttrDesc("id", True),
402                                      AttrDesc("title", True),
403                                      AttrDesc("left", True, conversion="table"),
404                                      AttrDesc("leftcolumn", True),
405                                      AttrDesc("right", True, conversion="table"),
406                                      AttrDesc("rightcolumn", True),
407    
408                                      # jointype is required for file
409                                      # version 0.9 but this attribute
410                                      # wasn't in the 0.8 version because of
411                                      # an oversight so we assume it's
412                                      # optional since we want to handle
413                                      # both file format versions here.
414                                      AttrDesc("jointype", False,
415                                               default="INNER")])
416    
417            jointype = attrs["jointype"]
418            if jointype == "LEFT OUTER":
419                outer_join = True
420            elif jointype == "INNER":
421                outer_join = False
422            else:
423                raise LoadError("jointype %r not supported" % jointype )
424            table = TransientJoinedTable(self.theSession.TransientDB(),
425                                         attrs["left"], attrs["leftcolumn"],
426                                         attrs["right"], attrs["rightcolumn"],
427                                         outer_join = outer_join)
428            table.SetTitle(attrs["title"])
429            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
430    
431      def start_map(self, name, qname, attrs):      def start_map(self, name, qname, attrs):
432          """Start a map."""          """Start a map."""
433          self.aMap = Map(attrs.get((None, 'title'), None))          self.aMap = Map(self.encode(attrs.get((None, 'title'), None)))
         self.__projReceiver = self.aMap  
434    
435      def end_map(self, name, qname):      def end_map(self, name, qname):
436          self.theSession.AddMap(self.aMap)          self.theSession.AddMap(self.aMap)
437          self.__projReceiver = None          self.aMap = None
438    
439      def start_projection(self, name, qname, attrs):      def start_projection(self, name, qname, attrs):
440          self.ProjectionName = attrs.get((None, 'name'), None)          attrs = self.check_attrs(name, attrs,
441          self.ProjectionParams = [ ]                                   [AttrDesc("name", conversion=self.encode),
442                                      AttrDesc("epsg", default=None,
443                                               conversion=self.encode)])
444            self.projection_name = attrs["name"]
445            self.projection_epsg = attrs["epsg"]
446            self.projection_params = [ ]
447    
448      def end_projection(self, name, qname):      def end_projection(self, name, qname):
449          self.__projReceiver.SetProjection(          if self.aLayer is not None:
450              Projection(self.ProjectionParams, self.ProjectionName))              obj = self.aLayer
451            elif self.aMap is not None:
452                obj = self.aMap
453            else:
454                assert False, "projection tag out of context"
455                pass
456    
457            obj.SetProjection(Projection(self.projection_params,
458                                         self.projection_name,
459                                         epsg = self.projection_epsg))
460    
461      def start_parameter(self, name, qname, attrs):      def start_parameter(self, name, qname, attrs):
462          s = attrs.get((None, 'value'))          s = attrs.get((None, 'value'))
463          s = str(s) # we can't handle unicode in proj          s = str(s) # we can't handle unicode in proj
464          self.ProjectionParams.append(s)          self.projection_params.append(s)
465    
466      def start_layer(self, name, qname, attrs, layer_class = Layer):      def start_layer(self, name, qname, attrs, layer_class = Layer):
467          """Start a layer          """Start a layer
# Line 227  class SessionLoader(XMLReader): Line 470  class SessionLoader(XMLReader):
470          attrs which may be a dictionary as well as the normal SAX attrs          attrs which may be a dictionary as well as the normal SAX attrs
471          object and bind it to self.aLayer.          object and bind it to self.aLayer.
472          """          """
473          title = attrs.get((None, 'title'), "")          title = self.encode(attrs.get((None, 'title'), ""))
474          filename = attrs.get((None, 'filename'), "")          filename = attrs.get((None, 'filename'), "")
475          filename = os.path.join(self.GetDirectory(), filename)          filename = os.path.join(self.GetDirectory(), filename)
476            filename = self.encode(filename)
477            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
478          fill = parse_color(attrs.get((None, 'fill'), "None"))          fill = parse_color(attrs.get((None, 'fill'), "None"))
479          stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))          stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
480          stroke_width = int(attrs.get((None, 'stroke_width'), "1"))          stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
481          self.aLayer = layer_class(title,          if attrs.has_key((None, "shapestore")):
482                                    self.theSession.OpenShapefile(filename),              store = self.idmap[attrs[(None, "shapestore")]]
483                                    fill = fill, stroke = stroke,          else:
484                                    lineWidth = stroke_width)              store = self.open_shapefile(filename)
485    
486          self.__projReceiver = self.aLayer          self.aLayer = layer_class(title, store,
487                                      fill = fill, stroke = stroke,
488                                      lineWidth = stroke_width,
489                                      visible = visible)
490    
491      def end_layer(self, name, qname):      def end_layer(self, name, qname):
492          self.aMap.AddLayer(self.aLayer)          self.aMap.AddLayer(self.aLayer)
493          self.__projReceiver = None          self.aLayer = None
494    
495        def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
496            title = self.encode(attrs.get((None, 'title'), ""))
497            filename = attrs.get((None, 'filename'), "")
498            filename = os.path.join(self.GetDirectory(), filename)
499            filename = self.encode(filename)
500            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
501    
502            self.aLayer = layer_class(title, filename, visible = visible)
503    
504        def end_rasterlayer(self, name, qname):
505            self.aMap.AddLayer(self.aLayer)
506            self.aLayer = None
507    
508      def start_classification(self, name, qname, attrs):      def start_classification(self, name, qname, attrs):
509          field = attrs.get((None, 'field'), None)          attrs = self.check_attrs(name, attrs,
510                                     [AttrDesc("field", True),
511                                      AttrDesc("field_type", True)])
512            field = attrs["field"]
513            fieldType = attrs["field_type"]
514    
         fieldType = attrs.get((None, 'field_type'), None)  
515          dbFieldType = self.aLayer.GetFieldType(field)          dbFieldType = self.aLayer.GetFieldType(field)
516    
517          if fieldType != dbFieldType:          if fieldType != dbFieldType:
# Line 262  class SessionLoader(XMLReader): Line 526  class SessionLoader(XMLReader):
526          elif fieldType == FIELDTYPE_DOUBLE:          elif fieldType == FIELDTYPE_DOUBLE:
527              self.conv = float              self.conv = float
528    
529          self.aLayer.GetClassification().SetField(field)          self.aLayer.SetClassificationColumn(field)
   
530    
531      def end_classification(self, name, qname):      def end_classification(self, name, qname):
532          pass          pass
533    
534      def start_clnull(self, name, qname, attrs):      def start_clnull(self, name, qname, attrs):
535          self.cl_group = ClassGroupDefault()          self.cl_group = ClassGroupDefault()
536          self.cl_group.SetLabel(attrs.get((None, 'label'), ""))          self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
537          self.cl_prop = ClassGroupProperties()          self.cl_prop = ClassGroupProperties()
538    
539      def end_clnull(self, name, qname):      def end_clnull(self, name, qname):
# Line 281  class SessionLoader(XMLReader): Line 544  class SessionLoader(XMLReader):
544      def start_clpoint(self, name, qname, attrs):      def start_clpoint(self, name, qname, attrs):
545          attrib_value = attrs.get((None, 'value'), "0")          attrib_value = attrs.get((None, 'value'), "0")
546    
547          #try:          field = self.aLayer.GetClassificationColumn()
548              #value  = Str2Num(attrib_value)          if self.aLayer.GetFieldType(field) == FIELDTYPE_STRING:
549          #except:              value = self.encode(attrib_value)
550              #value  = attrib_value          else:
551                value = self.conv(attrib_value)
         value = self.conv(attrib_value)  
   
552          self.cl_group = ClassGroupSingleton(value)          self.cl_group = ClassGroupSingleton(value)
553          self.cl_group.SetLabel(attrs.get((None, 'label'), ""))          self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
554          self.cl_prop = ClassGroupProperties()          self.cl_prop = ClassGroupProperties()
555    
556    
# Line 300  class SessionLoader(XMLReader): Line 561  class SessionLoader(XMLReader):
561    
562      def start_clrange(self, name, qname, attrs):      def start_clrange(self, name, qname, attrs):
563    
564            range = attrs.get((None, 'range'), None)
565            # for backward compatibility (min/max are not saved)
566            min   = attrs.get((None, 'min'), None)
567            max   = attrs.get((None, 'max'), None)
568    
569          try:          try:
570              min = self.conv(attrs.get((None, 'min'), "0"))              if range is not None:
571              max = self.conv(attrs.get((None, 'max'), "0"))                  self.cl_group = ClassGroupRange(Range(range))
572              #min = Str2Num(attrs.get((None, 'min'), "0"))              elif min is not None and max is not None:
573              #max = Str2Num(attrs.get((None, 'max'), "0"))                  self.cl_group = ClassGroupRange((self.conv(min),
574                                                     self.conv(max)))
575                else:
576                    self.cl_group = ClassGroupRange(Range(None))
577    
578          except ValueError:          except ValueError:
579              raise ValueError(_("Classification range is not a number!"))              raise ValueError(_("Classification range is not a number!"))
580    
         self.cl_group = ClassGroupRange(min, max)  
581          self.cl_group.SetLabel(attrs.get((None, 'label'), ""))          self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
582          self.cl_prop = ClassGroupProperties()          self.cl_prop = ClassGroupProperties()
583    
# Line 323  class SessionLoader(XMLReader): Line 592  class SessionLoader(XMLReader):
592              parse_color(attrs.get((None, 'stroke'), "None")))              parse_color(attrs.get((None, 'stroke'), "None")))
593          self.cl_prop.SetLineWidth(          self.cl_prop.SetLineWidth(
594              int(attrs.get((None, 'stroke_width'), "0")))              int(attrs.get((None, 'stroke_width'), "0")))
595            self.cl_prop.SetSize(int(attrs.get((None, 'size'), "5")))
596          self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))          self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
597    
598      def end_cldata(self, name, qname):      def end_cldata(self, name, qname):
599          pass          pass
600    
     def start_table(self, name, qname, attrs):  
         #print "table title: %s" % attrs.get('title', None)  
         pass  
   
     def end_table(self, name, qname):  
         pass  
   
601      def start_labellayer(self, name, qname, attrs):      def start_labellayer(self, name, qname, attrs):
602          self.aLayer = self.aMap.LabelLayer()          self.aLayer = self.aMap.LabelLayer()
603    
604      def start_label(self, name, qname, attrs):      def start_label(self, name, qname, attrs):
605          x = float(attrs[(None, 'x')])          attrs = self.check_attrs(name, attrs,
606          y = float(attrs[(None, 'y')])                                   [AttrDesc("x", True, conversion = float),
607          text = attrs[(None, 'text')]                                    AttrDesc("y", True, conversion = float),
608          halign = attrs[(None, 'halign')]                                    AttrDesc("text", True),
609          valign = attrs[(None, 'valign')]                                    AttrDesc("halign", True,
610                                               conversion = "ascii"),
611                                      AttrDesc("valign", True,
612                                               conversion = "ascii")])
613            x = attrs['x']
614            y = attrs['y']
615            text = attrs['text']
616            halign = attrs['halign']
617            valign = attrs['valign']
618            if halign not in ("left", "center", "right"):
619                raise LoadError("Unsupported halign value %r" % halign)
620            if valign not in ("top", "center", "bottom"):
621                raise LoadError("Unsupported valign value %r" % valign)
622          self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)          self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
623    
624      def characters(self, chars):      def characters(self, chars):
625          pass          pass
626    
627    
628  def load_session(filename):  def load_session(filename, db_connection_callback = None,
629      """Load a Thuban session from the file object file"""                             shapefile_callback = None):
630        """Load a Thuban session from the file object file
631      handler = SessionLoader()  
632        The db_connection_callback, if given should be a callable object
633        that can be called like this:
634           db_connection_callback(params, message)
635    
636        where params is a dictionary containing the known connection
637        parameters and message is a string with a message why the connection
638        failed. db_connection_callback should return a new dictionary with
639        corrected and perhaps additional parameters like a password or None
640        to indicate that the user cancelled.
641        """
642        handler = SessionLoader(db_connection_callback, shapefile_callback)
643      handler.read(filename)      handler.read(filename)
644    
645      session = handler.theSession      session = handler.theSession
646      # Newly loaded session aren't modified      # Newly loaded session aren't modified
647      session.UnsetModified()      session.UnsetModified()
648    
649        handler.Destroy()
650    
651      return session      return session
652    

Legend:
Removed from v.744  
changed lines
  Added in v.2446

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26