Completed
Push — v2 ( 7179d5...641c9a )
by Joschi
06:18
created

JsonLD::parseNodeProperties()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 46
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 0
cts 0
cp 0
rs 5.5555
c 0
b 0
f 0
cc 8
eloc 21
nc 12
nop 1
crap 72
1
<?php
2
3
/**
4
 * micrometa
5
 *
6
 * @category Jkphl
7
 * @package Jkphl\Micrometa
8
 * @subpackage Jkphl\Micrometa\Infrastructure\Parser
9
 * @author Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright Copyright © 2017 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2017 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Jkphl\Micrometa\Infrastructure\Parser;
38
39
use Jkphl\Micrometa\Application\Contract\ParsingResultInterface;
40
use Jkphl\Micrometa\Ports\Format;
41
use ML\JsonLD\Exception\JsonLdException;
42
use ML\JsonLD\JsonLD as JsonLDParser;
43
use ML\JsonLD\Node;
44
use ML\JsonLD\NodeInterface;
45
use ML\JsonLD\TypedValue;
46
use ML\JsonLD\Value;
47
48
/**
49
 * JsonLD parser
50
 *
51
 * @package Jkphl\Micrometa
52
 * @subpackage Jkphl\Micrometa\Infrastructure
53
 */
54
class JsonLD extends AbstractParser
55
{
56
    /**
57
     * Format
58
     *
59
     * @var int
60
     */
61
    const FORMAT = Format::JSON_LD;
62
    /**
63
     * Regex pattern for matching leading comments in a JSON string
64
     *
65
     * @var string
66
     */
67
    const JSON_COMMENT_PATTERN = '#(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|([\s\t]//.*)|(^//.*)#';
68
69
    /**
70
     * Parse a DOM document
71
     *
72
     * @param \DOMDocument $dom DOM Document
73
     * @return ParsingResultInterface Micro information items
74
     */
75
    public function parseDom(\DOMDocument $dom)
76
    {
77
        $items = [];
78
79
        // Find and process all JSON-LD blocks
80
        $xpath = new \DOMXPath($dom);
81
        $xpath->registerNamespace('html', self::HTML_PROFILE_URI);
82
        foreach ($xpath->query('//html:script[@type = "application/ld+json"]') as $jsonLDDoc) {
83
            $jsonLDDocSource = preg_replace(self::JSON_COMMENT_PATTERN, '', $jsonLDDoc->textContent);
84
            $items = array_merge($items, $this->parseDocument($jsonLDDocSource));
85
        }
86
87
        // TODO: Implement parseDom() method.
88
        return new ParsingResult(self::FORMAT, $items);
89
    }
90
91
    /**
92
     * Parse a JSON-LD document
93
     *
94
     * @param string $jsonLDDocSource JSON-LD document
95
     * @return array Items
96
     */
97
    protected function parseDocument($jsonLDDocSource)
98
    {
99
        $jsonLDDoc = @json_decode($jsonLDDocSource);
100
        return array_filter(
101
            is_array($jsonLDDoc) ?
102
                array_map([$this, 'parseRootNode'], $jsonLDDoc) : [$this->parseRootNode($jsonLDDoc)]
103
        );
104
    }
105
106
    /**
107
     * Parse a JSON-LD root node
108
     *
109
     * @param \stdClass $jsonDLRoot JSON-LD root node
110
     */
111
    protected function parseRootNode($jsonDLRoot)
112
    {
113
        $item = null;
114
115
        try {
116
            // Run through all nodes to parse the first one
117
            /** @var Node $node */
118
            foreach (JsonLDParser::getDocument($jsonDLRoot)->getGraph()->getNodes() as $node) {
119
                $item = $this->parseNode($node);
120
                break;
121
            }
122
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
123
        } catch (JsonLdException $e) {
124
            trigger_error($e->getMessage(), E_USER_WARNING);
125
        }
126
127
        return $item;
128
    }
129
130
    /**
131
     * Parse a JSON-LD fragment
132
     *
133
     * @param Node|TypedValue|array $jsonLD JSON-LD fragment
134
     * @return mixed Parsed fragment
135
     */
136
    protected function parse($jsonLD)
137
    {
138
        // If it's a node object
139
        if ($jsonLD instanceof NodeInterface) {
140
            return $this->parseNode($jsonLD);
141
142
            // Else if it's a value
143
        } elseif ($jsonLD instanceof Value) {
144
            return $this->parseValue($jsonLD);
145
146
            // Else if it's a list of items
147
        } elseif (is_array($jsonLD)) {
148
            return array_map([$this, 'parse'], $jsonLD);
149
        }
150
151
        trigger_error('Unknown JSON-LD item: '.gettype($jsonLD), E_USER_NOTICE);
152
        return null;
153
    }
154
155
    /**
156
     * Parse a JSON-LD node
157
     *
158
     * @param Node $node Node
159
     * @return \stdClass Item
160
     */
161
    protected function parseNode(Node $node)
162
    {
163
        return (object)[
164
            'type' => $this->parseNodeType($node),
165
            'id' => $node->getId() ?: null,
166
            'properties' => $this->parseNodeProperties($node),
167
        ];
168
    }
169
170
    /**
171
     * Parse the type of a JSON-LD node
172
     *
173
     * @param Node $node Node
174
     * @return array Item type
175
     */
176
    protected function parseNodeType(Node $node)
177
    {
178
        /** @var Node $itemType */
179
        return ($itemType = $node->getType()) ? [$itemType->getId()] : [];
180
    }
181
182
    /**
183
     * Parse the properties of a JSON-LD node
184
     *
185
     * @param Node $node Node
186
     * @return array Item properties
187
     */
188
    protected function parseNodeProperties(Node $node)
189
    {
190
        $properties = [];
191
192
        // Run through all node properties
193
        foreach ($node->getProperties() as $name => $property) {
194
            // Skip the node type
195
            if ($name === Node::TYPE) {
196
                continue;
197
            }
198
199
            // Initialize the property
200
            if (empty($properties[$name])) {
201
                $properties[$name] = (object)[
202
                    'name' => $name,
203
                    'profile' => '',
204
                    'values' => [],
205
                ];
206
            }
207
208
            // Parse the property value
209
            $value = $this->parse($property);
210
211
            // If this is a nested item
212
            if (is_object($value)) {
213
                if (!empty($value->type)) {
214
                    $properties[$name]->values[] = $value;
215
216
                    // @type = @id
217
                } else {
218
                    $properties[$name]->values[] = $value->id;
219
                }
220
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
221
            } elseif (is_array($value)) {
222
                $properties[$name]->values = array_merge($properties[$name]->values, $value);
223
224
                // Else
225
            } elseif ($value) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $value of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
226
                $properties[$name]->values[] = $value;
227
            }
228
        }
229
230
//        print_r($properties);
231
232
        return $properties;
233
    }
234
235
    /**
236
     * Parse a typed value
237
     *
238
     * @param TypedValue $value Typed value
239
     * @return string Value
240
     */
241
    protected function parseValue(TypedValue $value)
242
    {
243
        return $value->getValue();
244
    }
245
246
    /**
247
     * Filter empty values
248
     *
249
     * @param array|string $value Value
250
     * @return bool Value is not empty
251
     */
252
    protected function filter($value)
253
    {
254
        return is_array($value) ? !!count($value) : strlen($value);
255
    }
256
}
257