Completed
Push — master ( da3279...199ae8 )
by Joschi
02:50
created

RdfaliteElementProcessor   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 35
c 0
b 0
f 0
lcom 1
cbo 5
dl 0
loc 201
ccs 0
cts 112
cp 0
rs 9

7 Methods

Rating   Name   Duplication   Size   Complexity  
A processElement() 0 16 2
A processVocab() 0 9 2
A processPrefix() 0 13 3
A processProperty() 0 18 4
D getPropertyValue() 0 42 17
B getThing() 0 27 4
A processTypeof() 0 16 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
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
     * Process a DOM element
58
     *
59
     * @param \DOMElement $element DOM element
60
     * @param Context $context Context
61
     * @return Context Context for children
62
     */
63
    public function processElement(\DOMElement $element, Context $context)
64
    {
65
        // Process default vocabulary registrations
66
        $context = $this->processVocab($element, $context);
67
68
        // Register vocabulary prefixes
69
        $context = $this->processPrefix($element, $context);
70
71
        // Create properties
72
        if ($element->hasAttribute('property')) {
73
            return $this->processProperty($element, $context);
74
        }
75
76
        // Process nested children
77
        return $this->processTypeof($element, $context);
78
    }
79
80
    /**
81
     * Process changes of the default vocabulary
82
     *
83
     * @param \DOMElement $element DOM element
84
     * @param Context $context Context
85
     * @return Context Context for children
86
     */
87
    protected function processVocab(\DOMElement $element, Context $context)
88
    {
89
        if ($element->hasAttribute('vocab')) {
90
            $defaultVocabulary = new Vocabulary($element->getAttribute('vocab'));
91
            $context = $context->setDefaultVocabulary($defaultVocabulary);
92
        }
93
94
        return $context;
95
    }
96
97
    /**
98
     * Process vocabulary prefixes
99
     *
100
     * @param \DOMElement $element DOM element
101
     * @param Context $context Context
102
     * @return Context Context for children
103
     */
104
    protected function processPrefix(\DOMElement $element, Context $context)
105
    {
106
        if ($element->hasAttribute('prefix')) {
107
            $prefixes = preg_split('/\s+/', $element->getAttribute('prefix'));
108
            while (count($prefixes)) {
109
                $prefix = rtrim(array_shift($prefixes), ':');
110
                $uri = array_shift($prefixes);
111
                $context = $context->registerVocabulary($prefix, $uri);
112
            }
113
        }
114
115
        return $context;
116
    }
117
118
    /**
119
     * Create properties
120
     *
121
     * @param \DOMElement $element DOM element
122
     * @param Context $context Context
123
     * @return Context Context for children
124
     */
125
    protected function processProperty(\DOMElement $element, Context $context)
126
    {
127
        if ($element->hasAttribute('property')) {
128
            $property = explode(':', $element->getAttribute('property'));
129
            $name = array_pop($property);
130
            $prefix = array_pop($property);
131
132
            // Determine the vocabulary to use
133
            $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
134
            if ($vocabulary instanceof VocabularyInterface) {
135
                // Add the property to the current parent thing
136
                $property = new Property($name, $vocabulary, $this->getPropertyValue($element, $context));
0 ignored issues
show
Bug introduced by
It seems like $this->getPropertyValue($element, $context) targeting Jkphl\Rdfalite\Infrastru...sor::getPropertyValue() can also be of type null or object<Jkphl\Rdfalite\Do...n\Thing\ThingInterface>; however, Jkphl\Rdfalite\Domain\Pr...Property::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
137
                $context->getParentThing()->addProperty($property);
138
            }
139
        }
140
141
        return $context;
142
    }
143
144
    /**
145
     * Return a property value (type and tag name dependent)
146
     *
147
     * @param \DOMElement $element DOM element
148
     * @param Context $context Context
149
     * @return ThingInterface|NULL|string Property value
150
     */
151
    protected function getPropertyValue(\DOMElement $element, Context $context)
152
    {
153
        // If the property creates a new type: Return the element itself
154
        if ($element->hasAttribute('typeof')) {
155
            return $this->getThing($element->getAttribute('typeof'), $context);
156
        }
157
158
        // Else: Depend on the tag name
159
        switch (strtoupper($element->tagName)) {
160
            case 'META':
161
                return $element->getAttribute('content');
162
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
163
            case 'AUDIO':
164
            case 'EMBED':
165
            case 'IFRAME':
166
            case 'IMG':
167
            case 'SOURCE':
168
            case 'TRACK':
169
            case 'VIDEO':
170
                return $element->getAttribute('src');
171
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
172
            case 'A':
173
            case 'AREA':
174
            case 'LINK':
175
                return $element->getAttribute('href');
176
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
177
            case 'OBJECT':
178
                return $element->getAttribute('data');
179
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
180
            case 'DATA':
181
                return $element->getAttribute('value');
182
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
183
            case 'TIME':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
184
                $datetime = $element->getAttribute('datetime');
185
                if (!empty($datetime)) {
186
                    return $datetime;
187
                }
188
            default:
189
//              trigger_error(sprintf('RDFa Lite 1.1 element processor: Unhandled tag name "%s"', $element->tagName), E_USER_WARNING);
190
                return $element->textContent;
191
        }
192
    }
193
194
    /**
195
     * Return a property value (type and tag name dependent)
196
     *
197
     * @param string $typeof Thing type
198
     * @param Context $context Context
199
     * @return ThingInterface|NULL|string Property value
200
     * @throws OutOfBoundsException If the default vocabulary is empty
201
     * @throws OutOfBoundsException If the vocabulary prefix is unknown
202
     */
203
    protected function getThing($typeof, Context $context)
204
    {
205
        $typeof = explode(':', $typeof);
206
        $type = array_pop($typeof);
207
        $prefix = array_pop($typeof);
208
209
        // Determine the vocabulary to use
210
        $vocabulary = empty($prefix) ? $context->getDefaultVocabulary() : $context->getVocabulary($prefix);
211
        if ($vocabulary instanceof VocabularyInterface) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
212
213
            // Return a new thing
214
            return new Thing($type, $vocabulary);
215
        }
216
217
        // If the default vocabulary is empty
218
        if (empty($prefix)) {
219
            throw new OutOfBoundsException(
220
                OutOfBoundsException::EMPTY_DEFAULT_VOCABULARY_STR,
221
                OutOfBoundsException::EMPTY_DEFAULT_VOCABULARY
222
            );
223
        }
224
225
        throw new OutOfBoundsException(
226
            sprintf(OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX_STR, $prefix),
227
            OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX
228
        );
229
    }
230
231
    /**
232
     * Create nested children
233
     *
234
     * @param \DOMElement $element DOM element
235
     * @param Context $context Context
236
     * @return Context Context for children
237
     */
238
    protected function processTypeof(\DOMElement $element, Context $context)
239
    {
240
        if ($element->hasAttribute('typeof')) {
241
            $thing = $this->getThing($element->getAttribute('typeof'), $context);
242
243
            if ($thing instanceof ThingInterface) {
244
                // Add the new thing as a child to the current context
245
                $context->addChild($thing);
246
247
                // Set the thing as parent thing for nested iterations
248
                $context = $context->setParentThing($thing);
249
            }
250
        }
251
252
        return $context;
253
    }
254
}
255