Completed
Push — master ( 4c1b75...efb757 )
by Joschi
02:59
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
            $property = explode(':', $element->getAttribute('property'));
148 4
            $name = strval(array_pop($property));
149 4
            $prefix = strval(array_pop($property));
150
151
            // Determine the vocabulary to use
152 4
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
153 4
            if ($vocabulary instanceof VocabularyInterface) {
154 4
                $context = $this->addProperty($element, $context, $name, $vocabulary);
155 4
            }
156 4
        }
157
158 8
        return $context;
159
    }
160
161
    /**
162
     * Create properties
163
     *
164
     * @param \DOMElement $element DOM element
165
     * @param Context $context Inherited Context
166
     * @param string $name Property name
167
     * @param VocabularyInterface $vocabulary Property vocabulary
168
     * @return Context Local context for this element
169
     */
170 4
    protected function addProperty(\DOMElement $element, Context $context, $name, VocabularyInterface $vocabulary)
171
    {
172
        // Try to get a resource ID
173 4
        $resourceId = trim($element->getAttribute('resource')) ?: null;
174
175
        // Get the property value
176 4
        $propertyValue = $this->getPropertyValue($element, $context);
177 4
        $property = new Property($name, $vocabulary, $propertyValue, $resourceId);
178
179
        // Add the property to the current parent thing
180 4
        $context->getParentThing()->addProperty($property);
181
182
        // If the property value is a thing
183 4
        if ($propertyValue instanceof ThingInterface) {
184
            // Set the thing as parent thing for nested iterations
185 2
            $context = $context->setParentThing($propertyValue);
186 2
        }
187
188 4
        return $context;
189
    }
190
191
    /**
192
     * Return a property value (type and tag name dependent)
193
     *
194
     * @param \DOMElement $element DOM element
195
     * @param Context $context Context
196
     * @return ThingInterface|string Property value
197
     */
198 4
    protected function getPropertyValue(\DOMElement $element, Context $context)
199
    {
200
        // If the property creates a new type: Return the element itself
201 4
        if ($element->hasAttribute('typeof')) {
202 2
            return $this->getThing(
203 2
                $element->getAttribute('typeof'),
204 2
                trim($element->getAttribute('resource')) ?: null,
205
                $context
206 2
            );
207
        }
208
209
        // Return a string property value
210 4
        return $this->getPropertyStringValue($element);
211
    }
212
213
    /**
214
     * Return a property value (type and tag name dependent)
215
     *
216
     * @param string $typeof Thing type
217
     * @param string $resourceId Resource ID
218
     * @param Context $context Context
219
     * @return ThingInterface Thing
220
     * @throws RuntimeException If the default vocabulary is empty
221
     * @throws OutOfBoundsException If the vocabulary prefix is unknown
222
     */
223 8
    protected function getThing($typeof, $resourceId, Context $context)
224
    {
225 8
        $typeof = explode(':', $typeof);
226 8
        $type = array_pop($typeof);
227 8
        $prefix = array_pop($typeof);
228
229
        // Determine the vocabulary to use
230
        try {
231 8
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
232 6
            if ($vocabulary instanceof VocabularyInterface) {
233
                // Return a new thing
234 4
                return new Thing($type, $vocabulary, $resourceId);
235
            }
236
237
            // If the default vocabulary is empty
238 2
            if (empty($prefix)) {
239 2
                throw new RuntimeException(
240 2
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY_STR,
241
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY
242 2
                );
243
            }
244 4
        } catch (\Jkphl\Rdfalite\Application\Exceptions\OutOfBoundsException $e) {
245
            // Promote to client level exception
246
        }
247
248 2
        throw new OutOfBoundsException(
249 2
            sprintf(OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX_STR, $prefix),
250
            OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX
251 2
        );
252
    }
253
254
    /**
255
     * Return a property value (type and tag name dependent)
256
     *
257
     * @param \DOMElement $element DOM element
258
     * @return ThingInterface|string Property value
259
     */
260 4
    protected function getPropertyStringValue(\DOMElement $element)
261
    {
262 4
        $tagName = strtoupper($element->tagName);
263
264
        // Map to an attribute (if applicable)
265 4
        if (array_key_exists($tagName, self::$tagNameAttributes)) {
266 4
            $value = strval($element->getAttribute(self::$tagNameAttributes[$tagName]));
267 4
            if (($tagName != 'TIME') || !empty($value)) {
268 4
                return $value;
269
            }
270
        }
271
272
        // Return the text content
273
//        trigger_error(sprintf('RDFa Lite 1.1 element processor: Unhandled tag name "%s"', $element->tagName), E_USER_WARNING);
274 4
        return $element->textContent;
275
    }
276
277
    /**
278
     * Process a DOM element's children
279
     *
280
     * @param \DOMElement $element DOM element
281
     * @param Context $context Context
282
     * @return Context Context for children
283
     */
284 8
    public function processElementChildren(\DOMElement $element, Context $context)
285
    {
286
        // Process nested children
287 8
        return $this->processTypeof($element, $context);
288
    }
289
290
    /**
291
     * Create nested children
292
     *
293
     * @param \DOMElement $element DOM element
294
     * @param Context $context Context
295
     * @return Context Context for children
296
     */
297 8
    protected function processTypeof(\DOMElement $element, Context $context)
298
    {
299 8
        if ($element->hasAttribute('typeof') && empty($element->getAttribute('property'))) {
300 8
            $thing = $this->getThing(
301 8
                $element->getAttribute('typeof'),
302 8
                trim($element->getAttribute('resource')) ?: null,
303
                $context
304 8
            );
305
306
            // Add the new thing as a child to the current context
307
            // and set the thing as parent thing for nested iterations
308 4
            $context = $context->addChild($thing)->setParentThing($thing);
309 4
        }
310
311 8
        return $context;
312
    }
313
}
314