/[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 6 by bh, Tue Aug 28 15:41:52 2001 UTC revision 1354 by jonathan, Wed Jul 2 09:37:07 2003 UTC
# Line 1  Line 1 
1  # Copyright (C) 2001 by Intevation GmbH  # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Jan-Oliver Wagner <[email protected]>  # Jan-Oliver Wagner <[email protected]>
4    # Bernhard Herzog <[email protected]>
5    # Jonathan Coles <[email protected]>
6  #  #
7  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
8  # Read the file COPYING coming with GRASS for details.  # Read the file COPYING coming with GRASS for details.
# Line 11  Parser for thuban session files. Line 13  Parser for thuban session files.
13    
14  __version__ = "$Revision$"  __version__ = "$Revision$"
15    
16  import sys, string, os  import string, os
17    
18    import xml.sax
19    import xml.sax.handler
20    from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
21    
22    from Thuban import _
23    
24    from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
25         FIELDTYPE_STRING
26    
27    from Thuban.Model.color import Color, Transparent
28    
29  from Thuban.Model.session import Session  from Thuban.Model.session import Session
30  from Thuban.Model.map import Map  from Thuban.Model.map import Map
31  from Thuban.Model.layer import Layer  from Thuban.Model.layer import Layer, RasterLayer
 from Thuban.Model.color import Color  
32  from Thuban.Model.proj import Projection  from Thuban.Model.proj import Projection
33    from Thuban.Model.range import Range
34    from Thuban.Model.classification import Classification, \
35        ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
36        ClassGroupProperties
37    from Thuban.Model.data import DerivedShapeStore, ShapefileStore
38    from Thuban.Model.table import DBFTable
39    from Thuban.Model.transientdb import TransientJoinedTable
40    
41  oldPython=0  class LoadError(Exception):
42        pass
 if not sys.__dict__.has_key("version_info"):  
     # We can assume to have python 1.5.2 or lower here now  
     oldPython=1  
   
 if oldPython:  
     try:  
         from xml.sax.saxexts import make_parser  
         from xml.sax.saxlib import HandlerBase  
         from xml.sax import saxutils  
     except ImportError:  
         sys.stdout.write(("You need to have Python-XML installed or"  
                           " a modern Python!\n"  
                           "Check www.python.org/sigs/xml-sig/\n\n"))  
         raise  
 else:  
     # Do the python 2.0 standard xml thing and map it on the old names  
     import xml.sax  
     import xml.sax.handler  
     HandlerBase=xml.sax.handler.ContentHandler  
     from xml.sax import make_parser  
   
 class testSAXContentHandler(HandlerBase):  
 # SAX compliant  
     def characters(self, ch, start, length):  
         pass  
       
 def test_for_broken_SAX():  
     ch=testSAXContentHandler()  
     try:  
         xml.sax.parseString("""<?xml version="1.0"?>  
             <child1 name="paul">Text goes here</child1>  
         """,ch)  
     except TypeError:  
         return 1  
     return 0  
43    
44    from Thuban.Model.xmlreader import XMLReader
45    import resource
46    
47  def parse_color(color):  def parse_color(color):
48      """      """Return the color object for the string color.
49      Return the color object for the string color. Color may be either  
50      'None' or of the form '#RRGGBB' in the usual HTML color notation      Color may be either 'None' or of the form '#RRGGBB' in the usual
51        HTML color notation
52      """      """
53      color = string.strip(color)      color = string.strip(color)
54      if color == "None":      if color == "None":
55          result = None          result = Transparent
56      elif color[0] == '#':      elif color[0] == '#':
57          if len(color) == 7:          if len(color) == 7:
58              r = string.atoi(color[1:3], 16) / 255.0              r = string.atoi(color[1:3], 16) / 255.0
# Line 72  def parse_color(color): Line 60  def parse_color(color):
60              b = string.atoi(color[5:7], 16) / 255.0              b = string.atoi(color[5:7], 16) / 255.0
61              result = Color(r, g, b)              result = Color(r, g, b)
62          else:          else:
63              raise ValueError("Invalid hexadecimal color specification %s"              raise ValueError(_("Invalid hexadecimal color specification %s")
64                               % color)                               % color)
65      else:      else:
66          raise ValueError("Invalid color specification %s" % color)          raise ValueError(_("Invalid color specification %s") % color)
67      return result      return result
68    
69    class AttrDesc:
70    
71  class ProcessSession(HandlerBase):      def __init__(self, name, required = False, default = "",
72                     conversion = None):
73      def __init__(self, directory):          if not isinstance(name, tuple):
74          """Inititialize the Sax handler.              fullname = (None, name)
75            else:
76                fullname = name
77                name = name[1]
78            self.name = name
79            self.fullname = fullname
80            self.required = required
81            self.default = default
82            self.conversion = conversion
83    
84            # set by the SessionLoader's check_attrs method
85            self.value = None
86    
87    
88    class SessionLoader(XMLReader):
89    
90        def __init__(self):
91            """Inititialize the Sax handler."""
92            XMLReader.__init__(self)
93    
         directory is the directory containing the session file. It's  
         needed to interpret embedded relative filenames  
         """  
         self.directory = directory  
         self.chars = ''  
94          self.theSession = None          self.theSession = None
95          self.aMap = None          self.aMap = None
96          self.aLayer = None          self.aLayer = None
97    
98      def startElement(self, name, attrs):          # Map ids used in the thuban file to the corresponding objects
99          if name == 'session':          # in the session
100              self.theSession = Session(attrs.get('title', None))          self.idmap = {}
101          elif name == 'map':  
102              self.aMap = Map(attrs.get('title', None))          dispatchers = {
103          elif name == 'projection':              'session'       : ("start_session",        "end_session"),
104              self.ProjectionParams = [ ]              'fileshapesource': ("start_fileshapesource", None),
105          elif name == 'parameter':              'derivedshapesource': ("start_derivedshapesource", None),
106              self.ProjectionParams.append(attrs.get('value', None))              'filetable': ("start_filetable", None),
107          elif name == 'layer':              'jointable': ("start_jointable", None),
108              title = attrs.get('title', "")  
109              filename = attrs.get('filename', "")              'map'           : ("start_map",            "end_map"),
110              filename = os.path.join(self.directory, filename)              'projection'    : ("start_projection",     "end_projection"),
111              fill = parse_color(attrs.get('fill', "None"))              'parameter'     : ("start_parameter",      None),
112              stroke = parse_color(attrs.get('stroke', "#000000"))              'layer'         : ("start_layer",          "end_layer"),
113              self.aLayer = Layer(title, filename, fill = fill, stroke = stroke)              'rasterlayer'   : ("start_rasterlayer",    "end_rasterlayer"),
114          elif name == 'table':              'classification': ("start_classification", "end_classification"),
115              print "table title: %s" % attrs.get('title', None)              'clnull'        : ("start_clnull",         "end_clnull"),
116          elif name == 'labellayer':              'clpoint'       : ("start_clpoint",        "end_clpoint"),
117              self.aLayer = self.aMap.LabelLayer()              'clrange'       : ("start_clrange",        "end_clrange"),
118          elif name == 'label':              'cldata'        : ("start_cldata",         "end_cldata"),
119              x = float(attrs['x'])              'table'         : ("start_table",          "end_table"),
120              y = float(attrs['y'])              'labellayer'    : ("start_labellayer",     None),
121              text = attrs['text']              'label'         : ("start_label",          None)}
122              halign = attrs['halign']  
123              valign = attrs['valign']          # all dispatchers should be used for the 0.8 namespace
124              self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)          xmlns = "http://thuban.intevation.org/dtds/thuban-0.8.dtd"
125                    for key, value in dispatchers.items():
126                dispatchers[(xmlns, key)] = value
127      if not oldPython and test_for_broken_SAX():  
128          # works with python 2.0, but is not SAX compliant          XMLReader.AddDispatchers(self, dispatchers)
129          def characters(self, ch):  
130              self.my_characters(ch)      def start_session(self, name, qname, attrs):
131      else:          self.theSession = Session(self.encode(attrs.get((None, 'title'),
132          # SAX compliant                                                          None)))
133          def characters(self, ch, start, length):  
134              self.my_characters(ch[start:start+length])      def end_session(self, name, qname):
135            pass
136      def my_characters(self, ch):  
137          self.chars = self.chars + ch      def check_attrs(self, element, attrs, descr):
138            """Check and convert some of the attributes of an element
139      def endElement(self, name):  
140          # If it's not a parameter element, ignore it          Parameters:
141          if name == 'session':             element -- The element name
142              #print "end of session"             attrs -- The attrs mapping as passed to the start_* methods
143              pass             descr -- Sequence of attribute descriptions (AttrDesc instances)
144          if name == 'map':  
145              self.theSession.AddMap(self.aMap)          Return a dictionary containig normalized versions of the
146          if name == 'projection':          attributes described in descr. The keys of that dictionary are
147              self.aMap.SetProjection(Projection(self.ProjectionParams))          the name attributes of the attribute descriptions. The attrs
148          if name == 'layer':          dictionary will not be modified.
149              self.aMap.AddLayer(self.aLayer)  
150          if name == 'table':          If the attribute is required, i.e. the 'required' attribute of
151              #print "end of table"          the descrtiption is true, but it is not in attrs, raise a
152            LoadError.
153    
154            If the attribute has a default value and it is not present in
155            attrs, use that default value as the value in the returned dict.
156    
157            If a conversion is specified, convert the value before putting
158            it into the returned dict. The following conversions are
159            available:
160    
161               'filename' -- The attribute is a filename.
162    
163                             If the filename is a relative name, interpret
164                             it relative to the directory containing the
165                             .thuban file and make it an absolute name
166    
167               'shapestore' -- The attribute is the ID of a shapestore
168                               defined earlier in the .thuban file. Look it
169                               up self.idmap
170    
171               'table' -- The attribute is the ID of a table or shapestore
172                          defined earlier in the .thuban file. Look it up
173                          self.idmap. If it's the ID of a shapestore the
174                          value will be the table of the shapestore.
175            """
176            normalized = {}
177    
178            for d in descr:
179                if d.required and not attrs.has_key(d.fullname):
180                    pass
181                #raise LoadError("Element %s requires an attribute %r"
182                #                    % (element, d.name))
183                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            store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
228            self.theSession.AddShapeStore(store)
229            self.idmap[attrs["id"]] = store
230    
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                                      AttrDesc("rightcolumn")])
253            table = TransientJoinedTable(self.theSession.TransientDB(),
254                                         attrs["left"], attrs["leftcolumn"],
255                                         attrs["right"], attrs["rightcolumn"])
256            table.SetTitle(attrs["title"])
257            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
258    
259        def start_map(self, name, qname, attrs):
260            """Start a map."""
261            self.aMap = Map(attrs.get((None, 'title'), None))
262    
263        def end_map(self, name, qname):
264            self.theSession.AddMap(self.aMap)
265            self.aMap = None
266    
267        def start_projection(self, name, qname, attrs):
268            self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
269            self.ProjectionParams = [ ]
270    
271        def end_projection(self, name, qname):
272            if self.aLayer is not None:
273                obj = self.aLayer
274            elif self.aMap is not None:
275                obj = self.aMap
276            else:
277                assert False, "projection tag out of context"
278              pass              pass
279    
280            obj.SetProjection(
281                Projection(self.ProjectionParams, self.ProjectionName))
282    
283        def start_parameter(self, name, qname, attrs):
284            s = attrs.get((None, 'value'))
285            s = str(s) # we can't handle unicode in proj
286            self.ProjectionParams.append(s)
287    
288        def start_layer(self, name, qname, attrs, layer_class = Layer):
289            """Start a layer
290    
291            Instantiate a layer of class layer_class from the attributes in
292            attrs which may be a dictionary as well as the normal SAX attrs
293            object and bind it to self.aLayer.
294            """
295            title = self.encode(attrs.get((None, 'title'), ""))
296            filename = attrs.get((None, 'filename'), "")
297            filename = os.path.join(self.GetDirectory(), filename)
298            filename = self.encode(filename)
299            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
300            fill = parse_color(attrs.get((None, 'fill'), "None"))
301            stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
302            stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
303            if attrs.has_key((None, "shapestore")):
304                store = self.idmap[attrs[(None, "shapestore")]]
305            else:
306                store = self.theSession.OpenShapefile(filename)
307            self.aLayer = layer_class(title, store,
308                                      fill = fill, stroke = stroke,
309                                      lineWidth = stroke_width,
310                                      visible = visible)
311    
312        def end_layer(self, name, qname):
313            self.aMap.AddLayer(self.aLayer)
314            self.aLayer = None
315    
316        def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
317            title = self.encode(attrs.get((None, 'title'), ""))
318            filename = attrs.get((None, 'filename'), "")
319            filename = os.path.join(self.GetDirectory(), filename)
320            filename = self.encode(filename)
321            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
322    
323            self.aLayer = layer_class(title, filename, visible = visible)
324    
325        def end_rasterlayer(self, name, qname):
326            self.aMap.AddLayer(self.aLayer)
327            self.aLayer = None
328    
329        def start_classification(self, name, qname, attrs):
330            field = attrs.get((None, 'field'), None)
331    
332            fieldType = attrs.get((None, 'field_type'), None)
333            dbFieldType = self.aLayer.GetFieldType(field)
334    
335            if fieldType != dbFieldType:
336                raise ValueError(_("xml field type differs from database!"))
337    
338            # setup conversion routines depending on the kind of data
339            # we will be seeing later on
340            if fieldType == FIELDTYPE_STRING:
341                self.conv = str
342            elif fieldType == FIELDTYPE_INT:
343                self.conv = lambda p: int(float(p))
344            elif fieldType == FIELDTYPE_DOUBLE:
345                self.conv = float
346    
347            self.aLayer.GetClassification().SetFieldInfo(field, fieldType)
348    
349        def end_classification(self, name, qname):
350            pass
351    
352        def start_clnull(self, name, qname, attrs):
353            self.cl_group = ClassGroupDefault()
354            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
355            self.cl_prop = ClassGroupProperties()
356    
357        def end_clnull(self, name, qname):
358            self.cl_group.SetProperties(self.cl_prop)
359            self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
360            del self.cl_group, self.cl_prop
361    
362        def start_clpoint(self, name, qname, attrs):
363            attrib_value = attrs.get((None, 'value'), "0")
364    
365            value = self.conv(attrib_value)
366    
367            self.cl_group = ClassGroupSingleton(value)
368            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
369            self.cl_prop = ClassGroupProperties()
370    
371    
372        def end_clpoint(self, name, qname):
373            self.cl_group.SetProperties(self.cl_prop)
374            self.aLayer.GetClassification().AppendGroup(self.cl_group)
375            del self.cl_group, self.cl_prop
376    
377        def start_clrange(self, name, qname, attrs):
378    
379            range = attrs.get((None, 'range'), None)
380            # for backward compatibility (min/max are not saved)
381            min   = attrs.get((None, 'min'), None)
382            max   = attrs.get((None, 'max'), None)
383    
384            try:
385                if range is not None:
386                    self.cl_group = ClassGroupRange(Range(range))
387                elif min is not None and max is not None:
388                    self.cl_group = ClassGroupRange((self.conv(min),
389                                                     self.conv(max)))
390                else:
391                    self.cl_group = ClassGroupRange(Range(None))
392    
393            except ValueError:
394                raise ValueError(_("Classification range is not a number!"))
395    
396            self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
397            self.cl_prop = ClassGroupProperties()
398    
399    
400        def end_clrange(self, name, qname):
401            self.cl_group.SetProperties(self.cl_prop)
402            self.aLayer.GetClassification().AppendGroup(self.cl_group)
403            del self.cl_group, self.cl_prop
404    
405        def start_cldata(self, name, qname, attrs):
406            self.cl_prop.SetLineColor(
407                parse_color(attrs.get((None, 'stroke'), "None")))
408            self.cl_prop.SetLineWidth(
409                int(attrs.get((None, 'stroke_width'), "0")))
410            self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
411    
412        def end_cldata(self, name, qname):
413            pass
414    
415        def start_labellayer(self, name, qname, attrs):
416            self.aLayer = self.aMap.LabelLayer()
417    
418        def start_label(self, name, qname, attrs):
419            x = float(attrs[(None, 'x')])
420            y = float(attrs[(None, 'y')])
421            text = self.encode(attrs[(None, 'text')])
422            halign = attrs[(None, 'halign')]
423            valign = attrs[(None, 'valign')]
424            self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
425    
426        def characters(self, chars):
427            pass
428    
429    
430  def load_session(filename):  def load_session(filename):
431      """Load a Thuban session from the file object file"""      """Load a Thuban session from the file object file"""
432      dir = os.path.dirname(filename)  
433      file = open(filename)      handler = SessionLoader()
434      handler = ProcessSession(dir)      handler.read(filename)
435    
     if oldPython:  
         parser = make_parser()  
         parser.setDocumentHandler(handler)  
         parser.setErrorHandler(saxutils.ErrorPrinter())  
         parser.parseFile(file)  
         parser.close()  
     else:  
         xml.sax.parse(file,handler)  
436      session = handler.theSession      session = handler.theSession
437      # Newly loaded session aren't modified      # Newly loaded session aren't modified
438      session.UnsetModified()      session.UnsetModified()
439    
440      return session      return session
441    
 if __name__ == "__main__":  
     # find out the command to run  
     if len(sys.argv) > 1:  
         print "usage: cat <file> | " + sys.argv[0]  
     else:  
         parseSession(sys.stdin)  

Legend:
Removed from v.6  
changed lines
  Added in v.1354

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26