| Code Coverage
| Classes and Traits | Functions and Methods | Lines
Total |  | 0.00% | 0 / 1 |  | 42.86% | 3 / 7 | CRAP |  | 85.37% | 70 / 82
LoggerPatternParser |  | 0.00% | 0 / 1 |  | 42.86% | 3 / 7 | 25.81 |  | 85.37% | 70 / 82
__construct($pattern, $converterMap) |  | 100.00% | 1 / 1 | 1 |  | 100.00% | 9 / 9
parse() |  | 0.00% | 0 / 1 | 9.03 |  | 92.59% | 25 / 27
addLiteral($string) |  | 100.00% | 1 / 1 | 1 |  | 100.00% | 3 / 3
addConverter($word, $modifiers, $option) |  | 0.00% | 0 / 1 | 2.01 |  | 87.50% | 7 / 8
getConverter($word, $info, $option) |  | 0.00% | 0 / 1 | 4.59 |  | 66.67% | 6 / 9
addToChain(LoggerPatternConverter $converter) |  | 100.00% | 1 / 1 | 2 |  | 100.00% | 7 / 7
parseModifiers($modifiers) |  | 0.00% | 0 / 1 | 5.79 |  | 68.42% | 13 / 19

1 | <?php
2 | /**
3 | * Licensed to the Apache Software Foundation (ASF) under one or more
4 | * contributor license agreements. See the NOTICE file distributed with
5 | * this work for additional information regarding copyright ownership.
6 | * The ASF licenses this file to You under the Apache License, Version 2.0
7 | * (the "License"); you may not use this file except in compliance with
8 | * the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * @package log4php
19 | */
20 |
21 | /**
22 | * Most of the work of the {@link LoggerPatternLayout} class
23 | * is delegated to the {@link LoggerPatternParser} class.
24 | *
25 | * <p>It is this class that parses conversion patterns and creates
26 | * a chained list of {@link LoggerPatternConverter} converters.</p>
27 | *
28 | * @version $Revision: 1395467 $
29 | * @package log4php
30 | * @subpackage helpers
31 | *
32 | * @since 0.3
33 | */
34 | class LoggerPatternParser {
35 |
36 | /** Escape character for conversion words in the conversion pattern. */
37 | const ESCAPE_CHAR = '%';
38 |
39 | /** Maps conversion words to relevant converters. */
40 | private $converterMap;
41 |
42 | /** Conversion pattern used in layout. */
43 | private $pattern;
44 |
45 | /** Regex pattern used for parsing the conversion pattern. */
46 | private $regex;
47 |
48 | /**
49 | * First converter in the chain.
50 | * @var LoggerPatternConverter
51 | */
52 | private $head;
53 |
54 | /** Last converter in the chain. */
55 | private $tail;
56 |
57 | public function __construct($pattern, $converterMap) {
58 | $this->pattern = $pattern;
59 | $this->converterMap = $converterMap;
60 |
61 | // Construct the regex pattern
62 | $this->regex =
63 | '/' . // Starting regex pattern delimiter
64 | self::ESCAPE_CHAR . // Character which marks the start of the conversion pattern
65 | '(?P<modifiers>[0-9.-]*)' . // Format modifiers (optional)
66 | '(?P<word>[a-zA-Z]+)' . // The conversion word
67 | '(?P<option>{[^}]*})?' . // Conversion option in braces (optional)
68 | '/'; // Ending regex pattern delimiter
69 | }
70 |
71 | /**
72 | * Parses the conversion pattern string, converts it to a chain of pattern
73 | * converters and returns the first converter in the chain.
74 | *
75 | * @return LoggerPatternConverter
76 | */
77 | public function parse() {
78 |
79 | // Skip parsing if the pattern is empty
80 | if (empty($this->pattern)) {
81 | $this->addLiteral('');
82 | return $this->head;
83 | }
84 |
85 | // Find all conversion words in the conversion pattern
86 | $count = preg_match_all($this->regex, $this->pattern, $matches, PREG_OFFSET_CAPTURE);
87 | if ($count === false) {
88 | $error = error_get_last();
89 | throw new LoggerException("Failed parsing layotut pattern: {$error['message']}");
90 | }
91 |
92 | $prevEnd = 0;
93 |
94 | foreach($matches[0] as $key => $item) {
95 |
96 | // Locate where the conversion command starts and ends
97 | $length = strlen($item[0]);
98 | $start = $item[1];
99 | $end = $item[1] + $length;
100 |
101 | // Find any literal expressions between matched commands
102 | if ($start > $prevEnd) {
103 | $literal = substr($this->pattern, $prevEnd, $start - $prevEnd);
104 | $this->addLiteral($literal);
105 | }
106 |
107 | // Extract the data from the matched command
108 | $word = !empty($matches['word'][$key]) ? $matches['word'][$key][0] : null;
109 | $modifiers = !empty($matches['modifiers'][$key]) ? $matches['modifiers'][$key][0] : null;
110 | $option = !empty($matches['option'][$key]) ? $matches['option'][$key][0] : null;
111 |
112 | // Create a converter and add it to the chain
113 | $this->addConverter($word, $modifiers, $option);
114 |
115 | $prevEnd = $end;
116 | }
117 |
118 | // Add any trailing literals
119 | if ($end < strlen($this->pattern)) {
120 | $literal = substr($this->pattern, $end);
121 | $this->addLiteral($literal);
122 | }
123 |
124 | return $this->head;
125 | }
126 |
127 | /**
128 | * Adds a literal converter to the converter chain.
129 | * @param string $string The string for the literal converter.
130 | */
131 | private function addLiteral($string) {
132 | $converter = new LoggerPatternConverterLiteral($string);
133 | $this->addToChain($converter);
134 | }
135 |
136 | /**
137 | * Adds a non-literal converter to the converter chain.
138 | *
139 | * @param string $word The conversion word, used to determine which
140 | * converter will be used.
141 | * @param string $modifiers Formatting modifiers.
142 | * @param string $option Option to pass to the converter.
143 | */
144 | private function addConverter($word, $modifiers, $option) {
145 | $formattingInfo = $this->parseModifiers($modifiers);
146 | $option = trim($option, "{} ");
147 |
148 | if (isset($this->converterMap[$word])) {
149 | $converter = $this->getConverter($word, $formattingInfo, $option);
150 | $this->addToChain($converter);
151 | } else {
152 | trigger_error("log4php: Invalid keyword '%$word' in converison pattern. Ignoring keyword.", E_USER_WARNING);
153 | }
154 | }
155 |
156 | /**
157 | * Determines which converter to use based on the conversion word. Creates
158 | * an instance of the converter using the provided formatting info and
159 | * option and returns it.
160 | *
161 | * @param string $word The conversion word.
162 | * @param LoggerFormattingInfo $info Formatting info.
163 | * @param string $option Converter option.
164 | *
165 | * @throws LoggerException
166 | *
167 | * @return LoggerPatternConverter
168 | */
169 | private function getConverter($word, $info, $option) {
170 | if (!isset($this->converterMap[$word])) {
171 | throw new LoggerException("Invalid keyword '%$word' in converison pattern. Ignoring keyword.");
172 | }
173 |
174 | $converterClass = $this->converterMap[$word];
175 | if(!class_exists($converterClass)) {
176 | throw new LoggerException("Class '$converterClass' does not exist.");
177 | }
178 |
179 | $converter = new $converterClass($info, $option);
180 | if(!($converter instanceof LoggerPatternConverter)) {
181 | throw new LoggerException("Class '$converterClass' is not an instance of LoggerPatternConverter.");
182 | }
183 |
184 | return $converter;
185 | }
186 |
187 | /** Adds a converter to the chain and updates $head and $tail pointers. */
188 | private function addToChain(LoggerPatternConverter $converter) {
189 | if (!isset($this->head)) {
190 | $this->head = $converter;
191 | $this->tail = $this->head;
192 | } else {
193 | $this->tail->next = $converter;
194 | $this->tail = $this->tail->next;
195 | }
196 | }
197 |
198 | /**
199 | * Parses the formatting modifiers and produces the corresponding
200 | * LoggerFormattingInfo object.
201 | *
202 | * @param string $modifier
203 | * @return LoggerFormattingInfo
204 | * @throws LoggerException
205 | */
206 | private function parseModifiers($modifiers) {
207 | $info = new LoggerFormattingInfo();
208 |
209 | // If no modifiers are given, return default values
210 | if (empty($modifiers)) {
211 | return $info;
212 | }
213 |
214 | // Validate
215 | $pattern = '/^(-?[0-9]+)?\.?-?[0-9]+$/';
216 | if (!preg_match($pattern, $modifiers)) {
217 | trigger_error("log4php: Invalid modifier in conversion pattern: [$modifiers]. Ignoring modifier.", E_USER_WARNING);
218 | return $info;
219 | }
220 |
221 | $parts = explode('.', $modifiers);
222 |
223 | if (!empty($parts[0])) {
224 | $minPart = (integer) $parts[0];
225 | $info->min = abs($minPart);
226 | $info->padLeft = ($minPart > 0);
227 | }
228 |
229 | if (!empty($parts[1])) {
230 | $maxPart = (integer) $parts[1];
231 | $info->max = abs($maxPart);
232 | $info->trimLeft = ($maxPart < 0);
233 | }
234 |
235 | return $info;
236 | }
237 | }