Xorn
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Pages
xmlwrite.py
Go to the documentation of this file.
1 # Copyright (C) 2013-2016 Roland Lutz
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 
17 ## \namespace xorn.geda.xmlwrite
18 ## Writing gEDA schematic/symbol files in XML format.
19 
20 import xorn.fixednum
21 import xorn.hybridnum
22 import xorn.storage
23 import xorn.xml_writer
24 import xorn.geda.attrib
25 from xorn.geda.xmlformat import *
26 
27 class Writer:
28  def __init__(self, raw_file, w, use_hybridnum, omit_symbols, omit_pixmaps):
29  self.raw_file = raw_file
30  self.w = w
31  self.use_hybridnum = use_hybridnum
32  self.omit_symbols = omit_symbols
33  self.omit_pixmaps = omit_pixmaps
34 
35  self.symbol_ids = {}
36  self.pixmap_ids = {}
37 
38  def fmt(self, x):
39  if self.use_hybridnum:
40  return xorn.hybridnum.format(x, 2)
41  else:
42  return xorn.fixednum.format(int(round(x)), 2)
43 
44  def write_line(self, line):
45  if line.width:
46  self.w.write_attribute('linewidth', self.fmt(line.width))
47  if line.cap_style:
48  self.w.write_attribute('capstyle', ENUM_CAPSTYLE[line.cap_style])
49  if line.dash_style:
50  self.w.write_attribute('dashstyle', ENUM_DASHSTYLE[line.dash_style])
51  if line.dash_style != 0 and line.dash_style != 1:
52  self.w.write_attribute('dashlength', self.fmt(line.dash_length))
53  if line.dash_style != 0:
54  self.w.write_attribute('dashspace', self.fmt(line.dash_space))
55 
56  def write_fill(self, fill):
57  if fill.type:
58  self.w.write_attribute('filltype', ENUM_FILLTYPE[fill.type])
59  if fill.type == 2 or fill.type == 3:
60  self.w.write_attribute('fillwidth', self.fmt(fill.width))
61  self.w.write_attribute('angle0', str(fill.angle0))
62  self.w.write_attribute('pitch0', self.fmt(fill.pitch0))
63  if fill.type == 2:
64  self.w.write_attribute('angle1', str(fill.angle1))
65  self.w.write_attribute('pitch1', self.fmt(fill.pitch1))
66 
67  def write_object(self, ob):
68  data = ob.data()
69  if isinstance(data, xorn.storage.Arc):
70  self.w.start_element('arc')
71  self.w.write_attribute('x', self.fmt(data.x))
72  self.w.write_attribute('y', self.fmt(data.y))
73  self.w.write_attribute('radius', self.fmt(data.radius))
74  self.w.write_attribute('startangle', str(data.startangle))
75  self.w.write_attribute('sweepangle', str(data.sweepangle))
76  if data.color != 3:
77  self.w.write_attribute('color', ENUM_COLOR[data.color])
78  self.write_line(data.line)
79  self.w.end_element()
80 
81  elif isinstance(data, xorn.storage.Box):
82  self.w.start_element('box')
83  self.w.write_attribute('x', self.fmt(data.x))
84  self.w.write_attribute('y', self.fmt(data.y))
85  self.w.write_attribute('width', self.fmt(data.width))
86  self.w.write_attribute('height', self.fmt(data.height))
87  if data.color != 3:
88  self.w.write_attribute('color', ENUM_COLOR[data.color])
89  self.write_line(data.line)
90  self.write_fill(data.fill)
91  self.w.end_element()
92 
93  elif isinstance(data, xorn.storage.Circle):
94  self.w.start_element('circle')
95  self.w.write_attribute('x', self.fmt(data.x))
96  self.w.write_attribute('y', self.fmt(data.y))
97  self.w.write_attribute('radius', self.fmt(data.radius))
98  if data.color != 3:
99  self.w.write_attribute('color', ENUM_COLOR[data.color])
100  self.write_line(data.line)
101  self.write_fill(data.fill)
102  self.w.end_element()
103 
104  elif isinstance(data, xorn.storage.Component):
105  self.w.start_element('component')
106  self.w.write_attribute('x', self.fmt(data.x))
107  self.w.write_attribute('y', self.fmt(data.y))
108  if not data.selectable:
109  self.w.write_attribute('selectable',
110  ENUM_BOOLEAN[data.selectable])
111  if data.angle:
112  self.w.write_attribute('angle', str(data.angle))
113  if data.mirror:
114  self.w.write_attribute('mirror', ENUM_BOOLEAN[data.mirror])
115  self.w.write_attribute('symbol', self.symbol_ids[data.symbol])
116  for ob in ob.attached_objects():
117  self.write_object(ob)
118  self.w.end_element()
119 
120  elif isinstance(data, xorn.storage.Line):
121  self.w.start_element('line')
122  self.w.write_attribute('x0', self.fmt(data.x))
123  self.w.write_attribute('y0', self.fmt(data.y))
124  self.w.write_attribute('x1', self.fmt(data.x + data.width))
125  self.w.write_attribute('y1', self.fmt(data.y + data.height))
126  if data.color != 3:
127  self.w.write_attribute('color', ENUM_COLOR[data.color])
128  self.write_line(data.line)
129  self.w.end_element()
130 
131  elif isinstance(data, xorn.storage.Net):
132  if data.is_pin:
133  self.w.start_element('pin')
134  default_color = 1
135  else:
136  self.w.start_element('net')
137  if data.is_bus:
138  default_color = 10
139  else:
140  default_color = 4
141  self.w.write_attribute('x0', self.fmt(data.x))
142  self.w.write_attribute('y0', self.fmt(data.y))
143  self.w.write_attribute('x1', self.fmt(data.x + data.width))
144  self.w.write_attribute('y1', self.fmt(data.y + data.height))
145  if data.color != default_color:
146  self.w.write_attribute('color', ENUM_COLOR[data.color])
147  if data.is_bus:
148  self.w.write_attribute('type', ENUM_NETTYPE[data.is_bus])
149  if data.is_pin and data.is_inverted:
150  self.w.write_attribute('inverted',
151  ENUM_BOOLEAN[data.is_inverted])
152  for ob in ob.attached_objects():
153  self.write_object(ob)
154  self.w.end_element()
155 
156  elif isinstance(data, xorn.storage.Path):
157  self.w.start_element('path', preserve_whitespace = True)
158  if data.color != 3:
159  self.w.write_attribute('color', ENUM_COLOR[data.color])
160  self.write_line(data.line)
161  self.write_fill(data.fill)
162  for i, line in enumerate(data.pathdata.split('\n')):
163  if i:
164  self.w.start_element('br')
165  self.w.end_element()
166  self.w.write_character_data(line)
167  self.w.end_element()
168 
169  elif isinstance(data, xorn.storage.Picture):
170  self.w.start_element('picture')
171  self.w.write_attribute('x', self.fmt(data.x))
172  self.w.write_attribute('y', self.fmt(data.y))
173  self.w.write_attribute('width', self.fmt(data.width))
174  self.w.write_attribute('height', self.fmt(data.height))
175  if data.angle:
176  self.w.write_attribute('angle', str(data.angle))
177  if data.mirror:
178  self.w.write_attribute('mirrored', ENUM_BOOLEAN[data.mirror])
179  self.w.write_attribute('pixmap', self.pixmap_ids[data.pixmap])
180  self.w.end_element()
181 
182  elif isinstance(data, xorn.storage.Text):
183  try:
184  name, value = xorn.geda.attrib.parse_string(data.text)
185  if '\n' in name:
188  self.w.start_element('text', True)
189  is_attribute = False
190  text = data.text
191  else:
192  self.w.start_element('attribute', True)
193  self.w.write_attribute('name', name)
194  is_attribute = True
195  text = value
196 
197  self.w.write_attribute('x', self.fmt(data.x))
198  self.w.write_attribute('y', self.fmt(data.y))
199  if data.color != (5 if is_attribute else 9):
200  self.w.write_attribute('color', ENUM_COLOR[data.color])
201  self.w.write_attribute('size', str(data.text_size))
202  if is_attribute or not data.visibility:
203  self.w.write_attribute(
204  'visible', ENUM_BOOLEAN[data.visibility])
205  if is_attribute or data.show_name_value:
206  self.w.write_attribute(
207  'show', ENUM_SHOW_NAME_VALUE[data.show_name_value])
208  if data.angle:
209  self.w.write_attribute('angle', str(data.angle))
210  if data.alignment:
211  self.w.write_attribute('alignment',
212  ENUM_ALIGNMENT[data.alignment])
213 
214  # convert \_...\_ into <overbar>...</overbar>
215  for i, part in enumerate(text.split('\\_')):
216  if i % 2:
217  self.w.start_element('overbar')
218  # replace \\ with \ and remove single \
219  part = '\\'.join(s.replace('\\', '')
220  for s in part.split('\\\\'))
221  # replace newline characters with <br/>
222  for j, line in enumerate(part.split('\n')):
223  if j:
224  self.w.start_element('br')
225  self.w.end_element()
226  self.w.write_character_data(line.decode('utf-8'))
227  if i % 2:
228  self.w.end_element()
229 
230  self.w.end_element()
231  else:
232  raise AssertionError
233 
234  def write_symbol(self, symbol):
235  if symbol.prim_objs is None and not self.omit_symbols:
236  raise ValueError, 'symbol contents missing'
237  self.w.start_element('symbol')
238  self.w.write_attribute('id', self.symbol_ids[symbol])
239  self.w.write_attribute('name', symbol.basename)
240  if symbol.embedded:
241  self.w.write_attribute('mode', 'embedded')
242  self.write_rev(xorn.proxy.RevisionProxy(symbol.prim_objs))
243  elif not self.omit_symbols:
244  self.w.write_attribute('mode', 'referenced')
245  self.write_rev(xorn.proxy.RevisionProxy(symbol.prim_objs))
246  else:
247  self.w.write_attribute('mode', 'omitted')
248  self.w.end_element()
249 
250  def write_pixmap(self, pixmap):
251  if pixmap.data is None and not self.omit_pixmaps:
252  raise ValueError, 'pixmap contents missing'
253  self.w.start_element('pixmap', preserve_whitespace = True)
254  self.w.write_attribute('id', self.pixmap_ids[pixmap])
255  self.w.write_attribute('name', pixmap.filename)
256  if not self.omit_pixmaps:
257  if pixmap.embedded:
258  self.w.write_attribute('mode', 'embedded')
259  else:
260  self.w.write_attribute('mode', 'referenced')
261  self.w.write_character_data('') # close starting tag
262  self.raw_file.write('\n')
263  xorn.base64.encode(self.raw_file, pixmap.data)
264  self.raw_file.write(' ' * (len(self.w.stack) - 1))
265  else:
266  self.w.write_attribute('mode', 'omitted')
267  self.w.end_element()
268 
269  def write_rev(self, rev):
270  ids = set()
271  def get_unique_id(name):
272  i, id = 0, name
273  while id in ids:
274  i += 1
275  id = "%s.%d" % (name, i)
276  ids.add(id)
277  return id
278 
279  for ob in rev.all_objects():
280  data = ob.data()
281  if isinstance(data, xorn.storage.Component):
282  if data.symbol not in self.symbol_ids:
283  tmp = data.symbol.basename
284  if tmp.lower().endswith('.sym'):
285  tmp = tmp[:-4]
286  if tmp.lower().endswith('.sym.xml'):
287  tmp = tmp[:-8]
288  self.symbol_ids[data.symbol] = get_unique_id(tmp)
289  elif isinstance(data, xorn.storage.Picture):
290  if data.pixmap not in self.pixmap_ids:
291  tmp = data.pixmap.filename.split('/')[-1]
292  # TODO: need to use the actual list of pixmap extensions
293  # supported by gEDA/gaf here
294  if tmp.lower().endswith('.jpg') or \
295  tmp.lower().endswith('.png') or \
296  tmp.lower().endswith('.gif') or \
297  tmp.lower().endswith('.svg'):
298  tmp = tmp[:-4]
299  self.pixmap_ids[data.pixmap] = get_unique_id(tmp)
300 
301  self.w.start_element('content')
302  for ob in rev.toplevel_objects():
303  self.write_object(ob)
304  self.w.end_element()
305 
306  written_symbols = set()
307  written_pixmaps = set()
308  for ob in rev.all_objects():
309  data = ob.data()
310  if isinstance(data, xorn.storage.Component):
311  if data.symbol not in written_symbols:
312  self.write_symbol(data.symbol)
313  written_symbols.add(data.symbol)
314  elif isinstance(data, xorn.storage.Picture):
315  if data.pixmap not in written_pixmaps:
316  self.write_pixmap(data.pixmap)
317  written_pixmaps.add(data.pixmap)
318 
319 def write_file(f, rev, is_symbol, use_hybridnum = False,
320  omit_symbols = False,
321  omit_pixmaps = False):
322  w = xorn.xml_writer.XMLWriter(lambda s: f.write(s.encode('utf-8')))
323  if is_symbol:
324  w.start_element('symbol')
325  else:
326  w.start_element('schematic')
327  w.write_attribute('xmlns', NAMESPACE)
328  fff = ['experimental']
329  if use_hybridnum:
330  fff.append('hybridnum')
331  if fff:
332  w.write_attribute('file-format-features', ' '.join(fff))
333  Writer(f, w, use_hybridnum, omit_symbols, omit_pixmaps).write_rev(rev)
334  w.end_element()
335  assert w.is_done()
Raised when trying to parse a text object that is not recognized as an attribute. ...
Definition: attrib.py:44
Attribute parsing and lookup.
Definition: attrib.py:1
Schematic net segment, bus segment, or pin.
Definition: storage.py:549
Schematic arc.
Definition: storage.py:492
def encode
Write a binary string to a file in base64 representation.
Definition: base64.py:68
Common definitions for XML file format.
Definition: xmlformat.py:1
High-level revision proxy class.
Definition: proxy.py:24
def format
Convert a floating-point number to its hybrid string representation.
Definition: hybridnum.py:42
Schematic picture.
Definition: storage.py:571
Writing XML documents.
Definition: xml_writer.py:1
Schematic path.
Definition: storage.py:562
Schematic box.
Definition: storage.py:504
Schematic component.
Definition: storage.py:527
Schematic circle.
Definition: storage.py:516
Fixed-point numbers.
Definition: fixednum.py:1
Schematic text or attribute.
Definition: storage.py:583
Xorn storage backend.
Definition: storage.py:1
Hybrid fixed-/floating-point numbers.
Definition: hybridnum.py:1
def format
Convert an integer to its fixed-point string representation.
Definition: fixednum.py:32
Schematic line.
Definition: storage.py:538
Writing XML documents.
Definition: xml_writer.py:97
def parse_string
Parse an attribute string of the form name=value into its name and value parts.
Definition: attrib.py:55