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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 932 - (show 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 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # 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)
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 from types import UnicodeType
20
21 import Thuban.Lib.fileutil
22
23 from Thuban.Model.color import Color
24 from Thuban.Model.layer import Layer, RasterLayer
25
26 from Thuban.Model.classification import \
27 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
28
29 #
30 # one level of indention
31 #
32 TAB = " "
33
34 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 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 class XMLWriter:
57 """Abstract XMLWriter.
58
59 Should be overridden to provide specific object saving functionality.
60 """
61
62 def __init__(self):
63 self.filename = None
64 pass
65
66 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 presence of a write method) all filenames will be absolute
78 filenames.
79 """
80
81 # 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
86 if hasattr(file_or_filename, "write"):
87 # it's a file object
88 self.file = file_or_filename
89 self.dir = ""
90 else:
91 self.filename = file_or_filename
92 self.dir = os.path.dirname(self.filename)
93 self.file = open(self.filename, 'w')
94
95 def close(self):
96 assert self.indent_level == 0
97 if self.filename is not None:
98 self.file.close()
99
100 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 def open_element(self, element, attrs = {}):
106
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 # Helper function to write an element open tag with attributes
120 self.file.write("%s<%s" % (TAB*self.indent_level, element))
121 self.__write_attribs(attrs)
122
123 self.indent_level += 1
124
125 def close_element(self, element):
126 self.indent_level -= 1
127 assert self.indent_level >= 0
128
129 # 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 def write_element(self, element, attrs = {}):
137 """write an element that won't need a closing tag"""
138 self.open_element(element, attrs)
139 self.close_element(element)
140
141 def __write_attribs(self, attrs):
142 for name, value in attrs.items():
143 self.file.write(' %s="%s"' % (self.encode(name),
144 self.encode(value)))
145
146 def encode(self, str):
147 """Return an XML-escaped and UTF-8 encoded copy of the string str."""
148
149 esc = escape(str)
150
151 if isinstance(esc, UnicodeType):
152 return esc.encode("utf8")
153 else:
154 return unicode(escape(str),'latin1').encode("utf8")
155
156 class SessionSaver(XMLWriter):
157
158 """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 XMLWriter.__init__(self)
171 self.session = session
172
173 def write(self, file_or_filename):
174 XMLWriter.write(self, file_or_filename)
175
176 self.write_header("session", "thuban.dtd")
177 self.write_session(self.session)
178 self.close()
179
180 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 self.open_element("session", attrs)
202 for map in session.Maps():
203 self.write_map(map)
204 self.close_element("session")
205
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 self.open_element('map title="%s"' % self.encode(map.title))
215 self.write_projection(map.projection)
216 for layer in map.Layers():
217 self.write_layer(layer)
218 self.write_label_layer(map.LabelLayer())
219 self.close_element('map')
220
221 def write_projection(self, projection):
222 """Write the projection.
223 """
224 if projection and len(projection.params) > 0:
225 self.open_element("projection", {"name": projection.GetName()})
226 for param in projection.params:
227 self.write_element('parameter value="%s"' %
228 self.encode(param))
229 self.close_element("projection")
230
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
239 if attrs is None:
240 attrs = {}
241
242 attrs["title"] = layer.title
243 attrs["filename"] = relative_filename(self.dir, layer.filename)
244 attrs["visible"] = ("false", "true")[int(layer.Visible())]
245
246 if isinstance(layer, Layer):
247
248 lc = layer.GetClassification()
249 attrs["stroke"] = lc.GetDefaultLineColor().hex()
250 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
251 attrs["fill"] = lc.GetDefaultFill().hex()
252
253 self.open_element("layer", attrs)
254 self.write_projection(layer.GetProjection())
255 self.write_classification(layer)
256 self.close_element("layer")
257
258 elif isinstance(layer, RasterLayer):
259
260 self.open_element("rasterlayer", attrs)
261 self.write_projection(layer.GetProjection())
262 self.close_element("rasterlayer")
263
264 def write_classification(self, layer, attrs = None):
265 if attrs is None:
266 attrs = {}
267
268 lc = layer.GetClassification()
269
270 field = lc.GetField()
271
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 attrs["field_type"] = str(lc.GetFieldType())
280 self.open_element("classification", attrs)
281
282
283 types = [[lambda p: 'clnull label="%s"' % self.encode(p.GetLabel()),
284 lambda p: 'clnull'],
285 [lambda p: 'clpoint label="%s" value="%s"' %
286 (self.encode(p.GetLabel()), str(p.GetValue())),
287 lambda p: 'clpoint'],
288 [lambda p: 'clrange label="%s" range="%s"' %
289 (self.encode(p.GetLabel()),
290 str(p.GetRange())),
291 lambda p: 'clrange']]
292
293 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 assert type >= 0
300
301 if type <= 2:
302 data = group.GetProperties()
303 dict = {'stroke' : data.GetLineColor().hex(),
304 'stroke_width': str(data.GetLineWidth()),
305 '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 for i in lc:
313 write_class_group(i)
314
315 self.close_element("classification")
316
317 def write_label_layer(self, layer):
318 """Write the label layer.
319 """
320 labels = layer.Labels()
321 if labels:
322 self.open_element('labellayer')
323 for label in labels:
324 self.write_element(('label x="%g" y="%g" text="%s"'
325 ' halign="%s" valign="%s"')
326 % (label.x, label.y,
327 self.encode(label.text),
328 label.halign,
329 label.valign))
330 self.close_element('labellayer')
331
332
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 SessionSaver.
342
343 If writing the session is successful call the session's
344 UnsetModified method
345 """
346 if saver_class is None:
347 saver_class = SessionSaver
348 saver = saver_class(session)
349 saver.write(file)
350
351 # 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