Passed
Push — master ( ab4527...297d0e )
by Joschi
01:46
created

CalcLengthFactory::handleWordToken()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 6
nop 4
dl 0
loc 32
ccs 0
cts 15
cp 0
crap 42
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * responsive-images-css
5
 *
6
 * @category   Jkphl
7
 * @package    Jkphl\Respimgcss
8
 * @subpackage Jkphl\Respimgcss\Application\Factory
9
 * @author     Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright  Copyright © 2018 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 © 2018 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\Respimgcss\Application\Factory;
38
39
use ChrisKonnertz\StringCalc\Parser\Nodes\ContainerNode;
40
use ChrisKonnertz\StringCalc\StringCalc;
41
use ChrisKonnertz\StringCalc\Tokenizer\Token;
42
use Jkphl\Respimgcss\Application\Contract\UnitLengthInterface;
43
use Jkphl\Respimgcss\Application\Exceptions\InvalidArgumentException;
44
use Jkphl\Respimgcss\Application\Model\ViewportFunction;
45
46
/**
47
 * Length factory for calc() based values
48
 *
49
 * @package    Jkphl\Respimgcss
50
 * @subpackage Jkphl\Respimgcss\Application\Factory
51
 */
52
class CalcLengthFactory
53
{
54
    public static function createFromString(string $calcString, int $emPixel = 16)
55
    {
56
        // If the calc() string is ill-formatted
57
        if (!preg_match('/^calc\(.+\)$/', $calcString)) {
58
            throw new InvalidArgumentException(
59
                sprintf(InvalidArgumentException::ILL_FORMATTED_CALC_LENGTH_STRING_STR, $calcString),
60
                InvalidArgumentException::ILL_FORMATTED_CALC_LENGTH_STRING
61
            );
62
        }
63
64
        print_r(self::createCalculationContainerFromString($calcString, $emPixel));
65
66
//        // Parse using a dummy shell
67
//        $parser      = new Parser("a{width:$calcString}");
68
//        $cssDocument = $parser->parse();
69
//        /** @var DeclarationBlock $cssDeclarationBlock */
70
//        $cssDeclarationBlock = $cssDocument->getContents()[0];
71
//        /** @var Rule $cssRule */
72
//        $cssRule = $cssDeclarationBlock->getRules()[0];
73
//        /** @var CSSFunction $cssFunction */
74
//        $cssFunction = $cssRule->getValue();
75
////        print_r($cssFunction->getName());
76
        echo $calcString;
77
    }
78
79
    /**
80
     * Parse a calculation string and return a precompiled calculation node container
81
     *
82
     * @param string $calcString Calculation string
83
     * @param int $emPixel       EM to pixel ratio
84
     *
85
     * @return ContainerNode Calculation node container
86
     * @throws \ChrisKonnertz\StringCalc\Exceptions\ContainerException
87
     * @throws \ChrisKonnertz\StringCalc\Exceptions\InvalidIdentifierException
88
     * @throws \ChrisKonnertz\StringCalc\Exceptions\NotFoundException
89
     */
90
    protected static function createCalculationContainerFromString(string $calcString, int $emPixel = 16): ContainerNode
91
    {
92
        $stringCalc    = new StringCalc();
93
        $calcTokens    = $stringCalc->tokenize($calcString);
94
        $refinedTokens = self::refineCalculationTokens($calcTokens, $emPixel);
95
        $stringHelper  = $stringCalc->getContainer()->get('stringcalc_stringhelper');
96
        $stringCalc->getSymbolContainer()->add(new ViewportFunction($stringHelper));
97
98
        return $stringCalc->parse($refinedTokens);
99
    }
100
101
    /**
102
     * Refine a list of symbol tokens
103
     *
104
     * @param Token[] $tokens Symbol tokens
105
     * @param int $emPixel    EM to pixel ratio
106
     *
107
     * @return Token[] Refined symbol tokens
108
     */
109
    protected static function refineCalculationTokens(array $tokens, int $emPixel = 16): array
110
    {
111
        $refinedTokens = [];
112
        $previousToken = null;
113
114
        // Run through all tokens
115
        foreach ($tokens as $token) {
116
            $previousToken = self::handleToken($refinedTokens, $token, $previousToken, $emPixel);
117
        }
118
119
        // Add the last token
120
        if ($previousToken) {
121
            array_push($refinedTokens, $previousToken);
122
        }
123
124
        return $refinedTokens;
125
    }
126
127
    /**
128
     * Handle a particular token
129
     *
130
     * @param Token[] $refinedTokens    Refined tokens
131
     * @param Token $token              Token
132
     * @param Token|null $previousToken Previous token
133
     * @param int $emPixel              EM to pixel ratio
134
     *
135
     * @return Token|null               Stash token
136
     */
137
    protected static function handleToken(
138
        array &$refinedTokens,
139
        Token $token,
140
        Token $previousToken = null,
141
        int $emPixel = 16
142
    ): ?Token {
143
        // If it's a word token: Handle individually
144
        if ($token->getType() == Token::TYPE_WORD) {
145
            return self::handleWordToken($refinedTokens, $token, $previousToken, $emPixel);
146
        }
147
148
        // In all other cases: Register the previou token (if any)
149
        if ($previousToken) {
150
            array_push($refinedTokens, $previousToken);
151
        }
152
153
        // If it's a number token: Stash
154
        if ($token->getType() == Token::TYPE_NUMBER) {
155
            return $token;
156
        }
157
158
        array_push($refinedTokens, $token);
159
        return null;
160
    }
161
162
    /**
163
     * Handle a particular token
164
     *
165
     * The method returns a list of zero or more (possibly refined) tokens to preerve
166
     *
167
     * @param Token[] $refinedTokens    Refined tokens
168
     * @param Token $token              Token
169
     * @param Token|null $previousToken Previous token
170
     * @param int $emPixel              EM to pixel ratio
171
     *
172
     * @return Token|null               Stash token
173
     * @throws InvalidArgumentException If the word token is invalid
174
     */
175
    protected static function handleWordToken(
176
        array &$refinedTokens,
177
        Token $token,
178
        Token $previousToken = null,
179
        int $emPixel = 16
180
    ): ?Token {
181
        // If it's a calc() function call: Add the previous token and skip the current one
182
        if ($token->getValue() == 'calc') {
183
            if ($previousToken) {
184
                array_push($refinedTokens, $previousToken);
185
            }
186
            return null;
187
        }
188
189
        // If the previous token is a number: Try to generate a unit length
190
        if ($previousToken && ($previousToken->getType() == Token::TYPE_NUMBER)) {
191
            try {
192
                $unitLength = LengthFactory::createLengthFromString(
193
                    $previousToken->getValue().$token->getValue(),
194
                    $emPixel
195
                );
196
                self::handleUnitLengthToken($refinedTokens, $unitLength);
197
                return null;
198
            } catch (InvalidArgumentException $e) {
199
                // Ignore
200
            }
201
        }
202
203
        // Invalid word token
204
        throw new InvalidArgumentException(
205
            sprintf(InvalidArgumentException::INVALID_WORD_TOKEN_IN_SOURCE_SIZE_VALUE_STR, $token->getValue()),
206
            InvalidArgumentException::INVALID_WORD_TOKEN_IN_SOURCE_SIZE_VALUE
207
        );
208
    }
209
210
    /**
211
     * Handle a unit length token
212
     *
213
     * @param Token[] $refinedTokens          Refined tokens
214
     * @param UnitLengthInterface $unitLength Unit length
215
     */
216
    protected static function handleUnitLengthToken(
217
        array &$refinedTokens,
218
        UnitLengthInterface $unitLength
219
    ): void {
220
        // If it's an absolute value
221
        if ($unitLength->isAbsolute()) {
222
            array_push($refinedTokens, new Token(strval($unitLength->getValue()), Token::TYPE_NUMBER, 0));
223
            return;
224
        }
225
226
        // Else: Substitute with multiplied function expression
227
        array_push(
228
            $refinedTokens,
229
            new Token('(', Token::TYPE_CHARACTER, 0),
230
            new Token(strval($unitLength->getValue() / 100), Token::TYPE_NUMBER, 0),
231
            new Token('*', Token::TYPE_CHARACTER, 0),
232
            new Token('viewport', Token::TYPE_WORD, 0),
233
            new Token('(', Token::TYPE_CHARACTER, 0),
234
            new Token(')', Token::TYPE_CHARACTER, 0),
235
            new Token(')', Token::TYPE_CHARACTER, 0)
236
        );
237
    }
238
}