/[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 75 by bh, Mon Feb 4 19:22:22 2002 UTC revision 1282 by bh, Mon Jun 23 09:47:18 2003 UTC
# Line 1  Line 1 
1  # Copyright (C) 2001, 2002 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.session import Session  from Thuban.Model.session import Session
28  from Thuban.Model.map import Map  from Thuban.Model.map import Map
29  from Thuban.Model.layer import Layer  from Thuban.Model.layer import Layer, RasterLayer
30  from Thuban.Model.color import Color  from Thuban.Model.color import Color
31  from Thuban.Model.proj import Projection  from Thuban.Model.proj import Projection
32    from Thuban.Model.range import Range
33    from Thuban.Model.classification import Classification, \
34        ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
35        ClassGroupProperties
36    from Thuban.Model.data import DerivedShapeStore, ShapefileStore
37    from Thuban.Model.table import DBFTable
38    from Thuban.Model.transientdb import TransientJoinedTable
39    
40  oldPython=0  class LoadError(Exception):
41        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  
42    
43    from Thuban.Model.xmlreader import XMLReader
44    import resource
45    
46  def parse_color(color):  def parse_color(color):
47      """      """Return the color object for the string color.
48      Return the color object for the string color. Color may be either  
49      '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
50        HTML color notation
51      """      """
52      color = string.strip(color)      color = string.strip(color)
53      if color == "None":      if color == "None":
54          result = None          result = Color.Transparent
55      elif color[0] == '#':      elif color[0] == '#':
56          if len(color) == 7:          if len(color) == 7:
57              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 59  def parse_color(color):
59              b = string.atoi(color[5:7], 16) / 255.0              b = string.atoi(color[5:7], 16) / 255.0
60              result = Color(r, g, b)              result = Color(r, g, b)
61          else:          else:
62              raise ValueError("Invalid hexadecimal color specification %s"              raise ValueError(_("Invalid hexadecimal color specification %s")
63                               % color)                               % color)
64      else:      else:
65          raise ValueError("Invalid color specification %s" % color)          raise ValueError(_("Invalid color specification %s") % color)
66      return result      return result
67    
68    class AttrDesc:
69    
70  class ProcessSession(HandlerBase):      def __init__(self, name, required = False, default = "",
71                     conversion = None):
72      def __init__(self, directory):          if not isinstance(name, tuple):
73          """Inititialize the Sax handler.              fullname = (None, name)
74            else:
75                fullname = name
76                name = name[1]
77            self.name = name
78            self.fullname = fullname
79            self.required = required
80            self.default = default
81            self.conversion = conversion
82    
83            # set by the SessionLoader's check_attrs method
84            self.value = None
85    
86    
87    class SessionLoader(XMLReader):
88    
89        def __init__(self):
90            """Inititialize the Sax handler."""
91            XMLReader.__init__(self)
92    
         directory is the directory containing the session file. It's  
         needed to interpret embedded relative filenames  
         """  
         self.directory = directory  
         self.chars = ''  
93          self.theSession = None          self.theSession = None
94          self.aMap = None          self.aMap = None
95          self.aLayer = None          self.aLayer = None
96    
97      def startElement(self, name, attrs):          # Map ids used in the thuban file to the corresponding objects
98          if name == 'session':          # in the session
99              self.theSession = Session(attrs.get('title', None))          self.idmap = {}
100          elif name == 'map':  
101              self.aMap = Map(attrs.get('title', None))          dispatchers = {
102          elif name == 'projection':              'session'       : ("start_session",        "end_session"),
103              self.ProjectionParams = [ ]              'fileshapesource': ("start_fileshapesource", None),
104          elif name == 'parameter':              'derivedshapesource': ("start_derivedshapesource", None),
105              self.ProjectionParams.append(attrs.get('value', None))              'filetable': ("start_filetable", None),
106          elif name == 'layer':              'jointable': ("start_jointable", None),
107              title = attrs.get('title', "")  
108              filename = attrs.get('filename', "")              'map'           : ("start_map",            "end_map"),
109              filename = os.path.join(self.directory, filename)              'projection'    : ("start_projection",     "end_projection"),
110              fill = parse_color(attrs.get('fill', "None"))              'parameter'     : ("start_parameter",      None),
111              stroke = parse_color(attrs.get('stroke', "#000000"))              'layer'         : ("start_layer",          "end_layer"),
112              stroke_width = int(attrs.get("stroke_width", "1"))              'rasterlayer'   : ("start_rasterlayer",    "end_rasterlayer"),
113              self.aLayer = Layer(title, filename, fill = fill, stroke = stroke,              'classification': ("start_classification", "end_classification"),
114                                  stroke_width = stroke_width)              'clnull'        : ("start_clnull",         "end_clnull"),
115          elif name == 'table':              'clpoint'       : ("start_clpoint",        "end_clpoint"),
116              print "table title: %s" % attrs.get('title', None)              'clrange'       : ("start_clrange",        "end_clrange"),
117          elif name == 'labellayer':              'cldata'        : ("start_cldata",         "end_cldata"),
118              self.aLayer = self.aMap.LabelLayer()              'table'         : ("start_table",          "end_table"),
119          elif name == 'label':              'labellayer'    : ("start_labellayer",     None),
120              x = float(attrs['x'])              'label'         : ("start_label",          None)}
121              y = float(attrs['y'])  
122              text = attrs['text']          # all dispatchers should be used for the 0.8 namespace
123              halign = attrs['halign']          xmlns = "http://thuban.intevation.org/dtds/thuban-0.8.dtd"
124              valign = attrs['valign']          for key, value in dispatchers.items():
125              self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)              dispatchers[(xmlns, key)] = value
126            
127            XMLReader.AddDispatchers(self, dispatchers)
128      if not oldPython and test_for_broken_SAX():  
129          # works with python 2.0, but is not SAX compliant      def start_session(self, name, qname, attrs):
130          def characters(self, ch):          self.theSession = Session(self.encode(attrs.get((None, 'title'),
131              self.my_characters(ch)                                                          None)))
132      else:  
133          # SAX compliant      def end_session(self, name, qname):
134          def characters(self, ch, start, length):          pass
135              self.my_characters(ch[start:start+length])  
136        def check_attrs(self, element, attrs, descr):
137      def my_characters(self, ch):          """Check and convert some of the attributes of an element
138          self.chars = self.chars + ch  
139            Parameters:
140      def endElement(self, name):             element -- The element name
141          # If it's not a parameter element, ignore it             attrs -- The attrs mapping as passed to the start_* methods
142          if name == 'session':             descr -- Sequence of attribute descriptions (AttrDesc instances)
143              #print "end of session"  
144              pass          Return a dictionary containig normalized versions of the
145          if name == 'map':          attributes described in descr. The keys of that dictionary are
146              self.theSession.AddMap(self.aMap)          the name attributes of the attribute descriptions. The attrs
147          if name == 'projection':          dictionary will not be modified.
148              self.aMap.SetProjection(Projection(self.ProjectionParams))  
149          if name == 'layer':          If the attribute is required, i.e. the 'required' attribute of
150              self.aMap.AddLayer(self.aLayer)          the descrtiption is true, but it is not in attrs, raise a
151          if name == 'table':          LoadError.
152              #print "end of table"  
153            If the attribute has a default value and it is not present in
154            attrs, use that default value as the value in the returned dict.
155    
156            If a conversion is specified, convert the value before putting
157            it into the returned dict. The following conversions are
158            available:
159    
160               'filename' -- The attribute is a filename.
161    
162                             If the filename is a relative name, interpret
163                             it relative to the directory containing the
164                             .thuban file and make it an absolute name
165    
166               'shapestore' -- The attribute is the ID of a shapestore
167                               defined earlier in the .thuban file. Look it
168                               up self.idmap
169    
170               'table' -- The attribute is the ID of a table or shapestore
171                          defined earlier in the .thuban file. Look it up
172                          self.idmap. If it's the ID of a shapestore the
173                          value will be the table of the shapestore.
174            """
175            normalized = {}
176    
177            for d in descr:
178                if d.required and not attrs.has_key(d.fullname):
179                    pass
180                #raise LoadError("Element %s requires an attribute %r"
181                #                    % (element, d.name))
182                value = attrs.get(d.fullname, d.default)
183    
184                if d.conversion == "shapesource":
185                    if value in self.idmap:
186                        value = self.idmap[value]
187                    else:
188                        raise LoadError("Element %s requires an already defined ID"
189                                        " in attribute %r"
190                                        % (element, d.name))
191                elif d.conversion == "table":
192                    if value in self.idmap:
193                        value = self.idmap[value]
194                        if isinstance(value, ShapefileStore):
195                            value = value.Table()
196                    else:
197                        raise LoadError("Element %s requires an already defined ID"
198                                        " in attribute %r"
199                                        % (element, d.name))
200                elif d.conversion == "filename":
201                    value = os.path.abspath(os.path.join(self.GetDirectory(),
202                                                         value))
203    
204                normalized[d.name] = value
205            return normalized
206    
207        def start_fileshapesource(self, name, qname, attrs):
208            attrs = self.check_attrs(name, attrs,
209                                      [AttrDesc("id", True),
210                                       AttrDesc("filename", True,
211                                                conversion = "filename"),
212                                       AttrDesc("filetype", True)])
213            ID = attrs["id"]
214            filename = attrs["filename"]
215            filetype = attrs["filetype"]
216            if filetype != "shapefile":
217                raise LoadError("shapesource filetype %r not supported" % filetype)
218            self.idmap[ID] = self.theSession.OpenShapefile(filename)
219    
220        def start_derivedshapesource(self, name, qname, attrs):
221            attrs = self.check_attrs(name, attrs,
222                                     [AttrDesc("id", True),
223                                      AttrDesc("shapesource", True,
224                                               conversion = "shapesource"),
225                                      AttrDesc("table", True, conversion="table")])
226            store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
227            self.theSession.AddShapeStore(store)
228            self.idmap[attrs["id"]] = store
229    
230        def start_filetable(self, name, qname, attrs):
231            attrs = self.check_attrs(name, attrs,
232                                     [AttrDesc("id", True),
233                                      AttrDesc("title", True),
234                                      AttrDesc("filename", True,
235                                               conversion = "filename"),
236                                      AttrDesc("filetype")])
237            filetype = attrs["filetype"]
238            if filetype != "DBF":
239                raise LoadError("shapesource filetype %r not supported" % filetype)
240            table = DBFTable(attrs["filename"])
241            table.SetTitle(attrs["title"])
242            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
243    
244        def start_jointable(self, name, qname, attrs):
245            attrs = self.check_attrs(name, attrs,
246                                     [AttrDesc("id", True),
247                                      AttrDesc("title", True),
248                                      AttrDesc("left", True, conversion="table"),
249                                      AttrDesc("leftcolumn", True),
250                                      AttrDesc("right", True, conversion="table"),
251                                      AttrDesc("rightcolumn")])
252            table = TransientJoinedTable(self.theSession.TransientDB(),
253                                         attrs["left"], attrs["leftcolumn"],
254                                         attrs["right"], attrs["rightcolumn"])
255            table.SetTitle(attrs["title"])
256            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
257    
258        def start_map(self, name, qname, attrs):
259            """Start a map."""
260            self.aMap = Map(attrs.get((None, 'title'), None))
261    
262        def end_map(self, name, qname):
263            self.theSession.AddMap(self.aMap)
264            self.aMap = None
265    
266        def start_projection(self, name, qname, attrs):
267            self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
268            self.ProjectionParams = [ ]
269    
270        def end_projection(self, name, qname):
271            if self.aLayer is not None:
272                obj = self.aLayer
273            elif self.aMap is not None:
274                obj = self.aMap
275            else:
276                assert False, "projection tag out of context"
277              pass              pass
278    
279            obj.SetProjection(
280                Projection(self.ProjectionParams, self.ProjectionName))
281    
282        def start_parameter(self, name, qname, attrs):
283            s = attrs.get((None, 'value'))
284            s = str(s) # we can't handle unicode in proj
285            self.ProjectionParams.append(s)
286    
287        def start_layer(self, name, qname, attrs, layer_class = Layer):
288            """Start a layer
289    
290            Instantiate a layer of class layer_class from the attributes in
291            attrs which may be a dictionary as well as the normal SAX attrs
292            object and bind it to self.aLayer.
293            """
294            title = self.encode(attrs.get((None, 'title'), ""))
295            filename = attrs.get((None, 'filename'), "")
296            filename = os.path.join(self.GetDirectory(), filename)
297            filename = self.encode(filename)
298            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
299            fill = parse_color(attrs.get((None, 'fill'), "None"))
300            stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
301            stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
302            if attrs.has_key((None, "shapestore")):
303                store = self.idmap[attrs[(None, "shapestore")]]
304            else:
305                store = self.theSession.OpenShapefile(filename)
306            self.aLayer = layer_class(title, store,
307                                      fill = fill, stroke = stroke,
308                                      lineWidth = stroke_width,
309                                      visible = visible)
310    
311        def end_layer(self, name, qname):
312            self.aMap.AddLayer(self.aLayer)
313            self.aLayer = None
314    
315        def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
316            title = self.encode(attrs.get((None, 'title'), ""))
317            filename = attrs.get((None, 'filename'), "")
318            filename = os.path.join(self.GetDirectory(), filename)
319            filename = self.encode(filename)
320            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
321    
322            self.aLayer = layer_class(title, filename, visible = visible)
323    
324        def end_rasterlayer(self, name, qname):
325            self.aMap.AddLayer(self.aLayer)
326            self.aLayer = None
327    
328        def start_classification(self, name, qname, attrs):
329            field = attrs.get((None, 'field'), None)
330    
331            fieldType = attrs.get((None, 'field_type'), None)
332            dbFieldType = self.aLayer.GetFieldType(field)
333    
334            if fieldType != dbFieldType:
335                raise ValueError(_("xml field type differs from database!"))
336    
337            # setup conversion routines depending on the kind of data
338            # we will be seeing later on
339            if fieldType == FIELDTYPE_STRING:
340                self.conv = str
341            elif fieldType == FIELDTYPE_INT:
342                self.conv = lambda p: int(float(p))
343            elif fieldType == FIELDTYPE_DOUBLE:
344                self.conv = float
345    
346            self.aLayer.GetClassification().SetField(field)
347    
348        def end_classification(self, name, qname):
349            pass
350    
351        def start_clnull(self, name, qname, attrs):
352            self.cl_group = ClassGroupDefault()
353            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
354            self.cl_prop = ClassGroupProperties()
355    
356        def end_clnull(self, name, qname):
357            self.cl_group.SetProperties(self.cl_prop)
358            self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
359            del self.cl_group, self.cl_prop
360    
361        def start_clpoint(self, name, qname, attrs):
362            attrib_value = attrs.get((None, 'value'), "0")
363    
364            value = self.conv(attrib_value)
365    
366            self.cl_group = ClassGroupSingleton(value)
367            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
368            self.cl_prop = ClassGroupProperties()
369    
370    
371        def end_clpoint(self, name, qname):
372            self.cl_group.SetProperties(self.cl_prop)
373            self.aLayer.GetClassification().AppendGroup(self.cl_group)
374            del self.cl_group, self.cl_prop
375    
376        def start_clrange(self, name, qname, attrs):
377    
378            range = attrs.get((None, 'range'), None)
379            # for backward compatibility (min/max are not saved)
380            min   = attrs.get((None, 'min'), None)
381            max   = attrs.get((None, 'max'), None)
382    
383            try:
384                if range is not None:
385                    self.cl_group = ClassGroupRange(Range(range))
386                elif min is not None and max is not None:
387                    self.cl_group = ClassGroupRange(self.conv(min), self.conv(max))
388                else:
389                    self.cl_group = ClassGroupRange(Range(None))
390    
391            except ValueError:
392                raise ValueError(_("Classification range is not a number!"))
393    
394            self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
395            self.cl_prop = ClassGroupProperties()
396    
397    
398        def end_clrange(self, name, qname):
399            self.cl_group.SetProperties(self.cl_prop)
400            self.aLayer.GetClassification().AppendGroup(self.cl_group)
401            del self.cl_group, self.cl_prop
402    
403        def start_cldata(self, name, qname, attrs):
404            self.cl_prop.SetLineColor(
405                parse_color(attrs.get((None, 'stroke'), "None")))
406            self.cl_prop.SetLineWidth(
407                int(attrs.get((None, 'stroke_width'), "0")))
408            self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
409    
410        def end_cldata(self, name, qname):
411            pass
412    
413        def start_labellayer(self, name, qname, attrs):
414            self.aLayer = self.aMap.LabelLayer()
415    
416        def start_label(self, name, qname, attrs):
417            x = float(attrs[(None, 'x')])
418            y = float(attrs[(None, 'y')])
419            text = self.encode(attrs[(None, 'text')])
420            halign = attrs[(None, 'halign')]
421            valign = attrs[(None, 'valign')]
422            self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
423    
424        def characters(self, chars):
425            pass
426    
427    
428  def load_session(filename):  def load_session(filename):
429      """Load a Thuban session from the file object file"""      """Load a Thuban session from the file object file"""
430      dir = os.path.dirname(filename)  
431      file = open(filename)      handler = SessionLoader()
432      handler = ProcessSession(dir)      handler.read(filename)
433    
     if oldPython:  
         parser = make_parser()  
         parser.setDocumentHandler(handler)  
         parser.setErrorHandler(saxutils.ErrorPrinter())  
         parser.parseFile(file)  
         parser.close()  
     else:  
         xml.sax.parse(file,handler)  
434      session = handler.theSession      session = handler.theSession
435      # Newly loaded session aren't modified      # Newly loaded session aren't modified
436      session.UnsetModified()      session.UnsetModified()
437    
438      return session      return session
439    
 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.75  
changed lines
  Added in v.1282

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26