1#  Copyright 2008-2015 Nokia Networks
2#  Copyright 2016-     Robot Framework Foundation
3#
4#  Licensed under the Apache License, Version 2.0 (the "License");
5#  you may not use this file except in compliance with the License.
6#  You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10#  Unless required by applicable law or agreed to in writing, software
11#  distributed under the License is distributed on an "AS IS" BASIS,
12#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#  See the License for the specific language governing permissions and
14#  limitations under the License.
15
16from .markuputils import attribute_escape, html_escape, xml_escape
17from .robottypes import is_string
18from .robotio import file_writer
19
20
21class _MarkupWriter(object):
22
23    def __init__(self, output, write_empty=True):
24        """
25        :param output: Either an opened, file like object, or a path to the
26            desired output file. In the latter case, the file is created
27            and clients should use :py:meth:`close` method to close it.
28        :param write_empty: Whether to write empty elements and attributes.
29        """
30        if is_string(output):
31            output = file_writer(output)
32        self.output = output
33        self._write_empty = write_empty
34        self._preamble()
35
36    def _preamble(self):
37        pass
38
39    def start(self, name, attrs=None, newline=True):
40        attrs = self._format_attrs(attrs)
41        self._start(name, attrs, newline)
42
43    def _start(self, name, attrs, newline):
44        self._write('<%s %s>' % (name, attrs) if attrs else '<%s>' % name,
45                    newline)
46
47    def _format_attrs(self, attrs):
48        if not attrs:
49            return ''
50        attrs = [(k, attribute_escape(attrs[k] or ''))
51                 for k in self._order_attrs(attrs)]
52        write_empty = self._write_empty
53        return ' '.join('%s="%s"' % a for a in attrs if write_empty or a[1])
54
55    def _order_attrs(self, attrs):
56        return attrs
57
58    def content(self, content=None, escape=True, newline=False):
59        if content:
60            self._write(self._escape(content) if escape else content, newline)
61
62    def _escape(self, content):
63        raise NotImplementedError
64
65    def end(self, name, newline=True):
66        self._write('</%s>' % name, newline)
67
68    def element(self, name, content=None, attrs=None, escape=True,
69                newline=True, replace_newlines=False):
70        attrs = self._format_attrs(attrs)
71        if self._write_empty or content or attrs:
72            self._start(name, attrs, newline=False)
73            self.content(content, escape, replace_newlines)
74            self.end(name, newline)
75
76    def close(self):
77        """Closes the underlying output file."""
78        self.output.close()
79
80    def _write(self, text, newline=False):
81        self.output.write(text)
82        if newline:
83            self.output.write('\n')
84
85
86class HtmlWriter(_MarkupWriter):
87
88    def _order_attrs(self, attrs):
89        return sorted(attrs)  # eases testing
90
91    def _escape(self, content):
92        return html_escape(content)
93
94
95class XmlWriter(_MarkupWriter):
96
97    def _preamble(self):
98        self._write('<?xml version="1.0" encoding="UTF-8"?>', newline=True)
99
100    def _escape(self, text):
101        return xml_escape(text)
102
103
104class NullMarkupWriter(object):
105    """Null implementation of the _MarkupWriter interface."""
106
107    __init__ = start = content = element = end = close = \
108        lambda *args, **kwargs: None
109