Passed
Push — new-api ( 7cf340...18d26d )
by Sebastian
12:35 queued 08:17
created

Text   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Test Coverage

Coverage 97.24%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 145
c 1
b 0
f 0
dl 0
loc 294
ccs 141
cts 145
cp 0.9724
rs 7.92
wmc 51

15 Methods

Rating   Name   Duplication   Size   Complexity  
C render() 0 39 15
A __construct() 0 18 1
A getVariable() 0 3 1
B renderVariable() 0 27 8
A normalizeDateRange() 0 6 2
A renderMacro() 0 13 3
A renderPage() 0 19 5
A renderCitationNumber() 0 5 1
B factory() 0 35 7
A formatRenderedText() 0 6 1
A getParent() 0 3 1
A setParent() 0 3 1
A renderLocator() 0 14 3
A applyAdditionalMarkupFunction() 0 3 1
A getSource() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Text often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Text, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
/*
4
 * citeproc-php
5
 *
6
 * @link        http://github.com/seboettg/citeproc-php for the source repository
7
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
8
 * @license     https://opensource.org/licenses/MIT
9
 */
10
11
namespace Seboettg\CiteProc\Rendering\Text;
12
13
use Seboettg\CiteProc\CiteProc;
14
use Seboettg\CiteProc\Config\RenderingMode;
15
use Seboettg\CiteProc\Exception\CiteProcException;
16
use Seboettg\CiteProc\Config\RenderingState;
17
use Seboettg\CiteProc\Locale\Locale;
18
use Seboettg\CiteProc\Rendering\HasParent;
19
use Seboettg\CiteProc\Rendering\Observer\RenderingObserver;
20
use Seboettg\CiteProc\Rendering\Observer\RenderingObserverTrait;
21
use Seboettg\CiteProc\Rendering\Rendering;
22
use Seboettg\CiteProc\Style\Options\GlobalOptions;
23
use Seboettg\CiteProc\Styles\StylesRenderer;
24
use Seboettg\CiteProc\Terms\Locator;
25
use Seboettg\CiteProc\Util\CiteProcHelper;
26
use Seboettg\CiteProc\Util\NumberHelper;
27
use Seboettg\CiteProc\Util\PageHelper;
28
use Seboettg\CiteProc\Util\StringHelper;
29
use Seboettg\Collection\ArrayList\ArrayListInterface;
30
use SimpleXMLElement;
31
use stdClass;
32
use function Seboettg\CiteProc\getCurrentById;
33
use function Seboettg\CiteProc\ucfirst;
34
35
class Text implements HasParent, Rendering, RenderingObserver
36
{
37
    use RenderingObserverTrait;
38
39
    /** @var RenderType|null */
40
    private $renderType;
41
42
    /** @var string */
43
    private $renderObject;
44
45
    /** @var string */
46
    private $form;
47
48
    /** @var Locale|null */
49
    private $locale;
50
51
    /** @var ArrayListInterface */
52
    private $macros;
53
54
    /** @var StylesRenderer */
55
    private $stylesRenderer;
56
57
    /** @var GlobalOptions */
58
    private $globalOptions;
59
60
    private $parent;
61
62 120
    public static function factory(SimpleXMLElement $node): Text
63
    {
64 120
        $renderObject = "";
65 120
        $renderType = $form = null;
66 120
        foreach ($node->attributes() as $attribute) {
67 120
            $name = $attribute->getName();
68
            switch ($name) {
69 120
                case RenderType::TERM:
70 118
                case RenderType::MACRO:
71 114
                case RenderType::VARIABLE:
72 98
                case RenderType::VALUE:
73 120
                    $renderType = new RenderType($name);
74 120
                    $renderObject = (string) $attribute;
75 120
                    break;
76 78
                case "form":
77 42
                    $form = (string) $attribute;
78 120
                    break;
79
            }
80
        }
81 120
        $context = CiteProc::getContext();
82 120
        $locale = $context->getLocale();
83 120
        $macros = $context->getMacros();
84 120
        $globalOptions = $context->getGlobalOptions();
85 120
        $stylesRenderer = StylesRenderer::factory($node);
86 120
        $text = new self(
87 120
            $renderType,
88 120
            $renderObject,
89 120
            $form,
90 120
            $locale,
91 120
            $macros,
92 120
            $globalOptions,
93 120
            $stylesRenderer
94
        );
95 120
        $context->addObserver($text);
96 120
        return $text;
97
    }
98
99
    /**
100
     * Text constructor.
101
     * @param RenderType|null $renderType
102
     * @param string $renderObject
103
     * @param string|null $form
104
     * @param Locale|null $locale
105
     * @param ArrayListInterface $macros
106
     * @param ?GlobalOptions $globalOptions
107
     * @param StylesRenderer $stylesRenderer
108
     */
109 120
    public function __construct(
110
        ?RenderType $renderType,
111
        string $renderObject,
112
        ?string $form,
113
        ?Locale $locale,
114
        ArrayListInterface $macros,
115
        ?GlobalOptions $globalOptions,
116
        StylesRenderer $stylesRenderer
117
    ) {
118 120
        $this->renderType = $renderType;
119 120
        $this->renderObject = $renderObject;
120 120
        $this->form = ($form ?? "long");
121 120
        $this->locale = $locale;
122 120
        $this->macros = $macros;
123 120
        $this->globalOptions = $globalOptions;
124 120
        $this->stylesRenderer = $stylesRenderer;
125
126 120
        $this->initObserver();
127 120
    }
128
129
    /**
130
     * @param  stdClass $data
131
     * @param  int|null $citationNumber
132
     * @return string
133
     */
134 105
    public function render($data, $citationNumber = null): string
135
    {
136 105
        $lang = (isset($data->language) && $data->language != 'en') ? $data->language : 'en';
137 105
        $this->stylesRenderer->getTextCase()->setLanguage($lang);
138 105
        $renderedText = "";
139 105
        switch ((string)$this->renderType) {
140 105
            case RenderType::VALUE:
141 26
                $renderedText = $this->stylesRenderer->renderTextCase($this->renderObject);
142 26
                break;
143 98
            case RenderType::VARIABLE:
144 87
                if ($this->renderObject === "locator" && $this->mode->equals(RenderingMode::CITATION())) {
145 8
                    $renderedText = $this->renderLocator($data, $citationNumber);
146 87
                } elseif ($this->renderObject === "citation-number") {
147 16
                    $renderedText = $this->renderCitationNumber($data, $citationNumber);
148 16
                    break;
149 83
                } elseif (in_array($this->renderObject, ["page", "chapter-number", "folio"])) {
150 35
                    $renderedText = !empty($data->{$this->renderObject}) ?
151 35
                        $this->renderPage($data->{$this->renderObject}) : '';
152
                } else {
153 79
                    $renderedText = $this->renderVariable($data);
154
                }
155 85
                if ($this->state->equals(RenderingState::SUBSTITUTION())) {
156 7
                    unset($data->{$this->renderObject});
157
                }
158 85
                $renderedText = $this->applyAdditionalMarkupFunction($data, $renderedText);
159 85
                break;
160 71
            case RenderType::MACRO:
161 64
                $renderedText = $this->renderMacro($data);
162 64
                break;
163 27
            case RenderType::TERM:
164 27
                $term = $this->locale
165 27
                    ->filter("terms", $this->renderObject, $this->form)
0 ignored issues
show
Bug introduced by
The method filter() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

165
                    ->/** @scrutinizer ignore-call */ filter("terms", $this->renderObject, $this->form)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
166 27
                    ->single;
167 27
                $renderedText = !empty($term) ? $this->stylesRenderer->renderTextCase($term) : "";
168
        }
169 105
        if (!empty($renderedText)) {
170 105
            $renderedText = $this->formatRenderedText($renderedText);
171
        }
172 105
        return $renderedText;
173
    }
174
175 69
    public function getSource(): RenderType
176
    {
177 69
        return $this->renderType;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->renderType could return the type null which is incompatible with the type-hinted return Seboettg\CiteProc\Rendering\Text\RenderType. Consider adding an additional type-check to rule them out.
Loading history...
178
    }
179
180
    /**
181
     * @return string
182
     */
183 69
    public function getVariable(): string
184
    {
185 69
        return $this->renderObject;
186
    }
187
188 24
    private function renderPage($page): string
189
    {
190 24
        if (preg_match(NumberHelper::PATTERN_COMMA_AMPERSAND_RANGE, $page)) {
191 22
            $page = $this->normalizeDateRange($page);
192 22
            $ranges = preg_split("/[-–]/", trim($page));
193 22
            if (count($ranges) > 1) {
0 ignored issues
show
Bug introduced by
It seems like $ranges can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
            if (count(/** @scrutinizer ignore-type */ $ranges) > 1) {
Loading history...
194 21
                if (!empty($this->globalOptions)
195 21
                    && !empty($this->globalOptions->getPageRangeFormat())
196
                ) {
197 9
                    return PageHelper::processPageRangeFormats(
198 9
                        $ranges,
0 ignored issues
show
Bug introduced by
It seems like $ranges can also be of type false; however, parameter $ranges of Seboettg\CiteProc\Util\P...ocessPageRangeFormats() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

198
                        /** @scrutinizer ignore-type */ $ranges,
Loading history...
199 9
                        $this->globalOptions->getPageRangeFormat()
200
                    );
201
                }
202 12
                list($from, $to) = $ranges;
203 12
                return $from . "–" . $to;
204
            }
205
        }
206 4
        return $page;
207
    }
208
209 8
    private function renderLocator($data, $citationNumber): string
210
    {
211 8
        $citationItem = getCurrentById($this->citationItems, $data->id);
212 8
        if (!empty($citationItem->label)) {
213 4
            $locatorData = new stdClass();
214 4
            $propertyName = Locator::mapLocatorLabelToRenderVariable($citationItem->label);
215 4
            $locatorData->{$propertyName} = trim($citationItem->locator);
216 4
            $renderTypeValueTemp = $this->renderObject;
217 4
            $this->renderObject = $propertyName;
218 4
            $result = $this->render($locatorData, $citationNumber);
219 4
            $this->renderObject = $renderTypeValueTemp;
220 4
            return $result;
221
        }
222 4
        return isset($citationItem->locator) ? trim($citationItem->locator) : '';
223
    }
224
225 22
    private function normalizeDateRange($page): string
226
    {
227 22
        if (preg_match("/^(\d+)\s?--?\s?(\d+)$/", trim($page), $matches)) {
228 21
            return $matches[1]."-".$matches[2];
229
        }
230 1
        return $page;
231
    }
232
233
    /**
234
     * @param  $data
235
     * @param  $renderedText
236
     * @return string
237
     */
238 87
    private function applyAdditionalMarkupFunction($data, $renderedText)
239
    {
240 87
        return CiteProcHelper::applyAdditionMarkupFunction($data, $this->renderObject, $renderedText);
241
    }
242
243
    /**
244
     * @param  $data
245
     * @return string
246
     */
247 79
    private function renderVariable($data): string
248
    {
249
        // check if there is an attribute with prefix short or long e.g. shortTitle or longAbstract
250
        // test case group_ShortOutputOnly.json
251 79
        $value = "";
252 79
        if (in_array($this->form, ["short", "long"])) {
253 79
            $attrWithPrefix = $this->form . ucfirst($this->renderObject);
254 79
            $attrWithSuffix = sprintf("%s-%s", $this->renderObject, $this->form);
255 79
            if (isset($data->{$attrWithPrefix}) && !empty($data->{$attrWithPrefix})) {
256 1
                $value = $data->{$attrWithPrefix};
257
            } else {
258 78
                if (isset($data->{$attrWithSuffix}) && !empty($data->{$attrWithSuffix})) {
259 3
                    $value = $data->{$attrWithSuffix};
260
                } else {
261 78
                    if (isset($data->{$this->renderObject})) {
262 79
                        $value = $data->{$this->renderObject};
263
                    }
264
                }
265
            }
266
        } else {
267
            if (!empty($data->{$this->renderObject})) {
268
                $value = $data->{$this->renderObject};
269
            }
270
        }
271 79
        return $this->stylesRenderer->renderTextCase(
272 79
            StringHelper::clearApostrophes(
273 79
                htmlspecialchars((string)$value, ENT_HTML5)
274
            )
275
        );
276
    }
277
278
    /**
279
     * @param  $renderedText
280
     * @return string
281
     */
282 105
    private function formatRenderedText($renderedText): string
283
    {
284 105
        $text = $this->stylesRenderer->renderFormatting((string)$renderedText);
285 105
        $res = $this->stylesRenderer->renderAffixes($text);
286 105
        $res = $this->stylesRenderer->renderQuotes($res);
287 105
        return $this->stylesRenderer->renderDisplay($res);
288
    }
289
290
    /**
291
     * @param  $data
292
     * @param  $citationNumber
293
     * @return int|mixed
294
     */
295 16
    private function renderCitationNumber($data, $citationNumber): string
296
    {
297 16
        $renderedText = $citationNumber + 1;
298 16
        $renderedText = $this->applyAdditionalMarkupFunction($data, $renderedText);
299 16
        return (string)$renderedText;
300
    }
301
302
    /**
303
     * @param  $data
304
     * @return string
305
     */
306 64
    private function renderMacro($data): string
307
    {
308 64
        $macro = $this->macros->get($this->renderObject);
309 64
        if (is_null($macro)) {
310
            try {
311 1
                throw new CiteProcException("Macro \"".$this->renderObject."\" does not exist.");
312 1
            } catch (CiteProcException $e) {
313 1
                $renderedText = "";
314
            }
315
        } else {
316 64
            $renderedText = $macro->render($data);
317
        }
318 64
        return $renderedText;
319
    }
320
321
    public function getParent()
322
    {
323
        return $this->parent;
324
    }
325
326 79
    public function setParent($parent)
327
    {
328 79
        $this->parent = $parent;
329 79
    }
330
}
331