library/log4php/helpers/LoggerPatternParser.php
changeset 46 f11c31f7fa3e
parent 45 a56e7f9a0463
child 47 03388ec805b4
equal deleted inserted replaced
45:a56e7f9a0463 46:f11c31f7fa3e
     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 }