# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
[docs]
class JsonWriter:
def __init__(self, output, separator=""):
self._writer = JsonDumper(output)
self._separator = separator
[docs]
def write_json(self, prefix, data, postfix=";\n", mapping=None, separator=True):
self._writer.write(prefix)
self._writer.dump(data, mapping)
self._writer.write(postfix)
self._write_separator(separator)
[docs]
def write(self, string, postfix=";\n", separator=True):
self._writer.write(string + postfix)
self._write_separator(separator)
def _write_separator(self, separator):
if separator and self._separator:
self._writer.write(self._separator)
[docs]
class JsonDumper:
def __init__(self, output):
self.write = output.write
self._dumpers = (
MappingDumper(self),
IntegerDumper(self),
TupleListDumper(self),
StringDumper(self),
NoneDumper(self),
DictDumper(self),
)
[docs]
def dump(self, data, mapping=None):
for dumper in self._dumpers:
if dumper.handles(data, mapping):
dumper.dump(data, mapping)
return
raise ValueError(f"Dumping {type(data)} not supported.")
class _Dumper:
_handled_types = None
def __init__(self, jsondumper):
self._dump = jsondumper.dump
self._write = jsondumper.write
def handles(self, data, mapping):
return isinstance(data, self._handled_types)
def dump(self, data, mapping):
raise NotImplementedError
[docs]
class StringDumper(_Dumper):
_handled_types = str
_search_and_replace = [
("\\", "\\\\"),
('"', '\\"'),
("\t", "\\t"),
("\n", "\\n"),
("\r", "\\r"),
("</", "\\x3c/"),
]
[docs]
def dump(self, data, mapping):
data = self._escape(data) if data else ""
self._write(f'"{data}"')
def _escape(self, string):
for search, replace in self._search_and_replace:
if search in string:
string = string.replace(search, replace)
return string
[docs]
class IntegerDumper(_Dumper):
# Handles also bool
_handled_types = int
[docs]
def dump(self, data, mapping):
self._write(str(data).lower())
[docs]
class DictDumper(_Dumper):
_handled_types = dict
[docs]
def dump(self, data, mapping):
write = self._write
dump = self._dump
write("{")
last_index = len(data) - 1
for index, key in enumerate(sorted(data)):
dump(key, mapping)
write(":")
dump(data[key], mapping)
if index < last_index:
write(",")
write("}")
[docs]
class TupleListDumper(_Dumper):
_handled_types = (tuple, list)
[docs]
def dump(self, data, mapping):
write = self._write
dump = self._dump
write("[")
last_index = len(data) - 1
for index, item in enumerate(data):
dump(item, mapping)
if index < last_index:
write(",")
write("]")
[docs]
class MappingDumper(_Dumper):
[docs]
def handles(self, data, mapping):
try:
return mapping and data in mapping
except TypeError:
return False
[docs]
def dump(self, data, mapping):
self._write(mapping[data])
[docs]
class NoneDumper(_Dumper):
[docs]
def handles(self, data, mapping):
return data is None
[docs]
def dump(self, data, mapping):
self._write("null")