Completed
Push — master ( 0d820b...10ea42 )
by Joschi
02:40
created

RdfaliteElementProcessor::processProperty()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 30
ccs 17
cts 17
cp 1
rs 6.7272
c 3
b 0
f 0
cc 7
eloc 14
nc 11
nop 2
crap 7
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
48
/**
49
 * RDFa Lite 1.1 element processor
50
 *
51
 * @package Jkphl\Rdfalite
52
 * @subpackage Jkphl\Rdfalite\Infrastructure
53
 */
54
class RdfaliteElementProcessor implements ElementProcessorInterface
55
{
56
    /**
57
     * Tag name / attribute map
58
     *
59
     * @var array
60
     */
61
    protected static $tagNameAttributes = [
62
        'META' => 'content',
63
        'AUDIO' => 'src',
64
        'EMBED' => 'src',
65
        'IFRAME' => 'src',
66
        'IMG' => 'src',
67
        'SOURCE' => 'src',
68
        'TRACK' => 'src',
69
        'VIDEO' => 'src',
70
        'A' => 'href',
71
        'AREA' => 'href',
72
        'LINK' => 'href',
73
        'OBJECT' => 'data',
74
        'DATA' => 'value',
75
        'TIME' => 'datetime'
76
    ];
77
78
    /**
79
     * Process a DOM element
80
     *
81
     * @param \DOMElement $element DOM element
82
     * @param Context $context Inherited Context
83
     * @return Context Local context for this element
84
     */
85 5
    public function processElement(\DOMElement $element, Context $context)
86
    {
87
        // Process default vocabulary registrations
88 5
        $context = $this->processVocab($element, $context);
89
90
        // Register vocabulary prefixes
91 5
        $context = $this->processPrefix($element, $context);
92
93
        // Create properties
94 5
        $context = $this->processProperty($element, $context);
95 5
        return $context;
96
    }
97
98
    /**
99
     * Process changes of the default vocabulary
100
     *
101
     * @param \DOMElement $element DOM element
102
     * @param Context $context Inherited Context
103
     * @return Context Local context for this element
104
     */
105 5
    protected function processVocab(\DOMElement $element, Context $context)
106
    {
107 5
        if ($element->hasAttribute('vocab')) {
108 4
            $defaultVocabulary = new Vocabulary($element->getAttribute('vocab'));
109 4
            $context = $context->setDefaultVocabulary($defaultVocabulary);
110 4
        }
111
112 5
        return $context;
113
    }
114
115
    /**
116
     * Process vocabulary prefixes
117
     *
118
     * @param \DOMElement $element DOM element
119
     * @param Context $context Inherited Context
120
     * @return Context Local context for this element
121
     */
122 5
    protected function processPrefix(\DOMElement $element, Context $context)
123
    {
124 5
        if ($element->hasAttribute('prefix')) {
125 4
            $prefixes = preg_split('/\s+/', $element->getAttribute('prefix'));
126 4
            while (count($prefixes)) {
127 4
                $prefix = rtrim(array_shift($prefixes), ':');
128 4
                $uri = array_shift($prefixes);
129 4
                $context = $context->registerVocabulary($prefix, $uri);
130 4
            }
131 4
        }
132
133 5
        return $context;
134
    }
135
136
    /**
137
     * Create properties
138
     *
139
     * @param \DOMElement $element DOM element
140
     * @param Context $context Inherited Context
141
     * @return Context Local context for this element
142
     */
143 5
    protected function processProperty(\DOMElement $element, Context $context)
144
    {
145 5
        if ($element->hasAttribute('property') && ($context->getParentThing() instanceof ThingInterface)) {
146 3
            $property = explode(':', $element->getAttribute('property'));
147 3
            $name = strval(array_pop($property));
148 3
            $prefix = strval(array_pop($property));
149
150
            // Determine the vocabulary to use
151 3
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
152 3
            if ($vocabulary instanceof VocabularyInterface) {
153
                // Try to get a resource ID
154 3
                $resourceId = trim($element->getAttribute('resource')) ?: null;
155
156
                // Get the property value
157 3
                $propertyValue = $this->getPropertyValue($element, $context);
158 3
                $property = new Property($name, $vocabulary, $propertyValue, $resourceId);
159
160
                // Add the property to the current parent thing
161 3
                $context->getParentThing()->addProperty($property);
162
163
                // If the property value is a thing
164 3
                if ($propertyValue instanceof ThingInterface) {
165
                    // Set the thing as parent thing for nested iterations
166 1
                    $context = $context->setParentThing($propertyValue);
167 1
                }
168 3
            }
169 3
        }
170
171 5
        return $context;
172
    }
173
174
    /**
175
     * Return a property value (type and tag name dependent)
176
     *
177
     * @param \DOMElement $element DOM element
178
     * @param Context $context Context
179
     * @return ThingInterface|string Property value
180
     */
181 3
    protected function getPropertyValue(\DOMElement $element, Context $context)
182
    {
183
        // If the property creates a new type: Return the element itself
184 3
        if ($element->hasAttribute('typeof')) {
185 1
            return $this->getThing(
186 1
                $element->getAttribute('typeof'),
187 1
                trim($element->getAttribute('resource')) ?: null,
188
                $context
189 1
            );
190
        }
191
192
        // Return a string property value
193 3
        return $this->getPropertyStringValue($element);
194
    }
195
196
    /**
197
     * Return a property value (type and tag name dependent)
198
     *
199
     * @param string $typeof Thing type
200
     * @param string $resourceId Resource ID
201
     * @param Context $context Context
202
     * @return ThingInterface Thing
203
     * @throws OutOfBoundsException If the default vocabulary is empty
204
     * @throws OutOfBoundsException If the vocabulary prefix is unknown
205
     */
206 5
    protected function getThing($typeof, $resourceId, Context $context)
207
    {
208 5
        $typeof = explode(':', $typeof);
209 5
        $type = array_pop($typeof);
210 5
        $prefix = array_pop($typeof);
211
212
        // Determine the vocabulary to use
213
        try {
214 5
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
215 4
            if ($vocabulary instanceof VocabularyInterface) {
216
                // Return a new thing
217 3
                return new Thing($type, $vocabulary, $resourceId);
218
            }
219
220
            // If the default vocabulary is empty
221 1
            if (empty($prefix)) {
222 1
                throw new OutOfBoundsException(
223 1
                    OutOfBoundsException::EMPTY_DEFAULT_VOCABULARY_STR,
224
                    OutOfBoundsException::EMPTY_DEFAULT_VOCABULARY
225 1
                );
226
            }
227 2
        } catch (\Jkphl\Rdfalite\Application\Exceptions\OutOfBoundsException $e) {
228
            // Promote to client level exception
229
        }
230
231 1
        throw new OutOfBoundsException(
232 1
            sprintf(OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX_STR, $prefix),
233
            OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX
234 1
        );
235
    }
236
237
    /**
238
     * Return a property value (type and tag name dependent)
239
     *
240
     * @param \DOMElement $element DOM element
241
     * @return ThingInterface|string Property value
242
     */
243 3
    protected function getPropertyStringValue(\DOMElement $element)
244
    {
245 3
        $tagName = strtoupper($element->tagName);
246
247
        // Map to an attribute (if applicable)
248 3
        if (array_key_exists($tagName, self::$tagNameAttributes)) {
249 3
            $value = strval($element->getAttribute(self::$tagNameAttributes[$tagName]));
250 3
            if (($tagName != 'TIME') || !empty($value)) {
251 3
                return $value;
252
            }
253
        }
254
255
        // Return the text content
256
//        trigger_error(sprintf('RDFa Lite 1.1 element processor: Unhandled tag name "%s"', $element->tagName), E_USER_WARNING);
257 3
        return $element->textContent;
258
    }
259
260
    /**
261
     * Process a DOM element's children
262
     *
263
     * @param \DOMElement $element DOM element
264
     * @param Context $context Context
265
     * @return Context Context for children
266
     */
267 5
    public function processElementChildren(\DOMElement $element, Context $context)
268
    {
269
        // Process nested children
270 5
        return $this->processTypeof($element, $context);
271
    }
272
273
    /**
274
     * Create nested children
275
     *
276
     * @param \DOMElement $element DOM element
277
     * @param Context $context Context
278
     * @return Context Context for children
279
     */
280 5
    protected function processTypeof(\DOMElement $element, Context $context)
281
    {
282 5
        if ($element->hasAttribute('typeof') && empty($element->getAttribute('property'))) {
283 5
            $thing = $this->getThing(
284 5
                $element->getAttribute('typeof'),
285 5
                trim($element->getAttribute('resource')) ?: null,
286
                $context
287 5
            );
288
289
            // Add the new thing as a child to the current context
290
            // and set the thing as parent thing for nested iterations
291 3
            $context = $context->addChild($thing)->setParentThing($thing);
292 3
        }
293
294 5
        return $context;
295
    }
296
}
297