/* 
 * <copyright> 
 *  Copyright (c) 2001 by Hyperwave AG
 * </copyright> 
 * 
 * <file> 
 *  Name:        Resource.js
 *  Created:     2001-01-14
 *  $Id: Template.js,v 1.6.2.1 2004/10/12 08:29:12 mmair Exp $
 * </file>
 */

initPackage ( "com.hyperwave.res" );
loadClass ( "com.hyperwave.res.ResourceException" );
loadClass ( "com.hyperwave.res.AbstractCmd" );


//----------------------------------------------------------------------
/**
 * Templates are strings containing placeholders and simple commands.
 * They can be evaluated when they have an environment or map of
 * placeholdernames to resources, by recursively replacing all placeholders
 * by the string/template representation of the resources. The whole
 * mechanism consists of a <code>parser</code> and a <code>fill</code>
 * mechanism.
 */
// <JSClass Name="com.hyperwave.res.Template">

  //--------------------------------------------------------------------
  /**
   * Constructs a template and initializes the parsing
   * structure.
   * @param aParam: Object:
   * @param aParam.templateString_: String: the template
   */
  com.hyperwave.res.Template = function ( aParam )
  {
    if ( aParam == "__proto__" )
      return;

    if ( aParam == null || aParam.templateString_ == null )
    {
      this.error_ = new com.hyperwave.res.ResourceException (
        "com.hyperwave.res.Template: tried to construct a template " +
        "without a string." );
      this.templateString_ = null;
    }
    else
    {
      this.error_ = null;
      /**
       * Member holding the current template, line comments are removed from it!
       */
      this.templateString_ = aParam.templateString_.replace(/##.*(([\n\r]|[\n])|$)/g,"");
    }

    /**
     * The members for parsing ...
     */
    this.parsedSnips_ = [];
    this.parsedPHolders_ = [];
    this.parsedCmds_ = {};
    this.parsed_ = false;
    this.isTemplate_ = true;
  }
  class$ = doInherit ( com.hyperwave.res.Template );

  //--------------------------------------------------------------------
  /**
   * Gets the current resource template. It is <code>null</code>
   * if no template has already been assigned (which is in
   * fact a programming error).
   * @return String: the template
   */
  class$.getTemplateString = function ()
  {
    return this.templateString_; 
  }

  //--------------------------------------------------------------------
  /**
   * Sets the template string of the template.
   * @param aTemplate: string: a string containing the new template.
   */
  class$.setTemplate = function ( aTemplate )
  {
    this.templateString_ = aTemplate;
  }


  //--------------------------------------------------------------------
  /**
   * Static Regular expression for Template parsing.
   * Note: Executing the regular expression gives a special array.
   * Try the string "($xyz) ($#for $control) ($endfor)" 
   * for the possible results.
   */
  class$.static_.placeholderRegExp = 
    /\(\$(((\#)([^\)\s\$\"]*)\s*([\$|\"]?([^\)]*))?)|([^\)\s]*))\s*\)/;


  //--------------------------------------------------------------------
  /**
   * Static regexps for expression parsing. The actual regexp is divided
   * into several subexpressions that are assembled as needed. 
   */
  class$.static_.expressionRegExps_ = {
                                        // regexp to check for placeholder with leading '$' 
                                        s_expr: '(\\$(([a-zA-Z0-9_]*:)*([a-zA-Z0-9_]*))|\\"([^\\"]*)\\"|\\d+|TRUE|FALSE)',
                                        // regexp to match a single placeholder and strip trailing spaces
                                        ph    : '^\\$(([a-zA-Z0-9_]*:)*([a-zA-Z0-9_]*))\\s*$',
                                        // regexp to check for operators - AND/OR must gbe enclosed by spaces!
                                        op    : '((\\s*!=\\s*|\\s*==\\s*|\\s*>=\\s*|\\s*<=\\s*|\\s*<\\s*|\\s*>\\s*)|(\\s+AND\\s|\\s+OR\\s+))',
                                        // regexp to check for boolean operators
                                        boolop: '(AND|OR)'
                                      };


  //--------------------------------------------------------------------
  /**
   * A static counter for commands which are replaced by "pseudo" identifiers 
   */
  class$.static_.resIdCounter_ = 0;


  //--------------------------------------------------------------------
  /**
   * This is a static class method that checks whether the supplied string is a 
   * single placeholder or not. In the first case the function returns 
   * the placeholder, in the second case null.
   * return String: the parsed placeholder or <CODE>null</CODE>
   */
  class$.static_.parsePH = function ( thePH )
  {      
    // Create the regexp out of static object data
    var ph_reg = new RegExp( this.expressionRegExps_.ph );
    var parsed_ph = ph_reg.exec( thePH );
    
    if ( parsed_ph == null ) 
      return null;
    
    return parsed_ph[1];
  }

  //--------------------------------------------------------------------
  /**
   * This method is used for commands that can be supplied with command placeholders
   * as well as more complex expressions (involving AND, NOT, OR, !=, ...).
   * The method checks whether the supplied string is a single command 
   * placeholder or a command expression. In the first case it returns 
   * the placeholder. In the second case it returns a function that must 
   * be supplied with the actual environment and then computes a boolean value
   * representing the value of the expression.
   * @param thePHExpr: String: the command placeholder (expression) to parse.
   * @return Function: a function computing the value of the given expression within
   *                   the actual environment or <null> if nothing found
   */
  class$.static_.parseExpr = function ( thePHExpr )
  {
    //get the expressions regexps from static object
    var ph_const_exp = this.expressionRegExps_.s_expr;
    var op_exp = this.expressionRegExps_.op;
    var boolop_exp = this.expressionRegExps_.boolop;

    // the assembled regexp to parse expressions - every expression that can 
    // be matched by this regexp is a valid expression.
    var expr_reg = new RegExp("(^" + ph_const_exp + "(" + op_exp + ph_const_exp + ")*)", "i");
    
    // strip trailing spaces from the input string
    thePHExpr = thePHExpr.replace(/\s*$/,"");
    
    var parsed_ph = expr_reg.exec( thePHExpr );
          
    if ( (parsed_ph == null) || (parsed_ph[0] != thePHExpr) ) 
      return null;

    // a single placeholder was supplied
    if ( parsed_ph.length == 4 ) {      
      var fun = "return theEnv.getModified('" + parsed_ph[3] + "')";
      return new Function ("theEnv", fun);
    }
 
    // Replacing placeholders with corresponding environment access
    var eval_expr = thePHExpr.replace(/\$(([a-zA-Z0-9_]*:)*([a-zA-Z0-9_]*))/g,"theEnv.getModified('$1')");

    // Translating boolean operators to JS     
    eval_expr = eval_expr.replace(/\sOR\s/ig," || ").replace(/\sAND\s/ig," && ");
    // In JS booelean literals MUST be written in lower case
    eval_expr = eval_expr.replace(/TRUE/g,"true").replace(/FALSE/g,"false");
    
    
    var eval_fun = "return " + eval_expr;
    
    return new Function ("theEnv", eval_fun);
  }

//--------------------------------------------------------------------
  /**
   * Parses the template into the internal parse structure that consists
   * of template snips (containing no placeholders), placeholders and
   * a set of commands. Command bodies are parsed recursively. 
   * @param theText: string: the template's text
   * @param theEndCmdSet: Object: a JavaScript object containing
   *   keys which specify commands that should stop parsing
   * @return object: consists of the following properties
   * <BR><CODE>object.error_ [com.hyperwave.res.Exception | void]</CODE>: 
   *   an Exeption describing an error or void if all went ok.
   * <BR><CODE>object.textTail_ [String | void]</CODE>: the rest of the
   *   template if the parsing process has been ended or null
   *   if no characters were left.
   * <BR><CODE>object.endCmd_ [String | void]</CODE>: the End Command if 
   *   there was an end command given and found.
   */
  class$.continueParse = function ( theTemplate, theEndCmdSet )
  {
    this.parsed_ = true;
    var text_tail;
    var state = {};
    var regexp = this.static_.placeholderRegExp;

    for ( ;; )
    {
      var exec_out = regexp.exec ( theTemplate );

      if ( exec_out == null )
      {
        this.parsedSnips_ [this.parsedSnips_.length] = 
          theTemplate;
        state.textTail_ = null;
        break;
      }
 
      // Check JavaScript Engine Problems:
      if ( exec_out[0].length == 0 )
      {
        ++err_count;
        reg_exp.compile ();
        if ( err_count == 5 )
        {
          state.error_ = new com.hyperwave.res.ResourceException (
            "com.hyperwave.res.Template: Internal Parser Error" );
          state.textTail_ = theTemplate;
          return state;
        }
      }
 
      var placeholder_string = exec_out[0];

      this.parsedSnips_ [this.parsedSnips_.length] = 
        theTemplate.substring ( 0, exec_out.index );
      text_tail = theTemplate.substring ( exec_out.index + placeholder_string.length );

      if ( exec_out [3] == "#" )       // Command ... ?
      {
        var command = exec_out [4].toLowerCase ();

        if ( theEndCmdSet && theEndCmdSet [ command ] )
        {
          state.parsedEndCmd_ = command;
          state.textTail_ = text_tail;
          break;
        };
         
        var command_expr = exec_out[5];

        var cmd_node = 
          com.hyperwave.res.AbstractCmd.static_.getCommand (
            { command_: command,
              expression_: command_expr } );
 
        if ( cmd_node == null )
        {
          state.error_ = new com.hyperwave.res.ResourceException (
            "com.hyperwave.res.Template: Unsupported Template Command '" +
            exec_out [4] + "'." );
          state.textTail_ = text_tail;
          break;
        }
        
        if ( cmd_node.error_ )
        {
          state.error_ = cmd_node.error_;
          state.textTail_ = text_tail;
          break;
        }

        var ctr = ++this.static_.resIdCounter_;
        var res_id = "_intcmd" + ctr + "_";
        this.parsedPHolders_ [this.parsedPHolders_.length] = res_id;
        
        this.parsedCmds_ [res_id] = cmd_node;
        new_state = cmd_node.doParse ( text_tail );
        if ( new_state.error_ )
          return new_state;
        
        text_tail = new_state.textTail_;
      }
      else                             // Simple Placeholder!
        this.parsedPHolders_ [this.parsedPHolders_.length] = exec_out [7];

      theTemplate = text_tail;
    } /* for */

    return state;
  }

  //--------------------------------------------------------------------
  /**
   * Invokes the parser. Sets and returns the object's internal error 
   * condition.
   * @return com.hyperwave.res.ResourceException: an
   *   Exception type if an error occured otherwise null
   */
  class$.doParse = function ()
  {
    if ( this.parsed_ )
      return this.error_;
    
    if ( this.templateString_ == null )
    {
      this.error_ = new com.hyperwave.res.ResourceException (
        "com.hyperwave.res.Template: cannot parse an empty template." );
    }
    else
    {
      var state = this.continueParse ( this.templateString_ );
      if ( state.error_ )
        this.error_ = state.error_;
    }
    return this.error_
  }

  //--------------------------------------------------------------------
  /**
   * Fills a template with the resources of a given scoped map.
   * @param theEnv: com.hyperwave.res.ScopedMap: the current environment
   *   that includes all placeholder references to Resources and Templates.
   * @return String: the string representation.
   */
  class$.fill = function ( theEnv )
  {
    var ScopedMap = com.hyperwave.res.ScopedMap;
    if ( !this.parsed_ )
    {
      this.doParse ();
      if ( this.error_ )
        return ( this.error_.toString () );
    }

    var out_array = [];
    for ( var snip_ind = 0; snip_ind < this.parsedSnips_.length; ++snip_ind )
    {
      out_array [out_array.length] = this.parsedSnips_ [snip_ind];
      if ( snip_ind < this.parsedPHolders_.length )
      {
        var pholder = this.parsedPHolders_ [snip_ind];
        var value = theEnv.get ( pholder );
        try {
          var modifiers = ScopedMap.static_.getModifiers(pholder);
        }
        catch (ex) {
          var modifiers = null;
          value = "($UNKNOWN_MODIFIER:" + this.parsedPHolders_ [snip_ind] + ")";
        }
        if ( value == null )
        {
          value = this.parsedCmds_ [pholder];
          if ( value == null )
            out_array [out_array.length] = "($UNKNOWN:" + this.parsedPHolders_ [snip_ind] + ")";
          else
            out_array [out_array.length] = ScopedMap.static_.applyModifiers(modifiers, value.execCmd ( theEnv ));
        }
        else
          if ( value.isTemplate_ )
            out_array [out_array.length] = ScopedMap.static_.applyModifiers(modifiers, value.fill ( theEnv ));
          else
            out_array [out_array.length] = ScopedMap.static_.applyModifiers(modifiers, value.toString());
      }
    }

    return ( out_array.join ( "" ) );
  }

  //--------------------------------------------------------------------
  /**
   * Returns the template's string representation (not processed).
   */
  class$.toString = function () 
  {
    return "@com.hyperwave.Template: templateString_='" + this.templateString_ + "'";
  }

// </JSClass>
//----------------------------------------------------------------------
