Allow overriding default quotation mark
diff --git a/Makefile b/Makefile
index 9030ab2..76e3516 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@
python make_unicode_categories.py $< $@
pyjson5.cpp: pyjson5.pyx $(wildcard src/*.pyx)
- python -m cython -o $@ $<
+ python -m cython -f -o $@ $<
sdist: pyjson5.cpp ${FILES}
rm -f -- dist/pyjson5-*.tar.gz
diff --git a/src/_constants.pyx b/src/_constants.pyx
index c1edb56..eba4878 100644
--- a/src/_constants.pyx
+++ b/src/_constants.pyx
@@ -13,3 +13,7 @@
'latin_1', 'latin-1', 'iso-8859-1', 'iso8859-1',
8859, '8859', 'cp819', 'latin', 'latin1', 'l1',
))
+
+cdef object TEST_DECIMAL = Decimal('47.11')
+cdef object TEST_FLOAT = 47.11
+cdef object TEST_INT = 4711
diff --git a/src/_encoder.pyx b/src/_encoder.pyx
index b2807cc..c196f34 100644
--- a/src/_encoder.pyx
+++ b/src/_encoder.pyx
@@ -9,7 +9,7 @@
if length > 0:
writer.reserve(writer, 2 + length)
- writer.append_c(writer, <char> b'"')
+ writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
while True:
if UCSString is UCS1String:
sublength = length
@@ -76,7 +76,7 @@
length -= 1
if length <= 0:
break
- writer.append_c(writer, <char> b'"')
+ writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
else:
writer.append_s(writer, b'""', 2)
@@ -125,7 +125,7 @@
string = <char*> sub_writer.obj
writer.reserve(writer, 2 + length)
- writer.append_c(writer, <char> b'"')
+ writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
for index in range(length):
c = string[index]
if c not in b'\\"':
@@ -134,7 +134,7 @@
writer.append_s(writer, b'\\\\', 2)
else:
writer.append_s(writer, b'\\u0022', 6)
- writer.append_c(writer, <char> b'"')
+ writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
finally:
if sub_writer.obj is not NULL:
ObjectFree(sub_writer.obj)
@@ -281,9 +281,9 @@
cdef const char *string = PyUnicode_AsUTF8AndSize(stringified, &length)
writer.reserve(writer, 2 + length)
- writer.append_c(writer, <char> b'"')
+ writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
writer.append_s(writer, string, length)
- writer.append_c(writer, <char> b'"')
+ writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
return True
diff --git a/src/_encoder_options.pyx b/src/_encoder_options.pyx
index 70b44f4..daff41e 100644
--- a/src/_encoder_options.pyx
+++ b/src/_encoder_options.pyx
@@ -6,6 +6,7 @@
cdef object DEFAULT_FLOATFORMAT = '%.6e'
cdef object DEFAULT_DECIMALFORMAT = '%s'
cdef object DEFAULT_MAPPINGTYPES = (Mapping,)
+cdef object DEFAULT_QUOTATIONMARK = '"'
cdef object _options_ascii(object datum, boolean expect_ascii=True):
@@ -34,12 +35,13 @@
cdef _options_from_ascii(Options self):
return ', '.join(filter(bool, (
+ _option_from_ascii('quotationmark', self.quotationmark, DEFAULT_QUOTATIONMARK),
_option_from_ascii('tojson', self.tojson, None),
_option_from_ascii('posinfinity', self.posinfinity, DEFAULT_POSINFINITY),
_option_from_ascii('neginfinity', self.neginfinity, DEFAULT_NEGINFINITY),
_option_from_ascii('intformat', self.intformat, DEFAULT_INTFORMAT),
- _option_from_ascii('floatformat', self.floatformat, DEFAULT_DECIMALFORMAT),
- _option_from_ascii('decimalformat', self.decimalformat, DEFAULT_FLOATFORMAT),
+ _option_from_ascii('floatformat', self.floatformat, DEFAULT_FLOATFORMAT),
+ _option_from_ascii('decimalformat', self.decimalformat, DEFAULT_DECIMALFORMAT),
_option_from_ascii('nan', self.nan, DEFAULT_NAN),
)))
@@ -54,6 +56,9 @@
Parameters
----------
+ quotationmark : str|None
+ * **str**: One character string that is used to surround strings.
+ * **None**: Use default: ``'"'``.
tojson : str|False|None
* **str:** A special method to call on objects to return a custom JSON encoded string. Must return ASCII data!
* **False:** No such member exists. (Default.)
@@ -88,6 +93,9 @@
* **False:** There are no objects. Any object will be encoded as list of keys as in list(obj).
* **None:** Use default: ``[collections.abc.Mapping]``.
'''
+ cdef readonly unicode quotationmark
+ '''The creation argument ``quotationmark``.
+ '''
cdef readonly unicode tojson
'''The creation argument ``tojson``.
``None`` if ``False`` was specified.
@@ -150,11 +158,15 @@
return self.__repr__()
def __cinit__(self, *,
+ quotationmark=None,
tojson=None, posinfinity=None, neginfinity=None, nan=None,
decimalformat=None, intformat=None, floatformat=None,
mappingtypes=None):
cdef object cls
+ cdef object ex
+ if quotationmark is None:
+ quotationmark = DEFAULT_QUOTATIONMARK
if tojson is None:
tojson = DEFAULT_TOJSON
if posinfinity is None:
@@ -172,6 +184,7 @@
if mappingtypes is None:
mappingtypes = DEFAULT_MAPPINGTYPES
+ self.quotationmark = _options_ascii(quotationmark)
self.tojson = _options_ascii(tojson, False)
self.posinfinity = _options_ascii(posinfinity)
self.neginfinity = _options_ascii(neginfinity)
@@ -180,6 +193,27 @@
self.decimalformat = _options_ascii(decimalformat)
self.nan = _options_ascii(nan)
+ if self.quotationmark is None or PyUnicode_GET_LENGTH(self.quotationmark) != 1:
+ raise TypeError('quotationmark must be one ASCII character.')
+
+ if intformat is not None:
+ try:
+ intformat % TEST_INT
+ except Exception as ex:
+ raise ValueError('intformat is not a valid format string') from ex
+
+ if floatformat is not None:
+ try:
+ floatformat % TEST_FLOAT
+ except Exception as ex:
+ raise ValueError('floatformat is not a valid format string') from ex
+
+ if decimalformat is not None:
+ try:
+ decimalformat % TEST_DECIMAL
+ except Exception as ex:
+ raise ValueError('decimalformat is not a valid format string') from ex
+
if mappingtypes is False:
self.mappingtypes = ()
else:
@@ -208,6 +242,7 @@
elif not kw:
return arg
+ PyDict_SetDefault(kw, 'quotationmark', (<Options> arg).quotationmark)
PyDict_SetDefault(kw, 'tojson', (<Options> arg).tojson)
PyDict_SetDefault(kw, 'posinfinity', (<Options> arg).posinfinity)
PyDict_SetDefault(kw, 'neginfinity', (<Options> arg).neginfinity)
diff --git a/src/_exports.pyx b/src/_exports.pyx
index d04cf77..084d049 100644
--- a/src/_exports.pyx
+++ b/src/_exports.pyx
@@ -286,7 +286,7 @@
will be valid JSON data (as of RFC8259).
The result is always ASCII. All characters outside of the ASCII range
- are encoded.
+ are escaped.
The result safe to use in an HTML template, e.g.
``<a onclick='alert({{ encode(url) }})'>show message</a>``.