Emmet.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. <?php
  2. namespace Luracast\Restler\UI;
  3. use Luracast\Restler\UI\Tags as T;
  4. use Luracast\Restler\Util;
  5. /**
  6. * Class Emmet
  7. * @package Luracast\Restler\UI
  8. *
  9. * @version 3.1.0
  10. */
  11. class Emmet
  12. {
  13. const DELIMITERS = '.#*>+^[=" ]{$@-#}';
  14. /**
  15. * Create the needed tag hierarchy from emmet string
  16. *
  17. * @param string $string
  18. *
  19. * @param array|string $data
  20. *
  21. * @return array|T
  22. */
  23. public static function make($string, $data = null)
  24. {
  25. if (!strlen($string))
  26. return array();
  27. $implicitTag =
  28. function () use (& $tag) {
  29. if (empty($tag->tag)) {
  30. switch ($tag->parent->tag) {
  31. case 'ul':
  32. case 'ol':
  33. $tag->tag = 'li';
  34. break;
  35. case 'em':
  36. $tag->tag = 'span';
  37. break;
  38. case 'table':
  39. case 'tbody':
  40. case 'thead':
  41. case 'tfoot':
  42. $tag->tag = 'tr';
  43. break;
  44. case 'tr':
  45. $tag->tag = 'td';
  46. break;
  47. case 'select':
  48. case 'optgroup':
  49. $tag->tag = 'option';
  50. break;
  51. default:
  52. $tag->tag = 'div';
  53. }
  54. }
  55. };
  56. $parseText =
  57. function (
  58. $text, $round, $total, $data, $delimiter = null
  59. )
  60. use (
  61. & $tokens, & $tag
  62. ) {
  63. $digits = 0;
  64. if ($delimiter == null)
  65. $delimiter = array(
  66. '.' => true,
  67. '#' => true,
  68. '*' => true,
  69. '>' => true,
  70. '+' => true,
  71. '^' => true,
  72. '[' => true,
  73. ']' => true,
  74. '=' => true,
  75. );
  76. while (!empty($tokens) &&
  77. !isset($delimiter[$t = array_shift($tokens)])) {
  78. while ('$' === $t) {
  79. $digits++;
  80. $t = array_shift($tokens);
  81. }
  82. if ($digits) {
  83. $negative = false;
  84. $offset = 0;
  85. if ('@' == $t) {
  86. if ('-' == ($t = array_shift($tokens))) {
  87. $negative = true;
  88. if (is_numeric(reset($tokens))) {
  89. $offset = array_shift($tokens);
  90. }
  91. } elseif (is_numeric($t)) {
  92. $offset = $t;
  93. } else {
  94. array_unshift($tokens, $t);
  95. }
  96. } elseif ('#' == ($h = array_shift($tokens))) {
  97. if (!empty($t)) {
  98. $data = Util::nestedValue($data, $t);
  99. if (is_null($data)) {
  100. return null;
  101. }
  102. }
  103. if (is_numeric($data)) {
  104. $text .= sprintf("%0{$digits}d", (int)$data);
  105. } elseif (is_string($data)) {
  106. $text .= $data;
  107. }
  108. $digits = 0;
  109. continue;
  110. } else {
  111. array_unshift($tokens, $t, $h);
  112. }
  113. if ($negative) {
  114. $n = $total + 1 - $round + $offset;
  115. } else {
  116. $n = $round + $offset;
  117. }
  118. $text .= sprintf("%0{$digits}d", $n);
  119. $digits = 0;
  120. } else {
  121. $text .= $t;
  122. }
  123. }
  124. if (isset($t))
  125. array_unshift($tokens, $t);
  126. return $text;
  127. };
  128. $parseAttributes =
  129. function (Callable $self, $round, $total, $data)
  130. use (& $tokens, & $tag, $parseText) {
  131. $a = $parseText(
  132. '', $round, $total, $data
  133. );
  134. if (is_null($a))
  135. return;
  136. if ('=' == ($v = array_shift($tokens))) {
  137. //value
  138. if ('"' == ($v = array_shift($tokens))) {
  139. $text = '';
  140. $tag->$a($parseText(
  141. $text, $round, $total, $data,
  142. array('"' => true)
  143. ));
  144. } else {
  145. array_unshift($tokens, $v);
  146. $text = '';
  147. $tag->$a($parseText(
  148. $text, $round, $total, $data,
  149. array(' ' => true, ']' => true)
  150. ));
  151. }
  152. if (' ' == ($v = array_shift($tokens))) {
  153. $self($self, $round, $total, $data);
  154. }
  155. } elseif (']' == $v) {
  156. //end
  157. $tag->$a('');
  158. return;
  159. } elseif (' ' == $v) {
  160. $tag->$a('');
  161. $self($self, $round, $total, $data);
  162. }
  163. };
  164. $tokens = static::tokenize($string);
  165. $tag = new T(array_shift($tokens));
  166. $parent = $root = new T;
  167. $parse =
  168. function (
  169. Callable $self, $round = 1, $total = 1
  170. )
  171. use (
  172. & $tokens, & $parent, & $tag, & $data,
  173. $parseAttributes, $implicitTag, $parseText
  174. ) {
  175. $offsetTokens = null;
  176. $parent[] = $tag;
  177. $isInChild = false;
  178. while ($tokens) {
  179. switch (array_shift($tokens)) {
  180. //class
  181. case '.':
  182. $offsetTokens = array_values($tokens);
  183. array_unshift($offsetTokens, '.');
  184. $implicitTag();
  185. $e = array_filter(explode(' ', $tag->class));
  186. $e[] = $parseText('', $round, $total, $data);
  187. $tag->class(implode(' ', array_unique($e)));
  188. break;
  189. //id
  190. case '#':
  191. $offsetTokens = array_values($tokens);
  192. array_unshift($offsetTokens, '#');
  193. $implicitTag();
  194. $tag->id(
  195. $parseText(
  196. array_shift($tokens), $round, $total, $data
  197. )
  198. );
  199. break;
  200. //attributes
  201. case '[':
  202. $offsetTokens = array_values($tokens);
  203. array_unshift($offsetTokens, '[');
  204. $implicitTag();
  205. $parseAttributes(
  206. $parseAttributes, $round, $total, $data
  207. );
  208. break;
  209. //child
  210. case '{':
  211. $text = '';
  212. $tag[] = $parseText(
  213. $text, $round, $total, $data, array('}' => true)
  214. );
  215. break;
  216. case '>':
  217. $isInChild = true;
  218. $offsetTokens = null;
  219. if ('{' == ($t = array_shift($tokens))) {
  220. array_unshift($tokens, $t);
  221. $child = new T();
  222. $tag[] = $child;
  223. $parent = $tag;
  224. $tag = $child;
  225. } elseif ('[' == $t) {
  226. array_unshift($tokens, $t);
  227. } else {
  228. $child = new T($t);
  229. $tag[] = $child;
  230. $parent = $tag;
  231. $tag = $child;
  232. }
  233. break;
  234. //sibling
  235. case '+':
  236. $offsetTokens = null;
  237. if (!$isInChild && $round != $total) {
  238. $tokens = array();
  239. break;
  240. }
  241. if ('{' == ($t = array_shift($tokens))) {
  242. $tag = $tag->parent;
  243. array_unshift($tokens, $t);
  244. break;
  245. } elseif ('[' == $t) {
  246. array_unshift($tokens, $t);
  247. } else {
  248. $child = new T($t);
  249. $tag = $tag->parent;
  250. $tag[] = $child;
  251. $tag = $child;
  252. }
  253. break;
  254. //sibling of parent
  255. case '^':
  256. if ($round != $total) {
  257. $tokens = array();
  258. break;
  259. }
  260. $tag = $tag->parent;
  261. if ($tag->parent)
  262. $tag = $tag->parent;
  263. while ('^' == ($t = array_shift($tokens))) {
  264. if ($tag->parent)
  265. $tag = $tag->parent;
  266. }
  267. $child = new T($t);
  268. $tag[] = $child;
  269. $tag = $child;
  270. break;
  271. //clone
  272. case '*':
  273. $times = array_shift($tokens);
  274. $removeCount = 2;
  275. $delimiter = array(
  276. '.' => true,
  277. '#' => true,
  278. '*' => true,
  279. '>' => true,
  280. '+' => true,
  281. '^' => true,
  282. '[' => true,
  283. ']' => true,
  284. '=' => true,
  285. );
  286. if (!is_numeric($times)) {
  287. if (is_string($times)) {
  288. if (!isset($delimiter[$times])) {
  289. $data = Util::nestedValue($data, $times)
  290. ? : $data;
  291. } else {
  292. array_unshift($tokens, $times);
  293. $removeCount = 1;
  294. }
  295. }
  296. $indexed = array_values($data);
  297. $times = is_array($data) && $indexed == $data
  298. ? count($data) : 0;
  299. }
  300. $source = $tag;
  301. if (!empty($offsetTokens)) {
  302. if (false !== strpos($source->class, ' ')) {
  303. $class = explode(' ', $source->class);
  304. array_pop($class);
  305. $class = implode(' ', $class);
  306. } else {
  307. $class = null;
  308. }
  309. $tag->class($class);
  310. $star = array_search('*', $offsetTokens);
  311. array_splice($offsetTokens, $star, $removeCount);
  312. $remainingTokens = $offsetTokens;
  313. } else {
  314. $remainingTokens = $tokens;
  315. }
  316. $source->parent = null;
  317. $sourceData = $data;
  318. $currentParent = $parent;
  319. for ($i = 1; $i <= $times; $i++) {
  320. $tag = clone $source;
  321. $parent = $currentParent;
  322. $data = is_array($sourceData)
  323. && isset($sourceData[$i - 1])
  324. ? $sourceData[$i - 1]
  325. : @(string)$sourceData;
  326. $tokens = array_values($remainingTokens);
  327. $self($self, $i, $times);
  328. }
  329. $round = 1;
  330. $offsetTokens = null;
  331. $tag = $source;
  332. $tokens = array(); //$remainingTokens;
  333. break;
  334. }
  335. }
  336. };
  337. $parse($parse);
  338. return count($root) == 1 ? $root[0] : $root;
  339. }
  340. public static function tokenize($string)
  341. {
  342. $r = array();
  343. $f = strtok($string, static::DELIMITERS);
  344. $pos = 0;
  345. do {
  346. $start = $pos;
  347. $pos = strpos($string, $f, $start);
  348. $tokens = array();
  349. for ($i = $start; $i < $pos; $i++) {
  350. $token = $string[$i];
  351. if (('#' == $token || '.' == $token) &&
  352. (!empty($tokens) || $i == 0)
  353. ) {
  354. $r[] = '';
  355. }
  356. $r[] = $tokens[] = $token;
  357. }
  358. $pos += strlen($f);
  359. $r[] = $f;
  360. } while (false != ($f = strtok(static::DELIMITERS)));
  361. for ($i = $pos; $i < strlen($string); $i++) {
  362. $token = $string[$i];
  363. $r[] = $tokens[] = $token;
  364. }
  365. return $r;
  366. /* sample output produced by ".row*3>.col*3"
  367. [0] => div
  368. [1] => .
  369. [2] => row
  370. [3] => *
  371. [4] => 3
  372. [5] => >
  373. [6] => div
  374. [7] => .
  375. [8] => col
  376. [9] => *
  377. [10] => 4
  378. */
  379. }
  380. }