Completed
Push — master ( efb757...0e58f0 )
by Joschi
02:41
created

RdfaliteElementProcessor::addProperty()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
ccs 9
cts 9
cp 1
rs 9.4285
cc 3
eloc 8
nc 4
nop 4
crap 3
1
<?php
2
3
/**
4
 * rdfa-lite
5
 *
6
 * @category Jkphl
7
 * @package Jkphl\Rdfalite
8
 * @subpackage Jkphl\Rdfalite\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\Rdfalite\Infrastructure\Parser;
38
39
use Jkphl\Rdfalite\Application\Contract\ElementProcessorInterface;
40
use Jkphl\Rdfalite\Application\Parser\Context;
41
use Jkphl\Rdfalite\Domain\Property\Property;
42
use Jkphl\Rdfalite\Domain\Thing\Thing;
43
use Jkphl\Rdfalite\Domain\Thing\ThingInterface;
44
use Jkphl\Rdfalite\Domain\Vocabulary\Vocabulary;
45
use Jkphl\Rdfalite\Domain\Vocabulary\VocabularyInterface;
46
use Jkphl\Rdfalite\Infrastructure\Exceptions\OutOfBoundsException;
47
use Jkphl\Rdfalite\Infrastructure\Exceptions\RuntimeException;
48
49
/**
50
 * RDFa Lite 1.1 element processor
51
 *
52
 * @package Jkphl\Rdfalite
53
 * @subpackage Jkphl\Rdfalite\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 properties
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 6
        }
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 6
            }
132 6
        }
133
134 8
        return $context;
135
    }
136
137
    /**
138
     * Create properties
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
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
149 4
            if ($vocabulary instanceof VocabularyInterface) {
150 4
                $context = $this->addProperty($element, $context, $name, $vocabulary);
151 4
            }
152 4
        }
153
154 8
        return $context;
155
    }
156
157
    /**
158
     * Split a property into prefix and name
159
     *
160
     * @param string $property Prefixed property
161
     * @return array Prefix and name
162
     */
163 4
    protected function splitProperty($property)
164
    {
165 4
        $property = explode(':', $property);
166 4
        $name = strval(array_pop($property));
167 4
        $prefix = strval(array_pop($property));
168 4
        return [$prefix, $name];
169
    }
170
171
    /**
172
     * Create properties
173
     *
174
     * @param \DOMElement $element DOM element
175
     * @param Context $context Inherited Context
176
     * @param string $name Property name
177
     * @param VocabularyInterface $vocabulary Property vocabulary
178
     * @return Context Local context for this element
179
     */
180 4
    protected function addProperty(\DOMElement $element, Context $context, $name, VocabularyInterface $vocabulary)
181
    {
182
        // Try to get a resource ID
183 4
        $resourceId = trim($element->getAttribute('resource')) ?: null;
184
185
        // Get the property value
186 4
        $propertyValue = $this->getPropertyValue($element, $context);
187 4
        $property = new Property($name, $vocabulary, $propertyValue, $resourceId);
188
189
        // Add the property to the current parent thing
190 4
        $context->getParentThing()->addProperty($property);
191
192
        // If the property value is a thing
193 4
        if ($propertyValue instanceof ThingInterface) {
194
            // Set the thing as parent thing for nested iterations
195 2
            $context = $context->setParentThing($propertyValue);
196 2
        }
197
198 4
        return $context;
199
    }
200
201
    /**
202
     * Return a property value (type and tag name dependent)
203
     *
204
     * @param \DOMElement $element DOM element
205
     * @param Context $context Context
206
     * @return ThingInterface|string Property value
207
     */
208 4
    protected function getPropertyValue(\DOMElement $element, Context $context)
209
    {
210
        // If the property creates a new type: Return the element itself
211 4
        if ($element->hasAttribute('typeof')) {
212 2
            return $this->getThing(
213 2
                $element->getAttribute('typeof'),
214 2
                trim($element->getAttribute('resource')) ?: null,
215
                $context
216 2
            );
217
        }
218
219
        // Return a string property value
220 4
        return $this->getPropertyStringValue($element);
221
    }
222
223
    /**
224
     * Return a thing by typeof value
225
     *
226
     * @param string $typeof Thing type
227
     * @param string $resourceId Resource ID
228
     * @param Context $context Context
229
     * @return ThingInterface Thing
230
     */
231 8
    protected function getThing($typeof, $resourceId, Context $context)
232
    {
233 8
        $typeof = explode(':', $typeof);
234 8
        $type = array_pop($typeof);
235 8
        $prefix = array_pop($typeof);
236
237 8
        return $this->getThingByPrefixType($prefix, $type, $resourceId, $context);
238
    }
239
240
    /**
241
     * Return a thing by prefix and type
242
     *
243
     * @param string $prefix Prefix
244
     * @param string $type Type
245
     * @param string $resourceId Resource ID
246
     * @param Context $context Context
247
     * @return ThingInterface Thing
248
     * @throws RuntimeException If the default vocabulary is empty
249
     * @throws OutOfBoundsException If the vocabulary prefix is unknown
250
     */
251 8
    protected function getThingByPrefixType($prefix, $type, $resourceId, Context $context)
252
    {
253
        // Determine the vocabulary to use
254
        try {
255 8
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
256 6
            if ($vocabulary instanceof VocabularyInterface) {
257
                // Return a new thing
258 4
                return new Thing($type, $vocabulary, $resourceId);
259
            }
260
261
            // If the default vocabulary is empty
262 2
            if (empty($prefix)) {
263 2
                throw new RuntimeException(
264 2
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY_STR,
265
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY
266 2
                );
267
            }
268 4
        } catch (\Jkphl\Rdfalite\Application\Exceptions\OutOfBoundsException $e) {
269
            // Promote to client level exception
270
        }
271
272 2
        throw new OutOfBoundsException(
273 2
            sprintf(OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX_STR, $prefix),
274
            OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX
275 2
        );
276
    }
277
278
    /**
279
     * Return a property value (type and tag name dependent)
280
     *
281
     * @param \DOMElement $element DOM element
282
     * @return ThingInterface|string Property value
283
     */
284 4
    protected function getPropertyStringValue(\DOMElement $element)
285
    {
286 4
        $tagName = strtoupper($element->tagName);
287
288
        // Map to an attribute (if applicable)
289 4
        if (array_key_exists($tagName, self::$tagNameAttributes)) {
290 4
            $value = strval($element->getAttribute(self::$tagNameAttributes[$tagName]));
291 4
            if (($tagName != 'TIME') || !empty($value)) {
292 4
                return $value;
293
            }
294
        }
295
296
        // Return the text content
297
//        trigger_error(sprintf('RDFa Lite 1.1 element processor: Unhandled tag name "%s"', $element->tagName), E_USER_WARNING);
298 4
        return $element->textContent;
299
    }
300
301
    /**
302
     * Process a DOM element's children
303
     *
304
     * @param \DOMElement $element DOM element
305
     * @param Context $context Context
306
     * @return Context Context for children
307
     */
308 8
    public function processElementChildren(\DOMElement $element, Context $context)
309
    {
310
        // Process nested children
311 8
        return $this->processTypeof($element, $context);
312
    }
313
314
    /**
315
     * Create nested children
316
     *
317
     * @param \DOMElement $element DOM element
318
     * @param Context $context Context
319
     * @return Context Context for children
320
     */
321 8
    protected function processTypeof(\DOMElement $element, Context $context)
322
    {
323 8
        if ($element->hasAttribute('typeof') && empty($element->getAttribute('property'))) {
324 8
            $thing = $this->getThing(
325 8
                $element->getAttribute('typeof'),
326 8
                trim($element->getAttribute('resource')) ?: null,
327
                $context
328 8
            );
329
330
            // Add the new thing as a child to the current context
331
            // and set the thing as parent thing for nested iterations
332 4
            $context = $context->addChild($thing)->setParentThing($thing);
333 4
        }
334
335 8
        return $context;
336
    }
337
}
338