Passed
Push — master ( bdaf9b...dea557 )
by Thomas
02:29
created

Json::getKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Serialize a DOM into a Json structure.
4
 *
5
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
6
 * @copyright Copyright (c) 2009-2017 FluentDOM Contributors
7
 */
8
9
namespace FluentDOM\Serializer {
10
11
  use FluentDOM\DOM\Document;
12
  use FluentDOM\DOM\Element;
13
  use FluentDOM\DOM\Xpath;
14
15
  /**
16
   * Serialize a DOM into a Json structure. This loader allows to save an imported Json back as JSON.
17
   *
18
   * Using this on a standard XML document will ignore a lot of data. Namespaces and Attributes
19
   * are ignored, if here are two elements with the same name only the last will be in the output.
20
   * If an element has child elements, all text child nodes will be ignored.
21
   *
22
   * See the other serializers, to keep this data.
23
   *
24
   * This serializer recognizes attributes from the JsonDOM namespaces. If you import an JSON to a DOM
25
   * in FluentDOM, the additional information is stored in these attributes (types, names, ...)
26
   *
27
   * Here is a example of an XML:
28
   *
29
   * <?xml version="1.0" encoding="UTF-8"?>
30
   * <json:json xmlns:json="urn:carica-json-dom.2013">
31
   *   <boolean json:type="boolean">true</boolean>
32
   *   <int json:type="number">42</int>
33
   *   <null json:type="null"/>
34
   *   <string>Foo</string>
35
   *   <array json:type="array">
36
   *     <_ json:type="number">21</_>
37
   *   </array>
38
   *   <acomplexname json:type="object" json:name="a complex name"/>
39
   * </json:json>
40
   *
41
   * @license http://www.opensource.org/licenses/mit-license.php The MIT License
42
   * @copyright Copyright (c) 2009-2017 FluentDOM Contributors
43
   */
44
  class Json implements \JsonSerializable {
45
46
    const XMLNS_JSONDOM = 'urn:carica-json-dom.2013';
47
48
    /**
49
     * @var \DOMNode
50
     */
51
    protected $_node = NULL;
52
53
    /**
54
     * @var int
55
     */
56
    private $_options = 0;
57
58
    /**
59
     * @var int
60
     */
61
    private $_depth = 512;
62
63
    /**
64
     * Allow the use of the recursion limitation argument
65
     * @var bool
66
     */
67
    private $_useDepth = FALSE;
68
69
    /**
70
     * @param \DOMNode $node
71
     * @param int $options
72
     * @param int $depth
73
     */
74 14
    public function __construct(\DOMNode $node, int $options = 0, int $depth = 512) {
75 14
      $this->_node = $node;
76 14
      $this->_options = (int)$options;
77 14
      $this->_depth = (int)$depth;
78 14
    }
79
80
    /**
81
     * @return string
82
     */
83 8
    public function __toString(): string {
84 8
      $json = json_encode($this, $this->_options, $this->_depth);
85 8
      return $json ?: '';
86
    }
87
88
    /**
89
     * @return mixed
90
     */
91 7
    public function jsonSerialize() {
92
      /** @var Element|Document $node */
93 7
      $node = $this->_node;
94 7
      if ($node instanceof \DOMDocument) {
95 7
        $node = $node->documentElement;
96
      }
97 7
      if ($node instanceof \DOMElement) {
98 5
        return $this->getNode($node);
99
      }
100 2
      return $this->getEmpty();
101
    }
102
103
    /**
104
     * @param \DOMElement $node
105
     * @return mixed
106
     */
107 4
    protected function getNode(\DOMElement $node) {
108 4
      switch ($this->getType($node)) {
109 4
      case 'object' :
110 4
        $result = new \stdClass();
111
        /** @var \DOMElement $child */
112 4
        foreach ($this->getChildElements($node) as $child) {
113 4
          $result->{$this->getKey($child)} = $this->getNode($child);
114
        }
115 4
        break;
116 4
      case 'array' :
117 1
        $result = [];
118 1
        foreach ($this->getChildElements($node) as $child) {
119 1
          $result[] = $this->getNode($child);
120
        }
121 1
        break;
122 4
      case 'number' :
123 1
        return (float)$node->nodeValue;
124 4
      case 'boolean' :
125 1
        return $node->nodeValue === 'true';
126 4
      case 'null' :
127 1
        return NULL;
128
      default :
129 4
        return $node->nodeValue;
130
      }
131 4
      return $result;
132
    }
133
134
    /**
135
     * @param \DOMElement $source
136
     * @return \DOMNodeList
137
     */
138 4
    private function getChildElements(\DOMElement $source): \DOMNodeList {
139 4
      $xpath = new Xpath($source->ownerDocument);
140 4
      return $xpath('*', $source);
141
    }
142
143
    /**
144
     * @param \DOMElement $node
145
     * @return string
146
     */
147 4 View Code Duplication
    private function getType(\DOMElement $node): string {
148 4
      if ($node->hasAttributeNS(self::XMLNS_JSONDOM, 'type')) {
149 3
        return $node->getAttributeNS(self::XMLNS_JSONDOM, 'type');
150
      } else {
151 3
        $xpath = new Xpath($node->ownerDocument);
152 3
        return $xpath('count(*) > 0', $node) ? 'object' : 'string';
153
      }
154
    }
155
156
    /**
157
     * @param \DOMElement $node
158
     * @return string
159
     */
160 4
    private function getKey(\DOMElement $node): string {
161 4
      if ($node->hasAttributeNS(self::XMLNS_JSONDOM, 'name')) {
162 2
        return $node->getAttributeNS(self::XMLNS_JSONDOM, 'name');
163
      } else {
164 2
        return $node->localName;
165
      }
166
    }
167
168
    /**
169
     * @return mixed
170
     */
171 2
    protected function getEmpty() {
172 2
      return new \stdClass();
173
    }
174
175
    /**
176
     * Get the namespace definitions needed for this node.
177
     *
178
     * If compares the namespaces of the current node with the ones from
179
     * the parent node. Only definitions that are different are returned.
180
     *
181
     * @param \DOMElement $node
182
     * @return array
183
     */
184 1
    protected function getNamespaces(\DOMElement $node): array {
185 1
      $result = $this->getAllNamespaces($node);
186 1
      $inherited = [];
187 1
      if ($node->parentNode instanceOf \DOMElement) {
188 1
        $inherited = $this->getAllNamespaces($node->parentNode);
189
      }
190 1
      return array_diff_assoc($result, $inherited);
191
    }
192
193
    /**
194
     * @param \DOMElement $node
195
     * @return array
196
     */
197 1
    private function getAllNamespaces(\DOMElement $node): array {
198 1
      $xpath = new Xpath($node->ownerDocument);
199 1
      $result = [];
200
      /** @var \DOMNodeList $nodes */
201 1
      $nodes = $xpath->evaluate('namespace::*', $node);
202 1
      foreach ($nodes as $namespaceNode) {
203
        if (
204 1
          ($namespaceNode->nodeName !== 'xmlns:xml') &&
205 1
          ($namespaceNode->nodeName !== 'xmlns:xmlns')
206
        ) {
207 1
          $result[$namespaceNode->nodeName] = $namespaceNode->namespaceURI;
208
        }
209
      };
210 1
      return $result;
211
    }
212
  }
213
}