Completed
Push — master ( 701262...c78c30 )
by Joschi
02:44
created

RdfaLiteElementProcessor::getVocabulary()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 1
b 0
f 0
cc 2
eloc 2
nc 2
nop 2
crap 2
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\Contract\ElementProcessorInterface;
40
use Jkphl\RdfaLiteMicrodata\Application\Parser\Context;
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\Vocabulary\Vocabulary;
45
use Jkphl\RdfaLiteMicrodata\Domain\Vocabulary\VocabularyInterface;
46
use Jkphl\RdfaLiteMicrodata\Infrastructure\Exceptions\OutOfBoundsException;
47
use Jkphl\RdfaLiteMicrodata\Infrastructure\Exceptions\RuntimeException;
48
49
/**
50
 * RDFa Lite 1.1 element processor
51
 *
52
 * @package Jkphl\RdfaLiteMicrodata
53
 * @subpackage Jkphl\RdfaLiteMicrodata\Infrastructure
54
 */
55
class RdfaLiteElementProcessor 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
        'TIME' => 'datetime'
77
    ];
78
79
    /**
80
     * Process a DOM element
81
     *
82
     * @param \DOMElement $element DOM element
83
     * @param Context $context Inherited Context
84
     * @return Context Local context for this element
85
     */
86 8
    public function processElement(\DOMElement $element, Context $context)
87
    {
88
        // Process default vocabulary registrations
89 8
        $context = $this->processVocab($element, $context);
90
91
        // Register vocabulary prefixes
92 8
        $context = $this->processPrefix($element, $context);
93
94
        // Create a property
95 8
        $context = $this->processProperty($element, $context);
96 8
        return $context;
97
    }
98
99
    /**
100
     * Process changes of the default vocabulary
101
     *
102
     * @param \DOMElement $element DOM element
103
     * @param Context $context Inherited Context
104
     * @return Context Local context for this element
105
     */
106 8
    protected function processVocab(\DOMElement $element, Context $context)
107
    {
108 8
        if ($element->hasAttribute('vocab')) {
109 6
            $defaultVocabulary = new Vocabulary($element->getAttribute('vocab'));
110 6
            $context = $context->setDefaultVocabulary($defaultVocabulary);
111
        }
112
113 8
        return $context;
114
    }
115
116
    /**
117
     * Process vocabulary prefixes
118
     *
119
     * @param \DOMElement $element DOM element
120
     * @param Context $context Inherited Context
121
     * @return Context Local context for this element
122
     */
123 8
    protected function processPrefix(\DOMElement $element, Context $context)
124
    {
125 8
        if ($element->hasAttribute('prefix')) {
126 6
            $prefixes = preg_split('/\s+/', $element->getAttribute('prefix'));
127 6
            while (count($prefixes)) {
128 6
                $prefix = rtrim(array_shift($prefixes), ':');
129 6
                $uri = array_shift($prefixes);
130 6
                $context = $context->registerVocabulary($prefix, $uri);
131
            }
132
        }
133
134 8
        return $context;
135
    }
136
137
    /**
138
     * Create a property
139
     *
140
     * @param \DOMElement $element DOM element
141
     * @param Context $context Inherited Context
142
     * @return Context Local context for this element
143
     */
144 8
    protected function processProperty(\DOMElement $element, Context $context)
145
    {
146 8
        if ($element->hasAttribute('property') && ($context->getParentThing() instanceof ThingInterface)) {
147 4
            list($prefix, $name) = $this->splitProperty($element->getAttribute('property'));
148 4
            $context = $this->processPropertyPrefixName($prefix, $name, $element, $context);
149
        }
150
151 8
        return $context;
152
    }
153
154
    /**
155
     * Split a property into prefix and name
156
     *
157
     * @param string $property Prefixed property
158
     * @return array Prefix and name
159
     */
160 4
    protected function splitProperty($property)
161
    {
162 4
        $property = explode(':', $property);
163 4
        $name = strval(array_pop($property));
164 4
        $prefix = strval(array_pop($property));
165 4
        return [$prefix, $name];
166
    }
167
168
    /**
169
     * Create a property
170
     *
171
     * @param string $prefix Property prefix
172
     * @param string $name Property name
173
     * @param \DOMElement $element DOM element
174
     * @param Context $context Inherited Context
175
     * @return Context Local context for this element
176
     */
177 4
    protected function processPropertyPrefixName($prefix, $name, \DOMElement $element, Context $context)
178
    {
179 4
        $vocabulary = $this->getVocabulary($prefix, $context);
180 4
        if ($vocabulary instanceof VocabularyInterface) {
181 4
            $context = $this->addProperty($element, $context, $name, $vocabulary);
182
        }
183
184 4
        return $context;
185
    }
186
187
    /**
188
     * Return a vocabulary by prefix with fallback to the default vocabulary
189
     *
190
     * @param string $prefix Vocabulary prefix
191
     * @param Context $context Context
192
     * @return VocabularyInterface Vocabulary
193
     */
194 8
    protected function getVocabulary($prefix, Context $context)
195
    {
196 8
        return empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
197
    }
198
199
    /**
200
     * Create properties
201
     *
202
     * @param \DOMElement $element DOM element
203
     * @param Context $context Inherited Context
204
     * @param string $name Property name
205
     * @param VocabularyInterface $vocabulary Property vocabulary
206
     * @return Context Local context for this element
207
     */
208 4
    protected function addProperty(\DOMElement $element, Context $context, $name, VocabularyInterface $vocabulary)
209
    {
210
        // Try to get a resource ID
211 4
        $resourceId = trim($element->getAttribute('resource')) ?: null;
212
213
        // Get the property value
214 4
        $propertyValue = $this->getPropertyValue($element, $context);
215 4
        $property = new Property($name, $vocabulary, $propertyValue, $resourceId);
216
217
        // Add the property to the current parent thing
218 4
        $context->getParentThing()->addProperty($property);
219
220
        // If the property value is a thing
221 4
        if ($propertyValue instanceof ThingInterface) {
222
            // Set the thing as parent thing for nested iterations
223 2
            $context = $context->setParentThing($propertyValue);
224
        }
225
226 4
        return $context;
227
    }
228
229
    /**
230
     * Return a property value (type and tag name dependent)
231
     *
232
     * @param \DOMElement $element DOM element
233
     * @param Context $context Context
234
     * @return ThingInterface|string Property value
235
     */
236 4
    protected function getPropertyValue(\DOMElement $element, Context $context)
237
    {
238
        // If the property creates a new type: Return the element itself
239 4
        if ($element->hasAttribute('typeof')) {
240 2
            return $this->getThing(
241 2
                $element->getAttribute('typeof'),
242 2
                trim($element->getAttribute('resource')) ?: null,
243
                $context
244
            );
245
        }
246
247
        // Return a string property value
248 4
        return $this->getPropertyStringValue($element);
249
    }
250
251
    /**
252
     * Return a thing by typeof value
253
     *
254
     * @param string $typeof Thing type
255
     * @param string $resourceId Resource ID
256
     * @param Context $context Context
257
     * @return ThingInterface Thing
258
     */
259 8
    protected function getThing($typeof, $resourceId, Context $context)
260
    {
261 8
        $typeof = explode(':', $typeof);
262 8
        $type = array_pop($typeof);
263 8
        $prefix = array_pop($typeof);
264
265 8
        return $this->getThingByPrefixType($prefix, $type, $resourceId, $context);
266
    }
267
268
    /**
269
     * Return a thing by prefix and type
270
     *
271
     * @param string $prefix Prefix
272
     * @param string $type Type
273
     * @param string $resourceId Resource ID
274
     * @param Context $context Context
275
     * @return ThingInterface Thing
276
     * @throws RuntimeException If the default vocabulary is empty
277
     * @throws OutOfBoundsException If the vocabulary prefix is unknown
278
     */
279 8
    protected function getThingByPrefixType($prefix, $type, $resourceId, Context $context)
280
    {
281
        // Determine the vocabulary to use
282
        try {
283 8
            $vocabulary = $this->getVocabulary($prefix, $context);
284 6
            if ($vocabulary instanceof VocabularyInterface) {
285 4
                return new Thing($type, $vocabulary, $resourceId);
286
            }
287
288
            // If the default vocabulary is empty
289 2
            if (empty($prefix)) {
290 2
                throw new RuntimeException(
291 2
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY_STR,
292 2
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY
293
                );
294
            }
295 4
        } catch (\Jkphl\RdfaLiteMicrodata\Application\Exceptions\OutOfBoundsException $e) {
296
            // Promote to client level exception
297
        }
298
299 2
        throw new OutOfBoundsException(
300 2
            sprintf(OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX_STR, $prefix),
301 2
            OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX
302
        );
303
    }
304
305
    /**
306
     * Return a property value (type and tag name dependent)
307
     *
308
     * @param \DOMElement $element DOM element
309
     * @return ThingInterface|string Property value
310
     */
311 4
    protected function getPropertyStringValue(\DOMElement $element)
312
    {
313 4
        $tagName = strtoupper($element->tagName);
314
315
        // Map to an attribute (if applicable)
316 4
        if (array_key_exists($tagName, self::$tagNameAttributes)) {
317 4
            $value = strval($element->getAttribute(self::$tagNameAttributes[$tagName]));
318 4
            if (($tagName != 'TIME') || !empty($value)) {
319 4
                return $value;
320
            }
321
        }
322
323
        // Return the text content
324
//        trigger_error(sprintf('RDFa Lite 1.1 element processor: Unhandled tag name "%s"', $element->tagName), E_USER_WARNING);
325 4
        return $element->textContent;
326
    }
327
328
    /**
329
     * Process a DOM element's children
330
     *
331
     * @param \DOMElement $element DOM element
332
     * @param Context $context Context
333
     * @return Context Context for children
334
     */
335 8
    public function processElementChildren(\DOMElement $element, Context $context)
336
    {
337
        // Process nested children
338 8
        return $this->processTypeof($element, $context);
339
    }
340
341
    /**
342
     * Create nested children
343
     *
344
     * @param \DOMElement $element DOM element
345
     * @param Context $context Context
346
     * @return Context Context for children
347
     */
348 8
    protected function processTypeof(\DOMElement $element, Context $context)
349
    {
350 8
        if ($element->hasAttribute('typeof') && empty($element->getAttribute('property'))) {
351 8
            $thing = $this->getThing(
352 8
                $element->getAttribute('typeof'),
353 8
                trim($element->getAttribute('resource')) ?: null,
354
                $context
355
            );
356
357
            // Add the new thing as a child to the current context
358
            // and set the thing as parent thing for nested iterations
359 4
            $context = $context->addChild($thing)->setParentThing($thing);
360
        }
361
362 8
        return $context;
363
    }
364
}
365