Completed
Push — master ( c78c30...f35f13 )
by Joschi
02:42
created

AbstractElementProcessor   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 98.08%

Importance

Changes 0
Metric Value
wmc 17
lcom 2
cbo 5
dl 0
loc 244
ccs 51
cts 52
cp 0.9808
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A processElementChildren() 0 5 1
processChild() 0 1 ?
processProperty() 0 1 ?
A splitProperty() 0 7 1
A processPropertyPrefixName() 0 9 2
getVocabulary() 0 1 ?
A addProperty() 0 23 2
getResourceId() 0 1 ?
getPropertyValue() 0 1 ?
A getThing() 0 8 1
B getThingByPrefixType() 0 25 4
B getPropertyStringValue() 0 18 5
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\Context\ContextInterface;
40
use Jkphl\RdfaLiteMicrodata\Application\Contract\ElementProcessorInterface;
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\VocabularyInterface;
45
use Jkphl\RdfaLiteMicrodata\Infrastructure\Exceptions\OutOfBoundsException;
46
use Jkphl\RdfaLiteMicrodata\Infrastructure\Exceptions\RuntimeException;
47
48
/**
49
 * Abstract element processor
50
 *
51
 * @package Jkphl\RdfaLiteMicrodata
52
 * @subpackage Jkphl\RdfaLiteMicrodata\Infrastructure
53
 */
54
abstract class AbstractElementProcessor 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
        'METER' => 'value',
76
        'TIME' => 'datetime'
77
    ];
78
    /**
79
     * HTML mode
80
     *
81
     * @var boolean
82
     */
83
    protected $html;
84
85
    /**
86
     * Element processor constructor
87
     *
88
     * @param bool $html HTML mode
89
     */
90 13
    public function __construct($html = false)
91
    {
92 13
        $this->html = boolval($html);
93 13
    }
94
95
    /**
96
     * Process a DOM element's child
97
     *
98
     * @param \DOMElement $element DOM element
99
     * @param ContextInterface $context Context
100
     * @return ContextInterface Context for children
101
     */
102 13
    public function processElementChildren(\DOMElement $element, ContextInterface $context)
103
    {
104
        // Process a child
105 13
        return $this->processChild($element, $context);
106
    }
107
108
    /**
109
     * Create a nested child
110
     *
111
     * @param \DOMElement $element DOM element
112
     * @param ContextInterface $context Context
113
     * @return ContextInterface Context for children
114
     */
115
    abstract protected function processChild(\DOMElement $element, ContextInterface $context);
116
117
    /**
118
     * Create a property
119
     *
120
     * @param \DOMElement $element DOM element
121
     * @param ContextInterface $context Inherited Context
122
     * @return ContextInterface Local context for this element
123
     */
124
    abstract protected function processProperty(\DOMElement $element, ContextInterface $context);
125
126
    /**
127
     * Split a property into prefix and name
128
     *
129
     * @param string $property Prefixed property
130
     * @return array Prefix and name
131
     */
132 5
    protected function splitProperty($property)
133
    {
134 5
        $property = explode(':', $property);
135 5
        $name = strval(array_pop($property));
136 5
        $prefix = strval(array_pop($property));
137 5
        return [$prefix, $name];
138
    }
139
140
    /**
141
     * Create a property by prefix and name
142
     *
143
     * @param string $prefix Property prefix
144
     * @param string $name Property name
145
     * @param \DOMElement $element DOM element
146
     * @param ContextInterface $context Inherited Context
147
     * @return ContextInterface Local context for this element
148
     */
149 7
    protected function processPropertyPrefixName($prefix, $name, \DOMElement $element, ContextInterface $context)
150
    {
151 7
        $vocabulary = $this->getVocabulary($prefix, $context);
152 7
        if ($vocabulary instanceof VocabularyInterface) {
153 7
            $context = $this->addProperty($element, $context, $name, $vocabulary);
154 6
        }
155
156 6
        return $context;
157
    }
158
159
160
    /**
161
     * Return a vocabulary by prefix with fallback to the default vocabulary
162
     *
163
     * @param string $prefix Vocabulary prefix
164
     * @param ContextInterface $context Context
165
     * @return VocabularyInterface Vocabulary
166
     */
167
    abstract protected function getVocabulary($prefix, ContextInterface $context);
168
169
    /**
170
     * Add a single property
171
     *
172
     * @param \DOMElement $element DOM element
173
     * @param ContextInterface $context Inherited Context
174
     * @param string $name Property name
175
     * @param VocabularyInterface $vocabulary Property vocabulary
176
     * @return ContextInterface Local context for this element
177
     */
178 7
    protected function addProperty(
179
        \DOMElement $element,
180
        ContextInterface $context,
181
        $name,
182
        VocabularyInterface $vocabulary
183
    ) {
184 7
        $resourceId = $this->getResourceId($element);
185
186
        // Get the property value
187 7
        $propertyValue = $this->getPropertyValue($element, $context);
188 7
        $property = new Property($name, $vocabulary, $propertyValue, $resourceId);
189
190
        // Add the property to the current parent thing
191 6
        $context->getParentThing()->addProperty($property);
192
193
        // If the property value is a thing
194 6
        if ($propertyValue instanceof ThingInterface) {
195
            // Set the thing as parent thing for nested iterations
196 3
            $context = $context->setParentThing($propertyValue);
197 3
        }
198
199 6
        return $context;
200
    }
201
202
    /**
203
     * Return the resource ID
204
     *
205
     * @param \DOMElement $element DOM element
206
     * @return string|null Resource ID
207
     */
208
    abstract protected function getResourceId(\DOMElement $element);
209
210
    /**
211
     * Return a property value (type and tag name dependent)
212
     *
213
     * @param \DOMElement $element DOM element
214
     * @param ContextInterface $context Context
215
     * @return ThingInterface|string Property value
216
     */
217
    abstract protected function getPropertyValue(\DOMElement $element, ContextInterface $context);
218
219
    /**
220
     * Return a thing by typeof value
221
     *
222
     * @param string $typeof Thing type
223
     * @param string $resourceId Resource ID
224
     * @param ContextInterface $context Context
225
     * @return Thing Thing
226
     */
227 13
    protected function getThing($typeof, $resourceId, ContextInterface $context)
228
    {
229 13
        $typeof = explode(':', $typeof);
230 13
        $type = array_pop($typeof);
231 13
        $prefix = array_pop($typeof);
232
233 13
        return $this->getThingByPrefixType($prefix, $type, $resourceId, $context);
234
    }
235
236
    /**
237
     * Return a thing by prefix and type
238
     *
239
     * @param string $prefix Prefix
240
     * @param string $type Type
241
     * @param string $resourceId Resource ID
242
     * @param ContextInterface $context Context
243
     * @return Thing Thing
244
     * @throws RuntimeException If the default vocabulary is empty
245
     * @throws OutOfBoundsException If the vocabulary prefix is unknown
246
     */
247 13
    protected function getThingByPrefixType($prefix, $type, $resourceId, ContextInterface $context)
248
    {
249
        // Determine the vocabulary to use
250
        try {
251 13
            $vocabulary = $this->getVocabulary($prefix, $context);
252 10
            if ($vocabulary instanceof VocabularyInterface) {
253 7
                return new Thing($type, $vocabulary, $resourceId);
254
            }
255
256
            // If the default vocabulary is empty
257 3
            if (empty($prefix)) {
258 3
                throw new RuntimeException(
259 3
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY_STR,
260
                    RuntimeException::EMPTY_DEFAULT_VOCABULARY
261 3
                );
262
            }
263 6
        } catch (\Jkphl\RdfaLiteMicrodata\Application\Exceptions\OutOfBoundsException $e) {
264
            // Promote to client level exception
265
        }
266
267 3
        throw new OutOfBoundsException(
268 3
            sprintf(OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX_STR, $prefix),
269
            OutOfBoundsException::UNKNOWN_VOCABULARY_PREFIX
270 3
        );
271
    }
272
273
    /**
274
     * Return a property value (type and tag name dependent)
275
     *
276
     * @param \DOMElement $element DOM element
277
     * @return ThingInterface|string Property value
278
     */
279 7
    protected function getPropertyStringValue(\DOMElement $element)
280
    {
281
        // If HTML mode is active
282 7
        if ($this->html) {
283 6
            $tagName = strtoupper($element->tagName);
284
285
            // Map to an attribute (if applicable)
286 6
            if (array_key_exists($tagName, self::$tagNameAttributes)) {
287 5
                $value = strval($element->getAttribute(self::$tagNameAttributes[$tagName]));
288 5
                if (($tagName != 'TIME') || !empty($value)) {
289 5
                    return $value;
290
                }
291
            }
292 6
        }
293
294
        // Return the text content
295 7
        return $element->textContent;
296
    }
297
}
298