*/ /** * This is an XML HTML template library. * Include/require it in your code to use its functionality. * * This library defines function xht_htmlwchars & class xhtdoc with methods: * - xht_fill_template($template_name) * - xht_substitute($subtext) * * Check htt_error after defining a new xhtdoc(htt_file_contents). * * Assign xht_xmldoc (, xht_get_lang, xht_resource, xht_dbgn) * before calling the class methods. * * @package chamilo.library */ function xht_htmlwchars($s) // use only where ISO-8859-1 is not required! { //return ereg_replace('\[((/?(b|big|i|small|sub|sup|u))|br/)\]', '<\\1>', // str_replace('@�@', '&', // htmlspecialchars(ereg_replace('([0-9]+);', '@�@#\\1;', $s)))); global $charset; return api_ereg_replace('\[((/?(b|big|i|small|sub|sup|u))|br/)\]', '<\\1>', str_replace('@�@', '&', htmlspecialchars(api_ereg_replace('([0-9]+);', '@�@#\\1;', $s), ENT_QUOTES, $charset))); // replaces htmlspecialchars for double-escaped xml chars like '&#nnn;' // and when html tags <...> are represented as [...] } function xht_is_assoclist($s) // ":key1:value1,, key2:value2,, ..." { return is_string($s) && strpos($s, ',,'); } function xht_explode_assoclist($s) { $result = array(); if (!xht_is_assoclist($s)) return $result; foreach (explode(',,', api_substr($s, 1)) as $keyplusvalue) if ($cp = api_strpos($keyplusvalue, api_substr($s, 0, 1))) $result[trim(api_substr($keyplusvalue, 0, $cp))] = api_substr($keyplusvalue, $cp+1); return $result; } class xhtdoc { var $htt_array; // array with HTML templates var $htt_error; // error while parsing htt_file_contents to templates var $xht_xmldoc; // XML Mini-DOM document for which templates are processed var $xht_param; // parameter array for template processing var $xht_get_lang; // user-supplied function for {-L xx-}, e.g. Dokeos get_lang var $xht_resource; // user-supplied function for '=/' in path var $xht_dbgn; // set to a positive value for debugging output var $xht_dbgo; // debugging output accumulates here var $_prev_param; // old parameter values function xht_fill_template($template_name, $cur_elem = 0) { $template_name = trim($template_name); if (!$template_name || (api_strpos($template_name, ' ') !== FALSE)) return ''; if (!is_string($httext = $this->htt_array[$template_name])) return ''; if ($this->xht_dbgn) $this->xht_dbgo .= '\n"; $prev_lpp = 0; $prev_sub = ''; $scanpos = 0; while (($rpp = api_strpos($httext, XHT_RP, $scanpos)) !== FALSE) // first -} { if (($lpp = api_strpos($httext, XHT_LP)) === FALSE) break; // no {- for -} if ($lpp > $rpp) break; // no {- preceding -} while (($next_lpp = api_strpos($httext, XHT_LP, $lpp + XHT_PL)) !== FALSE) if ($next_lpp > $rpp) break; else $lpp = $next_lpp; // find {- closest to -} $subtext = api_substr($httext, $lpp + XHT_PL, $rpp - $lpp - XHT_PL); $httext = api_substr($httext, 0, $lpp) . $this->xht_substitute($subtext, $cur_elem, XHT_LP, XHT_RP) . api_substr($httext, $rpp + XHT_PL); // substitute or leave intact if ($lpp == $prev_lpp && $subtext == $prev_sub) // prevent looping { $scanpos = $rpp + 1; $prev_lpp = 0; $prev_sub = ''; } else { $prev_lpp = $lpp; $prev_sub = $subtext; } } return $httext; } function xht_substitute($subtext, $cur_elem = 0, $pre = '', $post = '') { global $charset; $regs = array(); // for use with ereg() if (!api_ereg(XHT_SUBS2, $subtext, $regs) && !api_ereg(XHT_SUBS1, $subtext, $regs)) return $pre . $subtext . $post; $type = $regs[1]; $text = $regs[2]; $result = ''; $subnum = FALSE; $subtext = isset($regs[3]) ? $regs[3] : ''; if ($this->xht_dbgn) // generate debugging output, with new number $subnum { $subnum = ++ $this->xht_dbgn; $this->xht_dbgo .= '\n"; } if ($type == 'D') // Define, e.g. {-D key value-} { // Assign the value to parameter [key] $this->xht_param[$text] = $subtext; // used to be: = $this->xht_substitute($subtext, $cur_elem); } elseif ($type == 'E') // Escape, e.g. {-E userFunction subtext-} { $result = call_user_func($text, FALSE); // get cached result, if any if ($result === FALSE) // no cached result available { $result = $this->xht_substitute($subtext, $cur_elem); $result = call_user_func($text, $result); // allow to cache } } elseif ($type == 'R') // Repeat, e.g. {-R general/title/string subtext-} { $rdepthn = 'rdepth' . (++ $this->xht_param['rdepth']); $n = 0; $this->xht_param['number'] = '0'; if (is_array($a = $this->_lang($text, $cur_elem))) // repeat on array { foreach ($a as $key => $value) { $this->xht_param['number'] = (string) (++ $n); $this->xht_param[$rdepthn] = (string) ($n); $this->xht_param['key'] = $key; $this->xht_param['value'] = $value; $result .= $this->xht_substitute($subtext, $cur_elem); } } elseif (xht_is_assoclist($a)) // repeat on associative list { foreach (xht_explode_assoclist($a) as $key => $value) { $this->xht_param['number'] = (string) (++ $n); $this->xht_param[$rdepthn] = (string) ($n); $this->xht_param['key'] = $key; $this->xht_param['value'] = $value; $result .= $this->xht_substitute($subtext, $cur_elem); } } elseif (!is_object($this->xht_xmldoc)) { $result = '? R Error: no XML doc has been assigned to xht_xmldoc'; } else // repeat on XML elements { $sub_elems = $this->xht_xmldoc->xmd_select_elements($text, $cur_elem); foreach ($sub_elems as $subElem) { $this->xht_param['number'] = (string) (++ $n); $this->xht_param[$rdepthn] = (string) ($n); $result .= $this->xht_substitute($subtext, $subElem); } } // removed 2004/10/05: template_array (security) // added 2005/03/08: associative list (lang arrays deprecated) $this->xht_param['rdepth'] --; // As there is only one ['number'] or one set ['key'] + ['value'], // using them in nested repeats may not have the desired result. } elseif ($type == 'T') // Test, e.g. {-T key1 == key2 text-} { if (api_ereg('^(=|==|<=|<|!=|<>|>|>=) +([^ ]+) +(.*)$', $subtext, $regs)) { // Comparand= parameter value if set, else languagevar value $cmp1 = isset($this->xht_param[$text]) ? $this->xht_param[$text] : $this->_lang($text, $cur_elem); $cmp3 = isset($this->xht_param[$cmp3 = $regs[2]]) ? $this->xht_param[$cmp3] : $this->_lang($cmp3, $cur_elem); $cmp = strcmp($cmp1, $cmp3); $op = ' ' . $regs[1] . ' '; if ($subnum) $this->xht_dbgo .= '\n"; // debugging output if ( ($cmp < 0 && api_strpos(' <= < != <> ', $op)) || ($cmp == 0 && api_strpos(' = == <= >= ', $op)) || ($cmp > 0 && api_strpos(' != <> > >= ', $op)) ) $result = $this->xht_substitute($regs[3], $cur_elem); // else $result is empty } else { $result = $pre . $subtext . $post; } } else { if (api_strpos('CLPVX', $type) !== FALSE) // used to be always $text = $this->xht_substitute($text, $cur_elem); // nested escape if ($type == 'C') // Call, e.g. {-C SUBTEMPLATE-} $result = $this->xht_fill_template($text, $cur_elem); elseif ($type == 'H') $result = htmlspecialchars($text, ENT_QUOTES, $charset); elseif ($type == 'L') $result = $this->_lang($text, $cur_elem); elseif ($type == 'P') $result = $this->xht_param[$text]; elseif ($type == 'U') $result = urlencode($text); elseif ($type == 'W') $result = xht_htmlwchars($text); elseif (!is_object($this->xht_xmldoc)) { $result = '? V/X Error: no XML doc has been assigned to xht_xmldoc'; } else // $type == 'V' or 'X' { if (api_ereg('^(.*)=/(.+)$', $text, $regs)) // special resource-marker { $path = $regs[1]; $text = $regs[2]; if (api_substr($path, -1) == '/') $path = api_substr($path, 0, -1); if ($path) $cur_elem = $this->xht_xmldoc-> xmd_select_single_element($path, $cur_elem); $cur_elem = call_user_func($this->xht_resource, $cur_elem); } $result = ($type == 'V') ? $this->xht_xmldoc->xmd_value($text, $cur_elem) : $this->xht_xmldoc-> xmd_html_value($text, $cur_elem, 'xht_htmlwchars'); } } if ($subnum) $this->xht_dbgo .= '\n"; return $result; } function xht_add_template($template_name, $httext) { $this->htt_array[$template_name] = $httext; // removed 2004/10/05: (substr($httext, 0, 6) == 'array(') ? // removed 2004/10/05: @eval('return ' . $httext . ';') : $httext; if (!$this->htt_array[$template_name]) { $this->htt_error = 'template ' . $template_name . ' is empty or invalid'; return; } } function xhtdoc($htt_file_contents) { $htt_file_contents = // normalize \r (Mac) and \r\n (Windows) to \n str_replace("\r", "\n", str_replace("\r\n", "\n", $htt_file_contents)); while (api_substr($htt_file_contents, -1) == "\n") $htt_file_contents = api_substr($htt_file_contents, 0, -1); $last_line = api_strrchr($htt_file_contents, "\n"); $this->htt_error = ''; if (api_strlen($last_line) < 12 || api_substr($last_line, 0, 6) != "\n") { $this->htt_error = 'last line must be of the form '; return; } define('XHT_PL', (int) (api_strlen($last_line) - 10) / 2); // Parentheses Lth define('XHT_LP', api_substr($last_line, 6, XHT_PL)); // Left Par define('XHT_RP', api_substr($last_line, 6 + XHT_PL, XHT_PL)); // Right Par if (XHT_LP == XHT_RP) { $this->htt_error = 'parentheses in last line must not be identical'; return; } $this->htt_array = array(); // named elements are arrays and strings foreach (explode("\n\n"))) { $template_name = trim(api_substr($name_and_text, 0, $name_length)); if (api_strpos($template_name, ' ') !== FALSE) give_up('Template ' . $template_name . ' has a space in its name'); $httext = api_substr($name_and_text, $name_length + XHT_PL + 5); while (api_substr($httext, 0, 1) == "\n") $httext = api_substr($httext, 1); while (api_substr($httext, -1) == "\n") $httext = api_substr($httext,0,-1); $this->xht_add_template($template_name, $httext); } } define('XHT_SUBS1', '^(C|H|L|P|U|V|W|X) +(.*)$'); // substitution types 1: // Call, Htmlchars, Lang, Param, Urlencode, Value, Wchars, Xvalue define('XHT_SUBS2', '^(D|E|R|T) +([^ ]+) +(.*)$'); // substitution types 2: // Define, Escape, Repeat, Test $this->xht_dbgo = ''; $this->xht_param = array(0 => '0', 1 => '1', '' => '', 'empty' => '', 'rdepth' => 0); $this->_prev_param = $this->xht_param; // empty: {-R * P empty-} puts the number of subelements in {-P number-} // rdepth: current number of nested R's // rdepth1, rdepth2, ...: key or number, see R } function _show_param() // for debugging info { global $charset; $result = ''; foreach ($this->xht_param as $k => $v) if ($v !== $this->_prev_param[$k]) { $this->_prev_param[$k] = $v; $result .= ', ' . $k . ': ' . ((api_strlen($v) <= 20) ? $v : api_substr($v, 0, 17).'...'); } return $result ? htmlspecialchars(api_substr($result, 1), ENT_QUOTES, $charset) : ''; } function _lang($var, $cur_elem = 0) { $result = @call_user_func($this->xht_get_lang, $var, $cur_elem); // This is a hack for proper working of the language selectors // in the forms that are generated through templates. if ($var == 'Langs') { global $langLangs; if (isset($langLangs)) { $result = $langLangs; } } return isset($result) ? $result : $var; } } /* A word of explanation... The last line of a template file (example below) defines the special markers that are used around template names such as INPUT and OPTION and around HTML escapes such as 'P value' and 'L Store', { and } in the example. You can also use markers of more than one character as long as both have the same length, e.g. <% and %>. The markers must not be equal. In templates with JavaScript or
{D label {L Language}}{D tip {L LanguageTip}}{C LABEL} |