Completed
Push — master ( 342540...c82ee6 )
by Joschi
02:27
created

AbstractElementProcessor   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 93.65%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 6
dl 0
loc 290
c 0
b 0
f 0
ccs 59
cts 63
cp 0.9365
rs 10

16 Methods

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