Hi Remy,

I'm really keen to have this sort of feature in Digester.

I've had this kind of functionality in my local application for some
time now, but it's implemented in a rather different manner.

Attached is my current implementation, for comparison.

Here's the major differences:

*
Your initial patch only does substitution for SetPropertiesRule.
What about CallParamRule, etc?

The attached implementation automatically applies to all attributes, and
therefore to all rules both built-in and custom. While somewhat less
efficient than your current proposal, it isn't likely to be invoked very
often (variable substitution is expected to be the exception, not the
normal case). See later for a proposed optimisation to the current
implementation.

*
The attached implementation does substitution for variables in body
text, and in the same style it does it for attributes.

*
The attached iplementation provides functionality to set the "marker"
character rather than hard-wiring "$". In fact, it supports any
arbitrary string prefix, not just a single char.


Features that both your patch and my implementation are missing:

*
There isn't any escape syntax to allow ${...} to really be passed as the
value of an attribute. I think a leading backslash implying that the
variable suppression should not occur would be a good idea.

*
I would actually like to see a different "source" associated with
different "marker" variables. ${foo} and #{foo} could then be expanded
from different sources. You could then even use syntax like:
  env{...}  --> substitutes from environment variables
  user{...}  --> substitutes from info about the current user
  session{...} --> substitutes from the current session variables
assuming of course that the Digester instance was properly configured
with those mappings before parsing began. NOTE: I'm not sure an
efficient "variable expansion" routine can be implemented for multiple
mappings. The expansion doesn't actually have to be too efficient, but
it *is* necessary to be able to quickly detect for any string whether a
variable substitution occurs in it.


Other notes:

My implementation of the beginElement method isn't as optimised as it
could be. An AttributesImpl object is always created; it would be
smarter to only create one if a variable substitution actually occurs in
the attributes (which will be the exception rather than the normal
case).

beginElement(...)
  if no attribute value has variable substitution
    newAttributes = attributes
  else if attributes.instanceOf(AttributesImpl)
    set the new attribute values
  else
    newAttributes = new AttributesImpl(attributes)
    set the new attribute values

  for each matching rule
    begin(..., newAttributes)


-----------

Note when reading the attached code:
* Start with ConnectorDigester.java, then ConstantsManager, then
  StrUtils.
* ConstantsManager is pretty much the same as the PropertySource
  class in the original patch; a manager of the key->value mappings
  available to be referenced from the input xml.

Regards,

Simon




package nz.co.ecnetwork.connector;

import org.apache.log4j.Logger;
import org.apache.commons.digester.Digester;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.SAXException;

/**
 * Subclass of the Apache Digester for use with the ECN Connector.
 * <p>
 * This class applies variable substitution to all xml attributes and 
 * element-body-text before passing the data to any digester rules. This 
 * functionality could also have been implemented
 * as a SAX filter...
 * 
 * @author  Simon Kitching
 */

public class ConnectorDigester extends Digester
{
    private static Logger log = Logger.getLogger(ConnectorDigester.class);
    
    private ConstantsManager cm_ = null;
    
    /**
     * Define the object that will be used to map variable names
     * to values.
     */
    public void setConstantsManager(ConstantsManager cm)
    {
        cm_ = cm;
    }
    
    /**
     * Invoke inherited implementation after applying variable
     * substitution to any attribute values containing variable
     * references. 
     */
    public void startElement(
    String namespaceURI, 
    String localName,
    String qName, 
    Attributes list)
    throws SAXException 
    {
        list = updateAttributes(list);
        super.startElement(namespaceURI, localName, qName, list);
    }
    
    /**
     * Invoke inherited implementation after applying variable substitution
     * to the character data contained in the current element.
     */
    public void endElement(
    String namespaceURI, 
    String localName,
    String qName) 
    throws SAXException 
    {
        bodyText = updateBodyText(bodyText);
        super.endElement(namespaceURI, localName, qName);
    }
    
    /**
     * Returns an attributes list which contains all the attributes
     * passed in, with any text of form "${xxx}" in an attribute value
     * replaced by the appropriate value from member cm_.
     */
    private Attributes updateAttributes(Attributes list)
    {
        if (list.getLength() == 0)
        {
            return list;
        }
        
        AttributesImpl newAttrs = new AttributesImpl(list);
        int nAttributes = newAttrs.getLength();
        for(int i=0; i<nAttributes; ++i)
        {
            String value = newAttrs.getValue(i);
            try
            {
                String newValue = cm_.eval(value);
                if (value != newValue) // identity comparison
                {
                    log.debug(
                        "Mapped attribute named [" + newAttrs.getQName(i) + "]"
                        + "[" + value + "] -> [" + newValue + "]");
                    newAttrs.setValue(i, newValue);
                }
            }
            catch(Exception e)
            {
                log.warn("Unable to map data [" + value + "]", e);
                // ignore - let the attribute have its original value
            }
        }
        
        return newAttrs;
    }
    
    /**
     * Return a new StringBuffer containing the same contents as the
     * input buffer, except that data of form ${varname} have been
     * replaced by the value of that var as defined in the 
     * cm_ member object.
     */
    private StringBuffer updateBodyText(StringBuffer bodyText)
    {
        String in = bodyText.toString();
        String out;
        try
        {
            out = cm_.eval(in);
        }
        catch(Exception e)
        {
            log.warn("Unable to map data [" + in + "]", e);
            return bodyText; // return unchanged data
        }

        if (out == in)  // identity comparison
        {
            // No substitutions required. Don't waste memory creating
            // a new buffer
            return bodyText;
        }
        else
        {
            log.debug("Mapped element content [" + in + "] -> [" + out + "]");
            return new StringBuffer(out);
        }
    }
}
package nz.co.ecnetwork.connector;

import org.apache.log4j.Logger;
import java.util.Properties;
import nz.co.ecnetwork.io.StrUtils;

/**
 * Manages a set of name->value mappings, and allows strings containing
 * references to the names to be expanded.
 *
 * @author Simon Kitching
 */

public class ConstantsManager
{
    private static Logger log = Logger.getLogger(ConstantsManager.class);

    public static final String DFLT_MARK = "#";
    private String mark_ = DFLT_MARK;
    private Properties constants_ = new Properties();

    /**
     * Specify what character sequence will be used to identify
     * variables. The default is "#", so variables are of form
     * #{varname}. Example: if mark is set to "%%", then only sequences of
     * form %%{varname} will be processed.
     */
    public void setMark(String mark)
    {
        log.debug("mark set to [" + mark + "]");
        mark_ = mark;
    }
    
    /**
     * Called by configuration process when a new constant is defined.
     * <p>
     * Note that the value parameter can reference previously-defined
     * constants.
     */
    public void addConstant(String name, String value)
    {
        constants_.put(name, value);
    }
    
    /**
     * Expand any variable references in the expression.
     * If no change was made, the expr object is returned.
     */
    public String eval(String expr)
    throws Exception
    {
        return StrUtils.substitute(expr, constants_, mark_);
    }
}
package nz.co.ecnetwork.io;

import java.util.Properties;

/**
 * Generic utilities for String-related operations.
 */

public class StrUtils
{
    public static final char DFLT_SEP_CHAR = ';';
    public static final char DFLT_ESCAPE_CHAR = '\\';
    public static final String DFLT_SUBST_MARK = "$";
    
    /**
     * Replace any occurrences within the string of the form
     * "${key}" with the value associated with key in the properties
     * object.
     * <p>
     * Returns the string after performing all substitutions.
     * <p>
     * If no substitutions were made, the input string object is
     * returned (not a copy).
     */
   
    public static String substitute(String str, Properties params)
    throws Exception
    {
        return substitute(str, params, DFLT_SUBST_MARK);
    }
    
    /**
     * As per substitute(String,Properties), except that variables are 
     * identified by the sequence MARK{varname}, where mark can be any
     * sequence.
     * Possible examples of MARK are "$", "#", "$$", "var".
     */
    public static String substitute(
    String str, 
    Properties params, 
    String mark)
    throws Exception
    {
        String startMark = mark + "{";
        int markLen = startMark.length();
        
        int index = 0;
        for(;;)
        {
            index = str.indexOf(startMark, index);
            if (index == -1)
            {
                return str;
            }
            
            int startIndex = index + markLen;
            if (startIndex > str.length())
            {
                throw new Exception("var expression starts at end of string");
            }
            
            int endIndex = str.indexOf("}", index + markLen);
            if (endIndex == -1)
            {
                throw new Exception("var expression starts but does not end");
            }
            
            String key = str.substring(index+markLen, endIndex);
            String param = params.getProperty(key);
            
            if (param == null)
            {
                throw new Exception("parameter [" + key + "] is not defined.");
            }
            
            str = str.substring(0, index) + param + str.substring(endIndex+1);
            index += param.length();
        }
    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to