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

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/save.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 932 - (hide annotations)
Tue May 20 15:23:33 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 12133 byte(s)
(XMLWriter.encode): Don't assume that we
        get a string in Latin1. If we get such as string convert it to
        unicode first, otherwise leave if alone before encoding.
(SessionSaver.write_layer): Add support for writing both Layers
        and RasterLayers.

1 bh 454 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4     # Bernhard Herzog <[email protected]>
5 jonathan 414 # Jonathan Coles <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with Thuban for details.
9    
10     """
11     Functions to save a session to a file
12     """
13    
14     __version__ = "$Revision$"
15    
16     import os
17     import string
18    
19 jonathan 932 from types import UnicodeType
20    
21 bh 201 import Thuban.Lib.fileutil
22 bh 6
23 jonathan 932 from Thuban.Model.color import Color
24     from Thuban.Model.layer import Layer, RasterLayer
25    
26 jonathan 876 from Thuban.Model.classification import \
27     ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
28 jonathan 429
29 jonathan 366 #
30     # one level of indention
31     #
32     TAB = " "
33    
34 bh 201 def relative_filename(dir, filename):
35     """Return a filename relative to dir for the absolute file name absname.
36    
37     This is almost the same as the function in fileutil, except that dir
38     can be an empty string in which case filename will be returned
39     unchanged.
40     """
41     if dir:
42     return Thuban.Lib.fileutil.relative_filename(dir, filename)
43     else:
44     return filename
45    
46 bh 6 def escape(data):
47     """Escape &, \", ', <, and > in a string of data.
48     """
49     data = string.replace(data, "&", "&amp;")
50     data = string.replace(data, "<", "&lt;")
51     data = string.replace(data, ">", "&gt;")
52     data = string.replace(data, '"', "&quot;")
53     data = string.replace(data, "'", "&apos;")
54     return data
55    
56 jonathan 710 class XMLWriter:
57     """Abstract XMLWriter.
58 bh 268
59 jonathan 697 Should be overridden to provide specific object saving functionality.
60 bh 268 """
61    
62 jonathan 697 def __init__(self):
63     self.filename = None
64     pass
65 jonathan 366
66 bh 268 def write(self, file_or_filename):
67     """Write the session to a file.
68    
69     The argument may be either a file object or a filename. If it's
70     a filename, the file will be opened for writing. Files of
71     shapefiles will be stored as filenames relative to the directory
72     the file is stored in (as given by os.path.dirname(filename)) if
73     they have a common parent directory other than the root
74     directory.
75    
76     If the argument is a file object (which is determined by the
77 jonathan 697 presence of a write method) all filenames will be absolute
78 bh 268 filenames.
79     """
80 jonathan 366
81 jonathan 391 # keep track of how many levels of indentation to write
82     self.indent_level = 0
83     # track whether an element is currently open. see open_element().
84     self.element_open = 0
85 jonathan 366
86 bh 268 if hasattr(file_or_filename, "write"):
87     # it's a file object
88     self.file = file_or_filename
89     self.dir = ""
90     else:
91 jonathan 697 self.filename = file_or_filename
92     self.dir = os.path.dirname(self.filename)
93     self.file = open(self.filename, 'w')
94 bh 268
95 jonathan 697 def close(self):
96 jonathan 605 assert self.indent_level == 0
97 jonathan 697 if self.filename is not None:
98     self.file.close()
99 jonathan 366
100 jonathan 697 def write_header(self, doctype, system):
101     """Write the XML header"""
102     self.file.write('<?xml version="1.0" encoding="UTF-8"?>\n')
103     self.file.write('<!DOCTYPE %s SYSTEM "%s">\n' % (doctype, system))
104    
105 jonathan 366 def open_element(self, element, attrs = {}):
106 jonathan 391
107     #
108     # we note when an element is opened so that if two open_element()
109     # calls are made successively we can end the currently open
110     # tag and will later write a proper close tag. otherwise,
111     # if a close_element() call is made directly after an open_element()
112     # call we will close the tag with a />
113     #
114     if self.element_open == 1:
115     self.file.write(">\n")
116    
117     self.element_open = 1
118    
119 jonathan 366 # Helper function to write an element open tag with attributes
120     self.file.write("%s<%s" % (TAB*self.indent_level, element))
121 jonathan 697 self.__write_attribs(attrs)
122 bh 268
123 jonathan 366 self.indent_level += 1
124    
125     def close_element(self, element):
126     self.indent_level -= 1
127 jonathan 605 assert self.indent_level >= 0
128 jonathan 366
129 jonathan 391 # see open_element() for an explanation
130     if self.element_open == 1:
131     self.element_open = 0
132     self.file.write("/>\n")
133     else:
134     self.file.write("%s</%s>\n" % (TAB*self.indent_level, element))
135    
136 jonathan 366 def write_element(self, element, attrs = {}):
137 jonathan 391 """write an element that won't need a closing tag"""
138     self.open_element(element, attrs)
139     self.close_element(element)
140 jonathan 366
141 jonathan 697 def __write_attribs(self, attrs):
142     for name, value in attrs.items():
143 jonathan 876 self.file.write(' %s="%s"' % (self.encode(name),
144     self.encode(value)))
145 bh 920
146 jonathan 876 def encode(self, str):
147 jonathan 932 """Return an XML-escaped and UTF-8 encoded copy of the string str."""
148 bh 920
149 jonathan 932 esc = escape(str)
150 jonathan 876
151 jonathan 932 if isinstance(esc, UnicodeType):
152     return esc.encode("utf8")
153     else:
154     return unicode(escape(str),'latin1').encode("utf8")
155 jonathan 876
156 jonathan 710 class SessionSaver(XMLWriter):
157 bh 268
158 jonathan 697 """Class to serialize a session into an XML file.
159    
160     Applications built on top of Thuban may derive from this class and
161     override or extend the methods to save additional information. This
162     additional information should take the form of additional attributes
163     or elements whose names are prefixed with a namespace. To define a
164     namespace derived classes should extend the write_session method to
165     pass the namespaces to the default implementation.
166     """
167    
168    
169     def __init__(self, session):
170 jonathan 710 XMLWriter.__init__(self)
171 jonathan 697 self.session = session
172    
173     def write(self, file_or_filename):
174 jonathan 710 XMLWriter.write(self, file_or_filename)
175 jonathan 697
176     self.write_header("session", "thuban.dtd")
177     self.write_session(self.session)
178     self.close()
179    
180 bh 268 def write_session(self, session, attrs = None, namespaces = ()):
181     """Write the session and its contents
182    
183     By default, write a session element with the title attribute and
184     call write_map for each map contained in the session.
185    
186     The optional argument attrs is for additional attributes and, if
187     given, should be a mapping from attribute names to attribute
188     values. The values should not be XML-escaped yet.
189    
190     The optional argument namespaces, if given, should be a sequence
191     of (name, URI) pairs. The namespaces are written as namespace
192     attributes into the session element. This is mainly useful for
193     derived classes that need to store additional information in a
194     thuban session file.
195     """
196     if attrs is None:
197     attrs = {}
198     attrs["title"] = session.title
199     for name, uri in namespaces:
200     attrs["xmlns:" + name] = uri
201 jonathan 366 self.open_element("session", attrs)
202 bh 268 for map in session.Maps():
203     self.write_map(map)
204 jonathan 366 self.close_element("session")
205 bh 268
206     def write_map(self, map):
207     """Write the map and its contents.
208    
209     By default, write a map element element with the title
210     attribute, call write_projection to write the projection
211     element, call write_layer for each layer contained in the map
212     and finally call write_label_layer to write the label layer.
213     """
214 jonathan 876 self.open_element('map title="%s"' % self.encode(map.title))
215 bh 268 self.write_projection(map.projection)
216     for layer in map.Layers():
217     self.write_layer(layer)
218     self.write_label_layer(map.LabelLayer())
219 jonathan 366 self.close_element('map')
220 bh 6
221 bh 268 def write_projection(self, projection):
222     """Write the projection.
223     """
224     if projection and len(projection.params) > 0:
225 jonathan 876 self.open_element("projection", {"name": projection.GetName()})
226 bh 268 for param in projection.params:
227 jonathan 876 self.write_element('parameter value="%s"' %
228     self.encode(param))
229 jonathan 366 self.close_element("projection")
230 bh 268
231     def write_layer(self, layer, attrs = None):
232     """Write the layer.
233    
234     The optional argument attrs is for additional attributes and, if
235     given, should be a mapping from attribute names to attribute
236     values. The values should not be XML-escaped yet.
237     """
238 jonathan 391
239 bh 268 if attrs is None:
240     attrs = {}
241 jonathan 429
242     attrs["title"] = layer.title
243     attrs["filename"] = relative_filename(self.dir, layer.filename)
244 jonathan 773 attrs["visible"] = ("false", "true")[int(layer.Visible())]
245 bh 268
246 jonathan 932 if isinstance(layer, Layer):
247 jonathan 740
248 jonathan 932 lc = layer.GetClassification()
249     attrs["stroke"] = lc.GetDefaultLineColor().hex()
250     attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
251     attrs["fill"] = lc.GetDefaultFill().hex()
252 jonathan 740
253 jonathan 932 self.open_element("layer", attrs)
254     self.write_projection(layer.GetProjection())
255     self.write_classification(layer)
256     self.close_element("layer")
257 jonathan 740
258 jonathan 932 elif isinstance(layer, RasterLayer):
259 jonathan 366
260 jonathan 932 self.open_element("rasterlayer", attrs)
261     self.write_projection(layer.GetProjection())
262     self.close_element("rasterlayer")
263    
264 jonathan 366 def write_classification(self, layer, attrs = None):
265     if attrs is None:
266     attrs = {}
267    
268 jonathan 414 lc = layer.GetClassification()
269 jonathan 366
270 jonathan 429 field = lc.GetField()
271 jonathan 366
272     #
273     # there isn't a classification of anything
274     # so don't do anything
275     #
276     if field is None: return
277    
278     attrs["field"] = field
279 jonathan 466 attrs["field_type"] = str(lc.GetFieldType())
280 jonathan 366 self.open_element("classification", attrs)
281    
282 jonathan 429
283 jonathan 876 types = [[lambda p: 'clnull label="%s"' % self.encode(p.GetLabel()),
284 jonathan 440 lambda p: 'clnull'],
285 jonathan 683 [lambda p: 'clpoint label="%s" value="%s"' %
286 jonathan 876 (self.encode(p.GetLabel()), str(p.GetValue())),
287 jonathan 440 lambda p: 'clpoint'],
288 jonathan 876 [lambda p: 'clrange label="%s" range="%s"' %
289     (self.encode(p.GetLabel()),
290     str(p.GetRange())),
291 jonathan 440 lambda p: 'clrange']]
292 jonathan 429
293 jonathan 440 def write_class_group(group):
294     type = -1
295     if isinstance(group, ClassGroupDefault): type = 0
296     elif isinstance(group, ClassGroupSingleton): type = 1
297     elif isinstance(group, ClassGroupRange): type = 2
298     elif isinstance(group, ClassGroupMap): type = 3
299 jonathan 605 assert type >= 0
300 jonathan 366
301 jonathan 440 if type <= 2:
302     data = group.GetProperties()
303 jonathan 466 dict = {'stroke' : data.GetLineColor().hex(),
304     'stroke_width': str(data.GetLineWidth()),
305 jonathan 440 'fill' : data.GetFill().hex()}
306    
307     self.open_element(types[type][0](group))
308     self.write_element("cldata", dict)
309     self.close_element(types[type][1](group))
310     else: pass # XXX: we need to handle maps
311    
312 jonathan 429 for i in lc:
313 jonathan 440 write_class_group(i)
314 jonathan 429
315 jonathan 366 self.close_element("classification")
316    
317 bh 268 def write_label_layer(self, layer):
318     """Write the label layer.
319     """
320     labels = layer.Labels()
321 bh 6 if labels:
322 jonathan 366 self.open_element('labellayer')
323 bh 6 for label in labels:
324 jonathan 366 self.write_element(('label x="%g" y="%g" text="%s"'
325     ' halign="%s" valign="%s"')
326 jonathan 876 % (label.x, label.y,
327     self.encode(label.text),
328     label.halign,
329 bh 268 label.valign))
330 jonathan 366 self.close_element('labellayer')
331 bh 6
332 bh 268
333    
334     def save_session(session, file, saver_class = None):
335     """Save the session session to a file.
336    
337     The file argument may either be a filename or an open file object.
338    
339     The optional argument saver_class is the class to use to serialize
340     the session. By default or if it's None, the saver class will be
341 jonathan 697 SessionSaver.
342 bh 268
343     If writing the session is successful call the session's
344     UnsetModified method
345     """
346     if saver_class is None:
347 jonathan 697 saver_class = SessionSaver
348 bh 268 saver = saver_class(session)
349     saver.write(file)
350    
351 bh 6 # after a successful save consider the session unmodified.
352     session.UnsetModified()

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26