Fork me on GitHub

FAQ / HowTo

How to determine if an element or attribute exists?

Since Version 1.4.0 you can expect a null value returned if an element does not exist. The projector will determine the type of the XPath expression and invoke the evaluation accordingly whether it is a function, a node value or a node itself. If you want to avoid null checks, specify java.util.Optional as return type. If you want non existing nodes to be handled like existing empty ones, set the flag ABSENT_IS_EMPTY on projector creation.

Since Version 1.4.1 you can choose to let the projection method throw an exception if a value is absent. Just declare your method with the suffix "throws some exception type", with your own exception or use a standard JDK exception. It may be a checked or unchecked exception. If your getter method takes parameters and your exception has a constructor matching these parameters, this constructor will be used to create the exception. You won't get any exception if you choose the ABSENT_IS_EMPTY flag. You may not specify Optional as return type if you declare an exception to be thrown.

What's the syntax for writable XPath expressions?

XPath expressions used in setters (methods with @XBWrite) are limited to absolute paths of non-ambiguous selectors. Functions are not allowed. The reason for this is to make a setter executable regardless of the current document structure, having a reproducible result. If you just need to change values in your DOM tree, use the @XBUpdate annotation on your method. This will allow full XPath syntax.

Changing attributes

Example: "/element1/element2/@AttributeName" This works for any setter value that is not a projection nor a collection. The toString() method will be used to create the attribute value.

Changing element values

Example: "/element1/element2" This works for any setter value that is not a projection nor a collection. The toString() method will be used to create the attribute value.

Changing child elements

Example: "/element1/element2/*" This works for projections or collections of projections only. If your setter value is a projection, all existing elements with the same element name will be removed and a single element will be attached.

How to access the DOM behind a projection?

Every projection is associated with a document, every sub projection with a DOM element. To access the DOM Node you may either:

  • Let your projection extend the interface org.xmlbeam.dom.DOMAccess. You will inherit getter methods for the document and the current node.
  • Cast your projection directly to DOMAccess. The projector makes sure that every projection instance implements this interface.
  • Just declare org.w3c.dom.Node as a return type or a parameter on a projection method.

You may change the DOM any time, projections will reflect the changes automatically.

    public interface ProjectionWithDomAccess {
        // Just declare Node as return type
        @XBRead("/some/path/to/element/or/@attribute")
        org.w3c.dom.Node getSomeValue();
    }
 
    public interface ProjectionExtendingDOMAccess extends DOMAccess {
 
        // add your projections here
 
        //you inherit some useful methods
        @Override
        Node getDOMNode();
 
        @Override
        Element getDOMBaseElement();
 
        @Override
        String asString(); // returns XML String for this projection
    }
    //END SNIPPET:
 
    //START SNIPPET: MixinOverridingToString
    public interface MixinOverridingToString {
        @Override
        String toString();
    }
    //END SNIPPET: MixinOverridingToString
 
    //START SNIPPET: Projection
    public interface Projection extends MixinOverridingToString {
        // Your projection methods here
        @XBRead("...")
        String getSomeValue();
    }
    //END SNIPPET: Projection
 
    {
        //START SNIPPET: MixinRegistration
        Object mixin = new Object() {
            private Projection me;
            @Override
            public String toString() {
                return "I have value "+me.getSomeValue();
            };
        };
        projector.mixins().addProjectionMixin(Projection.class, mixin);
        //END SNIPPET: MixinRegistration
    }
 
    {
        //START SNIPPET: OwnXPathFactoriesImplementation
        XMLFactoriesConfig myConfig = new DefaultXMLFactoriesConfig() {
            {
                // We should change the behavior of the config to not
                // interfere with the name space handling of an
                // unknown XPath implementation.
                setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
            }
 
            /**
             * Override this method to inject your own factory.
             */
            @Override
            public XPathFactory createXPathFactory() {
                return new MyOwnXpathFactory();
            }
        };
 
        XBProjector projector = new XBProjector(myConfig);
        //END SNIPPET: OwnXPathFactoriesImplementation
        projector.hashCode();
    }
    {
        //START SNIPPET: OwnXPathImplementation
        XMLFactoriesConfig myConfig = new DefaultXMLFactoriesConfig() {
            {
                // We should change the behavior of the config to not
                // interfere with the name space handling of an
                // unknown XPath implementation.
                setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
            }
 
            /**
             * Or override this to bypass the factory and create your own XPath implementation here.
             */
            @Override
            public XPath createXPath(final Document... document) {
                return super.createXPath(document);
            }
        };
 
        XBProjector projector = new XBProjector(myConfig);
        //END SNIPPET: OwnXPathImplementation
        projector.hashCode();
    }
 
    public interface ExampleProjection {
        @XBRead
        List<String> getDepartmentUsersName();
    }
 
    @Test
    public void UnCamelCaseTest() {
        XBProjector projector = new XBProjector();
        projector.config().setExternalizer(new ExternalizerAdapter() {
            @Override
            public String resolveXPath(final String annotationValue, final Method method, final Object[] args) {
                // Simplest conversion of camel case getter to xpath expression.
                return method.getName().substring(3).replaceAll("[A-Z]", "/$0");
            }
        });
        List<String> departmentUsers = projector.projectXMLString("<Department><Users><Name>John Doe</Name><Name>Tommy Atkins</Name></Users></Department>", ExampleProjection.class).getDepartmentUsersName();
        assertTrue(departmentUsers.size() == 2);
        assertEquals("John Doe", departmentUsers.get(0));
        assertEquals("Tommy Atkins", departmentUsers.get(1));
    }
//START SNIPPET: NO_FORMATTING
/*
//START SNIPPET:Java8OverrideToString
        @XBOverride("toString")
        default String toString_() {
            return "String from overriden toString() method";
        }
//END SNIPPET:Java8OverrideToString
*/
}

How do I implement the toString() method for my projection?

You can easily implement your own toString() method which can access your projection getter methods. With Java 8, just define a default mehtod with the same signature as the Object.toString() signature in your projection interface, but a different name. For example you can choose "String asString()" or "String toString_()". Then add a @XBOverride annotation with the name of the method this default method should override.

@XBOverride("toString")
default String toString_() {
    return "String from overriden toString() method";
}

For Java 6 & 7 you need to create a mixin:

public interface Projection extends MixinOverridingToString {
    // Your projection methods here
    @XBRead("...")
    String getSomeValue();
}
public interface MixinOverridingToString {
    @Override
    String toString();
}

And register it in your projector:

Object mixin = new Object() {
    private Projection me;
    @Override
    public String toString() {
        return "I have value "+me.getSomeValue();
    };
};
projector.mixins().addProjectionMixin(Projection.class, mixin);

Why does the projector not cache values?

Caching is not a concern of this library. Implement your own cache to avoid unnecessary XPath overhead. The projector does not know anything about the purpose of the data it reads. To be able to circumvent unexpected caching side effects you need knowledge about the application context. The XPath expression however is cached, to avoid multiple compilations of the same expression.

How do I change the used XPath engine?

There are three different ways to change the underlying XPath implementation. You should be able to get everything running that implements the javax.xml.xpath.XPath interface.

  • If your XPath library keeps the contract of the standard API, it should be sufficient to just define the appropriate property for your URI as described here. This should work out of the box without any code change.
  • If you do not want your implementation be created by the original XPathFactory, you may change the way the projector creates this factory:
    XMLFactoriesConfig myConfig = new DefaultXMLFactoriesConfig() {
        {
            // We should change the behavior of the config to not
            // interfere with the name space handling of an
            // unknown XPath implementation.
            setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
        }
     
        /**
         * Override this method to inject your own factory.
         */
        @Override
        public XPathFactory createXPathFactory() {
            return new MyOwnXpathFactory();
        }
    };
     
    XBProjector projector = new XBProjector(myConfig);
  • If you do not want any XPathFactory involved, you even may bypass this:
    XMLFactoriesConfig myConfig = new DefaultXMLFactoriesConfig() {
        {
            // We should change the behavior of the config to not
            // interfere with the name space handling of an
            // unknown XPath implementation.
            setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
        }
     
        /**
         * Or override this to bypass the factory and create your own XPath implementation here.
         */
        @Override
        public XPath createXPath(final Document... document) {
            return super.createXPath(document);
        }
    };
     
    XBProjector projector = new XBProjector(myConfig);