1<?php 2/** 3 * DokuWiki plugin Typography; helper component 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Satoshi Sahara <sahara.satoshi@gmail.com> 7 */ 8// must be run within Dokuwiki 9if (!defined('DOKU_INC')) die(); 10 11class helper_plugin_typography_parser extends DokuWiki_Plugin 12{ 13 protected $properties, $specifications; 14 15 function __construct() { 16 // allowable parameters and relevant CSS properties 17 $this->properties = array( 18 'wf' => 'wf', // exceptional class="wf-webfont" 19 'ff' => 'font-family', 20 'fc' => 'color', 21 'bg' => 'background-color', 22 'fs' => 'font-size', 23 'fw' => 'font-weight', 24 'fv' => 'font-variant', 25 'lh' => 'line-height', 26 'ls' => 'letter-spacing', 27 'ws' => 'word-spacing', 28 'va' => 'vertical-align', 29 'sp' => 'white-space', 30 0 => 'text-shadow', 31 1 => 'text-transform', 32 ); 33 34 // valid patterns of css properties 35 $this->specifications = array( 36 'wf' => '/^[a-zA-Z_-]+$/', 37 'ff' => '/^((\'[^,]+?\'|[^ ,]+?) *,? *)+$/', 38 'fc' => '/(^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$)|' 39 .'(^rgb\((\d{1,3}%?,){2}\d{1,3}%?\)$)|' 40 .'(^[a-zA-Z]+$)/', 41 'bg' => '/(^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$)|' 42 .'(^rgb\((\d{1,3}%?,){2}\d{1,3}%?\)$)|' 43 .'(^rgba\((\d{1,3}%?,){3}[\d.]+\)$)|' 44 .'(^[a-zA-Z]+$)/', 45 'font-size' => 46 '/^(?:\d+(?:\.\d+)?(?:px|em|ex|pt|%)' 47 .'|(?:x{1,2}-)?small|medium|(?:x{1,2}-)?large|smaller|larger)$/', 48 'font-weight' => 49 '/^(?:\d00|normal|bold|bolder|lighter)$/', 50 'font-variant' => 51 '/^(?:normal|small-?caps)$/', 52 'line-height' => 53 '/^\d+(?:\.\d+)?(?:px|em|ex|pt|%)?$/', 54 'letter-spacing' => 55 '/^-?\d+(?:\.\d+)?(?:px|em|ex|pt|%)$/', 56 'word-spacing' => 57 '/^-?\d+(?:\.\d+)?(?:px|em|ex|pt|%)$/', 58 'vertical-align' => 59 '/^-?\d+(?:\.\d+)?(?:px|em|ex|pt|%)$|' 60 .'^(?:baseline|sub|super|top|text-top|middle|bottom|text-bottom|inherit)$/', 61 'white-space' => 62 '/^(?:normal|nowrap|pre|pre-line|pre-wrap)$/', 63 ); 64 } 65 66 /** 67 * Get allowed CSS properties 68 * 69 * @return array 70 */ 71 public function getAllowedProperties() 72 { 73 return $this->properties; 74 } 75 76 /** 77 * Set allowed CSS properties 78 * 79 * @param array $props allowable CSS property name 80 * @return bool 81 */ 82 public function setAllowedProperties(array $properties) 83 { 84 $this->properties = $properties; 85 return true; 86 } 87 88 /** 89 * validation of CSS property short name 90 * 91 * @param string $name short name of CSS property 92 * @return bool true if defined 93 */ 94 public function is_short_property($name) 95 { 96 return isset($this->properties[$name]); 97 } 98 99 /** 100 * parse style attribute of an element 101 * 102 * @param string $style style attribute of an element 103 * @param bool $filter allow only CSS properties defined in $this->props 104 * @return array an associative array holds 'declarations' and 'classes' 105 */ 106 public function parse_inlineCSS($style, $filter=true) 107 { 108 if (empty($style)) return array(); 109 110 $elem = array( 111 //'tag' => 'span', 112 'declarations' => array(), // css property:value pairs of style attribute 113 'classes' => array(), // splitted class attribute 114 ); 115 116 $tokens = explode(';', $style); 117 118 foreach ($tokens as $declaration) { 119 $item = array_map('trim', explode(':', $declaration, 2)); 120 if (!isset($item[1])) continue; 121 122 // check CSS property name 123 if (isset($this->properties[$item[0]])) { 124 $name = $this->properties[$item[0]]; 125 } elseif (in_array($item[0], $this->properties)) { 126 $name = $item[0]; 127 } elseif ($filter === false) { 128 $name = $item[0]; // assume as CSS property 129 } else { 130 continue; // ignore unknown property 131 } 132 133 // check CSS property value 134 if (isset($this->specifications[$name])) { 135 if (preg_match($this->specifications[$name], $item[1], $matches)) { 136 $value = $item[1]; 137 } else { 138 continue; // ignore invalid property value 139 } 140 if (($name == 'font-variant') && ($value == 'smallcaps')) { 141 $value = 'small-caps'; 142 } 143 } else { 144 $value = htmlspecialchars($item[1], ENT_COMPAT, 'UTF-8'); 145 } 146 147 if ($name == 'wf') { 148 // webfont : wf: webfont_class_without_prefix; 149 $elem['classes'] += array('webfont' => 'wf-'.$value); 150 } else { 151 // declaration : CSS property-value pairs 152 $elem['declarations'] += array($name => $value); 153 } 154 } 155 156 // unset empty attributes of an element 157 foreach (array_keys($elem) as $key) { 158 if (empty($elem[$key])) unset($elem[$key]); 159 } 160 return $elem; 161 } 162 163 /** 164 * build inline CSS for style attribute of an element 165 * 166 * @param array $declarations CSS property-value pairs 167 * @return string inline CSS for style attribute 168 */ 169 public function build_inlineCSS(array $declarations) 170 { 171 $css = array(); 172 foreach ($declarations as $name => $value) { 173 $css[] = $name.':'.$value.';'; 174 } 175 return implode(' ', $css); 176 } 177 178 /** 179 * build style and class attribute of an element 180 * 181 * @param array $elem holds 'declarations' and 'classes' 182 * @param array $addClasses class items to be added 183 * @return string attributes of an element 184 */ 185 public function build_attributes(array $elem, array $addClasses=array()) 186 { 187 $attr = $css = $item = array(); 188 189 if (isset($elem['declarations'])) { 190 foreach ($elem['declarations'] as $name => $value) { 191 $css[] = $name.':'.$value.';'; 192 } 193 $attr['style'] = implode(' ', $css); 194 } 195 196 if (!empty($addClasses)) { 197 $elem['classes'] = isset($elem['classes']) ?: array(); 198 $elem['classes'] = array_unique($elem['classes'] + $addClasses); 199 } 200 201 if (isset($elem['classes'])) { 202 $attr['class'] = implode(' ', $elem['classes']); 203 } 204 205 foreach ($attr as $key => $value) { 206 $item[] = $key.'="'.$value.'"'; 207 } 208 $out = empty($item) ? '' : ' '.implode(' ', $item); 209 return $out; 210 } 211 212} 213