1);\n" */ class PoHeader { /** * Language code. * * @var string */ private $_langcode; /** * Formula for the plural form. * * @var string */ private $_pluralForms; /** * Author(s) of the file. * * @var string */ private $_authors; /** * Date the po file got created. * * @var string */ private $_po_date; /** * Human readable language name. * * @var string */ private $_languageName; /** * Name of the project the translation belongs to. * * @var string */ private $_projectName; /** * Constructor, creates a PoHeader with default values. * * @param string $langcode * Language code. */ public function __construct($langcode = NULL) { $this->_langcode = $langcode; // Ignore errors when run during site installation before // date_default_timezone_set() is called. $this->_po_date = @date("Y-m-d H:iO"); $this->_pluralForms = 'nplurals=2; plural=(n > 1);'; } /** * Get the plural form. * * @return string * Plural form component from the header, for example: * 'nplurals=2; plural=(n > 1);'. */ function getPluralForms() { return $this->_pluralForms; } /** * Set the human readable language name. * * @param string $languageName * Human readable language name. */ function setLanguageName($languageName) { $this->_languageName = $languageName; } /** * Get the human readable language name. * * @return string * The human readable language name. */ function getLanguageName() { return $this->_languageName; } /** * Set the project name. * * @param string $projectName * Human readable project name. */ function setProjectName($projectName) { $this->_projectName = $projectName; } /** * Get the project name. * * @return string * The human readable project name. */ function getProjectName() { return $this->_projectName; } /** * Populate internal values from a string. * * @param string $header * Full header string with key-value pairs. */ public function setFromString($header) { // Get an array of all header values for processing. $values = $this->parseHeader($header); // There is only one value relevant for our header implementation when // reading, and that is the plural formula. if (!empty($values['Plural-Forms'])) { $this->_pluralForms = $values['Plural-Forms']; } } /** * Generate a Gettext PO formatted header string based on data set earlier. */ public function __toString() { $output = ''; $isTemplate = empty($this->_languageName); $output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->_languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->_projectName) . "\n"; if (!empty($this->_authors)) { $output .= '# Generated by ' . implode("\n# ", $this->_authors) . "\n"; } $output .= "#\n"; // Add the actual header information. $output .= "msgid \"\"\n"; $output .= "msgstr \"\"\n"; $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; $output .= "\"POT-Creation-Date: " . $this->_po_date . "\\n\"\n"; $output .= "\"PO-Revision-Date: " . $this->_po_date . "\\n\"\n"; $output .= "\"Last-Translator: NAME \\n\"\n"; $output .= "\"Language-Team: LANGUAGE \\n\"\n"; $output .= "\"MIME-Version: 1.0\\n\"\n"; $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; $output .= "\"Plural-Forms: " . $this->_pluralForms . "\\n\"\n"; $output .= "\n"; return $output; } /** * Parses a Plural-Forms entry from a Gettext Portable Object file header. * * @param string $pluralforms * The Plural-Forms entry value. * * @return * An array containing the number of plural forms and the converted version * of the formula that can be evaluated with PHP later. */ function parsePluralForms($pluralforms) { // First, delete all whitespace. $pluralforms = strtr($pluralforms, array(" " => "", "\t" => "")); // Select the parts that define nplurals and plural. $nplurals = strstr($pluralforms, "nplurals="); if (strpos($nplurals, ";")) { // We want the string from the 10th char, because "nplurals=" length is 9. $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9); } else { return FALSE; } $plural = strstr($pluralforms, "plural="); if (strpos($plural, ";")) { // We want the string from the 8th char, because "plural=" length is 7. $plural = substr($plural, 7, strpos($plural, ";") - 7); } else { return FALSE; } // Get PHP version of the plural formula. $plural = $this->parseArithmetic($plural); if ($plural !== FALSE) { return array($nplurals, $plural); } else { throw new Exception('The plural formula could not be parsed.'); } } /** * Parses a Gettext Portable Object file header. * * @param string $header * A string containing the complete header. * * @return array * An associative array of key-value pairs. */ private function parseHeader($header) { $header_parsed = array(); $lines = array_map('trim', explode("\n", $header)); foreach ($lines as $line) { if ($line) { list($tag, $contents) = explode(":", $line, 2); $header_parsed[trim($tag)] = trim($contents); } } return $header_parsed; } /** * Parses and sanitizes an arithmetic formula into a PHP expression. * * While parsing, we ensure, that the operators have the right * precedence and associativity. * * @param string $string * A string containing the arithmetic formula. * * @return * A version of the formula to evaluate with PHP later. */ private function parseArithmetic($string) { // Operator precedence table. $precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8); // Right associativity. $right_associativity = array("?" => 1, ":" => 1); $tokens = $this->tokenizeFormula($string); // Parse by converting into infix notation then back into postfix // Operator stack - holds math operators and symbols. $operator_stack = array(); // Element Stack - holds data to be operated on. $element_stack = array(); foreach ($tokens as $token) { $current_token = $token; // Numbers and the $n variable are simply pushed into $element_stack. if (is_numeric($token)) { $element_stack[] = $current_token; } elseif ($current_token == "n") { $element_stack[] = '$n'; } elseif ($current_token == "(") { $operator_stack[] = $current_token; } elseif ($current_token == ")") { $topop = array_pop($operator_stack); while (isset($topop) && ($topop != "(")) { $element_stack[] = $topop; $topop = array_pop($operator_stack); } } elseif (!empty($precedence[$current_token])) { // If it's an operator, then pop from $operator_stack into // $element_stack until the precedence in $operator_stack is less // than current, then push into $operator_stack. $topop = array_pop($operator_stack); while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) { $element_stack[] = $topop; $topop = array_pop($operator_stack); } if ($topop) { // Return element to top. $operator_stack[] = $topop; } // Parentheses are not needed. $operator_stack[] = $current_token; } else { return FALSE; } } // Flush operator stack. $topop = array_pop($operator_stack); while ($topop != NULL) { $element_stack[] = $topop; $topop = array_pop($operator_stack); } // Now extract formula from stack. $previous_size = count($element_stack) + 1; while (count($element_stack) < $previous_size) { $previous_size = count($element_stack); for ($i = 2; $i < count($element_stack); $i++) { $op = $element_stack[$i]; if (!empty($precedence[$op])) { $f = ""; if ($op == ":") { $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")"; } elseif ($op == "?") { $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1]; } else { $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")"; } array_splice($element_stack, $i - 2, 3, $f); break; } } } // If only one element is left, the number of operators is appropriate. if (count($element_stack) == 1) { return $element_stack[0]; } else { return FALSE; } } /** * Tokenize the formula. * * @param string $formula * A string containing the arithmetic formula. * * @return array * List of arithmetic tokens identified in the formula. */ private function tokenizeFormula($formula) { $formula = str_replace(" ", "", $formula); $tokens = array(); for ($i = 0; $i < strlen($formula); $i++) { if (is_numeric($formula[$i])) { $num = $formula[$i]; $j = $i + 1; while ($j < strlen($formula) && is_numeric($formula[$j])) { $num .= $formula[$j]; $j++; } $i = $j - 1; $tokens[] = $num; } elseif ($pos = strpos(" =<>!&|", $formula[$i])) { $next = $formula[$i + 1]; switch ($pos) { case 1: case 2: case 3: case 4: if ($next == '=') { $tokens[] = $formula[$i] . '='; $i++; } else { $tokens[] = $formula[$i]; } break; case 5: if ($next == '&') { $tokens[] = '&&'; $i++; } else { $tokens[] = $formula[$i]; } break; case 6: if ($next == '|') { $tokens[] = '||'; $i++; } else { $tokens[] = $formula[$i]; } break; } } else { $tokens[] = $formula[$i]; } } return $tokens; } }