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 } |
|