Skip to content

Commit

Permalink
Add support for single-quoted strings and fix stringifying whitespace
Browse files Browse the repository at this point in the history
Fixes hjson#18
  • Loading branch information
dqsully committed Feb 10, 2019
1 parent 97a35e9 commit 3545a00
Show file tree
Hide file tree
Showing 15 changed files with 155 additions and 13 deletions.
17 changes: 12 additions & 5 deletions src/HJSON/HJSONParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function __construct()
{
$this->escapee = [
'"' => '"',
'\'' => '\'',
"\\" => "\\",
'/' => '/',
'b' => chr(8),
Expand Down Expand Up @@ -99,23 +100,27 @@ private function value()
case '[':
return $this->_array();
case '"':
return $this->string();
return $this->string('"');
case '\'':
if ($this->peek(0) !== '\'' || $this->peek(1) !== '\'') {
return $this->string('\'');
}
default:
return $this->tfnns();
}
}

private function string()
private function string($quote)
{
// Parse a string value.
$hex;
$string = '';
$uffff;

// When parsing for string values, we must look for " and \ characters.
if ($this->ch === '"') {
if ($this->ch === $quote) {
while ($this->next() !== null) {
if ($this->ch === '"') {
if ($this->ch === $quote) {
$this->next();
return $string;
}
Expand Down Expand Up @@ -410,7 +415,9 @@ private function keyname()
// unless they include {}[],: or whitespace.

if ($this->ch === '"') {
return $this->string();
return $this->string('"');
} else if ($this->ch === '\'') {
return $this->string('\'');
}

$name = "";
Expand Down
20 changes: 15 additions & 5 deletions src/HJSON/HJSONStringifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

namespace HJSON;

/**
* NOTE: this may return an empty string at the end of the array when the input
* string ends with a newline character
*/
function mb_str_split($string)
{
return preg_split('/(?<!^)(?!$)/u', $string);
return preg_split('/(?<!^)/u', $string);
}

class HJSONStringifier
Expand All @@ -13,11 +17,12 @@ class HJSONStringifier
// needsEscape tests if the string can be written without escapes
private $needsEscape = '/[\\\"\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u';
// needsQuotes tests if the string can be written as a quoteless string (includes needsEscape but without \\ and \")
private $needsQuotes = '/^\\s|^"|^\'\'\'|^#|^\\/\\*|^\\/\\/|^\\{|^\\}|^\\[|^\\]|^:|^,|\\s$|[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u';
private $needsQuotes = '/^\\s|^"|^\'|^\'\'\'|^#|^\\/\\*|^\\/\\/|^\\{|^\\}|^\\[|^\\]|^:|^,|\\s$|[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u';
// needsEscapeML tests if the string can be written as a multiline string (includes needsEscape but without \n, \r, \\ and \")
private $needsEscapeML = '/\'\'\'|[\x00-\x09\x0b\x0c\x0e-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u';
private $needsEscapeML = '/\'\'\'|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u';
private $onlyWhitespace = '/^\\s+$/';
private $startsWithKeyword = '/^(true|false|null)\s*((,|\]|\}|#|\/\/|\/\*).*)?$/';
private $needsEscapeName = '/[,\{\[\}\]\s:#"]|\/\/|\/\*|\'\'\'/';
private $needsEscapeName = '/[,\{\[\}\]\s:#"\']|\/\/|\/\*|\'\'\'/';
private $gap = '';
private $indent = ' ';

Expand All @@ -38,6 +43,7 @@ public function __construct()
"\n" => "\\n",
"\r" => "\\r",
'"' => '\\"',
'\'' => '\\\'',
'\\' => "\\\\"
];
$this->meta[chr(8)] = '\\b';
Expand Down Expand Up @@ -136,7 +142,11 @@ private function quote($string = null, $gap = null, $hasComment = null, $isRootO

if (!preg_match($this->needsEscape, $string)) {
return '"' . $string . '"';
} elseif (!preg_match($this->needsEscapeML, $string) && !$isRootObject) {
} elseif (
!preg_match($this->needsEscapeML, $string) &&
!preg_match($this->onlyWhitespace, $string) &&
!$isRootObject
) {
return $this->mlString($string, $gap);
} else {
return '"' . $this->quoteReplace($string) . '"';
Expand Down
1 change: 0 additions & 1 deletion tests/assets/failJSON24_test.json

This file was deleted.

4 changes: 4 additions & 0 deletions tests/assets/failKey5_test.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
# invalid name
'''foo''': 0
}
4 changes: 4 additions & 0 deletions tests/assets/failStr8a_test.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
# invalid ml-string
foo : ""'text'''
}
3 changes: 3 additions & 0 deletions tests/assets/keys_result.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"foo\"bar": test
"'''": test
"foo'''bar": test
"'": test
"'foo": test
"foo'bar": test
":": test
"foo:bar": test
"{": test
Expand Down
3 changes: 3 additions & 0 deletions tests/assets/keys_result.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"foo\"bar": "test",
"'''": "test",
"foo'''bar": "test",
"'": "test",
"'foo": "test",
"foo'bar": "test",
":": "test",
"foo:bar": "test",
"{": "test",
Expand Down
3 changes: 3 additions & 0 deletions tests/assets/keys_test.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"foo\"bar": test
"'''": test
"foo'''bar": test
"'": test
"'foo": test
"foo'bar": test
# control char in key name
":": test
"foo:bar": test
Expand Down
29 changes: 29 additions & 0 deletions tests/assets/strings2_result.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
key1: a key in single quotes
"key 2": a key in single quotes
"key \"": a key in single quotes
text:
[
single quoted string
'''You need quotes for escapes'''
" untrimmed "
"untrimmed "
containing " double quotes
containing " double quotes
containing " double quotes
'''"containing more " double quotes"'''
containing ' single quotes
containing ' single quotes
containing ' single quotes
"'containing more ' single quotes'"
"'containing more ' single quotes'"
"\n"
" \n"
"\n \n \n \n"
"\t\n"
]
foo3a: asdf'''
foo3b: "'''asdf"
foo4a: "asdf'''\nasdf"
foo4b: "asdf\n'''asdf"
}
28 changes: 28 additions & 0 deletions tests/assets/strings2_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"key1": "a key in single quotes",
"key 2": "a key in single quotes",
"key \"": "a key in single quotes",
"text": [
"single quoted string",
"You need quotes\tfor escapes",
" untrimmed ",
"untrimmed ",
"containing \" double quotes",
"containing \" double quotes",
"containing \" double quotes",
"\"containing more \" double quotes\"",
"containing ' single quotes",
"containing ' single quotes",
"containing ' single quotes",
"'containing more ' single quotes'",
"'containing more ' single quotes'",
"\n",
" \n",
"\n \n \n \n",
"\t\n"
],
"foo3a": "asdf'''",
"foo3b": "'''asdf",
"foo4a": "asdf'''\nasdf",
"foo4b": "asdf\n'''asdf"
}
36 changes: 36 additions & 0 deletions tests/assets/strings2_test.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
# Hjson 3 allows the use of single quotes

'key1': a key in single quotes
'key 2': a key in single quotes
'key "': a key in single quotes

text: [
'single quoted string'
'You need quotes\tfor escapes'
' untrimmed '
'untrimmed '
'containing " double quotes'
'containing \" double quotes'
"containing \" double quotes"
'"containing more " double quotes"'
'containing \' single quotes'
"containing ' single quotes"
"containing \' single quotes"
"'containing more ' single quotes'"
"\'containing more \' single quotes\'"

'\n'
' \n'
'\n \n \n \n'
'\t\n'
]

# escapes/no escape

foo3a: 'asdf\'\'\''
foo3b: '\'\'\'asdf'

foo4a: 'asdf\'\'\'\nasdf'
foo4b: 'asdf\n\'\'\'asdf'
}
6 changes: 5 additions & 1 deletion tests/assets/strings_result.hjson
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{
text1: This is a valid string value.
text2: a \ is just a \
text3: "You need quotes\tfor escapes"
text3: '''You need quotes for escapes'''
text4a: " untrimmed "
text4b: " untrimmed"
text4c: "untrimmed "
notml1: "\n"
notml2: " \n"
notml3: "\n \n \n \n"
notml4: "\t\n"
multiline1:
'''
first line
Expand Down
4 changes: 4 additions & 0 deletions tests/assets/strings_result.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"text4a": " untrimmed ",
"text4b": " untrimmed",
"text4c": "untrimmed ",
"notml1": "\n",
"notml2": " \n",
"notml3": "\n \n \n \n",
"notml4": "\t\n",
"multiline1": "first line\n indented line\nlast line",
"multiline2": "first line\n indented line\nlast line",
"multiline3": "first line\n indented line\nlast line\n",
Expand Down
5 changes: 5 additions & 0 deletions tests/assets/strings_test.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
text4b: " untrimmed"
text4c: "untrimmed "

notml1: "\n"
notml2: " \n"
notml3: "\n \n \n \n"
notml4: "\t\n"

# multiline string

multiline1:
Expand Down
5 changes: 4 additions & 1 deletion tests/assets/testlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ failJSON20_test.json
failJSON21_test.json
failJSON22_test.json
failJSON23_test.json
failJSON24_test.json
failJSON26_test.json
failJSON28_test.json
failJSON29_test.json
Expand All @@ -33,6 +32,7 @@ failKey1_test.hjson
failKey2_test.hjson
failKey3_test.hjson
failKey4_test.hjson
failKey5_test.hjson
failMLStr1_test.hjson
failObj1_test.hjson
failObj2_test.hjson
Expand Down Expand Up @@ -61,8 +61,10 @@ failStr6a_test.hjson
failStr6b_test.hjson
failStr6c_test.hjson
failStr6d_test.hjson
failStr8a_test.hjson
kan_test.hjson
keys_test.hjson
mltabs_test.json
oa_test.hjson
pass1_test.json
pass2_test.json
Expand All @@ -71,5 +73,6 @@ pass4_test.json
passSingle_test.hjson
root_test.hjson
stringify1_test.hjson
strings2_test.hjson
strings_test.hjson
trail_test.hjson

0 comments on commit 3545a00

Please sign in to comment.