130 $this->translator = $translator;
132 'int' =>
'\\Erebot\\Styling\\Variables\\IntegerVariable',
133 'float' =>
'\\Erebot\\Styling\\Variables\\FloatVariable',
134 'string' =>
'\\Erebot\\Styling\\Variables\\StringVariable',
152 if (!isset($this->cls[$type])) {
153 throw new \InvalidArgumentException(
'Invalid type');
155 return $this->cls[$type];
172 if (!isset($this->cls[$type])) {
173 throw new \InvalidArgumentException(
'Invalid type');
175 if (!is_string($cls)) {
176 throw new \InvalidArgumentException(
177 'Expected a string for the class' 180 if (!class_exists($cls)) {
181 throw new \InvalidArgumentException(
'Class not found');
184 throw new \InvalidArgumentException(
185 'Must be a subclass of \\Erebot\\Styling\\VariableInterface' 188 $this->cls[$type] = $cls;
206 if (!preg_match(
'/^[a-zA-Z0-9_\.]+$/D', $var)) {
207 throw new \InvalidArgumentException(
208 'Invalid variable name "'.$var.
'". '.
209 'Variable names may only contain alphanumeric '.
210 'characters, underscores ("_") and dots (".").' 216 public function _($template, array $vars = array())
218 $source = $this->translator->_($template);
219 return $this->render($source, $vars);
223 public function render($template, array $vars = array())
227 if (strpos($template,
'<') ===
false &&
228 strpos($template,
'&') ===
false) {
239 $variables = array();
240 foreach ($vars as $name => $var) {
241 $variables[$name] = $this->wrapScalar($var, $name);
244 $dom = self::parseTemplate($template);
245 $result = $this->parseNode(
246 $dom->documentElement,
254 '\\003(?:[0-9]{2})?,(?:[0-9]{2})?(?:\\002\\002)?(?=\\003)'.
256 '(\\003(?:[0-9]{2})?,)\\002\\002(?![0-9])'.
258 '(\\003[0-9]{2})\\002\\002(?!,)'.
261 $result = preg_replace($pattern, $replace, $result);
267 return $this->translator;
306 self::checkVariableName($name);
308 if (is_object($var)) {
313 if (!is_callable(array($var,
'__toString'),
false)) {
314 throw new \InvalidArgumentException(
315 $name.
' must be a scalar or an instance of '.
316 '\\Erebot\\Styling\\VariableInterface' 321 if (is_array($var)) {
325 if (is_string($var) || is_callable(array($var,
'__toString'),
false)) {
326 $cls = $this->cls[
'string'];
327 } elseif (is_int($var)) {
328 $cls = $this->cls[
'int'];
329 } elseif (is_float($var)) {
330 $cls = $this->cls[
'float'];
332 throw new \InvalidArgumentException(
333 'Unsupported scalar type ('.gettype($var).
') for "'.$name.
'"' 336 return new $cls($var);
356 '<msg xmlns="http://www.erebot.net/xmlns/erebot/styling">'.
359 $schema = dirname(__DIR__) .
360 DIRECTORY_SEPARATOR .
'data' .
361 DIRECTORY_SEPARATOR .
'styling.rng';
362 $dom = new \Erebot\DOM();
363 $dom->substituteEntities =
true;
364 $dom->resolveExternals =
false;
365 $dom->recover =
true;
366 $ue = libxml_use_internal_errors(
true);
367 $dom->loadXML($source);
368 $valid = $dom->relaxNGValidate($schema);
369 $errors = $dom->getErrors();
370 libxml_use_internal_errors($ue);
372 if (!$valid || count($errors)) {
375 if (class_exists(
'\\Plop')) {
376 $logger = \Plop::getInstance();
377 $logger->error(print_r($errors,
true));
379 throw new \InvalidArgumentException(
380 'Error while validating the message' 401 protected function parseNode($node, &$attributes, $vars)
404 $saved = $attributes;
406 if ($node->nodeType == XML_TEXT_NODE) {
407 return $node->nodeValue;
410 if ($node->nodeType != XML_ELEMENT_NODE) {
415 switch ($node->tagName) {
417 $lexer = new \Erebot\Styling\Lexer(
418 $node->getAttribute(
'name'),
421 $var = $lexer->getResult();
423 return (
string) $var;
425 return $var->render($this->translator);
428 if (!$attributes[
'underline']) {
429 $result .= self::CODE_UNDERLINE;
431 $attributes[
'underline'] = 1;
435 if (!$attributes[
'bold']) {
436 $result .= self::CODE_BOLD;
438 $attributes[
'bold'] = 1;
442 $colors = array(
'',
'');
443 $mapping = array(
'fg',
'bg');
445 foreach ($mapping as $pos => $color) {
446 $value = $node->getAttribute($color);
448 $value = str_replace(array(
' ',
'-'),
'_', $value);
449 if (strspn($value,
'1234567890') !== strlen($value)) {
450 $reflector = new \ReflectionClass(
'\\Erebot\\StylingInterface');
451 if (!$reflector->hasConstant(
'COLOR_'.strtoupper($value))) {
452 throw new \InvalidArgumentException(
453 'Invalid color "'.$value.
'"' 456 $value = $reflector->getConstant(
'COLOR_'.strtoupper($value));
458 $attributes[$color] = sprintf(
'%02d', $value);
459 if ($attributes[$color] != $saved[$color]) {
460 $colors[$pos] = $attributes[$color];
465 $code = implode(
',', $colors);
466 if ($colors[0] !=
'' && $colors[1] !=
'') {
467 $result .= self::CODE_COLOR.$code;
468 } elseif ($code !=
',') {
469 $result .= self::CODE_COLOR.rtrim($code,
',').
470 self::CODE_BOLD.self::CODE_BOLD;
475 if ($node->tagName ==
'for') {
477 $savedVariables = $vars;
478 $separator = array(
', ',
' & ');
480 foreach (array(
'separator',
'sep') as $attr) {
481 $attrNode = $node->getAttributeNode($attr);
482 if ($attrNode !==
false) {
483 $separator[0] = $separator[1] = $attrNode->nodeValue;
488 foreach (array(
'last_separator',
'last') as $attr) {
489 $attrNode = $node->getAttributeNode($attr);
490 if ($attrNode !==
false) {
491 $separator[1] = $attrNode->nodeValue;
496 $loopKey = $node->getAttribute(
'key');
497 $loopItem = $node->getAttribute(
'item');
498 $loopFrom = $node->getAttribute(
'from');
499 $count = count($vars[$loopFrom]);
500 reset($vars[$loopFrom]);
502 for ($i = 1; $i < $count; $i++) {
504 $result .= $separator[0];
507 $key = key($vars[$loopFrom]);
508 next($vars[$loopFrom]);
509 if ($loopKey !== null) {
510 $cls = $this->cls[
'string'];
511 $vars[$loopKey] =
new $cls($key);
513 $vars[$loopItem] = $this->wrapScalar(
514 $vars[$loopFrom][$key],
518 $result .= $this->parseChildren(
525 $key = key($vars[$loopFrom]);
527 $item = array(
'key' =>
'',
'value' =>
'');
529 $item = array(
'key' => $key,
'value' => $vars[$loopFrom][$key]);
532 if ($loopKey !== null) {
533 $cls = $this->cls[
'string'];
534 $vars[$loopKey] =
new $cls($item[
'key']);
537 $vars[$loopItem] = $this->wrapScalar($item[
'value'], $loopItem);
539 $result .= $separator[1];
542 $result .= $this->parseChildren($node, $attributes, $vars);
543 $vars = $savedVariables;
544 } elseif ($node->tagName ==
'plural') {
550 $attrNode = $node->getAttributeNode(
'var');
551 if ($attrNode ===
false) {
552 throw new \InvalidArgumentException(
553 'No variable name given' 557 $lexer = new \Erebot\Styling\Lexer($attrNode->nodeValue, $vars);
558 $value = $lexer->getResult();
560 $value = $value->getValue();
562 $value = (int) $value;
564 $subcontents = array();
565 $pattern =
'{0,plural,';
566 for ($child = $node->firstChild; $child != null; $child = $child->nextSibling) {
567 if ($child->nodeType != XML_ELEMENT_NODE ||
568 $child->tagName !=
'case') {
574 $form = $child->getAttribute(
'form');
575 $subcontents[$form] = $this->parseNode($child, $attributes, $vars);
576 $pattern .= $form.
'{'.$form.
'} ';
579 $locale = $this->translator->getLocale(
580 \
Erebot\IntlInterface::LC_MESSAGES
582 $formatter = new \MessageFormatter($locale, $pattern);
586 if ($formatter === null) {
587 throw new \InvalidArgumentException(
'Invalid plural forms');
589 $correctForm = $formatter->format(array($value));
590 $result .= $subcontents[$correctForm];
593 $result .= $this->parseChildren($node, $attributes, $vars);
597 switch ($node->tagName) {
599 if (!$saved[
'underline']) {
600 $result .= self::CODE_UNDERLINE;
602 $attributes[
'underline'] = 0;
606 if (!$saved[
'bold']) {
607 $result .= self::CODE_BOLD;
609 $attributes[
'bold'] = 0;
613 $colors = array(
'',
'');
614 $mapping = array(
'fg',
'bg');
616 foreach ($mapping as $pos => $color) {
617 if ($attributes[$color] != $saved[$color]) {
618 $colors[$pos] = $saved[$color];
620 $attributes[$color] = $saved[$color];
623 $code = implode(
',', $colors);
624 if ($colors[0] !=
'' && $colors[1] !=
'') {
625 $result .= self::CODE_COLOR.$code;
626 } elseif ($code !=
',') {
627 $result .= self::CODE_COLOR.rtrim($code,
',').
628 self::CODE_BOLD.self::CODE_BOLD;
655 for ($child = $node->firstChild; $child != null; $child = $child->nextSibling) {
656 $result .= $this->parseNode($child, $attributes, $vars);
Provides styling (formatting) features.
Interface to provide internationalization.
_($template, array $vars=array())
parseNode($node, &$attributes, $vars)
render($template, array $vars=array())
static parseTemplate($source)
Interface for styling (formatting) capabilities.
static checkVariableName($var)
$cls
Maps some scalar types to a typed variable.
$translator
Translator to use to improve rendering.
parseChildren($node, &$attributes, $vars)
__construct(\Erebot\IntlInterface $translator)