Search code examples
xmlapache-commons-digester

How do you ignore an element with Apache Digester?


How do I set a digester rule to ignore an element with a certain attribute value? For example, given the below XML and digester-rules, how do I tell it to ignore the Parameter which has a Name equal to "Alex"?

XML:

<input-parameters>
    <parameter>
        <name><![CDATA[Steve]]></name>
        <description><![CDATA[Footballer]]></description>
    </parameter>
    <parameter>
        <name><![CDATA[Adam]]></name>
        <description><![CDATA[Author]]></description>
    </parameter>
    <parameter>
        <name><![CDATA[Lynda]]></name>
        <description><![CDATA[Blacksmith]]></description>
    </parameter>
    <parameter> <!--I want to ignore this whole parameter element.-->
        <name><![CDATA[Alex]]></name>
        <description><![CDATA[Showjumper]]></description>
    </parameter>            
</input-parameters>

Current XML Rules:

<pattern value="input-parameters">
    <object-create-rule classname="java.util.HashSet"/>
    <set-next-rule methodname="addParameters"/>

    <pattern value="parameter">
        <object-create-rule classname="com.foo"/>
        <set-next-rule methodname="add"/>
        <bean-property-setter-rule pattern="name" propertyname="name" />
        <bean-property-setter-rule pattern="description" propertyname="description" />
    </pattern>
</pattern>

Solution

  • I don't think Digester supports this natively, but I can suggest a couple of options that may help to get you there.

    Simplest is to just use a custom HashSet implementation which rejects the names you don't like:

    public class MyHashSet extends HashSet<Parameter> {
      @Override
      public boolean add(Parameter param) {
        if (param != null && "Alex".equals(param.getName())) {
          return false;
        }
        return super.add(param);
      }
    }
    

    And then use this instead of the standard HashSet in your rules:

    <pattern value="input-parameters">
        <object-create-rule classname="com.foo.MyHashSet"/>
        <set-next-rule methodname="addParameters"/>
        ...
    </pattern>
    

    The main drawback I can see from this approach is that you are left with your custom hash set implementation in your resulting object, so if you later try to add a parameter with name = "Alex" directly from code it will again be rejected; this may or may not be a problem.

    A slightly more complex approach would be to switch out the standard SetNextRule with a custom implementation which checked the name before adding the parameter. The standard SetNextRule fires on the event end, so the following rule would do the trick:

    public class SetNextParamRule extends SetNextRule {
    
      public SetNextParamRule() {
        super("add");
      }
    
      @Override
      public void end(String namespace, String name) throws Exception {
        Parameter param = (Parameter)getChild();
        if (!"Alex".equals(param.getName())) {
          super.end(namespace, name);
        }
      }
    }
    

    One problem with this approach is that I can't see a way to add custom rules using the XML configuration (I rarely use XML config so could be wrong here), so you would need to use code configuration instead:

    final Rule setNextParamRule = new SetNextParamRule();
    
    RulesModule rules = new AbstractRulesModule() {
      @Override
      public void configure() {
        forPattern("input-parameters")
            .createObject().ofType(HashSet.class)
            .then().setNext("addParameters");
        forPattern("input-parameters/parameter")
            .createObject().ofType(Parameter.class)
            .then().addRule(setNextParamRule);
        forPattern("input-parameters/parameter/name").setBeanProperty();
        forPattern("input-parameters/parameter/description").setBeanProperty();
      }
    };
    
    DigesterLoader loader = DigesterLoader.newLoader(rules);
    Digester digester = loader.newDigester();
    

    One advantage of this however is that you could potentially supply the list of names to ignore from a separate source:

    import com.google.common.collect.ImmutableSet;
    
    final Set<String> skipNames = ImmutableSet.of("Alex", "Adam");
    
    final Rule setNextParamRule = new SetNextRule("add") {
      @Override
      public void end(String namespace, String name) throws Exception {
        Parameter param = (Parameter)getChild();
        if (!skipNames.contains(param.getName())) {
          super.end(namespace, name);
        }
      }
    };
    
    // rest as above
    

    Hope some of this helps.