Completed
Push — master ( 81ca7b...002125 )
by Joschi
04:07
created

AbstractElementProcessor::getPropertyStringValue()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 9
cts 10
cp 0.9
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 4
nop 1
crap 5.025
1
<?php
2
3
/**
4
 * rdfa-lite-microdata
5
 *
6
 * @category Jkphl
7
 * @package Jkphl\RdfaLiteMicrodata
8
 * @subpackage Jkphl\RdfaLiteMicrodata\Infrastructure
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\RdfaLiteMicrodata\Infrastructure\Parser;
38
39
use Jkphl\RdfaLiteMicrodata\Application\Context\ContextInterface;
40
use Jkphl\RdfaLiteMicrodata\Application\Contract\ElementProcessorInterface;
41
use Jkphl\RdfaLiteMicrodata\Domain\Property\Property;
42
use Jkphl\RdfaLiteMicrodata\Domain\Thing\Thing;
43
use Jkphl\RdfaLiteMicrodata\Domain\Thing\ThingInterface;
44
use Jkphl\RdfaLiteMicrodata\Domain\Type\Type;
45
use Jkphl\RdfaLiteMicrodata\Domain\Type\TypeInterface;
46
use Jkphl\RdfaLiteMicrodata\Domain\Vocabulary\VocabularyInterface;
47
use Jkphl\RdfaLiteMicrodata\Infrastructure\Exceptions\RuntimeException;
48
49
/**
50
 * Abstract element processor
51
 *
52
 * @package Jkphl\RdfaLiteMicrodata
53
 * @subpackage Jkphl\RdfaLiteMicrodata\Infrastructure
54
 */
55
abstract class AbstractElementProcessor implements ElementProcessorInterface
56
{
57
    /**
58
     * Tag name / attribute map
59
     *
60
     * @var array
61
     */
62
    protected static $tagNameAttributes = [
63
        'META' => 'content',
64
        'AUDIO' => 'src',
65
        'EMBED' => 'src',
66
        'IFRAME' => 'src',
67
        'IMG' => 'src',
68
        'SOURCE' => 'src',
69
        'TRACK' => 'src',
70
        'VIDEO' => 'src',
71
        'A' => 'href',
72
        'AREA' => 'href',
73
        'LINK' => 'href',
74
        'OBJECT' => 'data',
75
        'DATA' => 'value',
76
        'METER' => 'value',
77
        'TIME' => 'datetime'
78
    ];
79
    /**
80
     * Property cache
81
     *
82
     * @var array
83
     */
84
    protected static $propertyCache = [];
85
    /**
86
     * HTML mode
87
     *
88
     * @var boolean
89
     */
90
    protected $html;
91
92
    /**
93
     * Enable / disable HTML element value resolution
94
     *
95
     * @param bool $html Enable HTML element value resolution
96
     * @return ElementProcessorInterface Self reference
97
     */
98 11
    public function setHtml($html)
99
    {
100 11
        $this->html = boolval($html);
101 11
        return $this;
102
    }
103
104
    /**
105
     * Process a DOM element's child
106
     *
107
     * @param \DOMElement $element DOM element
108
     * @param ContextInterface $context Context
109
     * @return ContextInterface Context for children
110
     */
111 14
    public function processElementChildren(\DOMElement $element, ContextInterface $context)
112
    {
113
        // Process a child
114 14
        return $this->processChild($element, $context);
115
    }
116
117
    /**
118
     * Create a nested child
119
     *
120
     * @param \DOMElement $element DOM element
121
     * @param ContextInterface $context Context
122
     * @return ContextInterface Context for children
123
     */
124
    abstract protected function processChild(\DOMElement $element, ContextInterface $context);
125
126
    /**
127
     * Create a property
128
     *
129
     * @param \DOMElement $element DOM element
130
     * @param ContextInterface $context Inherited Context
131
     * @return ContextInterface Local context for this element
132
     */
133
    abstract protected function processProperty(\DOMElement $element, ContextInterface $context);
134
135
    /**
136
     * Create a property by prefix and name
137
     *
138
     * @param string $prefix Property prefix
139
     * @param string $name Property name
140
     * @param \DOMElement $element DOM element
141
     * @param ContextInterface $context Inherited Context
142
     * @param boolean $last Last property
143
     * @return ContextInterface Local context for this element
144
     */
145 8
    protected function processPropertyPrefixName($prefix, $name, \DOMElement $element, ContextInterface $context, $last)
146
    {
147 8
        $vocabulary = $this->getVocabulary($prefix, $context);
148 8
        if ($vocabulary instanceof VocabularyInterface) {
149 8
            $context = $this->addProperty($element, $context, $name, $vocabulary, $last);
150 7
        }
151
152 7
        return $context;
153
    }
154
155
    /**
156
     * Return a vocabulary by prefix with fallback to the default vocabulary
157
     *
158
     * @param string $prefix Vocabulary prefix
159
     * @param ContextInterface $context Context
160
     * @return VocabularyInterface Vocabulary
161
     */
162
    abstract protected function getVocabulary($prefix, ContextInterface $context);
163
164
    /**
165
     * Add a single property
166
     *
167
     * @param \DOMElement $element DOM element
168
     * @param ContextInterface $context Inherited Context
169
     * @param string $name Property name
170
     * @param VocabularyInterface $vocabulary Property vocabulary
171
     * @param boolean $last Last property
172
     * @return ContextInterface Local context for this element
173
     */
174 8
    protected function addProperty(
175
        \DOMElement $element,
176
        ContextInterface $context,
177
        $name,
178
        VocabularyInterface $vocabulary,
179
        $last
180
    ) {
181 8
        $resourceId = $this->getResourceId($element);
182
183
        // Get the property value
184 8
        $propertyValue = $this->getPropertyValue($element, $context);
185 8
        $property = new Property($name, $vocabulary, $propertyValue, $resourceId);
186
187
        // Add the property to the current parent thing
188 7
        $context->getParentThing()->addProperty($property);
189
190
        // If the property value is a thing and this is the element's last property
191 7
        if (($propertyValue instanceof ThingInterface) && $last) {
192
            // Set the thing as parent thing for nested iterations
193 4
            $context = $context->setParentThing($propertyValue);
194 4
        }
195
196 7
        return $context;
197
    }
198
199
    /**
200
     * Return the resource ID
201
     *
202
     * @param \DOMElement $element DOM element
203
     * @return string|null Resource ID
204
     */
205
    abstract protected function getResourceId(\DOMElement $element);
206
207
    /**
208
     * Return a property value (type and tag name dependent)
209
     *
210
     * @param \DOMElement $element DOM element
211
     * @param ContextInterface $context Context
212
     * @return ThingInterface|string Property value
213
     */
214 8
    protected function getPropertyValue(\DOMElement $element, ContextInterface $context)
215
    {
216 8
        $value = $this->getPropertyCache($element);
217 8
        if ($value !== null) {
218 1
            return $value;
219
        }
220
221 8
        $propertyChild = $this->getPropertyChildValue($element, $context);
222 8
        if ($propertyChild instanceof ThingInterface) {
223 4
            return $this->setPropertyCache($element, $propertyChild);
224
        }
225
226
        // Return a string property value
227 8
        return $this->setPropertyCache($element, $this->getPropertyStringValue($element));
228
    }
229
230
    /**
231
     * Return a cached property value (if available)
232
     *
233
     * @param \DOMElement $element Element
234
     * @return mixed|null Property value
235
     */
236 8
    protected function getPropertyCache(\DOMElement $element)
237
    {
238 8
        $elementHash = spl_object_hash($element);
239 8
        return isset(self::$propertyCache[$elementHash]) ? self::$propertyCache[$elementHash] : null;
240
    }
241
242
    /**
243
     * Return a property child value
244
     *
245
     * @param \DOMElement $element DOM element
246
     * @param ContextInterface $context Context
247
     * @return ThingInterface|null Property child value
248
     */
249
    abstract protected function getPropertyChildValue(\DOMElement $element, ContextInterface $context);
250
251
    /**
252
     * Cache a property value
253
     *
254
     * @param \DOMElement $element DOM element
255
     * @param mixed $value Value
256
     * @return mixed $value Value
257
     */
258 8
    protected function setPropertyCache(\DOMElement $element, $value)
259
    {
260 8
        return self::$propertyCache[spl_object_hash($element)] = $value;
261
    }
262
263
    /**
264
     * Return a property value (type and tag name dependent)
265
     *
266
     * @param \DOMElement $element DOM element
267
     * @return ThingInterface|string Property value
268
     */
269 8
    protected function getPropertyStringValue(\DOMElement $element)
270
    {
271
        // If HTML mode is active
272 8
        if ($this->html) {
273 7
            $tagName = strtoupper($element->tagName);
274
275
            // Map to an attribute (if applicable)
276 7
            if (array_key_exists($tagName, self::$tagNameAttributes)) {
277 5
                $value = strval($element->getAttribute(self::$tagNameAttributes[$tagName]));
278 5
                if (($tagName != 'TIME') || !empty($value)) {
279 5
                    return $value;
280
                }
281
            }
282 7
        }
283
284
        // Return the text content
285 8
        return $element->textContent;
286
    }
287
288
    /**
289
     * Return a thing by typeof value
290
     *
291
     * @param string|null $typeof Thing type
292
     * @param string|null $resourceId Resource ID
293
     * @param ContextInterface $context Context
294
     * @return Thing Thing
295
     * @throws RuntimeException If the default vocabulary is empty
296
     */
297 14
    protected function getThing($typeof, $resourceId, ContextInterface $context)
298
    {
299
        /** @var TypeInterface[] $types */
300 14
        $types = [];
301 14
        if (strlen($typeof)) {
302 13
            foreach (preg_split('/\s+/', $typeof) as $prefixedType) {
303 13
                $types[] = $this->getType($prefixedType, $context);
304 7
            }
305 7
        }
306
307 8
        return new Thing($types, $resourceId);
308
    }
309
310
    /**
311
     * Instanciate a type
312
     *
313
     * @param string $prefixedType Prefixed type
314
     * @param ContextInterface $context Context
315
     * @return TypeInterface Type
316
     */
317 13
    protected function getType($prefixedType, ContextInterface $context)
318
    {
319 13
        list($prefix, $typeName) = $this->getPrefixName($prefixedType);
320 13
        $vocabulary = $this->getVocabulary($prefix, $context);
321 10
        if ($vocabulary instanceof VocabularyInterface) {
322 7
            return new Type($typeName, $vocabulary);
323
        }
324
325
        // If the default vocabulary is empty
326 3
        throw new RuntimeException(
327 3
            RuntimeException::EMPTY_DEFAULT_VOCABULARY_STR,
328
            RuntimeException::EMPTY_DEFAULT_VOCABULARY
329 3
        );
330
    }
331
332
    /**
333
     * Split a value into a vocabulary prefix and a name
334
     *
335
     * @param string $prefixName Prefixed name
336
     * @return array Prefix and name
337
     */
338
    abstract protected function getPrefixName($prefixName);
339
}
340