Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
JET FAQ/How do I access multi-valued attributes?
Contents
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