/[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 105 by jan, Fri Apr 19 15:36:57 2002 UTC revision 1268 by bh, Fri Jun 20 16:10:12 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              s = attrs.get('value')              'filetable': ("start_filetable", None),
106              s = str(s) # we can't handle unicode in proj              'jointable': ("start_jointable", None),
107              self.ProjectionParams.append(s)  
108          elif name == 'layer':              'map'           : ("start_map",            "end_map"),
109              title = attrs.get('title', "")              'projection'    : ("start_projection",     "end_projection"),
110              filename = attrs.get('filename', "")              'parameter'     : ("start_parameter",      None),
111              filename = os.path.join(self.directory, filename)              'layer'         : ("start_layer",          "end_layer"),
112              fill = parse_color(attrs.get('fill', "None"))              'rasterlayer'   : ("start_rasterlayer",    "end_rasterlayer"),
113              stroke = parse_color(attrs.get('stroke', "#000000"))              'classification': ("start_classification", "end_classification"),
114              stroke_width = int(attrs.get("stroke_width", "1"))              'clnull'        : ("start_clnull",         "end_clnull"),
115              self.aLayer = Layer(title, filename, fill = fill, stroke = stroke,              'clpoint'       : ("start_clpoint",        "end_clpoint"),
116                                  stroke_width = stroke_width)              'clrange'       : ("start_clrange",        "end_clrange"),
117          elif name == 'table':              'cldata'        : ("start_cldata",         "end_cldata"),
118              print "table title: %s" % attrs.get('title', None)              'table'         : ("start_table",          "end_table"),
119          elif name == 'labellayer':              'labellayer'    : ("start_labellayer",     None),
120              self.aLayer = self.aMap.LabelLayer()              'label'         : ("start_label",          None)}
121          elif name == 'label':  
122              x = float(attrs['x'])          # all dispatchers should be used for the 0.8 namespace
123              y = float(attrs['y'])          xmlns = "http://thuban.intevation.org/dtds/thuban-0.8.dtd"
124              text = attrs['text']          for key, value in dispatchers.items():
125              halign = attrs['halign']              dispatchers[(xmlns, key)] = value
126              valign = attrs['valign']  
127              self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)          XMLReader.AddDispatchers(self, dispatchers)
128            
129        def start_session(self, name, qname, attrs):
130      if not oldPython and test_for_broken_SAX():          self.theSession = Session(self.encode(attrs.get((None, 'title'),
131          # works with python 2.0, but is not SAX compliant                                                          None)))
132          def characters(self, ch):  
133              self.my_characters(ch)      def end_session(self, name, qname):
134      else:          pass
135          # SAX compliant  
136          def characters(self, ch, start, length):      def check_attrs(self, element, attrs, descr):
137              self.my_characters(ch[start:start+length])          """Check and convert some of the attributes of an element
138    
139      def my_characters(self, ch):          Parameters:
140          self.chars = self.chars + ch             element -- The element name
141               attrs -- The attrs mapping as passed to the start_* methods
142      def endElement(self, name):             descr -- Sequence of attribute descriptions (AttrDesc instances)
143          # If it's not a parameter element, ignore it  
144          if name == 'session':          Return a dictionary containig normalized versions of the
145              #print "end of session"          attributes described in descr. The keys of that dictionary are
146              pass          the name attributes of the attribute descriptions. The attrs
147          if name == 'map':          dictionary will not be modified.
148              self.theSession.AddMap(self.aMap)  
149          if name == 'projection':          If the attribute is required, i.e. the 'required' attribute of
150              self.aMap.SetProjection(Projection(self.ProjectionParams))          the descrtiption is true, but it is not in attrs, raise a
151          if name == 'layer':          LoadError.
152              self.aMap.AddLayer(self.aLayer)  
153          if name == 'table':          If the attribute has a default value and it is not present in
154              #print "end of table"          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            self.idmap[attrs["id"]] = DerivedShapeStore(attrs["shapesource"],
227                                                        attrs["table"])
228    
229        def start_filetable(self, name, qname, attrs):
230            attrs = self.check_attrs(name, attrs,
231                                     [AttrDesc("id", True),
232                                      AttrDesc("title", True),
233                                      AttrDesc("filename", True,
234                                               conversion = "filename"),
235                                      AttrDesc("filetype")])
236            filetype = attrs["filetype"]
237            if filetype != "DBF":
238                raise LoadError("shapesource filetype %r not supported" % filetype)
239            table = DBFTable(attrs["filename"])
240            table.SetTitle(attrs["title"])
241            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
242    
243        def start_jointable(self, name, qname, attrs):
244            attrs = self.check_attrs(name, attrs,
245                                     [AttrDesc("id", True),
246                                      AttrDesc("title", True),
247                                      AttrDesc("left", True, conversion="table"),
248                                      AttrDesc("leftcolumn", True),
249                                      AttrDesc("right", True, conversion="table"),
250                                      AttrDesc("rightcolumn")])
251            table = TransientJoinedTable(self.theSession.TransientDB(),
252                                         attrs["left"], attrs["leftcolumn"],
253                                         attrs["right"], attrs["rightcolumn"])
254            table.SetTitle(attrs["title"])
255            self.idmap[attrs["id"]] = self.theSession.AddTable(table)
256    
257        def start_map(self, name, qname, attrs):
258            """Start a map."""
259            self.aMap = Map(attrs.get((None, 'title'), None))
260    
261        def end_map(self, name, qname):
262            self.theSession.AddMap(self.aMap)
263            self.aMap = None
264    
265        def start_projection(self, name, qname, attrs):
266            self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
267            self.ProjectionParams = [ ]
268    
269        def end_projection(self, name, qname):
270            if self.aLayer is not None:
271                obj = self.aLayer
272            elif self.aMap is not None:
273                obj = self.aMap
274            else:
275                assert False, "projection tag out of context"
276              pass              pass
277    
278            obj.SetProjection(
279                Projection(self.ProjectionParams, self.ProjectionName))
280    
281        def start_parameter(self, name, qname, attrs):
282            s = attrs.get((None, 'value'))
283            s = str(s) # we can't handle unicode in proj
284            self.ProjectionParams.append(s)
285    
286        def start_layer(self, name, qname, attrs, layer_class = Layer):
287            """Start a layer
288    
289            Instantiate a layer of class layer_class from the attributes in
290            attrs which may be a dictionary as well as the normal SAX attrs
291            object and bind it to self.aLayer.
292            """
293            title = self.encode(attrs.get((None, 'title'), ""))
294            filename = attrs.get((None, 'filename'), "")
295            filename = os.path.join(self.GetDirectory(), filename)
296            filename = self.encode(filename)
297            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
298            fill = parse_color(attrs.get((None, 'fill'), "None"))
299            stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
300            stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
301            if attrs.has_key((None, "shapestore")):
302                store = self.idmap[attrs[(None, "shapestore")]]
303            else:
304                store = self.theSession.OpenShapefile(filename)
305            self.aLayer = layer_class(title, store,
306                                      fill = fill, stroke = stroke,
307                                      lineWidth = stroke_width,
308                                      visible = visible)
309    
310        def end_layer(self, name, qname):
311            self.aMap.AddLayer(self.aLayer)
312            self.aLayer = None
313    
314        def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
315            title = self.encode(attrs.get((None, 'title'), ""))
316            filename = attrs.get((None, 'filename'), "")
317            filename = os.path.join(self.GetDirectory(), filename)
318            filename = self.encode(filename)
319            visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
320    
321            self.aLayer = layer_class(title, filename, visible = visible)
322    
323        def end_rasterlayer(self, name, qname):
324            self.aMap.AddLayer(self.aLayer)
325            self.aLayer = None
326    
327        def start_classification(self, name, qname, attrs):
328            field = attrs.get((None, 'field'), None)
329    
330            fieldType = attrs.get((None, 'field_type'), None)
331            dbFieldType = self.aLayer.GetFieldType(field)
332    
333            if fieldType != dbFieldType:
334                raise ValueError(_("xml field type differs from database!"))
335    
336            # setup conversion routines depending on the kind of data
337            # we will be seeing later on
338            if fieldType == FIELDTYPE_STRING:
339                self.conv = str
340            elif fieldType == FIELDTYPE_INT:
341                self.conv = lambda p: int(float(p))
342            elif fieldType == FIELDTYPE_DOUBLE:
343                self.conv = float
344    
345            self.aLayer.GetClassification().SetField(field)
346    
347        def end_classification(self, name, qname):
348            pass
349    
350        def start_clnull(self, name, qname, attrs):
351            self.cl_group = ClassGroupDefault()
352            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
353            self.cl_prop = ClassGroupProperties()
354    
355        def end_clnull(self, name, qname):
356            self.cl_group.SetProperties(self.cl_prop)
357            self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
358            del self.cl_group, self.cl_prop
359    
360        def start_clpoint(self, name, qname, attrs):
361            attrib_value = attrs.get((None, 'value'), "0")
362    
363            value = self.conv(attrib_value)
364    
365            self.cl_group = ClassGroupSingleton(value)
366            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
367            self.cl_prop = ClassGroupProperties()
368    
369    
370        def end_clpoint(self, name, qname):
371            self.cl_group.SetProperties(self.cl_prop)
372            self.aLayer.GetClassification().AppendGroup(self.cl_group)
373            del self.cl_group, self.cl_prop
374    
375        def start_clrange(self, name, qname, attrs):
376    
377            range = attrs.get((None, 'range'), None)
378            # for backward compatibility (min/max are not saved)
379            min   = attrs.get((None, 'min'), None)
380            max   = attrs.get((None, 'max'), None)
381    
382            try:
383                if range is not None:
384                    self.cl_group = ClassGroupRange(Range(range))
385                elif min is not None and max is not None:
386                    self.cl_group = ClassGroupRange(self.conv(min), self.conv(max))
387                else:
388                    self.cl_group = ClassGroupRange(Range(None))
389    
390            except ValueError:
391                raise ValueError(_("Classification range is not a number!"))
392    
393            self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
394            self.cl_prop = ClassGroupProperties()
395    
396    
397        def end_clrange(self, name, qname):
398            self.cl_group.SetProperties(self.cl_prop)
399            self.aLayer.GetClassification().AppendGroup(self.cl_group)
400            del self.cl_group, self.cl_prop
401    
402        def start_cldata(self, name, qname, attrs):
403            self.cl_prop.SetLineColor(
404                parse_color(attrs.get((None, 'stroke'), "None")))
405            self.cl_prop.SetLineWidth(
406                int(attrs.get((None, 'stroke_width'), "0")))
407            self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
408    
409        def end_cldata(self, name, qname):
410            pass
411    
412        def start_labellayer(self, name, qname, attrs):
413            self.aLayer = self.aMap.LabelLayer()
414    
415        def start_label(self, name, qname, attrs):
416            x = float(attrs[(None, 'x')])
417            y = float(attrs[(None, 'y')])
418            text = self.encode(attrs[(None, 'text')])
419            halign = attrs[(None, 'halign')]
420            valign = attrs[(None, 'valign')]
421            self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
422    
423        def characters(self, chars):
424            pass
425    
426    
427  def load_session(filename):  def load_session(filename):
428      """Load a Thuban session from the file object file"""      """Load a Thuban session from the file object file"""
429      dir = os.path.dirname(filename)  
430      file = open(filename)      handler = SessionLoader()
431      handler = ProcessSession(dir)      handler.read(filename)
432    
     if oldPython:  
         parser = make_parser()  
         parser.setDocumentHandler(handler)  
         parser.setErrorHandler(saxutils.ErrorPrinter())  
         parser.parseFile(file)  
         parser.close()  
     else:  
         xml.sax.parse(file,handler)  
433      session = handler.theSession      session = handler.theSession
434      # Newly loaded session aren't modified      # Newly loaded session aren't modified
435      session.UnsetModified()      session.UnsetModified()
436    
437      return session      return session
438    
 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.105  
changed lines
  Added in v.1268

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26