fb34adf17a362b553e9dfcc14980792530f6694a
[profile/ivi/gobject-introspection.git] / giscanner / xmlwriter.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the
17 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 # Boston, MA 02111-1307, USA.
19 #
20
21 from __future__ import with_statement
22
23 import os
24
25 from contextlib import contextmanager
26 from cStringIO import StringIO
27 from xml.sax.saxutils import escape, quoteattr
28
29 from .libtoolimporter import LibtoolImporter
30
31
32 def _calc_attrs_length(attributes, indent, self_indent):
33     if indent == -1:
34         return -1
35     attr_length = 0
36     for attr, value in attributes:
37         # FIXME: actually, if we have attributes with None as value this
38         # should be considered a bug and raise an error. We are just
39         # ignoring them here while we fix GIRParser to create the right
40         # ast with the correct attributes.
41         if value is None:
42             continue
43         attr_length += 2 + len(attr) + len(quoteattr(value))
44     return attr_length + indent + self_indent
45
46
47 def collect_attributes(tag_name, attributes, self_indent,
48                        self_indent_char, indent=-1):
49     if not attributes:
50         return ''
51     if _calc_attrs_length(attributes, indent, self_indent) > 79:
52         indent_len = self_indent + len(tag_name) + 1
53     else:
54         indent_len = 0
55     first = True
56     attr_value = ''
57     for attr, value in attributes:
58         # FIXME: actually, if we have attributes with None as value this
59         # should be considered a bug and raise an error. We are just
60         # ignoring them here while we fix GIRParser to create the right
61         # ast with the correct attributes.
62         if value is None:
63             continue
64         if indent_len and not first:
65             attr_value += '\n%s' % (self_indent_char * indent_len)
66         attr_value += ' %s=%s' % (attr, quoteattr(value))
67         if first:
68             first = False
69     return attr_value
70
71
72 with LibtoolImporter(None, None):
73     if 'UNINSTALLED_INTROSPECTION_SRCDIR' in os.environ:
74         from _giscanner import collect_attributes
75     else:
76         from giscanner._giscanner import collect_attributes
77
78
79 class XMLWriter(object):
80
81     def __init__(self):
82         self._data = StringIO()
83         self._data.write('<?xml version="1.0"?>\n')
84         self._tag_stack = []
85         self._indent = 0
86         self._indent_unit = 2
87         self.enable_whitespace()
88
89     # Private
90
91     def _open_tag(self, tag_name, attributes=None):
92         if attributes is None:
93             attributes = []
94         attrs = collect_attributes(
95             tag_name, attributes, self._indent,
96             self._indent_char,
97             len(tag_name) + 2)
98         self.write_line(u'<%s%s>' % (tag_name, attrs))
99
100     def _close_tag(self, tag_name):
101         self.write_line(u'</%s>' % (tag_name, ))
102
103     # Public API
104
105     def enable_whitespace(self):
106         self._indent_char = ' '
107         self._newline_char = '\n'
108
109     def disable_whitespace(self):
110         self._indent_char = ''
111         self._newline_char = ''
112
113     def get_xml(self):
114         return self._data.getvalue()
115
116     def write_line(self, line=u'', indent=True, do_escape=False):
117         if isinstance(line, str):
118             line = line.decode('utf-8')
119         assert isinstance(line, unicode)
120         if do_escape:
121             line = escape(line.encode('utf-8')).decode('utf-8')
122         if indent:
123             self._data.write('%s%s%s' % (
124                     self._indent_char * self._indent,
125                     line.encode('utf-8'),
126                     self._newline_char))
127         else:
128             self._data.write('%s%s' % (line.encode('utf-8'), self._newline_char))
129
130     def write_comment(self, text):
131         self.write_line('<!-- %s -->' % (text, ))
132
133     def write_tag(self, tag_name, attributes, data=None):
134         if attributes is None:
135             attributes = []
136         prefix = u'<%s' % (tag_name, )
137         if data is not None:
138             if isinstance(data, str):
139                 data = data.decode('UTF-8')
140             suffix = u'>%s</%s>' % (escape(data), tag_name)
141         else:
142             suffix = u'/>'
143         attrs = collect_attributes(
144             tag_name, attributes,
145             self._indent,
146             self._indent_char,
147             len(prefix) + len(suffix))
148         self.write_line(prefix + attrs + suffix)
149
150     def push_tag(self, tag_name, attributes=None):
151         if attributes is None:
152             attributes = []
153         self._open_tag(tag_name, attributes)
154         self._tag_stack.append(tag_name)
155         self._indent += self._indent_unit
156
157     def pop_tag(self):
158         self._indent -= self._indent_unit
159         tag_name = self._tag_stack.pop()
160         self._close_tag(tag_name)
161         return tag_name
162
163     @contextmanager
164     def tagcontext(self, tag_name, attributes=None):
165         self.push_tag(tag_name, attributes)
166         try:
167             yield
168         finally:
169             self.pop_tag()
170
171
172 def test():
173     w = XMLWriter()
174     w.push_tag('repository')
175     w.push_tag('namespace')
176     w.push_tag('enumeration')
177     w.push_tag('member',
178                [('name', 'west'),
179                 ('value', '7'),
180                 ('c:identifier', 'GTK_ANCHOR_WEST'),
181                 ('glib:nick', 'west')])
182
183     w.pop_tag()
184     w.pop_tag()
185     w.pop_tag()
186     x = w.get_xml()
187     lines = x.split('\n')
188     import pprint
189     pprint.pprint(lines)
190     assert len(lines[3]) < 80, len(lines[3])
191
192 if __name__ == '__main__':
193     test()