PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/system/cms/libraries/Lex/Parser.php

http://github.com/pyrocms/pyrocms
PHP | 789 lines | 480 code | 95 blank | 214 comment | 47 complexity | 75beaa9add8f76e366aed8b4bc18e836 MD5 | raw file
Possible License(s): CC0-1.0, MIT
  1. <?php
  2. /**
  3. * Part of the Lex Template Parser.
  4. *
  5. * @author Dan Horrigan
  6. * @license MIT License
  7. * @copyright 2011 Dan Horrigan
  8. */
  9. class LexParsingException extends Exception { }
  10. class Lex_Parser
  11. {
  12. protected $allow_php = false;
  13. protected $regex_setup = false;
  14. protected $scope_glue = '.';
  15. protected $tag_regex = '';
  16. protected $cumulative_noparse = false;
  17. protected $in_condition = false;
  18. protected $variable_regex = '';
  19. protected $variable_loop_regex = '';
  20. protected $variable_tag_regex = '';
  21. protected $callback_tag_regex = '';
  22. protected $callback_loop_tag_regex = '';
  23. protected $noparse_regex = '';
  24. protected $conditional_regex = '';
  25. protected $conditional_else_regex = '';
  26. protected $conditional_end_regex = '';
  27. protected $conditional_data = array();
  28. protected static $extractions = array(
  29. 'noparse' => array(),
  30. );
  31. protected static $data = null;
  32. protected static $callback_data = array();
  33. /**
  34. * The main Lex parser method. Essentially acts as dispatcher to
  35. * all of the helper parser methods.
  36. *
  37. * @param string $text Text to parse
  38. * @param array|object $data Array or object to use
  39. * @param mixed $callback Callback to use for Callback Tags
  40. * @return string
  41. */
  42. public function parse($text, $data = array(), $callback = false, $allow_php = false)
  43. {
  44. $this->setup_regex();
  45. $this->allow_php = $allow_php;
  46. // Is this the first time parse() is called?
  47. if (Lex_Parser::$data === null)
  48. {
  49. // Let's store the local data array for later use.
  50. Lex_Parser::$data = $data;
  51. }
  52. else
  53. {
  54. // Let's merge the current data array with the local scope variables
  55. // So you can call local variables from within blocks.
  56. $data = array_merge(Lex_Parser::$data, $data);
  57. // Since this is not the first time parse() is called, it's most definately a callback,
  58. // let's store the current callback data with the the local data
  59. // so we can use it straight after a callback is called.
  60. Lex_Parser::$callback_data = $data;
  61. }
  62. // The parse_conditionals method executes any PHP in the text, so clean it up.
  63. if ( ! $allow_php)
  64. {
  65. $text = str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $text);
  66. }
  67. $text = $this->parse_comments($text);
  68. $text = $this->extract_noparse($text);
  69. $text = $this->extract_looped_tags($text, $data, $callback);
  70. // Order is important here. We parse conditionals first as to avoid
  71. // unnecessary code from being parsed and executed.
  72. $text = $this->parse_conditionals($text, $data, $callback);
  73. $text = $this->inject_extractions($text, 'looped_tags');
  74. $text = $this->parse_variables($text, $data, $callback);
  75. $text = $this->inject_extractions($text, 'callback_blocks');
  76. if ($callback)
  77. {
  78. $text = $this->parse_callback_tags($text, $data, $callback);
  79. }
  80. // To ensure that {{ noparse }} is never parsed even during consecutive parse calls
  81. // set $cumulative_noparse to true and use Lex_Parser::inject_noparse($text); immediately
  82. // before the final output is sent to the browser
  83. if ( ! $this->cumulative_noparse)
  84. {
  85. $text = $this->inject_extractions($text);
  86. }
  87. return $text;
  88. }
  89. /**
  90. * Removes all of the comments from the text.
  91. *
  92. * @param string $text Text to remove comments from
  93. * @return string
  94. */
  95. public function parse_comments($text)
  96. {
  97. $this->setup_regex();
  98. return preg_replace('/\{\{#.*?#\}\}/s', '', $text);
  99. }
  100. /**
  101. * Recursivly parses all of the variables in the given text and
  102. * returns the parsed text.
  103. *
  104. * @param string $text Text to parse
  105. * @param array|object $data Array or object to use
  106. * @return string
  107. */
  108. public function parse_variables($text, $data, $callback = null)
  109. {
  110. $this->setup_regex();
  111. /**
  112. * $data_matches[][0][0] is the raw data loop tag
  113. * $data_matches[][0][1] is the offset of raw data loop tag
  114. * $data_matches[][1][0] is the data variable (dot notated)
  115. * $data_matches[][1][1] is the offset of data variable
  116. * $data_matches[][2][0] is the content to be looped over
  117. * $data_matches[][2][1] is the offset of content to be looped over
  118. */
  119. if (preg_match_all($this->variable_loop_regex, $text, $data_matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE))
  120. {
  121. foreach ($data_matches as $index => $match)
  122. {
  123. if ($loop_data = $this->get_variable($match[1][0], $data))
  124. {
  125. $looped_text = '';
  126. foreach ($loop_data as $item_data)
  127. {
  128. $str = $this->parse_conditionals($match[2][0], $item_data, $callback);
  129. $str = $this->parse_variables($str, $item_data, $callback);
  130. if ($callback !== null)
  131. {
  132. $str = $this->parse_callback_tags($str, $item_data, $callback);
  133. }
  134. $looped_text .= $str;
  135. }
  136. $text = preg_replace('/'.preg_quote($match[0][0], '/').'/m', addcslashes($looped_text, '\\$'), $text, 1);
  137. }
  138. else // It's a callback block.
  139. {
  140. // Let's extract it so it doesn't conflict
  141. // with the local scope variables in the next step.
  142. $text = $this->create_extraction('callback_blocks', $match[0][0], $match[0][0], $text);
  143. }
  144. }
  145. }
  146. /**
  147. * $data_matches[0] is the raw data tag
  148. * $data_matches[1] is the data variable (dot notated)
  149. */
  150. if (preg_match_all($this->variable_tag_regex, $text, $data_matches))
  151. {
  152. foreach ($data_matches[1] as $index => $var)
  153. {
  154. if ($val = $this->get_variable($var, $data))
  155. {
  156. $text = str_replace($data_matches[0][$index], $val, $text);
  157. }
  158. }
  159. }
  160. return $text;
  161. }
  162. /**
  163. * Parses all Callback tags, and sends them through the given $callback.
  164. *
  165. * @param string $text Text to parse
  166. * @param mixed $callback Callback to apply to each tag
  167. * @param bool $in_conditional Whether we are in a conditional tag
  168. * @return string
  169. */
  170. public function parse_callback_tags($text, $data, $callback)
  171. {
  172. $this->setup_regex();
  173. $in_condition = $this->in_condition;
  174. if ($in_condition)
  175. {
  176. $regex = '/\{\s*('.$this->variable_regex.')(\s+.*?)?\s*\}/ms';
  177. }
  178. else
  179. {
  180. $regex = '/\{\{\s*('.$this->variable_regex.')(\s+.*?)?\s*\}\}/ms';
  181. }
  182. /**
  183. * $match[0][0] is the raw tag
  184. * $match[0][1] is the offset of raw tag
  185. * $match[1][0] is the callback name
  186. * $match[1][1] is the offset of callback name
  187. * $match[2][0] is the parameters
  188. * $match[2][1] is the offset of parameters
  189. */
  190. while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE))
  191. {
  192. $parameters = array();
  193. $tag = $match[0][0];
  194. $start = $match[0][1];
  195. $name = $match[1][0];
  196. if (isset($match[2]))
  197. {
  198. $cb_data = $data;
  199. if ( !empty(Lex_Parser::$callback_data))
  200. {
  201. $cb_data = array_merge(Lex_Parser::$callback_data, $data);
  202. }
  203. $raw_params = $this->inject_extractions($match[2][0], '__cond_str');
  204. $parameters = $this->parse_parameters($raw_params, $cb_data, $callback);
  205. }
  206. $content = '';
  207. $temp_text = substr($text, $start + strlen($tag));
  208. if (preg_match('/\{\{\s*\/'.preg_quote($name, '/').'\s*\}\}/m', $temp_text, $match, PREG_OFFSET_CAPTURE))
  209. {
  210. $content = substr($temp_text, 0, $match[0][1]);
  211. $tag .= $content.$match[0][0];
  212. // Is there a nested block under this one existing with the same name?
  213. $nested_regex = '/\{\{\s*('.preg_quote($name, '/').')(\s.*?)\}\}(.*?)\{\{\s*\/\1\s*\}\}/ms';
  214. if (preg_match($nested_regex, $content.$match[0][0], $nested_matches))
  215. {
  216. $nested_content = preg_replace('/\{\{\s*\/'.preg_quote($name, '/').'\s*\}\}/m', '', $nested_matches[0]);
  217. $content = $this->create_extraction('nested_looped_tags', $nested_content, $nested_content, $content);
  218. }
  219. }
  220. $replacement = call_user_func_array($callback, array($name, $parameters, $content));
  221. $replacement = $this->parse_recursives($replacement, $content, $callback);
  222. if ($in_condition)
  223. {
  224. $replacement = $this->value_to_literal($replacement);
  225. }
  226. $text = preg_replace('/'.preg_quote($tag, '/').'/m', addcslashes($replacement, '\\$'), $text, 1);
  227. $text = $this->inject_extractions($text, 'nested_looped_tags');
  228. }
  229. return $text;
  230. }
  231. /**
  232. * Parses all conditionals, then executes the conditionals.
  233. *
  234. * @param string $text Text to parse
  235. * @param mixed $data Data to use when executing conditionals
  236. * @param mixed $callback The callback to be used for tags
  237. * @return string
  238. */
  239. public function parse_conditionals($text, $data, $callback)
  240. {
  241. $this->setup_regex();
  242. preg_match_all($this->conditional_regex, $text, $matches, PREG_SET_ORDER);
  243. $this->conditional_data = $data;
  244. /**
  245. * $matches[][0] = Full Match
  246. * $matches[][1] = Either 'if', 'unless', 'elseif', 'unlessif'
  247. * $matches[][2] = Condition
  248. */
  249. foreach ($matches as $match)
  250. {
  251. $this->in_condition = true;
  252. $condition = $match[2];
  253. // Extract all literal string in the conditional to make it easier
  254. if (preg_match_all('/(["\']).*?(?<!\\\\)\1/', $condition, $str_matches))
  255. {
  256. foreach ($str_matches[0] as $m)
  257. {
  258. $condition = $this->create_extraction('__cond_str', $m, $m, $condition);
  259. }
  260. }
  261. $condition = preg_replace_callback('/\b('.$this->variable_regex.')\b/', array($this, 'process_condition_var'), $condition);
  262. if ($callback)
  263. {
  264. $condition = preg_replace('/\b(?!\{\s*)('.$this->callback_name_regex.')(?!\s+.*?\s*\})\b/', '{$1}', $condition);
  265. $condition = $this->parse_callback_tags($condition, $data, $callback);
  266. }
  267. // Re-inject any strings we extracted
  268. $condition = $this->inject_extractions($condition, '__cond_str');
  269. $conditional = '<?php '.$match[1].' ('.$condition.'): ?>';
  270. $text = preg_replace('/'.preg_quote($match[0], '/').'/m', addcslashes($conditional, '\\$'), $text, 1);
  271. }
  272. $text = preg_replace($this->conditional_else_regex, '<?php else: ?>', $text);
  273. $text = preg_replace($this->conditional_end_regex, '<?php endif; ?>', $text);
  274. $text = $this->parse_php($text);
  275. $this->in_condition = false;
  276. return $text;
  277. }
  278. /**
  279. * Goes recursively through a callback tag with a passed child array.
  280. *
  281. * @param string $text - The replaced text after a callback.
  282. * @param string $orig_text - The original text, before a callback is called.
  283. * @param mixed $callback
  284. * @return string $text
  285. */
  286. public function parse_recursives($text, $orig_text, $callback)
  287. {
  288. // Is there a {{ *recursive [array_key]* }} tag here, let's loop through it.
  289. if (preg_match($this->recursive_regex, $text, $match))
  290. {
  291. $array_key = $match[1];
  292. $tag = $match[0];
  293. $next_tag = null;
  294. $children = Lex_Parser::$callback_data[$array_key];
  295. $child_count = count($children);
  296. $count = 1;
  297. // Is the array not multi-dimensional? Let's make it multi-dimensional.
  298. if ($child_count == count($children, COUNT_RECURSIVE))
  299. {
  300. $children = array($children);
  301. $child_count = 1;
  302. }
  303. foreach ($children as $child)
  304. {
  305. $has_children = true;
  306. // If this is a object let's convert it to an array.
  307. is_array($child) OR $child = (array) $child;
  308. // Does this child not contain any children?
  309. // Let's set it as empty then to avoid any errors.
  310. if ( ! array_key_exists($array_key, $child))
  311. {
  312. $child[$array_key] = array();
  313. $has_children = false;
  314. }
  315. $replacement = $this->parse($orig_text, $child, $callback, $this->allow_php);
  316. // If this is the first loop we'll use $tag as reference, if not
  317. // we'll use the previous tag ($next_tag)
  318. $current_tag = ($next_tag !== null) ? $next_tag : $tag;
  319. // If this is the last loop set the next tag to be empty
  320. // otherwise hash it.
  321. $next_tag = ($count == $child_count) ? '' : md5($tag.$replacement);
  322. $text = str_replace($current_tag, $replacement.$next_tag, $text);
  323. if ($has_children)
  324. {
  325. $text = $this->parse_recursives($text, $orig_text, $callback);
  326. }
  327. $count++;
  328. }
  329. }
  330. return $text;
  331. }
  332. /**
  333. * Gets or sets the Scope Glue
  334. *
  335. * @param string|null $glue The Scope Glue
  336. * @return string
  337. */
  338. public function scope_glue($glue = null)
  339. {
  340. if ($glue !== null)
  341. {
  342. $this->regex_setup = false;
  343. $this->scope_glue = $glue;
  344. }
  345. return $glue;
  346. }
  347. /**
  348. * Sets the noparse style. Immediate or cumulative.
  349. *
  350. * @param bool $mode
  351. * @return void
  352. */
  353. public function cumulative_noparse($mode)
  354. {
  355. $this->cumulative_noparse = $mode;
  356. }
  357. /**
  358. * Injects noparse extractions.
  359. *
  360. * This is so that multiple parses can store noparse
  361. * extractions and all noparse can then be injected right
  362. * before data is displayed.
  363. *
  364. * @param string $text Text to inject into
  365. * @return string
  366. */
  367. public function inject_noparse($text)
  368. {
  369. if (isset(Lex_Parser::$extractions['noparse']))
  370. {
  371. foreach (Lex_Parser::$extractions['noparse'] AS $hash => $replacement)
  372. {
  373. if (strpos($text, "noparse_{$hash}") !== FALSE)
  374. {
  375. $text = str_replace("noparse_{$hash}", $replacement, $text);
  376. }
  377. }
  378. }
  379. return $text;
  380. }
  381. /**
  382. * This is used as a callback for the conditional parser. It takes a variable
  383. * and returns the value of it, properly formatted.
  384. *
  385. * @param array $match A match from preg_replace_callback
  386. * @return string
  387. */
  388. protected function process_condition_var($match)
  389. {
  390. $var = is_array($match) ? $match[0] : $match;
  391. if (in_array(strtolower($var), array('true', 'false', 'null', 'or', 'and')) or
  392. strpos($var, '__cond_str') === 0 or
  393. is_numeric($var))
  394. {
  395. return $var;
  396. }
  397. $value = $this->get_variable($var, $this->conditional_data, '__process_condition_var__');
  398. if ($value === '__process_condition_var__')
  399. {
  400. return $this->in_condition ? $var : 'null';
  401. }
  402. return $this->value_to_literal($value);
  403. }
  404. /**
  405. * This is used as a callback for the conditional parser. It takes a variable
  406. * and returns the value of it, properly formatted.
  407. *
  408. * @param array $match A match from preg_replace_callback
  409. * @return string
  410. */
  411. protected function process_param_var($match)
  412. {
  413. return $match[1].$this->process_condition_var($match[2]);
  414. }
  415. /**
  416. * Takes a value and returns the literal value for it for use in a tag.
  417. *
  418. * @param string $value Value to convert
  419. * @return string
  420. */
  421. protected function value_to_literal($value)
  422. {
  423. if ($value === null)
  424. {
  425. return "null";
  426. }
  427. elseif ($value === true)
  428. {
  429. return "true";
  430. }
  431. elseif ($value === false)
  432. {
  433. return "false";
  434. }
  435. elseif (is_numeric($value))
  436. {
  437. return '"'.$value.'"';
  438. }
  439. elseif (is_string($value))
  440. {
  441. return '"'.addslashes($value).'"';
  442. }
  443. elseif (is_object($value) and is_callable(array($value, '__toString')))
  444. {
  445. return '"'.addslashes((string) $value).'"';
  446. }
  447. elseif (is_array($value))
  448. {
  449. return !empty($value) ? "true" : "false";
  450. }
  451. else
  452. {
  453. return $value;
  454. }
  455. }
  456. /**
  457. * Sets up all the global regex to use the correct Scope Glue.
  458. *
  459. * @return void
  460. */
  461. protected function setup_regex()
  462. {
  463. if ($this->regex_setup)
  464. {
  465. return;
  466. }
  467. $glue = preg_quote($this->scope_glue, '/');
  468. $this->variable_regex = $glue === '\\.' ? '[a-zA-Z0-9_'.$glue.']+' : '[a-zA-Z0-9_\.'.$glue.']+';
  469. $this->callback_name_regex = $this->variable_regex.$glue.$this->variable_regex;
  470. $this->variable_loop_regex = '/\{\{\s*('.$this->variable_regex.')\s*\}\}(.*?)\{\{\s*\/\1\s*\}\}/ms';
  471. $this->variable_tag_regex = '/\{\{\s*('.$this->variable_regex.')\s*\}\}/m';
  472. $this->callback_block_regex = '/\{\{\s*('.$this->variable_regex.')(\s.*?)\}\}(.*?)\{\{\s*\/\1\s*\}\}/ms';
  473. $this->recursive_regex = '/\{\{\s*\*recursive\s*('.$this->variable_regex.')\*\s*\}\}/ms';
  474. $this->noparse_regex = '/\{\{\s*noparse\s*\}\}(.*?)\{\{\s*\/noparse\s*\}\}/ms';
  475. $this->conditional_regex = '/\{\{\s*(if|elseif)\s*((?:\()?(.*?)(?:\))?)\s*\}\}/ms';
  476. $this->conditional_else_regex = '/\{\{\s*else\s*\}\}/ms';
  477. $this->conditional_end_regex = '/\{\{\s*(\/if|endif)\s*\}\}/ms';
  478. $this->regex_setup = true;
  479. }
  480. /**
  481. * Extracts the noparse text so that it is not parsed.
  482. *
  483. * @param string $text The text to extract from
  484. * @return string
  485. */
  486. protected function extract_noparse($text)
  487. {
  488. /**
  489. * $matches[][0] is the raw noparse match
  490. * $matches[][1] is the noparse contents
  491. */
  492. if (preg_match_all($this->noparse_regex, $text, $matches, PREG_SET_ORDER))
  493. {
  494. foreach ($matches as $match)
  495. {
  496. $text = $this->create_extraction('noparse', $match[0], $match[1], $text);
  497. }
  498. }
  499. return $text;
  500. }
  501. /**
  502. * Extracts the looped tags so that we can parse conditionals then re-inject.
  503. *
  504. * @param string $text The text to extract from
  505. * @return string
  506. */
  507. protected function extract_looped_tags($text, $data = array(), $callback = null)
  508. {
  509. /**
  510. * $matches[][0] is the raw match
  511. */
  512. if (preg_match_all($this->callback_block_regex, $text, $matches, PREG_SET_ORDER))
  513. {
  514. foreach ($matches as $match)
  515. {
  516. // Does this callback block contain parameters?
  517. if ($this->parse_parameters($match[2], $data, $callback))
  518. {
  519. // Let's extract it so it doesn't conflict with local variables when
  520. // parse_variables() is called.
  521. $text = $this->create_extraction('callback_blocks', $match[0], $match[0], $text);
  522. }
  523. else
  524. {
  525. $text = $this->create_extraction('looped_tags', $match[0], $match[0], $text);
  526. }
  527. }
  528. }
  529. return $text;
  530. }
  531. /**
  532. * Extracts text out of the given text and replaces it with a hash which
  533. * can be used to inject the extractions replacement later.
  534. *
  535. * @param string $type Type of extraction
  536. * @param string $extraction The text to extract
  537. * @param string $replacement Text that will replace the extraction when re-injected
  538. * @param string $text Text to extract out of
  539. * @return string
  540. */
  541. protected function create_extraction($type, $extraction, $replacement, $text)
  542. {
  543. $hash = md5($replacement);
  544. Lex_Parser::$extractions[$type][$hash] = $replacement;
  545. return str_replace($extraction, "{$type}_{$hash}", $text);
  546. }
  547. /**
  548. * Injects all of the extractions.
  549. *
  550. * @param string $text Text to inject into
  551. * @return string
  552. */
  553. protected function inject_extractions($text, $type = null)
  554. {
  555. if ($type === null)
  556. {
  557. foreach (Lex_Parser::$extractions as $type => $extractions)
  558. {
  559. foreach ($extractions as $hash => $replacement)
  560. {
  561. if (strpos($text, "{$type}_{$hash}") !== false)
  562. {
  563. $text = str_replace("{$type}_{$hash}", $replacement, $text);
  564. unset(Lex_Parser::$extractions[$type][$hash]);
  565. }
  566. }
  567. }
  568. }
  569. else
  570. {
  571. if ( ! isset(Lex_Parser::$extractions[$type]))
  572. {
  573. return $text;
  574. }
  575. foreach (Lex_Parser::$extractions[$type] as $hash => $replacement)
  576. {
  577. if (strpos($text, "{$type}_{$hash}") !== false)
  578. {
  579. $text = str_replace("{$type}_{$hash}", $replacement, $text);
  580. unset(Lex_Parser::$extractions[$type][$hash]);
  581. }
  582. }
  583. }
  584. return $text;
  585. }
  586. /**
  587. * Takes a dot-notated key and finds the value for it in the given
  588. * array or object.
  589. *
  590. * @param string $key Dot-notated key to find
  591. * @param array|object $data Array or object to search
  592. * @param mixed $default Default value to use if not found
  593. * @return mixed
  594. */
  595. protected function get_variable($key, $data, $default = null)
  596. {
  597. if (strpos($key, $this->scope_glue) === false)
  598. {
  599. $parts = explode('.', $key);
  600. }
  601. else
  602. {
  603. $parts = explode($this->scope_glue, $key);
  604. }
  605. foreach ($parts as $key_part)
  606. {
  607. if (is_array($data))
  608. {
  609. if ( ! array_key_exists($key_part, $data))
  610. {
  611. return $default;
  612. }
  613. $data = $data[$key_part];
  614. }
  615. elseif (is_object($data))
  616. {
  617. if ( ! isset($data->{$key_part}))
  618. {
  619. return $default;
  620. }
  621. $data = $data->{$key_part};
  622. }
  623. else
  624. {
  625. return $default;
  626. }
  627. }
  628. return $data;
  629. }
  630. /**
  631. * Evaluates the PHP in the given string.
  632. *
  633. * @param string $text Text to evaluate
  634. * @return string
  635. */
  636. protected function parse_php($text)
  637. {
  638. ob_start();
  639. $result = eval('?>'.$text.'<?php ');
  640. if (($result === false) and (ENVIRONMENT === PYRO_DEVELOPMENT))
  641. {
  642. echo '<br />You have a syntax error in your Lex tags. The snippet of text that contains the error has been output below:<br />';
  643. exit(str_replace(array('?>', '<?php '), '', $text));
  644. }
  645. elseif ($result === false)
  646. {
  647. log_message('error', str_replace(array('?>', '<?php '), '', $text));
  648. echo '<br />You have a syntax error in your Lex tags: The snippet of text that contains the error has been output to your application\'s log file.<br />';
  649. }
  650. return ob_get_clean();
  651. }
  652. /**
  653. * Parses a parameter string into an array
  654. *
  655. * @param string The string of parameters
  656. * @return array
  657. */
  658. protected function parse_parameters($parameters, $data, $callback)
  659. {
  660. $this->conditional_data = $data;
  661. $this->in_condition = true;
  662. // Extract all literal string in the conditional to make it easier
  663. if (preg_match_all('/(["\']).*?(?<!\\\\)\1/', $parameters, $str_matches))
  664. {
  665. foreach ($str_matches[0] as $m)
  666. {
  667. $parameters = $this->create_extraction('__param_str', $m, $m, $parameters);
  668. }
  669. }
  670. $parameters = preg_replace_callback(
  671. '/(.*?\s*=\s*(?!__))('.$this->variable_regex.')/is',
  672. array($this, 'process_param_var'),
  673. $parameters
  674. );
  675. if ($callback)
  676. {
  677. $parameters = preg_replace('/(.*?\s*=\s*(?!\{\s*)(?!__))('.$this->callback_name_regex.')(?!\s*\})\b/', '$1{$2}', $parameters);
  678. $parameters = $this->parse_callback_tags($parameters, $data, $callback);
  679. }
  680. // Re-inject any strings we extracted
  681. $parameters = $this->inject_extractions($parameters, '__param_str');
  682. $this->in_condition = false;
  683. if (preg_match_all('/(.*?)\s*=\s*(\'|"|&#?\w+;)(.*?)\2/s', trim($parameters), $matches))
  684. {
  685. $return = array();
  686. foreach ($matches[1] as $i => $attr)
  687. {
  688. $return[trim($matches[1][$i])] = $matches[3][$i];
  689. }
  690. return $return;
  691. }
  692. return array();
  693. }
  694. }