/
class.json.php
184 lines (168 loc) · 6.24 KB
/
class.json.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<?php
/**
* @package toolkit
*/
/**
* The `JSONException` class extends the base `Exception` class. It's only
* difference is that it will translate the `$code` to a human readable
* error.
*
* @since Symphony 2.3
*/
class JSONException extends Exception
{
/**
* Constructor takes a `$message`, `$code` and the original Exception, `$ex`.
* Upon translating the `$code` into a more human readable message, it will
* initialise the base `Exception` class. If the `$code` is unfamiliar, the original
* `$message` will be passed.
*
* @param string $message
* @param integer $code
* @param Exception $ex
*/
public function __construct($message, $code = null, Exception $ex = null)
{
switch ($code) {
case JSON_ERROR_NONE:
$message = __('No errors.');
break;
case JSON_ERROR_DEPTH:
$message = __('Maximum stack depth exceeded.');
break;
case JSON_ERROR_STATE_MISMATCH:
$message = __('Underflow or the modes mismatch.');
break;
case JSON_ERROR_CTRL_CHAR:
$message = __('Unexpected control character found.');
break;
case JSON_ERROR_SYNTAX:
$message = __('Syntax error, malformed JSON.');
break;
case JSON_ERROR_UTF8:
$message = __('Malformed UTF-8 characters, possibly incorrectly encoded.');
break;
default:
$message = __('Unknown JSON error');
break;
}
parent::__construct($message, $code, $ex);
}
}
/**
* The `JSON` class takes a JSON formatted string and converts it to XML.
* The majority of this class was originally written by Brent Burgoyne, thank you.
*
* @since Symphony 2.3
* @author Brent Burgoyne
*/
class JSON
{
private static $dom;
/**
* Given a JSON formatted string, this function will convert it to an
* equivalent XML version (either standalone or as a fragment). The JSON
* will be added under a root node of `<data>`.
*
* @throws JSONException
* @param string $json
* The JSON formatted class
* @param boolean $standalone
* If passed true (which is the default), this parameter will cause
* the function to return the XML with an XML declaration, otherwise
* the XML will be returned as a fragment.
* @return string
* Returns a XML string
*/
public static function convertToXML($json, $standalone = true)
{
self::$dom = new DomDocument('1.0', 'utf-8');
self::$dom->formatOutput = true;
// remove callback functions from JSONP
if (preg_match('/(\{|\[).*(\}|\])/s', $json, $matches)) {
$json = $matches[0];
} else {
throw new JSONException(__("JSON not formatted correctly"));
}
$data = json_decode($json);
if (function_exists('json_last_error')) {
if (json_last_error() !== JSON_ERROR_NONE) {
throw new JSONException(__("JSON not formatted correctly"), json_last_error());
}
} elseif (!$data) {
throw new JSONException(__("JSON not formatted correctly"));
}
$data_element = self::_process($data, self::$dom->createElement('data'));
self::$dom->appendChild($data_element);
if ($standalone) {
return self::$dom->saveXML();
} else {
return self::$dom->saveXML(self::$dom->documentElement);
}
}
/**
* This function recursively iterates over `$data` and uses `self::$dom`
* to create an XML structure that mirrors the JSON. The results are added
* to `$element` and then returned. Any arrays that are encountered are added
* to 'item' elements.
*
* @param mixed $data
* The initial call to this function will be of `stdClass` and directly
* from `json_decode`. Recursive calls after that may be of `stdClass`,
* `array` or `string` types.
* @param DOMElement $element
* The `DOMElement` to append the data to. The root node is `<data>`.
* @return DOMElement
*/
private static function _process($data, DOMElement $element)
{
if (is_array($data)) {
foreach ($data as $item) {
$item_element = self::_process($item, self::$dom->createElement('item'));
$element->appendChild($item_element);
}
} elseif (is_object($data)) {
$vars = get_object_vars($data);
foreach ($vars as $key => $value) {
$key = self::_valid_element_name($key);
$var_element = self::_process($value, $key);
$element->appendChild($var_element);
}
} else {
$element->appendChild(self::$dom->createTextNode($data));
}
return $element;
}
/**
* This function takes a string and returns an empty DOMElement
* with a valid name. If the passed `$name` is a valid QName, the handle of
* this name will be the name of the element, otherwise this will fallback to 'key'.
*
* @see toolkit.Lang#createHandle
* @param string $name
* If the `$name` is not a valid QName it will be ignored and replaced with
* 'key'. If this happens, a `@value` attribute will be set with the original
* `$name`. If `$name` is a valid QName, it will be run through `Lang::createHandle`
* to create a handle for the element.
* @return DOMElement
* An empty DOMElement with `@handle` and `@value` attributes.
*/
private static function _valid_element_name($name)
{
if (Lang::isUnicodeCompiled()) {
$valid_name = preg_match('/^[\p{L}]([0-9\p{L}\.\-\_]+)?$/u', $name);
} else {
$valid_name = preg_match('/^[A-z]([\w\d-_\.]+)?$/i', $name);
}
if ($valid_name) {
$xKey = self::$dom->createElement(
Lang::createHandle($name)
);
} else {
$xKey = self::$dom->createElement('key');
}
$xKey->setAttribute('handle', Lang::createHandle($name));
$xKey->setAttribute('value', General::sanitize($name));
return $xKey;
}
}