Roxen.git / server / modules / tags / rxmltags.pike

version» Context lines:

Roxen.git/server/modules/tags/rxmltags.pike:1:   // This is a roxen module. Copyright © 1996 - 2009, Roxen IS.   //      #define _stat RXML_CONTEXT->misc[" _stat"]   #define _error RXML_CONTEXT->misc[" _error"]   //#define _extra_heads RXML_CONTEXT->misc[" _extra_heads"]   #define _rettext RXML_CONTEXT->misc[" _rettext"]   #define _ok RXML_CONTEXT->misc[" _ok"]    - constant cvs_version = "$Id: rxmltags.pike,v 1.675 2012/05/10 19:43:46 mast Exp $"; + constant cvs_version = "$Id$";   constant thread_safe = 1;   constant language = roxen.language;      #include <module.h>   #include <config.h>   #include <request_trace.h>   inherit "module";         // ---------------- Module registration stuff ----------------
Roxen.git/server/modules/tags/rxmltags.pike:46:   }      private Regexp.PCRE.Plain rxml_var_splitter =   #if constant(Regexp.PCRE.UTF8_SUPPORTED)   #define LETTER "\\pL"    Regexp.PCRE.StudiedWidestring   #else   #define LETTER "A-Za-z"    Regexp.PCRE.Studied   #endif -  ("^(.*?)" +  ("(?s)^(.*?)"    // Must start with a letter or "_" and contain at least one dot.    // Also be as picky as possible when accepting a negation sign.    "(["LETTER"_]["LETTER"_0-9]*(?:\\.(?:["LETTER"_0-9]+|-[0-9]+)?)+)"    "(.*)$");      private string fix_rxml_vars (string code, RXML.Context ctx)   {    string res = "";    while (array split = rxml_var_splitter->split (code)) {    res += sprintf ("%s(index(%{%O,%}))",    split[0], ctx->parse_user_var (split[1]));    code = split[2];    }    return res + code;   }    - private object sexpr_funcs = class SExprFunctions + private object sexpr_funcs = class    {    // A class for the special functions in sexpr_constants. This is    // to give these function proper names, since those names can    // occur in the compiler errors that are shown to users.       mixed search (mixed a, mixed b)    { -  return search (a, b) + 1; // RXML uses base 1. +  return predef::search (a, b) + 1; // RXML uses base 1.    }       int INT (void|mixed x)    {    return intp (x) || floatp (x) || stringp (x) ? (int) x : 0;    }       float FLOAT (void|mixed x)    {    return intp (x) || floatp (x) || stringp (x) ? (float) x : 0.0;
Roxen.git/server/modules/tags/rxmltags.pike:106:    return RXML_CONTEXT->get_var (rxml_var_ref, scope);    }       array regexp_split (string regexp, string data)    {    Regexp.PCRE.Plain re;    if (mixed err = catch (re = Regexp.PCRE.Widestring (regexp)))    RXML.parse_error (describe_error (err));    return re->split2 (data) || Val.false;    } +  +  float exp(void|int x) +  { +  return predef::exp(intp(x) ? (float) x : x); +  } +  +  float log(void|mixed x) +  { +  return predef::log(intp(x) ? (float) x : x); +  } +  +  int floor(void|mixed x) +  { +  return (int) predef::floor(intp(x) ? (float) x : x); +  } +  +  int ceil(void|mixed x) +  { +  return (int) predef::ceil(intp(x) ? (float) x : x); +  } +  +  int round(void|mixed x) +  { +  return (int) predef::round(intp(x) ? (float) x : x); +  } +     }();      private mapping(string:mixed) sexpr_constants = ([ -  +  "this":0, +  "this_function":0,    "this_program":0,       // The (function) casts below is to avoid very bulky types that    // causes the compiler to take almost a second longer to compile    // this file.       "`+": (function) `+,    "`-": (function) `-,    "`*": (function) `*,    "`/": (function) `/,
Roxen.git/server/modules/tags/rxmltags.pike:133:    "`&": (function) `&,    "`|": (function) `|,    "`^": (function) `^,       "`<": (function) `<,    "`>": (function) `>,    "`==": (function) `==,    "`<=": (function) `<=,    "`>=": (function) `>=,    +  "arrayp": arrayp, +  "callablep": callablep, +  "floatp": floatp, +  "functionp": functionp, +  "intp": intp, +  "mappingp": mappingp, +  "multisetp": multisetp, +  "objectp": objectp, +  "programp": programp, +  "stringp": stringp, +  "undefinedp": undefinedp, +  "zero_type": zero_type, +  +  "has_index": has_index, +  "has_prefix": has_prefix, +  "has_suffix": has_suffix, +  "has_value": has_value, +  +  "indices": indices, +  "values": values, +  +  "combine_path": combine_path_unix, +     "equal": equal,    "sizeof": sizeof, -  +  "strlen": strlen,    "pow":pow, -  +  "exp": sexpr_funcs->exp, +  "log": sexpr_funcs->log,    "abs": abs,    "max": max,    "min": min, -  +  "round": sexpr_funcs->round, +  "floor": sexpr_funcs->floor, +  "ceil": sexpr_funcs->ceil,    "search": sexpr_funcs->search,    "reverse": reverse,    "uniq": Array.uniq,    "regexp_split": sexpr_funcs->regexp_split, -  +  "basename": basename, +  "dirname": dirname,       "INT": sexpr_funcs->INT,    "FLOAT": sexpr_funcs->FLOAT,    "STRING": sexpr_funcs->STRING,       "var": sexpr_funcs->var,    "index": sexpr_funcs->index,   ]);      private class SExprCompileHandler
Roxen.git/server/modules/tags/rxmltags.pike:263:    image = Image.ANY._decode(file_data);    };    if(image) {    return image->type;    }    return my_configuration()->type_from_filename("nonenonenone");   }      // ----------------- Vary callbacks ----------------------    - static string client_ip_cb(string ignored, RequestID id) + protected string client_ip_cb(string ignored, RequestID id)   {    return id->remoteaddr;   }    - static string client_host_cb(string ignored, RequestID id) + protected string client_host_cb(string ignored, RequestID id)   {    if (id->host) return id->host;    return id->host=roxen.quick_ip_to_host(id->remoteaddr);   }      // ----------------- Entities ----------------------      class EntityClientTM {    inherit RXML.Value;    mixed rxml_var_eval(RXML.Context c, string var, string scope_name, void|RXML.Type type) {
Roxen.git/server/modules/tags/rxmltags.pike:618:    constant name = "expire-time";    constant flags = RXML.FLAG_EMPTY_ELEMENT;    array(RXML.Type) result_types = ({RXML.t_nil}); // No result.       class Frame {    inherit RXML.Frame;       array do_return(RequestID id) {    int t,t2;    t = t2 = args["unix-time"] ? (int)args["unix-time"] : time(1); +  int deltat = 0;    if(!args->now) {    t = Roxen.time_dequantifier(args, t); -  CACHE( max(t-t2,0) ); +  deltat = max(t-t2,0);    } -  if(t==t2) { +  if(!deltat) {    NOCACHE();    id->add_response_header("Pragma", "no-cache");    id->add_response_header("Cache-Control", "no-cache"); -  +  } else { +  CACHE( deltat ); +  id->add_response_header("Cache-Control", "max-age=" + deltat);    }       // It's meaningless to have several Expires headers, so just    // override.    id->set_response_header("Expires", Roxen.http_date(t));       // Update Last-Modified to negative expire time.    int last_modified = 2*t2 - t;    if (last_modified > id->misc->last_modified) {    RXML_CONTEXT->set_id_misc ("last_modified", last_modified);
Roxen.git/server/modules/tags/rxmltags.pike:647:    return 0;    }    }   }      class TagHeader {    inherit RXML.Tag;    constant name = "header";    constant flags = RXML.FLAG_NONE;    mapping(string:RXML.Type) opt_arg_types = ([ "name": RXML.t_text(RXML.PEnt), -  "value": RXML.t_text(RXML.PEnt) ]); +  "value": RXML.t_narrowtext(RXML.PEnt) ]);    array(RXML.Type) result_types = ({RXML.t_any}); // Variable result.       class Frame {    inherit RXML.Frame;       array do_return(RequestID id) {    if (!args->name || !args->value) {    // HTML 5.0 header tag.    // FIXME: return ({ propagate_tag(args, content) });    return ({
Roxen.git/server/modules/tags/rxmltags.pike:736:    http_code = ([    "permanent": Protocols.HTTP.HTTP_MOVED_PERM,    "found": Protocols.HTTP.HTTP_FOUND,    "see-other": Protocols.HTTP.HTTP_SEE_OTHER,    "temporary": Protocols.HTTP.HTTP_TEMP_REDIRECT,    ])[type];    if (!http_code)    if (sscanf (type, "%d%*c", http_code) != 1) http_code = 0;    }    +  // Don't expose internal path if the request has been internally +  // redirected already and 'to' is a relative path. +  string org_not_query = id->not_query; +  id->not_query = id->misc->redirected_not_query || id->not_query; +     mapping r = Roxen.http_redirect(args->to, id, prestate, 0, http_code);    -  +  // Restore internal path +  id->not_query = org_not_query; +     if (r->error)    RXML_CONTEXT->set_misc (" _error", r->error);    if (r->extra_heads)    RXML_CONTEXT->extend_scope ("header", r->extra_heads);    // We do not need this as long as r only contains strings and numbers    // foreach(r->extra_heads; string tmp;)    // id->add_response_header(tmp, r->extra_heads[tmp]);    if (args->text)    RXML_CONTEXT->set_misc (" _rettext", args->text);   
Roxen.git/server/modules/tags/rxmltags.pike:1093:       class Frame {    inherit RXML.Frame;       array do_return(RequestID id) {    string size = m_delete(args, "size") || "medium";    string color = m_delete(args, "color") || "white";    mapping aargs = (["href": "http://www.roxen.com/"]);       args->src = "/internal-roxen-power-"+size+"-"+color; -  args->width = (["small":"40","medium":"60","large":"100"])[size]; -  args->height = (["small":"40","medium":"60","large":"100"])[size]; +  args->width = (["small":"40","medium":"60","large":"80"])[size]; +  args->height = (["small":"48","medium":"72","large":"96"])[size];    -  if( color == "white" && size == "large" ) args->height="99"; +     if(!args->alt) args->alt="Powered by Roxen"; -  if(!args->border) args->border="0"; +     int xml=!m_delete(args, "noxml");    if(args->target) aargs->target = m_delete (args, "target");    result = RXML.t_xml->format_tag ("a", aargs, Roxen.make_tag("img", args, xml));    return 0;    }    }   }      class TagDebug {    inherit RXML.Tag;    constant name = "debug"; -  constant flags = RXML.FLAG_EMPTY_ELEMENT|RXML.FLAG_CUSTOM_TRACE; +  constant flags = RXML.FLAG_CUSTOM_TRACE;    array(RXML.Type) result_types = ({RXML.t_any});       class Frame {    inherit RXML.Frame;       array do_return(RequestID id) { -  +  RXML.Context ctx = RXML_CONTEXT; +  +  if (string sleep_time_str = args->sleep) { +  float sleep_time = (float) sleep_time_str; +  if (sleep_time > 0) { +  report_debug ("<debug>: [%s] %s: Sleeping for %.1f sec.\n", +  id->conf->query_name(), id->not_query, sleep_time); +  sleep(sleep_time); +  } +  } +     if (string var = args->showvar) {    TAG_TRACE_ENTER("");    mixed val = RXML.user_get_var (var, args->scope);    result = "<pre>" +    (zero_type (val) ? "UNDEFINED" :    Roxen.html_encode_string (sprintf ("%O", val))) +    "</pre>";    TAG_TRACE_LEAVE("");    return 0;    }    -  +  if (string scope_name = args->showscope) { +  TAG_TRACE_ENTER(""); +  mixed scope = ctx->get_scope (scope_name); +  if (!scope) +  RXML.run_error ("No scope %O.\n", scope_name); +  result = "<pre>"; +  if (objectp (scope)) { +  result += sprintf ("[object scope %O]\n", scope); +  if (array(string) vars = ctx->list_var (scope_name, 1)) { +  mapping scope_map = ([]); +  foreach (vars, string var) +  scope_map[var] = ctx->get_var (var, scope_name); +  scope = scope_map; +  } +  } +  if (mappingp (scope)) +  result += Roxen.html_encode_string (sprintf ("%O", scope)); +  result += "</pre>"; +  TAG_TRACE_LEAVE(""); +  return 0; +  } +     if (args->showid) {    TAG_TRACE_ENTER("");    array path=lower_case(args->showid)/"->";    if(path[0]!="id" || sizeof(path)==1) RXML.parse_error("Can only show parts of the id object.");    mixed obj=id;    foreach(path[1..], string tmp) {    if(search(indices(obj),tmp)==-1) RXML.run_error("Could only reach "+tmp+".");    obj=obj[tmp];    }    result = "<pre>"+Roxen.html_encode_string(sprintf("%O",obj))+"</pre>";
Roxen.git/server/modules/tags/rxmltags.pike:1157:    if (st && st->isreg)    result =    "<pre>" +    Roxen.html_encode_string(Stdio.read_file(debuglog)) +    "</pre>";    TAG_TRACE_LEAVE("");    return 0;    }       if (args->werror) { -  string msg = replace(args->werror,"\\n","\n"); +  string msg = (({ args->werror, content, result }) - +  ({ 0 }) - ({ RXML.nil }) - ({ "" })) * "\n"; +  +  msg = replace(msg,"\\n","\n"); +  result = "";    report_debug ("<debug>: [%s] %s:\n"    "<debug>: %s\n",    id->conf->query_name(), id->not_query,    replace (msg, "\n", "\n<debug>: "));    TAG_TRACE_ENTER ("message: %s", msg);    }    else    TAG_TRACE_ENTER ("");       if (args->off)    RXML_CONTEXT->set_id_misc ("debug", 0);    else if (args->toggle)    RXML_CONTEXT->set_id_misc ("debug", !id->misc->debug);    else if (args->on)    RXML_CONTEXT->set_id_misc ("debug", 1);    -  if (result_type->subtype_of (RXML.t_any_text)) -  result = "<!-- Debug is "+(id->misc->debug?"enabled":"disabled")+" -->"; -  +     TAG_TRACE_LEAVE ("");    return 0;    }    }   }      class TagFSize {    inherit RXML.Tag;    constant name = "fsize";    constant flags = RXML.FLAG_EMPTY_ELEMENT;
Roxen.git/server/modules/tags/rxmltags.pike:1253:       class Frame {    inherit RXML.Frame;       array do_return(RequestID id) {    if (args->src[sizeof(args->src)-4..][0] == '.')    args->src = args->src[..sizeof(args->src)-5];       args->alt = args->alt || args->src;    args->src = "/internal-roxen-" + args->src; -  args->border = args->border || "0"; +        int xml=!m_delete(args, "noxml");    result = Roxen.make_tag("img", args, xml);    return 0;    }    }   }      class TagDate {    inherit RXML.Tag;
Roxen.git/server/modules/tags/rxmltags.pike:1461:    RXML.run_error("Unsupported date.\n");    }    return 0;    }    }   }      class TagInsert {    inherit RXML.Tag;    constant name = "insert"; -  constant flags = RXML.FLAG_EMPTY_ELEMENT | RXML.FLAG_SOCKET_TAG; +  constant flags = RXML.FLAG_SOCKET_TAG;       array(RXML.Type) result_types = ({RXML.t_any});       // FIXME: Check arg types for the plugins.       class Frame {    inherit RXML.Frame;    -  +  array do_enter(RequestID id) +  { +  // Default to being an empty element tag, but +  // allow the plugins to have content if needed. +  flags |= RXML.FLAG_EMPTY_ELEMENT; +  +  mapping(string:RXML.Tag) plugins = get_plugins(); +  RXML.Tag plugin = plugins[args->source]; +  if (!plugin) { +  plugins = args & plugins; +  if (sizeof(plugins)) +  plugin = values(plugins)[0]; +  } +  +  if (!plugin) RXML.parse_error("Unknown insertion source. " +  "Are the correct modules loaded?\n"); +  +  if (plugin->do_enter) { +  return plugin->do_enter(args, id, this); +  } +  } +     void do_insert(RXML.Tag plugin, string name, RequestID id) {    result=plugin->get_data(args[name], args, id, this);       if (RXML.Type new_type = plugin->get_type &&    plugin->get_type (args, result, this))    result_type = new_type;    else if(args->quote=="none")    result_type=RXML.t_xml;    else    result_type=RXML.t_text;
Roxen.git/server/modules/tags/rxmltags.pike:1706:    // Restore previous language state.    if (args->language && pl) {    pl->set_sorted(old_lang, old_qualities);    }       if( !result )    RXML.run_error("No such file ("+Roxen.fix_relative( var, id )+").\n");       if (args["decode-charset"]) {    if (result_mapping->charset) { -  object /*(Locale.Charset.Decoder)*/ decoder = -  Locale.Charset.decoder(result_mapping->charset); +  Charset.Decoder decoder = Charset.decoder(result_mapping->charset);    result = decoder->feed(result)->drain();    }    }      #if ROXEN_COMPAT <= 1.3    if(id->conf->old_rxml_compat)    return Roxen.parse_rxml(result, id);   #endif    return result;    }
Roxen.git/server/modules/tags/rxmltags.pike:1957:    inherit RXML.Tag;    constant name="charset";    RXML.Type content_type = RXML.t_same;       class Frame    {    inherit RXML.Frame;    array do_return( RequestID id )    {    if (string charset = args->in) { -  Locale.Charset.Decoder dec; -  if (catch (dec = Locale.Charset.decoder (charset))) +  Charset.Decoder dec; +  if (catch (dec = Charset.decoder (charset)))    RXML.parse_error ("Invalid charset %q\n", charset);    if (mixed err = catch (content = dec->feed (content || "")->drain())) {    if (objectp (err) && err->is_charset_decode_error)    RXML.run_error (describe_error (err));    else    throw (err);    }    }    if (args->out && id->set_output_charset) {    // Verify that encoder exists since we'll get internal errors    // later if it's invalid. (The same test also happens in    // id->set_output_charset() but only in debug mode.) -  if (catch { Locale.Charset.encoder(args->out); }) +  if (catch { Charset.encoder(args->out); })    RXML.parse_error("Invalid charset %q\n", args->out);    id->set_output_charset( args->out );    }    result_type = result_type (RXML.PXml);    result="";    return ({content});    }    }   }   
Roxen.git/server/modules/tags/rxmltags.pike:2006:    if( !content ) content = "";       switch(args->from)    {    case "safe-utf8":    catch (content = utf8_to_string (content));    break;       default:    if (string charset = args->from) { -  Locale.Charset.Decoder dec; -  if (catch (dec = Locale.Charset.decoder (charset))) +  if (String.width (content) > 8) +  // If it's wide it's already decoded by necessity. Some of +  // the decoders also throw an error on this that isn't +  // typed as a DecodeError. +  RXML.run_error ("Cannot charset decode a wide string.\n"); +  Charset.Decoder dec; +  if (catch (dec = Charset.decoder (charset)))    RXML.parse_error ("Invalid charset %q\n", charset);    if (mixed err = catch (content = dec->feed (content)->drain())) {    if (objectp (err) && err->is_charset_decode_error)    RXML.run_error (describe_error (err));    else    throw (err);    }    }    }       if (args->to) {    // User may provide substitution string or numeric entities for    // characters that don't fit in the requested encoding.    int use_entity_fallback =    lower_case(args["entity-fallback"] || "no") != "no";    string str_fallback = args["string-fallback"]; -  Locale.Charset.Encoder enc; -  if (catch (enc = Locale.Charset.encoder (args->to, str_fallback, +  Charset.Encoder enc; +  if (catch (enc = Charset.encoder (args->to, str_fallback,    use_entity_fallback &&    lambda(string ch) {    return "&#" + ch[0] + ";";    })))    RXML.parse_error ("Invalid charset %q\n", args->to);    if (mixed err = catch (content = enc->feed (content)->drain())) {    if (objectp (err) && err->is_charset_encode_error)    RXML.run_error (describe_error (err));    else    throw (err);
Roxen.git/server/modules/tags/rxmltags.pike:2098:      array(string) container_catch( string tag, mapping m, string c, RequestID id )   {    string r;    mixed e = catch(r=Roxen.parse_rxml(c, id));    if(e && objectp(e) && e->tag_throw) return ({ e->tag_throw });    if(e) throw(e);    return ({r});   }    + // Caches may request synchronization on a shared mutex to serialize + // expensive computations. It's flagged as weak so only locked mutexes + // are retained. + mapping(string:Thread.Mutex) cache_mutexes = +  set_weak_flag( ([ ]), Pike.WEAK_VALUES); + mapping(string:int) cache_mutex_concurrency = ([ ]); +  + class CacheTagEntry (mixed data) + { +  int cache_count_memory (int|mapping opts) +  { +  array(mixed) things; +  +  if (arrayp (data)) { +  things = ({ data }); +  foreach (data, mixed thing) { +  if (objectp (data) && data->is_RXML_PCode) +  things += data->collect_things_recur(); +  else +  things += ({ thing }); +  } +  } else if (objectp (data) && data->is_RXML_PCode) { +  things = data->collect_things_recur(); +  } else { +  werror ("CacheTagEntry: Unknown data %O.\n", data); +  return 0; +  } +  +  // Note 100k entry stack limit (use 99k as an upper safety +  // limit). Could split into multiple calls if necessary. +  return Pike.count_memory (opts + ([ "lookahead": 5 ]), @things[..99000]); +  } + } +  +  + // Settings object used to flag a cache where the caller may extend existing + // entries even after a successful cache_lookup() call. This silences a debug + // warning. + // + // NOTE: Used not only in TagCache but also elsewhere in this file. + cache.CacheManagerPrefs extend_entries_cache_prefs = cache.CacheManagerPrefs(1); +  +    class TagCache {    inherit RXML.Tag;    constant name = "cache";    constant flags = (RXML.FLAG_GET_RAW_CONTENT |    RXML.FLAG_GET_EVALED_CONTENT |    RXML.FLAG_DONT_CACHE_RESULT |    RXML.FLAG_CUSTOM_TRACE);    constant cache_tag_eval_loc = "RXML <cache> eval"; -  constant cache_tag_save_loc = "RXML <cache> save"; +  constant cache_tag_alts_loc = "RXML <cache> alternatives";    array(RXML.Type) result_types = ({RXML.t_any});    -  protected class TimeOutEntry ( -  TimeOutEntry next, -  // timeout_cache is a wrapper array to get a weak ref to the -  // timeout_cache mapping for the frame. This way the mapping will -  // be garbed when the frame disappears, in addition to the -  // timeout. -  array(mapping(string:array(int|RXML.PCode))) timeout_cache) -  {} +  mixed cache_set (string cache_name, mixed key, mixed data, void|int timeout, +  void|mapping|int(1..1) cache_context) +  { +  CacheTagEntry entry +  = cache.cache_set (cache_name, key, CacheTagEntry (data), timeout, +  cache_context); +  return entry && entry->data; +  }    -  protected TimeOutEntry timeout_list; +  mixed cache_lookup (string cache_name, mixed key, void|mapping cache_context) +  { +  CacheTagEntry entry = cache.cache_lookup (cache_name, key, cache_context); +  return entry && entry->data; +  }    -  protected void do_timeouts() +  mixed cache_peek (string cache_name, mixed key)    { -  int now = time (1); -  for (TimeOutEntry t = timeout_list, prev; t; t = t->next) { -  mapping(string:array(int|RXML.PCode)) cachemap = t->timeout_cache[0]; -  if (cachemap) { -  foreach (cachemap; string key; array(int|RXML.PCode) val) -  if (val[0] < now) m_delete (cachemap, key); -  prev = t; +  CacheTagEntry entry = cache.cache_peek (cache_name, key); +  return entry && entry->data;    } -  else -  if (prev) prev->next = t->next; -  else timeout_list = t->next; -  } -  roxen.background_run (roxen.query("mem_cache_gc_2"), do_timeouts); -  } +     -  protected void add_timeout_cache ( -  mapping(string:array(int|RXML.PCode)) timeout_cache) +  void cache_remove (string cache_name, mixed key)    { -  if (!timeout_list) -  roxen.background_run (roxen.query("mem_cache_gc_2"), do_timeouts); -  else -  for (TimeOutEntry t = timeout_list; t; t = t->next) -  if (t->timeout_cache[0] == timeout_cache) return; -  timeout_list = -  TimeOutEntry (timeout_list, -  set_weak_flag (({timeout_cache}), 1)); +  cache.cache_remove (cache_name, key);    }       class Frame {    inherit RXML.Frame;       int do_iterate;    mapping(string|int:mixed) keymap, overridden_keymap; -  string key; +  string key, key_level2; +  array(string) level2_keys; +  +  // evaled_content is cleared when Frame evaluation is completed +  // (either at cache hit in do_enter or when do_return returns.) We +  // want to avoid keeping a ref from the frame to make the RAM +  // cache more efficient / accurate. Each frame should only count +  // its own data, not data (PCode) in nested cache tags (frames).    RXML.PCode evaled_content; -  int timeout, persistent_cache = 0; +     -  +  int timeout, persistent_cache; +  +  // Mutex state to restrict concurrent generation of same cache entry. +  // This is enabled only if RXML code specifies a "mutex" attribute. +  // We store the mutex in a module-global table (with weak values) +  // indexed on the user-provided name together with the keymap; locking +  // the mutex will maintain a reference. Extra book-keeping is needed +  // to clean up mutexes after concurrent access completes. +  Thread.MutexKey mutex_key; +  string mutex_id; +     // The following are retained for frame reuse.       string cache_id;    // This is set whenever the cache is stored in the roxen RAM cache    // and we need to identify it from the frame. That means two    // cases: One is for shared caches, the other is when the cache is    // stored in RAM but the frame itself might get destructed and    // reinstated later from encoded p-code.    // -  // It's not necessary when both the cache and the frame remains in -  // RAM. In that case we keep the cache in the variable -  // alternatives. +  // FIXME: The current approach of storing persistent alternatives +  // in the RAM cache is not good. It would perhaps be better to +  // serialize cached alternatives as raw strings (using +  // PCodeEncoder) and decode them on-demand (PCodeDecoder), using +  // the Roxen RAM cache to store decoded PCode that can be purged +  // when needed.       array(string|int) subvariables; -  mapping(string:RXML.PCode|array(int|RXML.PCode)) alternatives; +  multiset(string) alternatives;    -  +  string get_full_key (string key) +  { +  if (!cache_id) cache_id = roxen.new_uuid_string(); +  return cache_id + key; +  } +  +  RXML.PCode|array(int|RXML.PCode) get_alternative (string key) +  { +  return cache_lookup (cache_tag_alts_loc, get_full_key (key)); +  } +  +  RXML.PCode|array(int|RXML.PCode) peek_alternative (string key) +  { +  return cache_peek (cache_tag_alts_loc, get_full_key (key)); +  } +  +  void set_alternative (string key, RXML.PCode|array(int|RXML.PCode) entry, +  void|int timeout, void|int no_lookup) +  { +  if (!timeout && arrayp (entry) && entry[0]) +  timeout = entry[0] - time(); +  else if (timeout) { +  if (arrayp (entry)) +  entry[0] = timeout + time(); +  else +  entry = ({ timeout + time(), entry, 0 }); +  } +  +  // A negative timeout means that the entry has already expired. +  if (timeout >= 0) { +  string full_key = get_full_key (key); +  cache_set (cache_tag_alts_loc, full_key, entry, timeout, no_lookup); +  if (!alternatives) alternatives = (<>); +  alternatives[key] = 1; +  } +  } +  +  void remove_alternative (string key) +  { +  string full_key = get_full_key (key); +  cache_remove (cache_tag_alts_loc, full_key); +  if (alternatives) +  alternatives[key] = 0; +  } +     protected constant rxml_empty_replacement = (<"eMp ty__">);       // Got ugly special cases below to avoid getting RXML.empty into    // the keymap since that doesn't work with encode_value_canonic    // (ought to have a canonic_nameof callback in the codec). This    // should cover most cases with object values, at least (e.g.    // RXML.nil should never occur by definition). - #define ADD_VARIABLE_TO_KEYMAP(ctx, var) do { \ + #define ADD_VARIABLE_TO_KEYMAP(ctx, var, is_level2) do { \    array splitted = ctx->parse_user_var (var, 1); \    if (intp (splitted[0])) { /* Depend on the whole scope. */ \    mapping|RXML.Scope scope = ctx->get_scope (var); \    array ind, val; \    if (mappingp (scope)) { \    ind = indices (scope); \    val = values (scope); \    } \    else if (scope) { \    ind = scope->_indices (ctx, var); \
Roxen.git/server/modules/tags/rxmltags.pike:2205:    else \    parse_error ("Unknown scope %O.\n", var); \    val = replace (val, RXML.empty, rxml_empty_replacement); \    keymap[var] = mkmapping (ind, val); \    } \    else { \    mixed val = ctx->get_var (splitted[1..], splitted[0]); \    if (!zero_type (val)) \    keymap[var] = (val == RXML.empty ? rxml_empty_replacement : val); \    } \ +  if (is_level2) { \ +  if (!level2_keys) \ +  level2_keys = ({ }); \ +  level2_keys += ({ var }); \ +  } \    } while (0)       protected void add_subvariables_to_keymap()    {    RXML.Context ctx = RXML_CONTEXT;    foreach (subvariables, string var)    // Note: Shouldn't get an invalid variable spec here. -  ADD_VARIABLE_TO_KEYMAP (ctx, var); +  ADD_VARIABLE_TO_KEYMAP (ctx, var, 0);    }       protected void make_key_from_keymap (RequestID id, int timeout)    {    // Protocol/client caching is disabled if there are keys except    // '1' and page.path, i.e. when different cache entries might be    // chosen for the same page.    //    // We could consider doing this for the cookie scope too since    // the protocol cache now tracks cookie dependencies through the
Roxen.git/server/modules/tags/rxmltags.pike:2246:    ;    else {    NO_PROTO_CACHE();    if (!args["enable-client-cache"])    NOCACHE();    }    }    else if (timeout)    id->lower_max_cache (timeout);    +  // For two-level keys the level 1 variables are placed in "key" and +  // the level 2 variables in "key_level2". There is no overlap since +  // we'll compare both during lookup. +  if (level2_keys) { +  key = encode_value_canonic(keymap - level2_keys); +  key_level2 = encode_value_canonic(keymap & level2_keys); +  } else {    key = encode_value_canonic (keymap); -  if (!args["disable-key-hash"]) +  key_level2 = 0; +  } +  if (!args["disable-key-hash"]) {    // Initialize with a 32 char string to make sure MD5 goes    // through all the rounds even if the key is very short.    // Otherwise the risk for coincidental equal keys gets much    // bigger. -  key = Crypto.MD5()->update ("................................") +  key = +  Crypto.MD5()->update ("................................")    ->update (key)    ->digest(); -  +  if (key_level2) { +  key_level2 = +  Crypto.MD5()->update ("................................") +  ->update (key_level2) +  ->digest();    } -  +  } +  }       array do_enter (RequestID id)    {    if( args->nocache || args["not-post-method"] && id->method == "POST" ) {    do_iterate = 1;    key = 0; -  +  key_level2 = 0;    TAG_TRACE_ENTER ("no cache due to %s",    args->nocache ? "nocache argument" : "POST method");    id->cache_status->cachetag = 0;    id->misc->cache_tag_miss = 1;    return 0;    }       RXML.Context ctx = RXML_CONTEXT;    int default_key = compat_level < 2.2;       overridden_keymap = 0;    if (!args->propagate ||    (!(keymap = ctx->misc->cache_key) &&    (m_delete (args, "propagate"), 1))) {    overridden_keymap = ctx->misc->cache_key;    keymap = ctx->misc->cache_key = ([]);    }    -  if (args->variable) { -  if (args->variable != "") -  foreach (args->variable / ",", string var) { -  var = String.trim_all_whites (var); -  ADD_VARIABLE_TO_KEYMAP (ctx, var); +  if (string var_list = args->variable) { +  if (var_list != "") { +  var_list = replace(String.normalize_space(var_list), " ", ""); +  foreach (var_list / ",", string var) +  ADD_VARIABLE_TO_KEYMAP (ctx, var, 0);    }    default_key = 0;    }    -  +  if (string uniq_var_list = args["generation-variable"]) { +  if (uniq_var_list != "") { +  uniq_var_list = +  replace(String.normalize_space(uniq_var_list), " ", ""); +  foreach (uniq_var_list / ",", string uniq_var) +  ADD_VARIABLE_TO_KEYMAP (ctx, uniq_var, 1); +  } +  default_key = 0; +  } +     if (args->profile) {    if (mapping avail_profiles = id->misc->rxml_cache_cur_profile)    foreach (args->profile / ",", string profile) {    profile = String.trim_all_whites (profile);    mixed profile_val = avail_profiles[profile];    if (zero_type (profile_val))    parse_error ("Unknown cache profile %O.\n", profile);    keymap[" " + profile] = profile_val;    }    else
Roxen.git/server/modules/tags/rxmltags.pike:2311:    }       if (args->propagate) {    if (args->key)    parse_error ("Argument \"key\" cannot be used together with \"propagate\".");    // Updated the key, so we're done. The enclosing cache tag    // should do the caching.    do_iterate = 1;    TAG_TRACE_ENTER ("propagating key, is now %s",    RXML.utils.format_short (keymap, 200)); -  key = keymap = 0; +  key = key_level2 = keymap = 0;    flags &= ~RXML.FLAG_DONT_CACHE_RESULT;    return 0;    }       if(args->key) keymap[0] += ({args->key});       if (default_key) {    // Include the form variables and the page path by default.    keymap->form = id->real_variables + ([]);    keymap["page.path"] = id->not_query;
Roxen.git/server/modules/tags/rxmltags.pike:2356:    ->update (content_type->name)    ->digest();    }    keymap[1] = ({id->conf->name, cache_id});    }    }       make_key_from_keymap (id, timeout);       // Now we have the cache key. +  int removed; +  object(RXML.PCode)|array(int|RXML.PCode|string) entry; +  int retry_lookup;    -  object(RXML.PCode)|array(int|RXML.PCode) entry = args->shared ? +  do { +  retry_lookup = 0; +  entry = args->shared ?    cache_lookup (cache_tag_eval_loc, key) : -  alternatives && alternatives[key]; +  get_alternative (key); +  removed = 0; // 0: not removed, 1: stale, 2: timeout, 3: pragma no-cache    -  int removed = 0; // 0: not removed, 1: stale, 2: timeout, 3: pragma no-cache -  +  got_entry:    if (entry) {    check_entry_valid: {    if (arrayp (entry)) { -  if (entry[0] < time (1)) { +  // If this represents a two-level entry the second key must +  // match as well for the entry to be considered a hit. A miss +  // in that comparison is however not necessarily a sign that +  // the entry is stale so we treat it as a regular miss. +  // +  // Finding an entry with a two-level keymap when none was +  // requested means whatever entry we got satisfies the +  // lookup. +  if (key_level2 && (sizeof(entry) > 2) && +  (entry[2] != key_level2)) { +  entry = 0; +  break got_entry; +  } +  +  if (entry[0] && (entry[0] < time (1))) {    removed = 2;    break check_entry_valid;    } -  else evaled_content = entry[1]; +  +  evaled_content = entry[1]; +  } else { +  if (key_level2) { +  // Inconsistent use of cache variables since at least one +  // generation variable was expected but none found. We'll +  // consider it a miss and regenerate the entry so it gets +  // stored with a proper two-level keymap. +  entry = 0; +  break got_entry;    } -  else evaled_content = entry; +  +  evaled_content = entry; +  }    if (evaled_content->is_stale())    removed = 1;    else if (id->pragma["no-cache"] && args["flush-on-no-cache"])    removed = 3;    }       if (removed) {    if (args->shared)    cache_remove (cache_tag_eval_loc, key);    else -  if (alternatives) m_delete (alternatives, key); +  remove_alternative (key);    }       else {    do_iterate = -1;    TAG_TRACE_ENTER ("cache hit%s for key %s",    args->shared ?    (timeout ?    " (shared " + timeout + "s timeout cache)" :    " (shared cache)") :    (timeout ? " (" + timeout + "s timeout cache)" : ""),    RXML.utils.format_short (keymap, 200)); -  key = keymap = 0; -  return ({evaled_content}); +  key = key_level2 = keymap = 0; +  // FIXME: The following is probably redundant +  // (handled by cleanup() further below), +  // but shouldn't hurt. +  if (mutex_key) { +  destruct(mutex_key); +  +  // vvv Relying on interpreter lock +  if (!--cache_mutex_concurrency[mutex_id]) +  m_delete(cache_mutexes, mutex_id); +  // ^^^ and here vvv +  if (!cache_mutex_concurrency[mutex_id]) +  m_delete(cache_mutex_concurrency, mutex_id); +  // ^^^    } -  +  +  RXML.PCode cache_hit = evaled_content; +  evaled_content = 0; +  return ({cache_hit});    } -  +  }    -  +  // Check for mutex synchronization during shared entry generation +  if (!mutex_key && args->shared) { +  if (args->mutex) { +  // We use the serialized keymap as the mutex ID so that +  // generation of unrelated entries in the same cache won't +  // block each other. Note that cache_id is already incorporated +  // into key. +  mutex_id = key; +  +  // Signal that we're about to enter mutex handling. This will +  // prevent any other thread from deallocating the same mutex +  // prematurely. +  // +  // vvv Relying on interpreter lock +  cache_mutex_concurrency[mutex_id]++; +  // ^^^ +  +  // Find existing mutex or allocate a new one +  Thread.Mutex mtx = cache_mutexes[mutex_id]; +  lock_mutex: +  { +  if (!mtx) { +  // Prepare a new mutex and lock it before registering it so +  // the weak mapping will retain it. We'll swap in the mutex +  // atomically to avoid a race, and if we lose the race we +  // can discard it and continue with the old one. +  Thread.Mutex new_mtx = Thread.Mutex(); +  Thread.MutexKey new_key = new_mtx->lock(); +  +  // vvv Relying on interpreter lock here +  if (!(mtx = cache_mutexes[mutex_id])) { +  // We're first so store our new mutex +  cache_mutexes[mutex_id] = new_mtx; +  // ^^^ +  mutex_key = new_key; +  break lock_mutex; +  } else { +  // Someone created it first so dispose of our prepared mutex +  // and carry on with the existing one. +  destruct(new_key); +  new_mtx = 0; +  } +  } +  mutex_key = mtx->lock(); +  } +  +  id->add_threadbound_session_object (mutex_key); +  +  retry_lookup = 1; +  } +  } +  } while (retry_lookup); +     keymap += ([]);    do_iterate = 1; -  +  persistent_cache = 0;    TAG_TRACE_ENTER ("cache miss%s for key %s, %s",    args->shared ?    (timeout ?    " (shared " + timeout + "s timeout cache)" :    " (shared cache)") :    (timeout ? " (" + timeout + "s timeout cache)" : ""),    RXML.utils.format_short (keymap, 200),    removed == 1 ? "entry p-code is stale" :    removed == 2 ? "entry had timed out" :    removed == 3 ? "a pragma no-cache request removed the entry" :    "no matching entry");    id->cache_status->cachetag = 0;    id->misc->cache_tag_miss = 1;    return 0;    }       array do_return (RequestID id)    {    if (key) { -  +  int key_updated;    mapping(string|int:mixed) subkeymap = RXML_CONTEXT->misc->cache_key;    if (sizeof (subkeymap) > sizeof (keymap)) {    // The test above assumes that no subtag removes entries in    // RXML_CONTEXT->misc->cache_key.    subvariables = filter (indices (subkeymap - keymap), stringp);    // subvariables is part of the persistent state, but we'll    // come to state_update later anyway if it should be called.    add_subvariables_to_keymap();    make_key_from_keymap (id, timeout); -  +  key_updated = 1;    }       if (args->shared) {    if (object/*(RXML.PikeCompile)*/ comp = evaled_content->p_code_comp) {    // Don't cache the PikeCompile objects.    comp->compile();    evaled_content->p_code_comp = 0;    } -  cache_set (cache_tag_eval_loc, key, evaled_content, timeout); +  +  object(RXML.PCode)|array(int|RXML.PCode|string) new_entry = +  level2_keys ? +  ({ 0, evaled_content, key_level2 }) : +  evaled_content; +  cache_set (cache_tag_eval_loc, key, new_entry, timeout);    TAG_TRACE_LEAVE ("added shared%s cache entry with key %s",    timeout ? " timeout" : "",    RXML.utils.format_short (keymap, 200));    }       else { -  +  if (object/*(RXML.PikeCompile)*/ comp = evaled_content->p_code_comp) +  comp->compile(); +  // Kludge to get a low cost in the RAM cache. Relatively +  // small but costly "RXML <cache> alternatives" entries may +  // otherwise "take over" the RAM cache. +  get_alternative (key);    if (timeout) {    if (args["persistent-cache"] == "yes") {    persistent_cache = 1;    RXML_CONTEXT->state_update();    } -  if (!alternatives) { -  alternatives = ([]); -  if (!persistent_cache) add_timeout_cache (alternatives); -  } -  alternatives[key] = ({time() + timeout, evaled_content}); -  if (cache_id) { -  // A persistent <cache> frame with a nonpersistent -  // cache. We need to ensure the cache exists in the -  // roxen RAM cache with a fresh timeout, since we -  // probably won't enter save() after this. - #ifdef DEBUG -  if (!has_prefix (cache_id, "ci")) -  error ("Unexpected non-shared cache identifier: %O\n", -  cache_id); - #endif -  // Avoid extra refs that mess up the size calculation in -  // cache_set. -  evaled_content = key = 0; -  cache_set (cache_tag_save_loc, cache_id, alternatives, timeout); -  } +  set_alternative (key, +  ({ time() + timeout, evaled_content, key_level2 }), +  timeout, +  key_updated);    TAG_TRACE_LEAVE ("added%s %ds timeout cache entry with key %s",    persistent_cache ? " (possibly persistent)" : "",    timeout,    RXML.utils.format_short (keymap, 200));    }       else { -  if (!alternatives) { -  alternatives = ([]); -  if (cache_id) { -  // A persistent <cache> frame with a nonpersistent -  // cache. The cache itself has gotten lost if we get -  // here, so we need to readd it to the Roxen cache -  // since we probably won't enter save() in this case. - #ifdef DEBUG -  if (!has_prefix (cache_id, "ci")) -  error ("Unexpected non-shared cache identifier: %O\n", -  cache_id); - #endif -  cache_set (cache_tag_save_loc, cache_id, alternatives, 0); -  } -  } -  alternatives[key] = evaled_content; +  set_alternative (key, +  key_level2 ? +  ({ 0, evaled_content, key_level2 }) : +  evaled_content, +  UNDEFINED, +  key_updated); +     if (args["persistent-cache"] != "no") {    persistent_cache = 1;    RXML_CONTEXT->state_update();    }    TAG_TRACE_LEAVE ("added%s cache entry with key %s",    persistent_cache ? " (possibly persistent)" : "",    RXML.utils.format_short (keymap, 200));    }    }    }    else    TAG_TRACE_LEAVE ("");       if (overridden_keymap) {    RXML_CONTEXT->misc->cache_key = overridden_keymap;    overridden_keymap = 0;    }    -  +  evaled_content = 0;    result += content;    return 0;    }    -  +  protected void cleanup() +  { +  if (mutex_key) { +  destruct(mutex_key); +  +  // Decrease parallel count for shared mutex. If we reach zero we +  // know no other thread depends on the same mutex so drop it from +  // global table. +  // +  // vvv Relying on interpreter lock +  if (!--cache_mutex_concurrency[mutex_id]) +  m_delete(cache_mutexes, mutex_id); +  // ^^^ and here vvv +  if (!cache_mutex_concurrency[mutex_id]) +  m_delete(cache_mutex_concurrency, mutex_id); +  // ^^^ +  } +  } +     array save()    { -  +  mapping(string:RXML.PCode|array(int|RXML.PCode)) persistent_alts;    if (alternatives) {    if (persistent_cache) { -  if (timeout) { -  // It's worth the effort to expunge stale entries before -  // we write the cache to disk. -  int now = time (1); -  foreach (alternatives; string key; array(int|RXML.PCode) entry) -  if (entry[0] < now) m_delete (alternatives, key); +  persistent_alts = ([]); +  // Get the entries so we can store them persistently. +  foreach (alternatives; string key;) { +  object(RXML.PCode)|array(int|RXML.PCode) entry = +  peek_alternative (key); +  if (entry) { +  persistent_alts[key] = entry;    }    } -  -  else { -  if (!cache_id && sizeof (alternatives)) { -  // Saving the frame of a nonpersistent cache, and it got -  // entries. Since the frame itself will probably go out of -  // memory after this, we put it into the RAM cache with a -  // unique identifier so that we find the cache again when -  // the tag is reinstated. -  cache_id = "ci" + roxen.new_uuid_string(); -  cache_set (cache_tag_save_loc, cache_id, alternatives, timeout); +     }    } -  } +     -  return ({cache_id, subvariables, persistent_cache, -  persistent_cache && alternatives}); +  return ({cache_id, subvariables, persistent_cache, persistent_alts });    }       void restore (array saved)    { -  [cache_id, subvariables, persistent_cache, alternatives] = saved; +  mapping(string:RXML.PCode|array(int|RXML.PCode)) persistent_alts; +  [cache_id, subvariables, persistent_cache, persistent_alts] = saved;    -  if (cache_id && has_prefix (cache_id, "ci")) { - #ifdef DEBUG -  if (alternatives) -  error ("Cache unexpectedly stored persistently.\n" -  "cache_id: %O, alternatives: %O)\n", cache_id, alternatives); - #endif -  alternatives = cache_lookup (cache_tag_save_loc, cache_id); +  if (persistent_alts) { +  foreach (persistent_alts; string key; object|array entry) { +  int timeout; +  if (arrayp (entry) && entry[0]) { +  timeout = entry[0] - time(); +  if (timeout <= 0) +  continue;    } -  +  +  // Put the persistently stored entries back into the RAM +  // cache. They might get expired over time (and hence won't +  // be encoded persistently if we're saved again), but then +  // they probably weren't that hot anyways. This method makes +  // sure we have control over memory usage. +  +  // FIXME: get_alternative is a hack to get a low cost (since +  // it's reinstantiated from a persistent entry). Would be +  // better to save the original creation cost of the entry to +  // reuse here, but Roxen's cache doesn't have API's to get +  // or set entry cost currently. +  // However, we won't update existing entries in the RAM +  // cache, since they are likely more up-to-date then what we +  // get from the frame restore. Also, this avoids unnecessary +  // count_memory rounds. +  if (!get_alternative (key)) +  set_alternative (key, entry, timeout);    } -  +  } +  }       void exec_array_state_update()    {    RXML_CONTEXT->state_update();    } -  +  +  void destroy() +  { +  // If our entries are stored persistently and the frame is +  // destructed we can free some memory in the RAM cache. The +  // entries will be restored from persistent storage by the +  // restore() function above, when the frame is reinstantiated. +  if (persistent_cache && alternatives) { +  foreach (alternatives; string key;) { +  string full_key = get_full_key (key); +  call_out(cache_remove, 0, cache_tag_alts_loc, full_key);    } -  +  } +  } +  }       protected void create()    { -  cache.cache_register (cache_tag_eval_loc); -  cache.cache_register (cache_tag_save_loc, "no_timings"); +  cache.cache_register (cache_tag_eval_loc, 0, extend_entries_cache_prefs); +  cache.cache_register (cache_tag_alts_loc, 0, extend_entries_cache_prefs);    }   }      class TagNocache   {    inherit RXML.Tag;    constant name = "nocache";    constant flags = RXML.FLAG_DONT_CACHE_RESULT;    array(RXML.Type) result_types = ({RXML.t_any});   
Roxen.git/server/modules/tags/rxmltags.pike:2606:      class TagCrypt {    inherit RXML.Tag;    constant name = "crypt";       class Frame {    inherit RXML.Frame;       array do_return(RequestID id) {    if(args->compare) { -  _ok=crypt(content,args->compare); +  _ok = verify_password(content,args->compare);    return 0;    } -  result=crypt(content); +  result = crypt_password(content);    return 0;    }    }   }      class TagHashHMAC   {    inherit RXML.Tag;    constant name = "hash-hmac";   
Roxen.git/server/modules/tags/rxmltags.pike:2779:    }    }    }       RXML.TagSet internal =    RXML.shared_tag_set (global::this, "maketag", ({ TagAttrib() }) );       class Frame {    inherit RXML.Frame;    RXML.TagSet additional_tags = internal; -  mapping(string:mixed) makeargs = ([]); +  mapping(string:mixed) makeargs;    -  +  array do_enter (RequestID id) +  { +  makeargs = ([]); +  } +     array do_return(RequestID id) {    if (!content) content = "";    switch(args->type) {    case "pi":    if(!args->name) parse_error("Type 'pi' requires a name attribute.\n");    result = RXML.t_xml->format_tag(args->name, 0, content, RXML.FLAG_PROC_INSTR);    break;    case "container":    if(!args->name) parse_error("Type 'container' requires a name attribute.\n");    result = RXML.t_xml->format_tag(args->name, makeargs, content, RXML.FLAG_RAW_ARGS);
Roxen.git/server/modules/tags/rxmltags.pike:3088:    }    else {    if (!m->checked) return 0;    m_delete(m, "checked" );    }       int xml=!m_delete(m, "noxml");       return ({ Roxen.make_tag(t, m, xml) });   } - array split_on_option( string what, Regexp r ) +  + private int|array internal_tag_select(string t, mapping m, string c, +  string name, multiset(string) value)   { -  string whatwhatwhat = string_to_utf8(what); -  array a = r->split( whatwhatwhat ); -  if( !a ) -  return ({ what }); -  return split_on_option( utf8_to_string(a[0]), r ) + map(a[1..],utf8_to_string); - } - private int|array internal_tag_select(string t, mapping m, string c, string name - , multiset(string) value) - { +     if(name && m->name!=name) return ({ RXML.t_xml->format_tag(t, m, c) });    -  // Split input into an array with the layout -  // ({ "option", option_args, stuff_before_next_option })*n -  // e.g. "fox<OPtioN foo='bar'>gazink</option>" will yield -  // tmp=({ "OPtioN", " foo='bar'", "gazink</option>" }) and -  // ret="fox" -  Regexp r = Regexp( "(.*)<([Oo][Pp][Tt][Ii][Oo][Nn])([^>]*)>(.*)" ); -  array(string) tmp=split_on_option(c,r); -  string ret=tmp[0],nvalue; -  int selected,stop; -  tmp=tmp[1..]; +  string cur_tag; +  mapping(string:mixed) cur_args; +  string cur_data = "";    -  while(sizeof(tmp)>2) { -  stop=search(tmp[2],"<"); -  if(sscanf(tmp[1],"%*svalue=%s",nvalue)!=2 && -  sscanf(tmp[1],"%*sVALUE=%s",nvalue)!=2) -  nvalue=tmp[2][..stop==-1?sizeof(tmp[2]):stop]; -  else if(!sscanf(nvalue, "\"%s\"", nvalue) && !sscanf(nvalue, "'%s'", nvalue)) -  sscanf(nvalue, "%s%*[ >]", nvalue); -  selected=Regexp(".*[Ss][Ee][Ll][Ee][Cc][Tt][Ee][Dd].*")->match(string_to_utf8(tmp[1])); -  ret+="<"+tmp[0]+tmp[1]; -  if(value[nvalue] && !selected) ret+=" selected=\"selected\""; -  ret+=">"+tmp[2]; -  if(!Regexp(".*</[Oo][Pp][Tt][Ii][Oo][Nn]")->match(string_to_utf8(tmp[2]))) ret+="</"+tmp[0]+">"; -  tmp=tmp[3..]; +  string finish_tag() +  { +  string _cur_tag = cur_tag; +  cur_tag = 0; +  mapping(string:mixed) _cur_args = cur_args || ([]); +  cur_args = 0; +  string _cur_data = cur_data; +  cur_data = ""; +  +  if (!_cur_args->selected && value[_cur_data]) +  _cur_args->selected = "selected"; +  +  if (_cur_tag) +  return RXML.t_xml->format_tag (_cur_tag, _cur_args, _cur_data); +  +  return _cur_data; +  }; +  +  array process_tag (Parser.HTML p, mapping args) +  { +  string res = ""; +  string tag_name = p->tag_name(); +  +  m_delete (args, "/"); // Self-closed tag. +  +  if (tag_name[-1] == '/') tag_name = tag_name[..<1]; +  +  res = finish_tag(); +  +  if (tag_name[0] != '/') { +  cur_tag = tag_name; +  +  if (value[args->value]) +  args->selected = "selected"; +  else +  m_delete (args, "selected"); +  +  cur_args = args;    } -  return ({ RXML.t_xml->format_tag(t, m, ret) }); +  +  return ({ res }); +  }; +  +  Parser.HTML parser = Parser.HTML(); +  parser->xml_tag_syntax(0); +  +  // Register opening, closing and self-closed tags directly rather +  // than using add_container to be able to handle some odd cases in +  // the testsuite (opening tags without closing tags, but with +  // content, etc.) +  parser->add_tag ("option", process_tag); +  parser->add_tag ("/option", process_tag); +  parser->add_tag ("option/", process_tag); +  parser->_set_data_callback (lambda (Parser.HTML p, string c) +  { +  cur_data += c; +  return ""; +  }); +  parser->ignore_unknown (1); +  parser->case_insensitive_tag (1); +  string res = parser->finish(c)->read() + finish_tag(); +  parser = 0; // Avoid trampoline garbage through callback functions. +  return ({ RXML.t_xml->format_tag (t, m, res) });   }      string simpletag_default( string t, mapping m, string c, RequestID id)   {    multiset value=(<>);    if(m->value) value=mkmultiset(((string)m->value)/(m->separator||","));    if(m->variable) value+=(<RXML.user_get_var(m->variable, m->scope)>);    if(value==(<>)) return c;       return parse_html(c, (["input":internal_tag_input]),
Roxen.git/server/modules/tags/rxmltags.pike:4150:   class TagValue   {    inherit RXML.Tag;    constant name = "value";       mapping(string:RXML.Type) opt_arg_types = ([    "type": RXML.t_type (RXML.PEnt),    "index": RXML.t_any (RXML.PEnt),    ]);    -  RXML.Type content_type = RXML.t_any (RXML.PXml); +     array(RXML.Type) result_types = ({RXML.t_any});    constant flags = RXML.FLAG_DONT_RECOVER;       class Frame    {    inherit RXML.Frame;       array do_enter (RequestID id)    {    RXML.Type type = args->type; -  if (type) content_type = type (RXML.PXml); +  if (type) { +  content_type = type(RXML.PXml); +  } else if (result_type->handle_literals) { +  // t_any, t_array, t_mapping, etc. +  content_type = RXML.t_any(RXML.PXml); +  } else { +  // Typically t_html. +  // Encode the content with the same type as our result_type. +  content_type = result_type(RXML.PXml); +  }       if (args->index) {    if (result_type != RXML.t_mapping)    parse_error ("\"index\" attribute only supported "    "in mapping contexts, got %O.\n", result_type);    }       else {    if (result_type == RXML.t_mapping)    parse_error ("\"index\" attribute required in mapping context.\n");
Roxen.git/server/modules/tags/rxmltags.pike:4461:    }   }         class TagUse {    inherit RXML.Tag;    constant name = "use";    constant flags = RXML.FLAG_EMPTY_ELEMENT;    array(RXML.Type) result_types = ({RXML.t_nil}); // No result.    +  protected void create() +  { +  cache.cache_register("macrofiles", 0, extend_entries_cache_prefs); +  } +     private array(string) list_packages() {    return filter(((get_dir("../local/rxml_packages")||({}))    |(get_dir("rxml_packages")||({}))),    lambda( string s ) {    return s!=".cvsignore" &&    (Stdio.file_size("../local/rxml_packages/"+s)+    Stdio.file_size( "rxml_packages/"+s )) > 0;    });    }   
Roxen.git/server/modules/tags/rxmltags.pike:4917:       array do_enter()    {    if (!upframe->got_content_result || args->eval) {    do_iterate = 1;    // Switch to the set of scopes that were defined at entry of    // the UserTag frame, to get static variable binding in the    // content. This is poking in the internals; there ought to be    // some sort of interface here.    RXML.Context ctx = RXML_CONTEXT; -  orig_ctx_scopes = ctx->scopes, ctx->scopes = upframe->saved_scopes; -  orig_ctx_hidden = ctx->hidden, ctx->hidden = upframe->saved_hidden; +  orig_ctx_scopes = ctx->scopes, ctx->scopes = upframe->get_saved_scopes(); +  orig_ctx_hidden = ctx->hidden, ctx->hidden = upframe->get_saved_hidden();    }    else    // Already have the result of the content evaluation.    do_iterate = -1;    return 0;    }       array do_return()    {    if (do_iterate >= 0) {
Roxen.git/server/modules/tags/rxmltags.pike:4968:    // Do the work in _eval, do_enter and do_return. Can't use the    // do_* functions since the frame isn't thread local here.       if (!upframe->got_content_result || args->eval) {    // Switch to the set of scopes that were defined at entry of    // the UserTag frame, to get static variable binding in the    // content. This is poking in the internals; there ought to be    // some sort of interface here.    RXML.Context ctx = RXML_CONTEXT;    mapping(string:mixed) orig_ctx_scopes = ctx->scopes; -  ctx->scopes = upframe->saved_scopes; +  ctx->scopes = upframe->get_saved_scopes();    mapping(RXML.Frame:array) orig_ctx_hidden = ctx->hidden; -  ctx->hidden = upframe->saved_hidden; +  ctx->hidden = upframe->get_saved_hidden();       RXML.PCode compiled_content = upframe->compiled_content;    if (compiled_content && !compiled_content->is_stale())    content = compiled_content->eval (ctx);    else if (upframe->compile)    [content, upframe->compiled_content] =    ctx->eval_and_compile (content_type, upframe->content_text);    else    content = content_type->eval (upframe->content_text);   
Roxen.git/server/modules/tags/rxmltags.pike:5034:    return flag == 'O' && sprintf ("ExpansionFrame(contents in %O)", upframe);    }    }   }      // This tag set can't be shared since we look at compat_level in   // UserTagContents.   RXML.TagSet user_tag_contents_tag_set =    RXML.TagSet (this_module(), "_user_tag", ({UserTagContents()}));    + mapping(string:mapping(string:mixed)) usertag_saved_scopes = ([]); + mapping(string:mapping(RXML.Frame:array)) usertag_saved_hidden = ([]); +  + class CompDefCacheEntry (array(string|RXML.PCode) comp_def) + { +  int cache_count_memory (int|mapping opts) +  { +  return Pike.count_memory (opts, comp_def, @comp_def); +  } + } +    class UserTag {    inherit RXML.Tag;    string name, lookup_name;    int flags = RXML.FLAG_COMPILE_RESULT;    RXML.Type content_type = RXML.t_xml;    array(RXML.Type) result_types = ({ RXML.t_any(RXML.PXml) }); -  +  constant user_tag_comp_def_loc = "RXML UserTag PCode";       // Note: We can't store the actual user tag definition directly in    // this object; it won't work correctly in p-code since we don't    // reparse the source and thus don't create a frame with the current    // runtime tag definition. By looking up the definition in    // RXML.Context.misc we can get the current definition even if it    // changes in loops etc.       void create(string _name, int moreflags) {    if (_name) {
Roxen.git/server/modules/tags/rxmltags.pike:5081:    string scope_name;    mapping vars;    string raw_tag_text;    int do_iterate;   #ifdef MODULE_LEVEL_SECURITY    object check_security_object = this_module();   #endif       constant is_user_tag = 1;    constant is_contents_nest_tag = 1; +     string content_text;    RXML.PCode compiled_content; -  +     mixed content_result;    int got_content_result; -  mapping(string:mixed) saved_scopes; -  mapping(RXML.Frame:array) saved_hidden; +  +  protected string _saved_id; +     int compile;       array tagdef;    array(string|RXML.PCode) comp_def;    -  +  string saved_id() +  { +  return _saved_id || (_saved_id = roxen.new_uuid_string()); +  } +  +  mapping(string:mixed) get_saved_scopes() +  { +  string sid = saved_id(); +  return usertag_saved_scopes[sid]; +  } +  +  void set_saved_scopes(mapping(string:mixed) _scopes) +  { +  string sid = saved_id(); +  usertag_saved_scopes[sid] = _scopes; +  } +  +  mapping(RXML.Frame:array) get_saved_hidden() +  { +  string sid = saved_id(); +  return usertag_saved_hidden[sid]; +  } +  +  void set_saved_hidden (mapping(RXML.Frame:array) _hidden) +  { +  string sid = saved_id(); +  usertag_saved_hidden[sid] = ([]) || _hidden; +  } +  +  void create() +  { +  cache.cache_register (user_tag_comp_def_loc, "no_timings"); +  } +  +  void destroy() +  { +  if (string sid = _saved_id) { +  m_delete (usertag_saved_scopes, sid); +  m_delete (usertag_saved_hidden, sid); +  } +  } +     array do_enter (RequestID id)    {    vars = 0;    do_iterate = content_text ? -1 : 1;    if ((tagdef = RXML_CONTEXT->misc[lookup_name]))    if (tagdef[4]) {    local_tags = RXML.empty_tag_set;    additional_tags = 0;    }    else {
Roxen.git/server/modules/tags/rxmltags.pike:5118:    array do_return(RequestID id) {    if (!tagdef) return ({propagate_tag()});    RXML.Context ctx = RXML_CONTEXT;       [array(string) src_def,    mapping defaults,    string def_scope_name,    UserTag ignored,    mapping(string:UserTagContents.ExpansionFrame) preparsed_contents_tags,    RXML.Type comp_type, -  comp_def] = tagdef; +  string comp_def_key] = tagdef; +  if (comp_def_key) { +  if (CompDefCacheEntry entry = +  cache_lookup (user_tag_comp_def_loc, comp_def_key)) +  comp_def = entry->comp_def; +  }    vars = defaults+args;    scope_name = def_scope_name || name;    -  if (comp_type != result_type) +  if (!comp_def || comp_type != result_type)    comp_def = src_def + ({});       if (content_text)    // A previously evaluated tag was restored.    content = content_text;    else {    if(content && args->trimwhites)    content = String.trim_all_whites(content);      #if ROXEN_COMPAT <= 1.3
Roxen.git/server/modules/tags/rxmltags.pike:5174:    vars->contents = content;    if (preparsed_contents_tags) vars += preparsed_contents_tags;    ctx->set_id_misc ("last_tag_args", vars);    got_content_result = 0;       if (compat_level > 2.1) {    // Save the scope state so that we can switch back in    // <contents/>, thereby achieving static variable binding in    // the content. This is poking in the internals; there ought    // to be some sort of interface here. -  saved_scopes = ctx->scopes + ([]); -  saved_hidden = ctx->hidden + ([]); +  set_saved_scopes (ctx->scopes + ([])); +  set_saved_hidden (ctx->hidden + ([]));    }    else { -  saved_scopes = ctx->scopes; -  saved_hidden = ctx->hidden; +  set_saved_scopes (ctx->scopes); +  set_saved_hidden (ctx->hidden);    }       return comp_def;    }       array save() {return ({content_text, compiled_content});}    void restore (array saved) {[content_text, compiled_content] = saved;}       void exec_array_state_update()    {    tagdef[5] = result_type; -  tagdef[6] = comp_def; +  +  if (string old_key = tagdef[6]) +  cache_remove (user_tag_comp_def_loc, old_key); +  string comp_def_key = "cdk" + roxen.new_uuid_string(); +  +  // Save comp_def in the RAM cache and use a string to reference +  // it. This avoids a circular reference involving PCode objects, +  // which in turn helps reduce garbage produced by user defined +  // tags by quite a lot. +  cache_set (user_tag_comp_def_loc, comp_def_key, +  CompDefCacheEntry (comp_def), 300); +  tagdef[6] = comp_def_key; +     RXML_CONTEXT->state_update();    }       string _sprintf ()    {    if (catch {return "UserTag.Frame(" + name + ")";})    return "UserTag.Frame(?)";    }    }   
Roxen.git/server/modules/tags/rxmltags.pike:5525:    // Note: \n is used sparingly in output to make it look nice even    // inside <pre>.    string resolv="<ol style='padding-left: 3ex'>";    int level;       string _sprintf()    {    return "Tracer()";    }    - #if efun(gethrtime) || efun(gethrvtime) + #if constant(gethrtime) || constant(gethrvtime)   #define HAVE_CLOCKS      #if constant (gethrtime)    mapping rtimes = ([]);   #endif   #if constant (gethrvtime)    mapping vtimes = ([]);   #endif       local void start_clock (int timestamp)    { - #if efun (gethrvtime) + #if constant (gethrvtime)    // timestamp is cputime. - #if efun (gethrtime) + #if constant (gethrtime)    rtimes[level] = gethrtime();   #endif    vtimes[level] = (timestamp || gethrvtime()) - id->misc->trace_overhead;   #else    // timestamp is realtime.    rtimes[level] = (timestamp || gethrtime()) - id->misc->trace_overhead;   #endif    }       local string stop_clock (int timestamp)    {    int hrnow, hrvnow;    - #if efun (gethrvtime) + #if constant (gethrvtime)    // timestamp is cputime. - #if efun (gethrtime) + #if constant (gethrtime)    hrnow = gethrtime();   #endif    hrvnow = (timestamp || gethrvtime()) - id->misc->trace_overhead;   #else    // timestamp is realtime.    hrnow = (timestamp || gethrtime()) - id->misc->trace_overhead;   #endif       return ({    hrvnow && ("CPU " + Roxen.format_hrtime (hrvnow - vtimes[level])),    hrnow && ("real " + Roxen.format_hrtime (hrnow - rtimes[level]))    }) * ", ";    } - #endif // efun (gethrtime) || efun (gethrvtime) + #endif // constant (gethrtime) || constant (gethrvtime)       void trace_enter_ol(string type, function|object thing, int timestamp)    {    if (orig_trace_enter) orig_trace_enter (type, thing, timestamp);       level++;       if (thing) {    string name = Roxen.get_modfullname (Roxen.get_owning_module (thing));    if (name)
Roxen.git/server/modules/tags/rxmltags.pike:6513:    RXML.user_set_var(args->remainderinfo, rem);    }    else if( do_iterate == object_iterate )    RXML.user_set_var(args->remainderinfo, res->num_rows_left());    }       do_iterate = 0;    return 0;    }    -  static void cleanup() +  +  protected void cleanup()    {    res = 0;    ::cleanup();    }    }   }      class TagComment {    inherit RXML.Tag;    constant name = "comment";
Roxen.git/server/modules/tags/rxmltags.pike:6924:    if(!wwwfile)    s=Stdio.read_bytes(f);    else    s=id->conf->try_get_file(Roxen.fix_relative(f,id), id);    return ((pass=simple_parse_users_file(s, u[1])) &&    (u[0] || match_passwd(u[2], pass)));    }       private int match_passwd(string try, string org) {    if(!strlen(org)) return 1; -  if(crypt(try, org)) return 1; +  if(verify_password(try, org)) return 1;    }       private string simple_parse_users_file(string file, string u) {    if(!file) return 0;    foreach(file/"\n", string line)    {    array(string) arr = line/":";    if (arr[0] == u && sizeof(arr) > 1)    return(arr[1]);    }
Roxen.git/server/modules/tags/rxmltags.pike:7459:    }    return res;    }   }      class TagEmitValues {    inherit RXML.Tag;    constant name="emit";    constant plugin_name="values";    -  static mixed post_process_value(mixed val, mapping(string:mixed) m) +  protected mixed post_process_value(mixed val, mapping(string:mixed) m)    {    if (arrayp(val)) {    if (m->trimwhites || m->case) {    return map(val, post_process_value, m);    }    return val;    }    if(m->trimwhites)    val=String.trim_all_whites(RXML.t_string->encode (val));    if(m->case=="upper")
Roxen.git/server/modules/tags/rxmltags.pike:7604:    }       // Randomize output order? (Can only be applied to arrays since other    // types are inherently random.)    if (lower_case(m->randomize || "no") == "yes") {    if (arrayp(m->values))    Array.shuffle(m->values);    }       if(mappingp(m->values)) -  return map( indices(m->values), +  return map( sort(indices(m->values)),    lambda(mixed ind, mapping(string:mixed) m) {    mixed val = post_process_value(m->values[ind], m);    return (["index":ind,"value":val]);    }, m);       if(arrayp(m->values)) {    if(m->distinct)    m->values = Array.uniq(m->values);    return map( m->values,    lambda(mixed val, mapping(string:mixed) m) {    val = post_process_value(val, m);    return (["value":val]);    }, m);    }       if(multisetp(m->values)) -  return map( m->values, +  return map( sort(m->values),    lambda(mixed val, mapping(string:mixed) m) {    val = post_process_value(val, m);    return (["index":val]);    }, m);       RXML.run_error("Values variable has wrong type %t.\n", m->values);    }   }      class TagEmitFonts
Roxen.git/server/modules/tags/rxmltags.pike:7662:    RXML.get_context()->get_var("key") )||    id->conf->getvar("license")->get_key());    if(!key) {    RXML.parse_error("No license key defined in the configuration\n");    return ({});    }    return key->get_warnings();    }   }    + inherit "emit_object"; +  + #if constant(Parser.CSV) + #define PARSER_CSV Parser.CSV + #else +  + // Based on Parser.CSV from Pike 8.0. + class PARSER_CSV + { +  // START Parser.Tabular from Pike 8.0. +  Stdio.FILE _in; +  int _eol; +  private int prefetch=1024; +  +  private String.Buffer alread=String.Buffer(prefetch); +  private mapping|array fms; +  private Regexp simple=Regexp("^[^[\\](){}<>^$|+*?\\\\]+$"); +  private Regexp emptyline=Regexp("^[ \t\v\r\x1a]*$"); +  private mixed severity=1; +  private int verb=0; +  private int recordcount=1; +  +  void +  create(void|string|Stdio.File|Stdio.FILE input, +  void|array|mapping|string|Stdio.File|Stdio.FILE format, +  void|int verbose) +  { +  if(zero_type(verbose)&&intp(format)) +  verbose=format; +  else +  fms=stringp(format)||objectp(format)?compile(format):format; +  verb=verbose==1?70:verbose; +  if(!input) +  input=" "; +  if(stringp(input)) +  input=Stdio.FakeFile(input); +  if(!input->unread) +  (_in=Stdio.FILE())->assign(input); +  else +  _in=input; +  } +  +  private string read(int n) +  { +  string s; +  s=_in->read(n); +  alread->add(s); +  if(sizeof(s)!=n) +  throw(severity); +  return s; +  } +  +  private string gets(int n) +  { +  string s; +  if(n) +  { +  s=read(n); +  if(has_value(s,"\n")||has_value(s,"\r")) +  throw(severity); +  } else { +  s=_in->gets(); +  if(!s) +  throw(severity); +  if(has_value(s,"\r")) { +  array t; +  t=s/"\r"; +  s=t[0];_in->unread(t[1..]*"\n"); +  } +  alread->add(s);alread->putchar('\n'); +  if(has_suffix(s,"\r")) +  s=s[..<1]; +  _eol=1; +  } +  return s; +  } +  +  class _checkpoint +  { +  private string oldalread; +  +  void create() +  { +  oldalread=alread->get(); +  } +  +  final void release() +  { +  string s=alread->get(); +  alread->add(oldalread); +  alread->add(s); +  oldalread=0; +  } +  +  protected void destroy() +  { +  if(oldalread) { +  string back=alread->get(); +  if(sizeof(back)) { +  _in->unread(back); +  if(verb<0) { +  back-="\n"; +  if(sizeof(back)) +  werror("Backtracking %O\n",back); +  } +  } +  alread->add(oldalread); +  } +  } +  } +  + #define FETCHAR(c,buf,i) (catch((c)=(buf)[(i)++])?((c)=-1):(c)) +  +  string _getdelimword(mapping m) +  { +  multiset delim=m->delim; +  int i,pref=m->prefetch || prefetch; +  String.Buffer word=String.Buffer(pref); +  string buf,skipclass; +  skipclass="%[^"+(string)indices(delim)+"\"\r\x1a\n]"; +  if(sizeof(delim-(<',',';','\t',' '>))) { + delimready: +  for(;;) { +  i=0; +  buf=_in->read(pref); +  int c; +  FETCHAR(c,buf,i); +  while(c>=0) { +  if(delim[c]) +  break delimready; +  else switch(c) { +  default: +  { +  string s; +  sscanf(buf[--i..],skipclass,s); +  word->add(s); +  i+=sizeof(s); +  break; +  } +  case '\n': +  FETCHAR(c,buf,i); +  switch(c) { +  default:i--; +  case '\r':case '\x1a':; +  } +  _eol=1; +  break delimready; +  case '\r': +  FETCHAR(c,buf,i); +  if(c!='\n') +  i--; +  _eol=1; +  break delimready; +  case '\x1a':; +  } +  FETCHAR(c,buf,i); +  } +  if(!sizeof(buf)) +  throw(severity); +  alread->add(buf); +  } +  } else { +  int leadspace=1,inquotes=0; +  csvready: +  for(;;) { +  i=0; +  buf=_in->read(pref); +  int c; +  FETCHAR(c,buf,i); +  while(c>=0) { +  if(delim[c]) { +  if(!inquotes) +  break csvready; +  word->putchar(c); +  } else switch(c) { +  case '"':leadspace=0; +  if(!inquotes) +  inquotes=1; +  else if(FETCHAR(c,buf,i)=='"') +  word->putchar(c); +  else { +  inquotes=0; +  continue; +  } +  break; +  default:leadspace=0; +  case ' ':case '\t': +  if(!leadspace) { +  string s; +  sscanf(buf[--i..],skipclass,s); +  word->add(s); +  i+=sizeof(s); +  } +  break; +  case '\n': +  FETCHAR(c,buf,i); +  switch(c) { +  default:i--; +  case '\r':case '\x1a':; +  } +  if(!inquotes) { +  _eol=1; +  break csvready; +  } +  word->putchar('\n'); +  break; +  case '\r': +  FETCHAR(c,buf,i); +  if(c!='\n') +  i--; +  if(!inquotes) { +  _eol=1; +  break csvready; +  } +  word->putchar('\n'); +  case '\x1a':; +  } +  FETCHAR(c,buf,i); +  } +  if(!sizeof(buf)) +  throw(severity); +  alread->add(buf); +  } +  } +  alread->add(buf[..i-1]); +  _in->unread(buf[i..]); +  return word->get(); +  } +  +  private mapping getrecord(array fmt,int found) +  { +  mapping ret=([]),options; +  if(stringp(fmt[0])) { +  options=(["name":fmt[0]]); +  if(fmt[1]) +  options+=fmt[1]; +  else +  fmt[1]=0; +  } else +  options=fmt[0]; +  if(found) { +  if(options->single) +  throw(severity); // early exit, already found one +  } +  else if(options->mandatory) +  severity=2; +  if(verb<0) +  werror("Checking record %d for %O\n",recordcount,options->name); +  _eol=0; +  foreach(fmt;int fi;array|mapping m) { +  if(fi<2) +  continue; +  string value; +  if(arrayp(m)) { +  array field=m; +  fmt[fi]=m=(["name":field[0]]); +  mixed nm=field[1]; +  if(!mappingp(nm)) { +  if(arrayp(nm)) +  ret+=getrecord(nm,found); +  else +  m+=([(intp(nm)?"width":(stringp(nm)?"match":"delim")):nm]); +  if(sizeof(field)>2) +  m+=field[2]; +  } +  fmt[fi]=m; +  } +  if(_eol) +  throw(severity); +  if(!zero_type(m->width)) +  value=gets(m->width); +  if(m->delim) +  value=_getdelimword(m); +  if(m->match) { +  Regexp rgx; +  if(stringp(m->match)) { +  if(!value && simple->match(m->match)) { +  m->width=sizeof(m->match); +  value=gets(m->width); +  } +  m->match=Regexp("^("+m->match+")"+(value?"$":"")); +  } +  rgx=m->match; +  if(value) { +  if(!rgx->match(value)) { +  if(verb<-3) +  werror(sprintf("Mismatch %O!=%O\n",value,rgx) +  -"Regexp.SimpleRegexp"); +  throw(severity); +  } +  } else { +  string buf=_in->read(m->prefetch || prefetch); +  array spr; +  if(!buf || !(spr=rgx->split(buf))) { +  alread->add(buf); +  if(verb<-3) +  werror(sprintf("Mismatch %O!=%O\n",buf[..32],rgx) +  -"Regexp.SimpleRegexp"); +  throw(severity); +  } +  _in->unread(buf[sizeof(value=spr[0])..]); +  alread->add(value); +  value-="\r"; +  if(has_suffix(value,"\n")) +  value=value[..<1]; +  } +  } +  if(!m->drop) +  ret[m->name]=value; +  } +  if(!_eol && gets(0)!="") +  throw(severity); +  severity=1; +  if(verb&&verb!=-1) { +  array s=({options->name,"::"}); +  foreach(sort(indices(ret)),string name) { +  string value=ret[name]; +  if(sizeof(value)) { +  if(verb<-2) +  s+=({name,":"}); +  s+=({value,","}); +  } +  } +  string out=replace(s[..<1]*"",({"\n"," "," "}),({""," "," "})); +  out=string_to_utf8(out); // FIXME Debugging output defaults to UTF-8 +  if(verb>0) +  werror("%d %.*s\r",recordcount,verb,out); +  else +  werror("%d %s\n",recordcount,out); +  } +  recordcount++; +  return options->fold?ret:([options->name:ret]); +  } +  +  private void add2map(mapping res,string name,mixed entry) +  { +  mapping|array tm = res[name]; +  if(tm) +  { +  if(arrayp(tm)) +  tm+=({entry}); +  else +  tm=({tm,entry}); +  res[name]=tm; +  } +  else +  res[name]=entry; +  } +  +  int skipemptylines() +  { +  string line; int eof=1; +  while((line=_in->gets()) && String.width(line)==8 && emptyline->match(line)) +  recordcount++; +  if(line) +  eof=0,_in->unread(line+"\n"); +  return eof; +  } +  +  mapping fetch(void|array|mapping format) +  { +  mapping ret=([]); +  int skipempty=0; +  if(!format) +  { +  if(skipemptylines()) +  return UNDEFINED; +  skipempty=1;format=fms; +  } + ret: +  { +  if(arrayp(format)) { +  mixed err=catch { +  _checkpoint checkp=_checkpoint(); +  foreach(format;;array|mapping fmt) +  if(arrayp(fmt)) +  for(int found=0;;found=1) { +  mixed err=catch { +  _checkpoint checkp=_checkpoint(); +  mapping rec=getrecord(fmt,found); +  foreach(rec;string name;mixed value) +  add2map(ret,name,value); +  checkp->release(); +  continue; +  }; +  severity=1; +  switch(err) { +  case 2: +  err=1; +  default: +  throw(err); +  case 1:; +  } +  break; +  } +  else if(fmt=fetch(fmt)) +  ret+=fmt; +  checkp->release(); +  break ret; +  }; +  switch(err) { +  default: +  throw(err); +  case 1: +  return 0; +  } +  if(skipempty) +  skipemptylines(); +  } else { +  int found; +  do { +  found=0; +  if(!mappingp(format)) +  error("Empty format definition\n"); +  foreach(format;string name;array|mapping subfmt) +  for(;;) { +  if(verb<0) +  werror("Trying format %O\n",name); +  mapping m; +  if(m=fetch(subfmt)) { +  found=1; +  add2map(ret,name,m); +  continue; +  } +  break; +  } +  if(skipempty && skipemptylines()) +  break; +  } +  while(found); +  } +  } +  return sizeof(ret) && ret; +  } +  +  object feed(string content) +  { +  _in->unread(content); +  return this; +  } +  +  array|mapping setformat(array|mapping format) +  { +  array|mapping oldfms=fms; +  fms=format; +  return oldfms; +  } +  +  private Regexp descrx=Regexp( +  "^([ :]*)([^] \t:;#]*)[ \t]*([0-9]*)[ \t]*(\\[([^]]+)\\]|)" +  "[ \t]*(\"(([^\"]|\\\\\")*)\"|)[ \t]*([a-z][a-z \t]*[a-z]|)[ \t]*([#;].*|)$" +  ); +  private Regexp tokenise=Regexp("^[ \t]*([^ \t]+)[ \t]*(.*)$"); +  array|mapping compile(string|Stdio.File|Stdio.FILE input) +  { +  if(!input) +  input=""; +  if(stringp(input)) +  input=Stdio.FakeFile(input); +  if(!input->unread) { +  Stdio.FILE tmpf = Stdio.FILE(); +  tmpf->assign(input); +  input = tmpf; +  } +  int started=0; +  int lineno=0; +  string beginend="Tabular description "; +  array fields= +  ({"level","name","width",0,"delim",0,"match",0,"options","comment"}); +  array strip=({"name","width","delim","match","options","comment"}); +  int garbage=0; +  +  mapping getline() +  { +  mapping m; +  if(started>=0) +  for(;;) { +  string line=input->gets(); +  if(!line) +  error("Missing begin record\n"); +  array res=descrx->split(line); +  lineno++; +  if(!res) +  if(!started) { +  if(!garbage) { +  garbage=1; +  werror("Skipping garbage lines... %O\n",line); +  } +  continue; +  } +  else +  error("Line %d parse error: %O\n",lineno,line); +  m=mkmapping(fields,res); +  m_delete(m,0); +  m->level=sizeof(m->level); +  foreach(strip,string s) +  if(m[s]&&!sizeof(m[s])||!m[s]&&intp(m[s])) +  m_delete(m,s); +  if(!started) { +  if(!m->level&&!m->name&&m->delim&&m->delim==beginend+"begin") +  started=1; +  continue; +  } +  if(!m->level&&!m->name) { +  if(m->delim==beginend+"end") { +  started=-1; +  break; +  } +  if(!m->comment||m->comment&& +  (has_prefix(m->comment,"#")||has_prefix(m->comment,";"))) +  continue; // skip comments and empty lines +  } +  if(m->options) { +  mapping options=([]); +  array sp; +  string left=m->options; +  m_delete(m,"options"); +  while(sp=tokenise->split(left)) +  options[sp[0]]=1, left=sp[1]; +  m+=options; +  } +  if(m->match) +  m->match=parsecstring(m->match); +  if(m->delim) { +  multiset delim=(<>); +  foreach(parsecstring(replace(m->delim,"\"","\\\""))/"", string cs) +  delim[cs[0]]=1; +  m->delim=delim; +  } +  if(m->width) +  m->width=(int)m->width; +  m_delete(m,"comment"); +  break; +  } +  return m; +  }; +  +  mapping m; +  +  array|mapping getlevel() +  { +  array|mapping cur=({}); +  cur=({m-(<"level">),0}); +  int lastlevel=m->level+1; +  m=0; +  for(;(m || (m=getline())) && lastlevel<=m->level;) +  if(lastlevel==m->level && sizeof(m&(<"delim","match","width">))) +  cur+=({m-(<"level">)}),m=0; +  else { +  array|mapping res=getlevel(); +  if(mappingp(res)) { +  if(mappingp(cur[sizeof(cur)-1])) { +  cur[sizeof(cur)-1]+=res; +  continue; +  } +  res=({res}); +  } +  cur+=res; +  } +  catch { +  if(arrayp(cur) && arrayp(cur[2])) +  return ([cur[0]->name:cur[2..]]); +  }; +  return ({cur}); +  }; +  +  array|mapping ret; +  m=getline(); +  while(started>=0 && m) { +  array|mapping val=getlevel(); +  catch { +  ret+=val; +  continue; +  }; +  ret=val; +  } +  return ret; +  } +  +  private string parsecstring(string s) +  { +  return compile_string("string s=\""+s+"\";")()->s; +  } +  +  // END Parser.Tabular from Pike 8.0. +  +  // START Parser.CSV from Pike 8.0 +  +  int parsehead(void|string delimiters,void|string|object matchfieldname) +  { +  if(skipemptylines()) +  return 0; +  string line=_in->gets(); +  if(!delimiters||!sizeof(delimiters)) +  { +  int countcomma,countsemicolon,counttab; +  countcomma=countsemicolon=counttab=0; +  foreach(line;;int c) +  switch(c) +  { +  case ',':countcomma++; +  break; +  case ';':countsemicolon++; +  break; +  case '\t':counttab++; +  break; +  } +  delimiters=countcomma>countsemicolon?countcomma>counttab?",":"\t": +  countsemicolon>counttab?";":"\t"; +  } +  _in->unread(line+"\n"); +  +  multiset delim=(<>); +  foreach(delimiters;;int c) +  delim+=(<c>); +  +  array res=({ (["single":1]),0 }); +  mapping m=(["delim":delim]); +  +  if(!objectp(matchfieldname)) +  matchfieldname=Regexp(matchfieldname||""); +  _eol=0; +  if(mixed err = catch { +  _checkpoint checkp=_checkpoint(); +  do { +  string field=_getdelimword(m); +  res+=({ m+(["name":field]) }); +  if(String.width(field)>8) +  field=string_to_utf8(field); // FIXME dumbing it down for Regexp() +  if(!matchfieldname->match(field)) +  throw(1); +  } +  while(!_eol); +  }) +  switch(err) { +  default: +  throw(err); +  case 1: +  return 0; +  } +  setformat( ({res}) ); +  return 1; +  } +  +  mapping fetchrecord(void|array|mapping format) +  { +  mapping res=fetch(format); +  if(!res) +  return UNDEFINED; +  foreach(res;;mapping v) +  return v; +  } +  +  // END Parser.CSV from Pike 8.0. + } +  + #endif +  + class TagEmitCSV { +  inherit RXML.Tag; +  constant name = "emit"; +  constant plugin_name = "csv"; +  +  class CSVResult(PARSER_CSV csv) +  { +  inherit EmitObject; +  +  protected mapping(string:mixed) really_get_row() +  { +  return csv->fetchrecord(); +  } +  } +  +  mapping(string:RXML.Type) opt_arg_types = +  ([ +  "path": RXML.t_text(RXML.PEnt), +  "realpath": RXML.t_text(RXML.PEnt), +  "header": RXML.t_text(RXML.PEnt), +  "delimiter": RXML.t_text(RXML.PEnt), +  ]); +  +  array|EmitObject get_dataset(mapping args, RequestID id) +  { +  PARSER_CSV csv; +  if (args->path) { +  string data = id->conf->try_get_file(args->path, id); +  if (stringp(data)) { +  csv = PARSER_CSV(data); +  } else { +  werror("Try get file failed with %O\n", data); +  } +  } else if (args->realpath) { +  Stdio.File file = Stdio.File(); +  if (file->open(args->realpath, "r")) { +  csv = PARSER_CSV(file); +  } +  } else if (!args->quiet) { +  RXML.run_error("Path to data not specified.\n"); +  } +  if (!csv) { +  if (!args->quiet) { +  RXML.run_error("Data file not found.\n"); +  } +  return ({}); +  } +  +  if (args->header) { +  // Explicit headerline. +  if (!has_suffix(args->header, "\n")) args->header += "\n"; +  csv->_in->unread(args->header); +  } +  +  if (!csv->parsehead(args->delimiter)) { +  if (!args->quiet) { +  RXML.run_error("Failed to parse csv header.\n"); +  } +  return ({}); +  } +  // Trow away the header row. +  csv->fetchrecord(); +  return CSVResult(csv); +  } + } +    // ---------------- API registration stuff ---------------      string api_query_modified(RequestID id, string f, int|void by)   {    mapping m = ([ "by":by, "file":f ]);    return tag_modified("modified", m, id, id);   }         // --------------------- Documentation -----------------------
Roxen.git/server/modules/tags/rxmltags.pike:8017: Inside #if defined(manual)
  </p></desc>",      "&client.tm;":#"<desc type='entity'><p><short>    Generates a trademark sign in a way that the client can    render.</short> Possible outcomes are \"&amp;trade;\",    \"&lt;sup&gt;TM&lt;/sup&gt;\", and \"&amp;gt;TM&amp;lt;\".</p>   </desc>",      //----------------------------------------------------------------------    + // NB: The page scope is implemented in Roxen.pmod. +    "&page;":#"<desc type='scope'><p><short>    This scope contains information specific to this page.</short></p>   </desc>",      "&page.realfile;":#"<desc type='entity'><p>    Path to this file in the file system. An example output:    \"/home/joe/html/index.html\".   </p></desc>",      "&page.virtroot;":#"<desc type='entity'><p>
Roxen.git/server/modules/tags/rxmltags.pike:8066: Inside #if defined(manual)
   The query part of the page URL. If the page URL is    \"http://www.server.com/index.html?a=1&amp;b=2\"    the value of this entity is \"a=1&amp;b=2\".   </p></desc>",      "&page.url;":#"<desc type='entity'><p>    The absolute path for this file from the web server's root    view including query variables.   </p></desc>",    + "&page.post-data;":#"<desc type='entity'><p> +  The raw data of the POST request (if any). + </p></desc>", +    "&page.last-true;":#"<desc type='entity'><p>    Is \"1\" if the last <tag>if</tag>-statement succeeded, otherwise 0.    (<xref href='../if/true.tag' /> and <xref href='../if/false.tag' />    is considered as <tag>if</tag>-statements here) See also: <xref    href='../if/' />.</p>   </desc>",      "&page.language;":#"<desc type='entity'><p>    What language the contents of this file is written in. The language    must be given as metadata to be found.
Roxen.git/server/modules/tags/rxmltags.pike:8364:    <p>Indicates which page should be linked to, if any other than the    present one.</p>   </attr>      <attr name='add' value='string'>    <p>The prestate or prestates that should be added, in a comma    separated list.</p>   </attr>      <attr name='drop' value='string'> -  <p>The prestate or prestates that should be dropped, in a comma separated +  <p>The prestate or prestates that should be dropped, in a comma-separated    list.</p>   </attr>      <attr name='class' value='string'>    <p>This cascading style sheet (CSS) class definition will apply to    the a-element.</p>   </attr>",      //----------------------------------------------------------------------   
Roxen.git/server/modules/tags/rxmltags.pike:8565:    <p>This is a comma-separated list of variables and scopes that the    cache should depend on. The value can be an empty string, which is    useful to only disable the default dependencies in compatibility    mode.</p>       <p>Since it's important to keep down the size of the cache, this    should typically be kept to only a few variables with a limited set    of possible values, or else the cache should have a timeout.</p>   </attr>    + <attr name='generation-variable' value='string'> +  <p>Similar to the \"variable\" attribute with the difference that the +  cache only keeps the most recently stored value for the combination of +  all referenced generation variables. This is particularly suitable for +  generation counters where old entries become garbage once a counter is +  bumped (though there is no requirement that variable values are numeric). +  </p> +  +  <p>In the current implementation a cache entry associated with at least +  one generation variable can be returned in lookups specifying zero +  generation variables if all other variables match. The inverse is however +  not true; a lookup including generation variables will never return entries +  stored without the same \"generation-variable\" attribute.</p> + </attr> +    <attr name='key' value='string'>    <p>Use the value of this attribute directly in the key. This    attribute mainly exist for compatibility; it's better to use the    \"variable\" attribute instead.</p>       <p>It is an error to use \"key\" together with \"propagate\", since    it wouldn't do what you'd expect: The value for \"key\" would not be    reevaluated when an entry is chosen from the cache, since the nested,    propagating <tag>cache</tag> isn't reached at all then.</p>   </attr>
Roxen.git/server/modules/tags/rxmltags.pike:8650:      <attr name='enable-protocol-cache'>    <p>Mark output as cachable by clients and in server-side protocol    cache. Note that this is likely to introduce overcaching since    neither the cache key(s) nor any timeout attributes are taken into    account by those caches. You can use <xref href='set-max-cache.tag'/>    to set a timeout for the protocol cache and for the client-side    caching.</p>   </attr>    + <attr name='mutex'> +  <p>For use in shared caches only. Following a cache miss for a shared +  entry, prevent reundant generation of a new result in concurrent threads. +  Only the first request will compute the value and all other threads will +  wait for this to complete, thereby saving CPU resources.</p> +  +  <p>The mutex protecting a particular <tag>cache</tag> tag depends on +  the variables given as cache key. This ensures unrestricted execution +  for entries where keys differ. Different <tag>cache</tag> instances will +  be protected by independent mutexes unless they 1) are given identical +  cache keys, and 2) have identical tag bodies (or uses the \"nohash\" +  attribute).</p> + </attr> +    <attr name='years' value='number'>    <p>Add this number of years to the time this entry is valid.</p>   </attr>   <attr name='months' value='number'>    <p>Add this number of months to the time this entry is valid.</p>   </attr>   <attr name='weeks' value='number'>    <p>Add this number of weeks to the time this entry is valid.</p>   </attr>   <attr name='days' value='number'>
Roxen.git/server/modules/tags/rxmltags.pike:8857:   //----------------------------------------------------------------------      "crypt":#"<desc type='cont'><p><short>    Encrypts the contents as a Unix style password.</short> Useful when    combined with services that use such passwords.</p>       <p>Unix style passwords are one-way encrypted, to prevent the actual    clear-text password from being stored anywhere. When a login attempt    is made, the password supplied is also encrypted and then compared to    the stored encrypted password.</p> +  +  <p>Depending on the version of Roxen and Pike this tag supports +  several different encryption schemes.</p>   </desc>      <attr name='compare' value='string'>    <p>Compares the encrypted string with the contents of the tag. The tag    will behave very much like an <xref href='../if/if.tag' /> tag.</p>   <ex><crypt compare=\"LAF2kkMr6BjXw\">Roxen</crypt>   <then>Yepp!</then>   <else>Nope!</else>   </ex>   </attr>",      //----------------------------------------------------------------------      "hash-hmac":#"<desc type='cont'><p><short>    Keyed-Hashing for Message Authentication (HMAC) tag.</short></p>    - <ex-box><hmac-hash hash='md5' password='key'>The quick brown fox jumps over the lazy dog</hmac-hash> + <ex-box><hash-hmac hash='md5' password='key'>The quick brown fox jumps over the lazy dog</hash-hmac>    Result: 80070713463e7749b90c2dc24911e275   </ex-box>   </desc>      <attr name='hash' value='string'>    <p>The hash algorithm to use (e.g. MD5, SHA1, SHA256 etc.) All hash algorithms   supported by Pike can be used.</p>   </attr>      <attr name='password' value='string'>
Roxen.git/server/modules/tags/rxmltags.pike:8897:   //----------------------------------------------------------------------      "date":#"<desc type='tag'><p><short>    Inserts the time and date.</short> Does not require attributes.   </p>      <ex><date/></ex>   </desc>      <attr name='unix-time' value='number of seconds'> -  <p>Display this time instead of the current. This attribute uses the -  specified Unix 'time_t' time as the starting time (which is -  <i>01:00, January the 1st, 1970</i>), instead of the current time. -  This is mostly useful when the <tag>date</tag> tag is used from a -  Pike-script or Roxen module.</p> +  <p>Display this time instead of the current. The time is taken as a +  unix timestamp (i.e. the number of seconds since 00:00:00 Jan 1 1970 +  UTC).</p>    - <ex><date unix-time='120'/></ex> + <ex><date unix-time='946684800'/></ex>   </attr>      <attr name='http-time' value='http time stamp'>    <p>Display this time instead of the current. This attribute uses the    specified http-time, instead of the current time.</p>    <p>All three http-time formats are supported:</p>      <ex><p>RFC 822, updated by RFC 1123:   <date http-time='Sun, 06 Nov 1994 08:49:37 GMT'/></p>   
Roxen.git/server/modules/tags/rxmltags.pike:9097:    <ex><date part='week' type='number'/></ex></c></row>   <row><c><p>seconds</p></c>    <c><p>Display the total number of seconds this year.</p>    <ex><date part='seconds' type='number'/></ex></c></row>   </xtable></attr>      <attr name='strftime' value='string'>    <p>If this attribute is given to date, it will format the result    according to the argument string.</p>    +  <p>The <tt>!</tt> or <tt>-</tt> (dash) modifier can be inserted to +  get rid of extra field padding in any of the formatters below. For +  instance, use <tt>%!m</tt> to get the month value without zero +  padding. The <tt>E</tt> modifier accessses alternative forms of month +  names, e.g. <tt>%EB</tt> which in Russian locale gives a genitive +  form. The <tt>^</tt> modifier can be used to convert the result +  string to uppercase, while <tt>~</tt> capitalizes the result +  string.</p> +    <xtable>    <row><h>Format</h><h>Meaning</h></row>    <row><c><p>%%</p></c><c><p>Percent character</p></c></row>    <row><c><p>%a</p></c><c><p>Abbreviated weekday name, e.g. \"Mon\"</p></c></row>    <row><c><p>%A</p></c><c><p>Weekday name</p></c></row>    <row><c><p>%b</p></c><c><p>Abbreviated month name, e.g. \"Jan\"</p></c></row>    <row><c><p>%B</p></c><c><p>Month name</p></c></row>    <row><c><p>%c</p></c><c><p>Date and time, e.g. \"%a %b %d %H:%M:%S %Y\"</p></c></row>    <row><c><p>%C</p></c><c><p>Century number, zero padded to two charachters.</p></c></row>    <row><c><p>%d</p></c><c><p>Day of month (1-31), zero padded to two characters.</p></c></row>
Roxen.git/server/modules/tags/rxmltags.pike:9120:    <row><c><p>%h</p></c><c><p>See %b</p></c></row>    <row><c><p>%I</p></c><c><p>Hour (12 hour clock, 1-12), zero padded to two charcters.</p></c></row>    <row><c><p>%j</p></c><c><p>Day numer of year (1-366), zero padded to three characters.</p></c></row>    <row><c><p>%k</p></c><c><p>Hour (24 hour clock, 0-23), space padded to two characters.</p></c></row>    <row><c><p>%l</p></c><c><p>Hour (12 hour clock, 1-12), space padded to two characters.</p></c></row>    <row><c><p>%m</p></c><c><p>Month number (1-12), zero padded to two characters.</p></c></row>    <row><c><p>%M</p></c><c><p>Minute (0-59), zero padded to two characters.</p></c></row>    <row><c><p>%n</p></c><c><p>Newline</p></c></row>    <row><c><p>%p</p></c><c><p>\"a.m.\" or \"p.m.\"</p></c></row>    <row><c><p>%P</p></c><c><p>\"am\" or \"pm\"</p></c></row> -  <row><c><p>%r</p></c><c><p>Time in 12 hour clock format with %p</p></c></row> +  <row><c><p>%q</p></c><c><p>Quarter number (1-4)</p></c></row> +  <row><c><p>%r</p></c><c><p>Time in 12-hour clock format. Equivalent to \"%I:%M:%S %p\".</p></c></row>    <row><c><p>%R</p></c><c><p>Time as \"%H:%M\"</p></c></row>    <row><c><p>%S</p></c><c><p>Seconds (0-60), zero padded to two characters. 60 only occurs in case of a leap second.</p></c></row>    <row><c><p>%t</p></c><c><p>Tab</p></c></row>    <row><c><p>%T</p></c><c><p>Time as \"%H:%M:%S\"</p></c></row>    <row><c><p>%u</p></c><c><p>Weekday as a decimal number (1-7), 1 is Sunday.</p></c></row>    <row><c><p>%U</p></c><c><p>Week number of year as a decimal number (0-53), with sunday as the first day of week 1,    zero padded to two characters.</p></c></row>    <row><c><p>%V</p></c><c><p>ISO week number of the year as a decimal number (1-53), zero padded to two characters.</p></c></row>    <row><c><p>%w</p></c><c><p>Weekday as a decimal number (0-6), 0 is Sunday.</p></c></row>    <row><c><p>%W</p></c><c><p>Week number of year as a decimal number (0-53), with sunday as the first day of week 1,
Roxen.git/server/modules/tags/rxmltags.pike:9182:      <attr name='toggle'>    <p>Toggles debug mode.</p>   </attr>      <attr name='showvar' value='variable'>    <p>Shows the value of the given variable in a generic debug format    that works regardless of the type.</p>   </attr>    + <attr name='showscope' value='scope'> +  <p>Shows all the variables in the given scope in a generic debug +  format.</p> + </attr> +    <attr name='showlog'>    <p>Shows the debug log.</p>   </attr>      <attr name='showid' value='string'>    <p>Shows a part of the id object. E.g. showid=\"id->request_headers\".</p>   </attr>    -  + <attr name='sleep' value='int|float'> +  <p>Delays RXML execution for the current request the specified number of +  seconds.</p> + </attr> +    <attr name='werror' value='string'>    <p>When you have access to the server debug log and want your RXML    page to write some kind of diagnostics message or similar, the    werror attribute is helpful.</p>       <p>This can be used on the error page, for instance, if you'd want    such errors to end up in the debug log:</p>       <ex-box><debug werror='File &page.url; not found!   \(linked from &client.referrer;)'/></ex-box>
Roxen.git/server/modules/tags/rxmltags.pike:9778:   </attr>",      //----------------------------------------------------------------------      "redirect":#"<desc type='tag'><p><short hide='hide'>    Redirects the user to another page.</short> Redirects the user to    another page by sending a HTTP redirect header to the client. If the    redirect is local, i.e. within the server, all prestates are preserved.    E.g. \"/index.html\" and \"index.html\" preserves the prestates, while    \"http://server.com/index.html\" does not. - </p></desc> + </p> + <note><p>Be aware that RXML code both before and after this tag will + generate output that is included in the resulting page.</p></note> + </desc>      <attr name='to' value='URL' required='required'>    <p>The location to where the client should be sent.</p>   </attr>      <attr name='type' value='string'>    <p>The type of redirect, i.e. the http status code. This can be one    of the strings \"permanent\" (301), \"found\" (302), \"see-other\"    (303), or \"temporary\" (307). It can also be an integer code. The    default is 302.</p>   </attr>      <attr name='add' value='string'> -  <p>The prestate or prestates that should be added, in a comma separated +  <p>The prestate or prestates that should be added, in a comma-separated    list.</p>   </attr>      <attr name='drop' value='string'> -  <p>The prestate or prestates that should be dropped, in a comma separated +  <p>The prestate or prestates that should be dropped, in a comma-separated    list.</p>   </attr>      <attr name='drop-all'>    <p>Removes all prestates from the redirect target.</p>   </attr>      <attr name='text' value='string'>    <p>Sends a text string to the browser, that hints from where and why the    page was redirected. Not all browsers will show this string. Only
Roxen.git/server/modules/tags/rxmltags.pike:10599:    be cast successfully. Instead a zero number or an empty string is    returned as appropriate. This is useful if <i>expr</i> is a value    from the client that may be bogus.</p></c></row>    </xtable>       <p>Note that values in the RXML form scope often are strings even    though they may appear as (formatted) numbers. It is therefore a good    idea to use the <tt>INT()</tt> or <tt>FLOAT()</tt> functions on them    before you do math.</p>    +  <p>Expressions for checking types:</p> +  +  <xtable> +  <row valign='top'> +  <c><p><tt>arrayp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is an array, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>callablep(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a function +  or similar, and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>floatp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a floating point number, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>functionp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a function, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>intp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is an integer, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>mappingp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a mapping, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>multisetp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a multiset, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>objectp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is an object, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>programp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a program, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>stringp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is a string, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>undefinedp(<i>expr</i>)</tt></p></c> +  <c><p>Returns 1 if the value of <i>expr</i> is UNDEFINED, +  and 0 otherwise.</p></c></row> +  </xtable> +  +  <p>Expressions for checking contents:</p> +  +  <xtable> +  <row valign='top'> +  <c><p><tt>has_index(<i>haystack</i>, <i>index</i>)</tt></p></c> +  <c><p>Returns 1 if <i>index</i> is in the index domain of <i>haystack</i>, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>has_prefix(<i>string</i>, <i>prefix</i>)</tt></p></c> +  <c><p>Returns 1 if <i>string</i> starts with <i>prefix</i>, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>has_suffix(<i>string</i>, <i>suffix</i>)</tt></p></c> +  <c><p>Returns 1 if <i>string</i> ends with <i>suffix</i>, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>has_value(<i>haystack</i>, <i>value</i>)</tt></p></c> +  <c><p>Returns 1 if <i>value</i> is in the value domain of <i>haystack</i>, +  and 0 otherwise.</p></c></row> +  <row valign='top'> +  <c><p><tt>indices(<i>expr</i>)</tt></p></c> +  <c><p>Returns an array with all indices present in <i>expr</i>.</p></c></row> +  <row valign='top'> +  <c><p><tt>values(<i>expr</i>)</tt></p></c> +  <c><p>Returns an array with all values present in <i>expr</i>.</p></c></row> +  </xtable> +     <p>Expressions for numeric operands:</p>       <xtable>    <row valign='top'>    <c><p><tt><i>expr1</i> * <i>expr2</i></tt></p></c>    <c><p>Multiplication.</p></c></row>       <row valign='top'>    <c><p><tt><i>expr1</i> / <i>expr2</i></tt></p></c>    <c><p>Division.</p></c></row>
Roxen.git/server/modules/tags/rxmltags.pike:10656:    <row valign='top'>    <c><p><tt><i>expr1</i> | <i>expr2</i></tt></p></c>    <c><p>Bitwise OR (integer operands only).</p></c></row>       <row valign='top'>    <c><p><tt>pow(<i>expr1</i>, <i>expr2</i>)</tt></p></c>    <c><p>Returns the value <i>expr1</i> raised to the power of    <i>expr2</i>.</p></c></row>       <row valign='top'> +  <c><p><tt>log(<i>expr</i>)</tt></p></c> +  <c><p>Returns the natural logarithm of the value <i>expr</i>. To get +  the logarithm in another base, divide the result with +  <tt>log(<i>base</i>)</tt>. +  This is the inverse operation of <tt>exp()</tt>.</p></c></row> +  +  <row valign='top'> +  <c><p><tt>exp(<i>expr</i>)</tt></p></c> +  <c><p>Returns the natural exponential of the value <i>expr</i>. +  This is the inverse operation of <tt>log()</tt>.</p></c></row> +  +  <row valign='top'>    <c><p><tt>abs(<i>expr</i>)</tt></p></c>    <c><p>Returns the absolute value of <i>expr</i>.</p></c></row>       <row valign='top'> -  +  <c><p><tt>floor(<i>expr</i>)</tt></p></c> +  <c><p>Returns the closest integer value less than or equal to the +  value <i>expr</i>.</p></c></row> +  +  <row valign='top'> +  <c><p><tt>ceil(<i>expr</i>)</tt></p></c> +  <c><p>Returns the closest integer value greater than or equal to the +  value <i>expr</i>.</p></c></row> +  +  <row valign='top'> +  <c><p><tt>round(<i>expr</i>)</tt></p></c> +  <c><p>Returns the closest integer value to the value <i>expr</i>. +  </p></c></row> +  +  <row valign='top'>    <c><p><tt>max(<i>expr</i>, ...)</tt></p></c>    <c><p>Returns the maximum value of the arguments.</p></c></row>       <row valign='top'>    <c><p><tt>min(<i>expr</i>, ...)</tt></p></c>    <c><p>Returns the minimum value of the arguments.</p></c></row>    </xtable>       <p>Expressions for string operands:</p>   
Roxen.git/server/modules/tags/rxmltags.pike:10701:    <c><p>Parses the string <i>expr</i> as an RXML variable reference    and returns its value. Useful e.g. if the immediate    <tt><i>scope</i>.<i>var</i></tt> form cannot be used due to    strange characters in the scope or variable names.</p></c></row>       <row valign='top'>    <c><p><tt>sizeof(<i>expr</i>)</tt></p></c>    <c><p>Returns the number of characters in <i>expr</i>.</p></c></row>       <row valign='top'> +  <c><p><tt>strlen(<i>expr</i>)</tt></p></c> +  <c><p>Returns the number of characters in <i>expr</i>.</p></c></row> +  +  <row valign='top'>    <c><p><tt>search(<i>expr1</i>, <i>expr2</i>)</tt></p></c>    <c><p>Returns the starting position of the first occurrence of the    substring <i>expr2</i> inside <i>expr1</i>, counting from 1, or 0    if <i>expr2</i> does not occur in <i>expr1</i>.</p></c></row>       <row valign='top'>    <c><p><tt>reverse(<i>expr</i>)</tt></p></c>    <c><p>Returns the reverse of <i>expr</i>.</p></c></row>       <row valign='top'>    <c><p><tt>regexp_split(<i>regexp</i>, <i>expr</i>)</tt></p></c>    <c><p>Matches <i>regexp</i> against the string <i>expr</i>. If it    matches then an array is returned that has the full match in the    first element, followed by what the corresponding submatches (if    any) match. Returns <ent>roxen.false</ent> if the regexp doesn't    match. The regexp follows    <a href='http://www.pcre.org/'>PCRE</a> syntax.</p></c></row> -  +  +  <row valign='top'> +  <c><p><tt>basename(<i>expr</i>)</tt></p></c> +  <c><p>Returns the basename of the path in <i>expr</i>.</p></c></row> +  +  <row valign='top'> +  <c><p><tt>dirname(<i>expr</i>)</tt></p></c> +  <c><p>Returns the dirname of the path in <i>expr</i>.</p></c></row> +  +  <row valign='top'> +  <c><p><tt>combine_path(<i>base</i>, <i>relative_path</i>, ...)</tt></p></c> +  <c><p>Returns the combined path <i>base</i> + <tt>\"/\"</tt> + +  <i>relative_path</i>, with any path-segments of <tt>'.'</tt> and +  <tt>'..'</tt> handled and removed.</p></c></row>    </xtable>       <p>Expressions for array operands:</p>       <xtable>    <row valign='top'>    <c><p><tt><i>expr</i> * <i>num</i></tt></p></c>    <c><p>Returns <i>expr</i> repeated <i>num</i> times.</p></c></row>       <row valign='top'>
Roxen.git/server/modules/tags/rxmltags.pike:11893:      "if#module":#"<desc type='plugin'><p><short>    Returns true if the selected module is enabled in the current    server.</short> This is useful when you are developing RXML applications    that you plan to move to other servers, to ensure that all required    modules are added. This is a <i>State</i> plugin.</p>   </desc>      <attr name='module' value='name'><p>    The \"real\" name of the module to look for, i.e. its filename -  without extension and without directory path.</p> +  without extension and without directory path. The name parameter is +  matched as a glob pattern.</p>   </attr>",      //----------------------------------------------------------------------      "if#accept":#"<desc type='plugin'><p><short>    Returns true if the browser accepts certain content types as specified    by it's Accept-header, for example image/jpeg or text/html.</short> If    browser states that it accepts */* that is not taken in to account as    this is always untrue. This is a <i>Match</i> plugin.   </p></desc>
Roxen.git/server/modules/tags/rxmltags.pike:12549:   //----------------------------------------------------------------------      "eval":#"<desc type='cont'><p><short>    Postparses its content.</short> Useful when an entity contains    RXML-code. <tag>eval</tag> is then placed around the entity to get    its content parsed.</p>   </desc>",      //----------------------------------------------------------------------    + "emit#csv":#"<desc type='plugin'><p><short> +  Emit the fields from a file containing a comma-separated list of +  values.</short> + </p></desc> +  + <attr name='path' value='string'><p> +  Path in the virtual filesystem to the csv-file. + </p></attr> + <attr name='realpath' value='string'><p> +  Path in the real filesystem to the csv-file. + </p></attr> + <attr name='header' value='string'><p> +  Header line containing the field names for the csv-file.</p> +  +  <p>CSV-files usually have a first line that contains the names for the +  fields, but in some cases the file only contains data, in which case +  this attribute needs to be set.</p> +  +  <p>Note that the header line fields must be separated with the same +  delimiter as the csv-file data.</p> + </attr> + <attr name='delimiter' value='string'><p> +  Delimiter used to separate the fields in the csv-file.</p> +  +  <p>The tag defaults to trying the delimiters <tt><b>,</b></tt>, +  <tt><b>;</b></tt> and <b>TAB</b>. If it selects the wrong delimiter +  the correct one can be explicitly specified by setting this attribute.</p> + </attr> + ", +  + //---------------------------------------------------------------------- +    "emit#path":({ #"<desc type='plugin'><p><short>    Prints paths.</short> This plugin traverses over all directories in    the path from the root up to the current one.</p>   </desc>      <attr name='path' value='string'><p>    Use this path instead of the document path</p>   </attr>      <attr name='trim' value='string'><p>