xht.lib.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. <?php /* <!-- xht.lib.php -->
  2. <!-- XML HTML Templates, 2006/12/14 -->
  3. <!-- Copyright (C) 2006 rene.haentjens@UGent.be - see note at end of text -->
  4. <!-- Released under the GNU GPL V2, see http://www.gnu.org/licenses/gpl.html -->
  5. */
  6. /**
  7. ==============================================================================
  8. * This is an XML HTML template library.
  9. * Include/require it in your code to use its functionality.
  10. *
  11. * This library defines function xht_htmlwchars & class xhtdoc with methods:
  12. * - xht_fill_template($template_name)
  13. * - xht_substitute($subtext)
  14. *
  15. * Check htt_error after defining a new xhtdoc(htt_file_contents).
  16. *
  17. * Assign xht_xmldoc (, xht_get_lang, xht_resource, xht_dbgn)
  18. * before calling the class methods.
  19. *
  20. * @package dokeos.library
  21. ==============================================================================
  22. */
  23. function xht_htmlwchars($s) // use only where ISO-8859-1 is not required!
  24. {
  25. return ereg_replace('\[((/?(b|big|i|small|sub|sup|u))|br/)\]', '<\\1>',
  26. str_replace('@§@', '&',
  27. htmlspecialchars(ereg_replace('&#([0-9]+);', '@§@#\\1;', $s))));
  28. // replaces htmlspecialchars for double-escaped xml chars like '&amp;#nnn;'
  29. // and when html tags <...> are represented as [...]
  30. }
  31. function xht_is_assoclist($s) // ":key1:value1,, key2:value2,, ..."
  32. {
  33. return is_string($s) && strpos($s, ',,');
  34. }
  35. function xht_explode_assoclist($s)
  36. {
  37. $result = array(); if (!xht_is_assoclist($s)) return $result;
  38. foreach (explode(',,', substr($s, 1)) as $keyplusvalue)
  39. if (($cp = strpos($keyplusvalue, $s{0})))
  40. $result[trim(substr($keyplusvalue, 0, $cp))] =
  41. substr($keyplusvalue, $cp+1);
  42. return $result;
  43. }
  44. class xhtdoc
  45. {
  46. var $htt_array; // array with HTML templates
  47. var $htt_error; // error while parsing htt_file_contents to templates
  48. var $xht_xmldoc; // XML Mini-DOM document for which templates are processed
  49. var $xht_param; // parameter array for template processing
  50. var $xht_get_lang; // user-supplied function for {-L xx-}, e.g. Dokeos get_lang
  51. var $xht_resource; // user-supplied function for '=/' in path
  52. var $xht_dbgn; // set to a positive value for debugging output
  53. var $xht_dbgo; // debugging output accumulates here
  54. var $_prev_param; // old parameter values
  55. function xht_fill_template($template_name, $cur_elem = 0)
  56. {
  57. $template_name = trim($template_name);
  58. if (!$template_name || (strpos($template_name, ' ') !== FALSE)) return '';
  59. if (!is_string($httext = $this->htt_array[$template_name])) return '';
  60. if ($this->xht_dbgn) $this->xht_dbgo .= '<!-- ' . XHT_LP . $template_name .
  61. ' ' . XHT_RP . $this->_show_param() . " -->\n";
  62. $prev_lpp = 0; $prev_sub = ''; $scanpos = 0;
  63. while (($rpp = strpos($httext, XHT_RP, $scanpos)) !== FALSE) // first -}
  64. {
  65. if (($lpp = strpos($httext, XHT_LP)) === FALSE) break; // no {- for -}
  66. if ($lpp > $rpp) break; // no {- preceding -}
  67. while (($next_lpp = strpos($httext, XHT_LP, $lpp + XHT_PL)) !== FALSE)
  68. if ($next_lpp > $rpp) break;
  69. else $lpp = $next_lpp; // find {- closest to -}
  70. $subtext = substr($httext, $lpp + XHT_PL, $rpp - $lpp - XHT_PL);
  71. $httext = substr($httext, 0, $lpp) .
  72. $this->xht_substitute($subtext, $cur_elem, XHT_LP, XHT_RP) .
  73. substr($httext, $rpp + XHT_PL); // substitute or leave intact
  74. if ($lpp == $prev_lpp && $subtext == $prev_sub) // prevent looping
  75. {
  76. $scanpos = $rpp + 1; $prev_lpp = 0; $prev_sub = '';
  77. }
  78. else
  79. {
  80. $prev_lpp = $lpp; $prev_sub = $subtext;
  81. }
  82. }
  83. return $httext;
  84. }
  85. function xht_substitute($subtext, $cur_elem = 0, $pre = '', $post = '')
  86. {
  87. $regs = array(); // for use with ereg()
  88. if (!ereg(XHT_SUBS2, $subtext, $regs) && !ereg(XHT_SUBS1, $subtext, $regs))
  89. return $pre . $subtext . $post;
  90. $type = $regs[1]; $text = $regs[2]; $result = ''; $subnum = FALSE;
  91. $subtext = isset($regs[3]) ? $regs[3] : '';
  92. if ($this->xht_dbgn) // generate debugging output, with new number $subnum
  93. {
  94. $subnum = ++ $this->xht_dbgn;
  95. $this->xht_dbgo .= '<!-- ' . XHT_LP . $type . $subnum . '+ ' .
  96. htmlspecialchars($text) . ' ' . XHT_RP .
  97. $this->_show_param() . " -->\n";
  98. }
  99. if ($type == 'D') // Define, e.g. {-D key value-}
  100. {
  101. // Assign the value to parameter [key]
  102. $this->xht_param[$text] = $subtext;
  103. // used to be: = $this->xht_substitute($subtext, $cur_elem);
  104. }
  105. elseif ($type == 'E') // Escape, e.g. {-E userFunction subtext-}
  106. {
  107. $result = call_user_func($text, FALSE); // get cached result, if any
  108. if ($result === FALSE) // no cached result available
  109. {
  110. $result = $this->xht_substitute($subtext, $cur_elem);
  111. $result = call_user_func($text, $result); // allow to cache
  112. }
  113. }
  114. elseif ($type == 'R') // Repeat, e.g. {-R general/title/string subtext-}
  115. {
  116. $rdepthn = 'rdepth' . (++ $this->xht_param['rdepth']);
  117. $n = 0; $this->xht_param['number'] = '0';
  118. if (is_array($a = $this->_lang($text, $cur_elem))) // repeat on array
  119. {
  120. foreach ($a as $key => $value)
  121. {
  122. $this->xht_param['number'] = (string) (++ $n);
  123. $this->xht_param[$rdepthn] = (string) ($n);
  124. $this->xht_param['key'] = $key;
  125. $this->xht_param['value'] = $value;
  126. $result .= $this->xht_substitute($subtext, $cur_elem);
  127. }
  128. }
  129. elseif (xht_is_assoclist($a)) // repeat on associative list
  130. {
  131. foreach (xht_explode_assoclist($a) as $key => $value)
  132. {
  133. $this->xht_param['number'] = (string) (++ $n);
  134. $this->xht_param[$rdepthn] = (string) ($n);
  135. $this->xht_param['key'] = $key;
  136. $this->xht_param['value'] = $value;
  137. $result .= $this->xht_substitute($subtext, $cur_elem);
  138. }
  139. }
  140. elseif (!is_object($this->xht_xmldoc))
  141. {
  142. $result = '? R Error: no XML doc has been assigned to xht_xmldoc';
  143. }
  144. else // repeat on XML elements
  145. {
  146. $sub_elems =
  147. $this->xht_xmldoc->xmd_select_elements($text, $cur_elem);
  148. foreach ($sub_elems as $subElem)
  149. {
  150. $this->xht_param['number'] = (string) (++ $n);
  151. $this->xht_param[$rdepthn] = (string) ($n);
  152. $result .= $this->xht_substitute($subtext, $subElem);
  153. }
  154. }
  155. // removed 2004/10/05: template_array (security)
  156. // added 2005/03/08: associative list (lang arrays deprecated)
  157. $this->xht_param['rdepth'] --;
  158. // As there is only one ['number'] or one set ['key'] + ['value'],
  159. // using them in nested repeats may not have the desired result.
  160. }
  161. elseif ($type == 'T') // Test, e.g. {-T key1 == key2 text-}
  162. {
  163. if (ereg('^(=|==|<=|<|!=|<>|>|>=) +([^ ]+) +(.*)$', $subtext, $regs))
  164. {
  165. // Comparand= parameter value if set, else languagevar value
  166. $cmp1 = isset($this->xht_param[$text]) ?
  167. $this->xht_param[$text] : $this->_lang($text, $cur_elem);
  168. $cmp3 = isset($this->xht_param[$cmp3 = $regs[2]]) ?
  169. $this->xht_param[$cmp3] : $this->_lang($cmp3, $cur_elem);
  170. $cmp = strcmp($cmp1, $cmp3); $op = ' ' . $regs[1] . ' ';
  171. if ($subnum) $this->xht_dbgo .= '<!-- ' . XHT_LP . $type . $subnum .
  172. ' ' . htmlspecialchars($cmp1.$op.$cmp3.' = '.$cmp) .
  173. ' ' . XHT_RP . " -->\n"; // debugging output
  174. if ( ($cmp < 0 && strpos(' <= < != <> ', $op)) ||
  175. ($cmp == 0 && strpos(' = == <= >= ', $op)) ||
  176. ($cmp > 0 && strpos(' != <> > >= ', $op)) )
  177. $result = $this->xht_substitute($regs[3], $cur_elem);
  178. // else $result is empty
  179. }
  180. else
  181. {
  182. $result = $pre . $subtext . $post;
  183. }
  184. }
  185. else
  186. {
  187. if (strpos('CLPVX', $type) !== FALSE) // used to be always
  188. $text = $this->xht_substitute($text, $cur_elem); // nested escape
  189. if ($type == 'C') // Call, e.g. {-C SUBTEMPLATE-}
  190. $result = $this->xht_fill_template($text, $cur_elem);
  191. elseif ($type == 'H') $result = htmlspecialchars($text);
  192. elseif ($type == 'L') $result = $this->_lang($text, $cur_elem);
  193. elseif ($type == 'P') $result = $this->xht_param[$text];
  194. elseif ($type == 'U') $result = urlencode($text);
  195. elseif ($type == 'W') $result = xht_htmlwchars($text);
  196. elseif (!is_object($this->xht_xmldoc))
  197. {
  198. $result = '? V/X Error: no XML doc has been assigned to xht_xmldoc';
  199. }
  200. else // $type == 'V' or 'X'
  201. {
  202. if (ereg('^(.*)=/(.+)$', $text, $regs)) // special resource-marker
  203. {
  204. $path = $regs[1]; $text = $regs[2];
  205. if (substr($path, -1) == '/') $path = substr($path, 0, -1);
  206. if ($path) $cur_elem = $this->xht_xmldoc->
  207. xmd_select_single_element($path, $cur_elem);
  208. $cur_elem = call_user_func($this->xht_resource, $cur_elem);
  209. }
  210. $result = ($type == 'V') ?
  211. $this->xht_xmldoc->xmd_value($text, $cur_elem) :
  212. $this->xht_xmldoc->
  213. xmd_html_value($text, $cur_elem, 'xht_htmlwchars');
  214. }
  215. }
  216. if ($subnum) $this->xht_dbgo .= '<!-- ' . XHT_LP . $type . $subnum . '- ' .
  217. htmlspecialchars((strlen($result) <= 60) ?
  218. $result . ' ': substr($result, 0, 57).'...') . XHT_RP . " -->\n";
  219. return $result;
  220. }
  221. function xht_add_template($template_name, $httext)
  222. {
  223. $this->htt_array[$template_name] = $httext;
  224. // removed 2004/10/05: (substr($httext, 0, 6) == 'array(') ?
  225. // removed 2004/10/05: @eval('return ' . $httext . ';') : $httext;
  226. if (!$this->htt_array[$template_name])
  227. {
  228. $this->htt_error = 'template ' . $template_name .
  229. ' is empty or invalid'; return;
  230. }
  231. }
  232. function xhtdoc($htt_file_contents)
  233. {
  234. $htt_file_contents = // normalize \r (Mac) and \r\n (Windows) to \n
  235. str_replace("\r", "\n", str_replace("\r\n", "\n", $htt_file_contents));
  236. while (substr($htt_file_contents, -1) == "\n")
  237. $htt_file_contents = substr($htt_file_contents, 0, -1);
  238. $last_line = strrchr($htt_file_contents, "\n"); $this->htt_error = '';
  239. if (strlen($last_line) < 12 or substr($last_line, 0, 6) != "\n<!-- "
  240. or strlen($last_line) % 2 != 0 or substr($last_line, -4) != " -->")
  241. {
  242. $this->htt_error = 'last line must be of the form <!-- {--} -->';
  243. return;
  244. }
  245. define('XHT_PL', (int) (strlen($last_line) - 10) / 2); // Parentheses Lth
  246. define('XHT_LP', substr($last_line, 6, XHT_PL)); // Left Par
  247. define('XHT_RP', substr($last_line, 6 + XHT_PL, XHT_PL)); // Right Par
  248. if (XHT_LP == XHT_RP)
  249. {
  250. $this->htt_error =
  251. 'parentheses in last line <!-- {--} --> must not be identical';
  252. return;
  253. }
  254. $this->htt_array = array(); // named elements are arrays and strings
  255. foreach (explode("\n<!-- " . XHT_LP, "\n" . $htt_file_contents)
  256. as $name_and_text)
  257. {
  258. if (($name_length = strpos($name_and_text, XHT_RP . " -->\n")))
  259. {
  260. $template_name = trim(substr($name_and_text, 0, $name_length));
  261. if (strpos($template_name, ' ') !== FALSE) give_up('Template ' .
  262. $template_name . ' has a space in its name');
  263. $httext = substr($name_and_text, $name_length + XHT_PL + 5);
  264. while (substr($httext, 0, 1) == "\n") $httext = substr($httext, 1);
  265. while (substr($httext, -1) == "\n") $httext = substr($httext,0,-1);
  266. $this->xht_add_template($template_name, $httext);
  267. }
  268. }
  269. define('XHT_SUBS1', '^(C|H|L|P|U|V|W|X) +(.*)$'); // substitution types 1:
  270. // Call, Htmlchars, Lang, Param, Urlencode, Value, Wchars, Xvalue
  271. define('XHT_SUBS2', '^(D|E|R|T) +([^ ]+) +(.*)$'); // substitution types 2:
  272. // Define, Escape, Repeat, Test
  273. $this->xht_dbgo = '';
  274. $this->xht_param = array(0 => '0', 1 => '1',
  275. '' => '', 'empty' => '', 'rdepth' => 0);
  276. $this->_prev_param = $this->xht_param;
  277. // empty: {-R * P empty-} puts the number of subelements in {-P number-}
  278. // rdepth: current number of nested R's
  279. // rdepth1, rdepth2, ...: key or number, see R
  280. }
  281. function _show_param() // for debugging info
  282. {
  283. $result = '';
  284. foreach ($this->xht_param as $k => $v)
  285. if ($v !== $this->_prev_param[$k])
  286. {
  287. $this->_prev_param[$k] = $v; $result .= ', ' . $k . ': ' .
  288. ((strlen($v) <= 20) ? $v : substr($v, 0, 17).'...');
  289. }
  290. return $result ? htmlspecialchars(substr($result, 1)) : '';
  291. }
  292. function _lang($var, $cur_elem = 0)
  293. {
  294. $result = @call_user_func($this->xht_get_lang, $var, $cur_elem);
  295. // This is a hack for proper working of the language selectors
  296. // in the forms that are generated through templates.
  297. if ($var == 'Langs')
  298. {
  299. global $langLangs;
  300. if (isset($langLangs))
  301. {
  302. $result = $langLangs;
  303. }
  304. }
  305. return isset($result) ? $result : $var;
  306. }
  307. }
  308. /*
  309. A word of explanation...
  310. The last line of a template file (example below) defines the special markers
  311. that are used around template names such as INPUT and OPTION and around HTML
  312. escapes such as 'P value' and 'L Store', { and } in the example. You can also
  313. use markers of more than one character as long as both have the same length,
  314. e.g. <% and %>. The markers must not be equal. In templates with JavaScript
  315. or <style>, the use of { and } might be confusing; however, it does work.
  316. A template starts with a special comment line giving the name of the template
  317. and ends where the next template starts.
  318. Templates contain escapes of the form {one-letter the-rest}, e.g.
  319. {L IdentifierTip}, where the-rest can contain a nested-escape as in e.g.
  320. {R metadata/lom/general/keyword C KEYWORDTEMPLATE}.
  321. Multiple spaces count as one in many places, but not in all. Best is to use
  322. correct spacing. In particular, a T escape such as the example below requires
  323. a space at the end of the first line:
  324. {T x == y
  325. text
  326. }
  327. Names (of templates, parameters, langvars and user-functions)
  328. are case sensitive and cannot contain spaces.
  329. Templates are called with a certain currency in the XML doc, usually the root
  330. element; for an xml-path repeat, the found element is the current one.
  331. One single array with named elements and string values contains the
  332. template parameters; they are shared for all template processing.
  333. Initially, params contains
  334. 0 => '0', 1 => '1', '' => '', 'empty' => '', 'rdepth' => 0.
  335. rdepth keeps track of the current number of nested repeats. In a repeat,
  336. params number, key, value and rdepthN (N is 1, 2, ...) get assigned values.
  337. Types of escapes:
  338. C template-name/nested-escape Call a sub-template
  339. D parameter-name text Define a parameter value
  340. E user-function nested-escape Escape to user-function or do nested-escape
  341. H text HtmlSpecialChars (e.g. transform < to &lt;)
  342. L languagevar/nested-escape Language variable value (see also below)
  343. P parameter-name/nested-escape Parameter value
  344. R repeat-part nested-escape Repeat nested-escape for each ... (see below)
  345. T key1 operator key2 nested-esc Test, do nested-esc if test succeeds (id.)
  346. U text UrlEncode (e.g. transform < to %XX)
  347. V xml-path/nested-escape Value from XML document element or attribute
  348. W text xht_htmlwchars (see below)
  349. X extended-xml-path/nested-esc eXtended Value from XML (see below)
  350. nested-escape= without the special markers. Nesting with special markers is
  351. always possible and in that case the inner nesting is evaluated first.
  352. Note that as from the 2005/03/15 version, some implicit nestings are no
  353. longer possible, e.g. {H {P key}} and {D label {L Keyword}} now require
  354. the inner markers.
  355. Escape allows the caller to cache template output:
  356. user-function is called with parameter (FALSE);
  357. if it returns a string, that string is the result;
  358. if it returns FALSE, nested-escape is executed to produce a result
  359. and user-function is called again with the result, allowing it
  360. to cache the result somewhere; user-function then returns the real result.
  361. repeat-part can be:
  362. the name of a languagevar which contains an associative list such as:
  363. ":key1:value1,, key2:value2,, key3:value3" (note double ,,):
  364. nested-escape is repeated for each list element,
  365. params 'key' and 'value' refer to the current element;
  366. first character defines key-value-separator (here a colon);
  367. key cannot contain key-value-separators and is trimmed;
  368. value can contain anything except ,, and it is not trimmed;
  369. put ',,' at the end of a 0- or 1-element-list.
  370. the name of a languagevar which has an array value:
  371. nested-escape is repeated for each array element,
  372. params 'key' and 'value' refer to the current element.
  373. an xml-path to 0, 1 or more elements in the XML document:
  374. nested-escape is repeated for each element, param 'number' = 1, 2, ...
  375. test operators compare strings: = == <= < != <> > >=
  376. key1 and key2 can be parameter-name or languagevar-name.
  377. xml-path: see XML Mini-DOM; examples:
  378. organizations/@default, body/p[1] (1=first), keyword/*, node/*[-3]
  379. / * /... starts from XML document root, regardless of context
  380. other extensions: .. - + @* @. (parent, prev, next, number, name)
  381. .. stops at the root if too many
  382. -name, +name and @*name means only elements of that name
  383. xht_htmlwchars is like HtmlSpecialChars, but moreover translates:
  384. [b] to <b>, likewise for big, i, small, sub, sup, u, br, and closing tags;
  385. &amp;#nnn; to &#nnn; (double-escape for non-UTF8-channels)
  386. eXtended Value: see method xmd_html_value of the XML Mini-DOM;
  387. the values (but not the pre-, in- and suffixes) are always W-processed;
  388. extended-xml-path examples:
  389. 'keyword/string ,' generates e.g. 'kwd1,kwd2,kwd3'
  390. 'keyword/string , ' generates e.g. 'kwd1, kwd2, kwd3'
  391. '( -% keyword/string , %- )' generates e.g. '(kwd1,kwd2,kwd3)'
  392. but will generate nothing if no keywords are found in the XML doc;
  393. note that the special markers ' -% ' and ' %- ' must have the spaces.
  394. V and X escapes can have an xml-path containing '=/': this calls a user-supplied
  395. function for finding an associated element. It can e.g. be used when reading
  396. a SCORM manifest, for finding the <resource> of an <item>.
  397. L also calls a user-supplied function. Its main target is language-dependent
  398. texts, e.g. Dokeos 'get_lang'. But the current element is passed as second
  399. argument, allowing other functionality in the callback.
  400. Array templates functionality has been removed on 2004/10/05, because of a
  401. security issue when users can submit templates.
  402. --------------------------------------------------------------------------------
  403. Example template file:
  404. <!-- {HEAD} -->
  405. <style type="text/css">
  406. body {font-family: Arial}
  407. </style>
  408. <!-- {METADATA} -->
  409. <h3>{H {L Tool}: {P entry}}</h3>
  410. <table>
  411. <tr>
  412. <td>{D label {L Language}}{D tip {L LanguageTip}}{C LABEL}</td>
  413. <td><select>{D thislang {V general/language}}{R Langs C OPTION}</select></td>
  414. <td><input type="text" disabled value="urn:ugent-be:minerva."/></td>
  415. </tr>
  416. {R general/keyword C KEYWORD}
  417. </table>
  418. <!---------------------- E-n-d Of script Output ---------------------->
  419. <!-- {LABEL} -->
  420. <span title="{H {P tip}}">{H {P label}}&#xa0;:</span>
  421. <!-- {OPTION} -->
  422. <option value="{H {P key}}" {T key == thislang selected}>{H {P value}}</option>
  423. <!-- {INPUT} -->
  424. <input type="text" title="{P title}" class="wide" value="{H {P value}}"/>
  425. <!-- {KEYWORD} -->
  426. <tr>
  427. <td>{D label {L Keyword}}{D tip {L KeywordTip}}{C LABEL}</td>
  428. <td nowrap><select>{D thislang {V string/@language}}{R Langs C OPTION}</select></td>
  429. <td>{D value {X string}}{D title general/keyword[{P number}]/string}{C INPUT}</td>
  430. </tr>
  431. <!-- {} -->
  432. --------------------------------------------------------------------------------
  433. <!--
  434. This program is free software; you can redistribute it and/or
  435. modify it under the terms of the GNU General Public License
  436. as published by the Free Software Foundation; either version 2
  437. of the License, or (at your option) any later version.
  438. This program is distributed in the hope that it will be useful,
  439. but WITHOUT ANY WARRANTY; without even the implied warranty of
  440. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  441. GNU General Public License for more details.
  442. -->
  443. */
  444. ?>