Jump to: navigation, search

JET FAQ/How do I access multi-valued attributes?

Question

My model has an attribute that is multi-valued. When I do an XPath expression such as $object/@myMVAttr, I only get the first value!


Answer

JET's XPath engine maps models to the XPath Data Model. Because XPath was developed from XML, JET's XPath engine does not expect attributes to be multi-valued, and returns just first instance when you access the attribute in an expression.

Create an XPath function

You can, however, work around this with an XPath function. Here is an example that works with any EMF-based meta-model.

Usage

Return all values of the current XPath context object:

allValues( 'mvAttributeToAccess' )

Return all values of the passed object:

allValues( someXpathExpressionYieldingAnObject, 'mvAttributeToAccess')

Declaring the function

The function can be declared in plugin.xml of your JET project:

<plugin>
   ... existing extension definitions ...
 
   <extension
         point="org.eclipse.jet.xpathFunctions">
      <function
            implementation="jetTransform.functions.AllValues"
            maxArgs="2"
            minArgs="1"
            name="allValues">
      </function>
   </extension>
</plugin>

The above extension indicates the function is defined by the Java class jetTransform.functions.AllValues.

Implementing the function

The implementation depends on EMF, so while you have plugin.xml open, add a dependency to 'org.eclipse.emf.ecore' on the Dependencies tab. Remember to save plugin.xml before continuing.

The function code is as follows:

package jetTransform.functions;
 
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
 
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jet.xpath.Context;
import org.eclipse.jet.xpath.XPathFunction;
import org.eclipse.jet.xpath.XPathFunctionWithContext;
import org.eclipse.jet.xpath.XPathUtil;
 
public class AllValues implements XPathFunction, XPathFunctionWithContext {
 
    private Context context;
 
    @Override
    public void setContext(Context context) {
        this.context = context;
    }
 
    @Override
    public Object evaluate(@SuppressWarnings("rawtypes") List args) {
        final int argCount = args.size();
        final Object ctxtNode = unpack(argCount == 1 ? context.getContextNode()
                : args.get(0));
        final String attributeName = XPathUtil.xpathString(args
                .get(argCount == 1 ? 0 : 1));
 
        if (ctxtNode instanceof EObject) {
            final EObject eObj = (EObject) ctxtNode;
            final EStructuralFeature feature = eObj.eClass()
                    .getEStructuralFeature(attributeName);
            if (feature instanceof EAttribute) {
                final Object eGetResult = eObj.eGet(feature);
                // always return a collection so that JET can iterate the result
                return eGetResult instanceof Collection<?> ? eGetResult
                        : Collections.singleton(eGetResult);
            }
        }
 
        return Collections.EMPTY_SET;
    }
 
	private Object unpack(Object possiblePackedObject) {
		if(possiblePackedObject instanceof Collection<?>) {
			final Collection<?> c = (Collection<?>)possiblePackedObject;
			final Iterator<?> i = c.iterator();
			return i.hasNext() ? i.next() : null;
		} else {
			return possiblePackedObject;
		}
	}
 
}

Usage examples

The following code assumes the current JET/XPath context object is has an single-valued attribute 'singleValue' with value 'sv1' and a multi-valued attribute 'multiValue' with values 'mv1', 'mv2' and 'mv3'. The following JET tags:

singleValue: ${@singleValue}
multiValue (will only show first): ${@multiValue}
allValues(singleValue): <c:iterate select="allValues('singleValue')" delimiter=", ">${.}</c:iterate>
allValues(multiValue): <c:iterate select="allValues('multiValue')" delimiter=", ">${.}</c:iterate>

will expand to:

singleValue: sv1
multiValue (will only show first): mv1
allValues(singleValue): sv1
allValues(multiValue): mv1, mv2, mv3