PregEngine::replace()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 7
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Util;
22
23
/**
24
 * PREG Regexp Engine.
25
 * Implements a regexp engine using PHP's preg_match(), preg_match_all(), and preg_replace() functions.
26
 *
27
 * @author  hans lellelid, [email protected]
28
 */
29
class PregEngine implements RegexpEngine
30
{
31
    /**
32
     * Pattern delimiter.
33
     */
34
    public const DELIMITER = '`';
35
    /**
36
     * Set to null by default to distinguish between false and not set.
37
     *
38
     * @var bool
39
     */
40
    private $ignoreCase;
41
42
    /**
43
     * Set to null by default to distinguish between false and not set.
44
     *
45
     * @var bool
46
     */
47
    private $multiline;
48
49
    /**
50
     * Pattern modifiers.
51
     *
52
     * @see http://php.net/manual/en/reference.pcre.pattern.modifiers.php
53
     *
54
     * @var string
55
     */
56
    private $modifiers = '';
57
58
    /**
59
     * Set the limit.
60
     *
61
     * @var int
62
     */
63
    private $limit = -1;
64
65
    /**
66
     * Sets pattern modifiers for regex engine.
67
     *
68
     * @param string $mods Modifiers to be applied to a given regex
69
     */
70 9
    public function setModifiers($mods)
71
    {
72 9
        $this->modifiers = (string) $mods;
73
    }
74
75
    /**
76
     * Gets pattern modifiers.
77
     *
78
     * @return string
79
     */
80 33
    public function getModifiers()
81
    {
82 33
        $mods = $this->modifiers;
83 33
        if ($this->getIgnoreCase()) {
84 11
            $mods .= 'i';
85 22
        } elseif (false === $this->getIgnoreCase()) {
0 ignored issues
show
introduced by
The condition false === $this->getIgnoreCase() is always true.
Loading history...
86 5
            $mods = str_replace('i', '', $mods);
87
        }
88 33
        if ($this->getMultiline()) {
89 2
            $mods .= 's';
90 33
        } elseif (false === $this->getMultiline()) {
0 ignored issues
show
introduced by
The condition false === $this->getMultiline() is always true.
Loading history...
91 5
            $mods = str_replace('s', '', $mods);
92
        }
93
        // filter out duplicates
94 33
        $mods = preg_split('//', $mods, -1, PREG_SPLIT_NO_EMPTY);
95
96 33
        return implode('', array_unique($mods));
97
    }
98
99
    /**
100
     * Sets whether or not regex operation is case sensitive.
101
     *
102
     * @param bool $bit
103
     */
104 16
    public function setIgnoreCase($bit)
105
    {
106 16
        $this->ignoreCase = (bool) $bit;
107
    }
108
109
    /**
110
     * Gets whether or not regex operation is case sensitive.
111
     *
112
     * @return bool
113
     */
114 34
    public function getIgnoreCase()
115
    {
116 34
        return $this->ignoreCase;
117
    }
118
119
    /**
120
     * Sets whether regexp should be applied in multiline mode.
121
     *
122
     * @param bool $bit
123
     */
124 5
    public function setMultiline($bit)
125
    {
126 5
        $this->multiline = $bit;
127
    }
128
129
    /**
130
     * Gets whether regexp is to be applied in multiline mode.
131
     *
132
     * @return bool
133
     */
134 34
    public function getMultiline()
135
    {
136 34
        return $this->multiline;
137
    }
138
139
    /**
140
     * Sets the maximum possible replacements for each pattern.
141
     *
142
     * @param int $limit
143
     */
144 1
    public function setLimit($limit)
145
    {
146 1
        $this->limit = $limit;
147
    }
148
149
    /**
150
     * Returns the maximum possible replacements for each pattern.
151
     *
152
     * @return int
153
     */
154
    public function getLimit()
155
    {
156
        return $this->limit;
157
    }
158
159
    /**
160
     * Matches pattern against source string and sets the matches array.
161
     *
162
     * @param string $pattern the regex pattern to match
163
     * @param string $source  the source string
164
     * @param array  $matches the array in which to store matches
165
     *
166
     * @return bool success of matching operation
167
     */
168 15
    public function match($pattern, $source, &$matches)
169
    {
170 15
        return preg_match($this->preparePattern($pattern), $source, $matches) > 0;
171
    }
172
173
    /**
174
     * Matches all patterns in source string and sets the matches array.
175
     *
176
     * @param string $pattern the regex pattern to match
177
     * @param string $source  the source string
178
     * @param array  $matches the array in which to store matches
179
     *
180
     * @return bool success of matching operation
181
     */
182 1
    public function matchAll($pattern, $source, &$matches)
183
    {
184 1
        return preg_match_all($this->preparePattern($pattern), $source, $matches) > 0;
185
    }
186
187
    /**
188
     * Replaces $pattern with $replace in $source string.
189
     * References to \1 group matches will be replaced with more preg-friendly
190
     * $1.
191
     *
192
     * @param string $pattern the regex pattern to match
193
     * @param string $replace the string with which to replace matches
194
     * @param string $source  the source string
195
     *
196
     * @return string the replaced source string
197
     */
198 8
    public function replace($pattern, $replace, $source)
199
    {
200
        // convert \1 -> $1, because we want to use the more generic \1 in the XML
201
        // but PREG prefers $1 syntax.
202 8
        $replace = preg_replace('/\\\(\d+)/', '\$$1', $replace);
203
204 8
        return preg_replace($this->preparePattern($pattern), $replace, $source, $this->limit);
205
    }
206
207
    /**
208
     * The pattern needs to be converted into PREG style -- which includes adding expression delims & any flags, etc.
209
     *
210
     * @param string $pattern
211
     *
212
     * @return string prepared pattern
213
     */
214 24
    private function preparePattern($pattern)
215
    {
216 24
        $delimiterPattern = '/\\\\*' . self::DELIMITER . '/';
217
218
        // The following block escapes usages of the delimiter in the pattern if it's not already escaped.
219 24
        if (preg_match_all($delimiterPattern, $pattern, $matches, PREG_OFFSET_CAPTURE)) {
220 2
            $diffOffset = 0;
221
222 2
            foreach ($matches[0] as $match) {
223 2
                $str = $match[0];
224 2
                $offset = $match[1] + $diffOffset;
225
226 2
                $escStr = (strlen($str) % 2) ? '\\' . $str : $str; // This will increase an even number of backslashes, before a forward slash, to an odd number.  I.e. '\\/' becomes '\\\/'.
227
228 2
                $diffOffset += strlen($escStr) - strlen($str);
229
230 2
                $pattern = substr_replace($pattern, $escStr, $offset, strlen($str));
231
            }
232
        }
233
234 24
        return self::DELIMITER . $pattern . self::DELIMITER . $this->getModifiers();
0 ignored issues
show
Bug introduced by
Are you sure $pattern of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

234
        return self::DELIMITER . /** @scrutinizer ignore-type */ $pattern . self::DELIMITER . $this->getModifiers();
Loading history...
235
    }
236
}
237