I have this XSD of Jira workflow:
<?xml version="1.0" encoding="utf-8"?>
<!--
Converted from workflow_2_8.dtd using Microsoft Visual Studio
-->
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="workflow">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1000" ref="meta" />
<xs:element minOccurs="0" maxOccurs="1" ref="registers" />
<xs:element minOccurs="0" maxOccurs="1" ref="trigger-functions" />
<xs:element minOccurs="0" maxOccurs="1" ref="global-conditions" />
<xs:element ref="initial-actions" />
<xs:element minOccurs="0" maxOccurs="1" ref="global-actions" />
<xs:element minOccurs="0" maxOccurs="1" ref="common-actions" />
<xs:element ref="steps" />
<xs:element minOccurs="0" maxOccurs="1" ref="splits" />
<xs:element minOccurs="0" maxOccurs="1" ref="joins" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="action">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="meta" />
<xs:element minOccurs="0" maxOccurs="1" ref="restrict-to" />
<xs:element minOccurs="0" maxOccurs="1" ref="validators" />
<xs:element minOccurs="0" maxOccurs="1" ref="pre-functions" />
<xs:element ref="results" />
<xs:element minOccurs="0" maxOccurs="1" ref="post-functions" />
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required" />
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="view" type="xs:string" />
<xs:attribute name="auto">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="TRUE" />
<xs:enumeration value="FALSE" />
<xs:enumeration value="true" />
<xs:enumeration value="false" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="finish">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="TRUE" />
<xs:enumeration value="FALSE" />
<xs:enumeration value="true" />
<xs:enumeration value="false" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="common-action">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" type="xs:string" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="actions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="common-action" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="action" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="arg">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="conditions">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="conditions" />
<xs:element ref="condition" />
</xs:choice>
<xs:attribute name="type">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="AND" />
<xs:enumeration value="OR" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="condition">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="arg" />
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required" />
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="negate" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="external-permissions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="permission" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="function">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="arg" />
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required" />
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="global-actions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="action" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="initial-actions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="action" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="common-actions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="action" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="join">
<xs:complexType>
<xs:sequence>
<xs:element ref="conditions" />
<xs:element ref="unconditional-result" />
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="joins">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="join" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="meta">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="permission">
<xs:complexType>
<xs:sequence>
<xs:element ref="restrict-to" />
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="id" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="post-functions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="function" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="pre-functions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="function" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="register">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="arg" />
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required" />
<xs:attribute name="variable-name" type="xs:string" use="required" />
<xs:attribute name="id" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="registers">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="register" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="restrict-to">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" ref="conditions" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="result">
<xs:complexType>
<xs:sequence>
<xs:element ref="conditions" />
<xs:element minOccurs="0" maxOccurs="1" ref="validators" />
<xs:element minOccurs="0" maxOccurs="1" ref="pre-functions" />
<xs:element minOccurs="0" maxOccurs="1" ref="post-functions" />
</xs:sequence>
<xs:attribute name="old-status" type="xs:string" use="required" />
<xs:attribute name="status" type="xs:string" />
<xs:attribute name="step" type="xs:string" />
<xs:attribute name="owner" type="xs:string" />
<xs:attribute name="split" type="xs:string" />
<xs:attribute name="join" type="xs:string" />
<xs:attribute name="due-date" type="xs:string" />
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="display-name" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="results">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="result" />
<xs:element ref="unconditional-result" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="split">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="unconditional-result" />
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="splits">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="split" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="step">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="meta" />
<xs:element minOccurs="0" maxOccurs="1" ref="pre-functions" />
<xs:element minOccurs="0" maxOccurs="1" ref="external-permissions" />
<xs:element minOccurs="0" maxOccurs="1" ref="actions" />
<xs:element minOccurs="0" maxOccurs="1" ref="post-functions" />
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required" />
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="steps">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="step" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="unconditional-result">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" ref="validators" />
<xs:element minOccurs="0" maxOccurs="1" ref="pre-functions" />
<xs:element minOccurs="0" maxOccurs="1" ref="post-functions" />
</xs:sequence>
<xs:attribute name="old-status" type="xs:string" use="required" />
<xs:attribute name="status" type="xs:string" />
<xs:attribute name="step" type="xs:string" />
<xs:attribute name="owner" type="xs:string" />
<xs:attribute name="split" type="xs:string" />
<xs:attribute name="join" type="xs:string" />
<xs:attribute name="due-date" type="xs:string" />
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="display-name" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="trigger-function">
<xs:complexType>
<xs:sequence>
<xs:element ref="function" />
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="trigger-functions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="trigger-function" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="validator">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="arg" />
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required" />
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="id" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="validators">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="validator" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="global-conditions">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" ref="conditions" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
I generated the classes using JAXB. In particular, Conditions class:
//
// This file was generated by the Eclipse Implementation of JAXB, v2.3.7
// See https://eclipse-ee4j.github.io/jaxb-ri
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2023.06.13 at 11:10:39 AM HKT
//
package com.igsl.configmigration.workflow.mapper.generated;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import com.igsl.configmigration.workflow.mapper.WorkflowPart;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType>
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <choice maxOccurs="unbounded" minOccurs="0">
* <element ref="{}conditions"/>
* <element ref="{}condition"/>
* </choice>
* <attribute name="type">
* <simpleType>
* <restriction base="{http://www.w3.org/2001/XMLSchema}NMTOKEN">
* <enumeration value="AND"/>
* <enumeration value="OR"/>
* </restriction>
* </simpleType>
* </attribute>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"conditionsOrCondition"
})
@XmlRootElement(name = "conditions")
public class Conditions implements WorkflowPart
{
@XmlElements({
@XmlElement(name = "conditions", type = Conditions.class),
@XmlElement(name = "condition", type = Condition.class)
})
protected List<Object> conditionsOrCondition;
@XmlAttribute(name = "type")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
protected String type;
/**
* Gets the value of the conditionsOrCondition property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the conditionsOrCondition property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getConditionsOrCondition().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Condition }
* {@link Conditions }
*
*
*/
public List<Object> getConditionsOrCondition() {
if (conditionsOrCondition == null) {
conditionsOrCondition = new ArrayList<Object>();
}
return this.conditionsOrCondition;
}
/**
* Gets the value of the type property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getType() {
return type;
}
/**
* Sets the value of the type property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setType(String value) {
this.type = value;
}
}
And Condition class:
//
// This file was generated by the Eclipse Implementation of JAXB, v2.3.7
// See https://eclipse-ee4j.github.io/jaxb-ri
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2023.06.13 at 11:10:39 AM HKT
//
package com.igsl.configmigration.workflow.mapper.generated;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import com.igsl.configmigration.workflow.mapper.WorkflowPart;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType>
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element ref="{}arg" maxOccurs="unbounded" minOccurs="0"/>
* </sequence>
* <attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="negate" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" />
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"arg"
})
@XmlRootElement(name = "condition")
public class Condition implements WorkflowPart
{
protected List<Arg> arg;
@XmlAttribute(name = "type", required = true)
protected String type;
@XmlAttribute(name = "id")
protected String id;
@XmlAttribute(name = "negate")
protected String negate;
@XmlAttribute(name = "name")
protected String name;
/**
* Gets the value of the arg property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the arg property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getArg().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Arg }
*
*
*/
public List<Arg> getArg() {
if (arg == null) {
arg = new ArrayList<Arg>();
}
return this.arg;
}
/**
* Gets the value of the type property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getType() {
return type;
}
/**
* Sets the value of the type property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setType(String value) {
this.type = value;
}
/**
* Gets the value of the id property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getId() {
return id;
}
/**
* Sets the value of the id property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setId(String value) {
this.id = value;
}
/**
* Gets the value of the negate property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getNegate() {
return negate;
}
/**
* Sets the value of the negate property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setNegate(String value) {
this.negate = value;
}
/**
* Gets the value of the name property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getName() {
return name;
}
/**
* Sets the value of the name property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setName(String value) {
this.name = value;
}
}
You can ignore the interface WorkflowPart, that's just a common interface (with some default methods) I added to handle the classes via generics.
Now I have this XML (pastebin because of size limit here): https://pastebin.com/VhRWrsak
If I run this query, I can find 1 expected single match:
//function[type='class'][arg[name='class.name'][value='com.igsl.customapproval.workflow.postfunction.InitializeApprovalPostFunction']]/arg[name='approvedStatus']
Which is looking for function that contains a particular arg, and return another arg under the same function.
But this query fails to match anything:
//condition[type='class'][arg[name='class.name'][value='com.atlassian.jira.workflow.condition.InGroupCFCondition']]/arg[name='groupcf']
Which is looking for a condition that contains a particular arg, and return another arg under the same condition.
If I omit the part about condition, then I can find a match:
//arg[name='class.name'][value='com.atlassian.jira.workflow.condition.InGroupCFCondition']
I can locate conditions, e.g.
//conditions
But I cannot locate condition, I get 0 match with this:
//condition
It seems to be a bug (because Conditions can contain both Condtions and Condition) but I'm not entirely sure.
These are possible workarounds. It works as long as I don't mention the condition element:
//conditions//arg[name='class.name'][value='com.atlassian.jira.workflow.condition.InGroupCFCondition']/../arg[name='groupcf']
//conditions/*[type='class']/arg[name='class.name'][value='com.atlassian.jira.workflow.condition.InGroupCFCondition']/../arg[name='groupcf']
Here's the testing program:
public static void main(String[] args) throws Exception {
List<String> workflowXML = Files.readLines(Paths.get(XML_SOURCE).toFile(), Charset.defaultCharset());
StringBuilder xml = new StringBuilder();
for (String s : workflowXML) {
xml.append(s);
}
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
// Disable DTD validation
try {
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
saxParserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
saxParserFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
System.out.println("Error configuring SAX Parser");
e.printStackTrace();
}
Source xmlSource = new SAXSource(saxParserFactory.newSAXParser().getXMLReader(),
new InputSource(new StringReader(xml.toString())));
JAXBContext ctx = JAXBContext.newInstance(Workflow.class);
Unmarshaller parser = ctx.createUnmarshaller();
Workflow wf = (Workflow) parser.unmarshal(xmlSource);
// Test JXPath
String xpath = "//arg[name='class.name'][value='com.atlassian.jira.workflow.condition.InGroupCFCondition']";
JXPathContext context = JXPathContext.newContext(wf);
Iterator<Pointer> it = (Iterator<Pointer>) context.iteratePointers(xpath);
//Iterator<?> it = context.iterate(xpath);
if (it != null) {
if (it.hasNext()) {
while (it.hasNext()) {
Pointer p = it.next();
WorkflowPart v = (WorkflowPart) p.getValue();
System.out.println(p.asPath() + ": " + v + " (" + v.getWorkflowPartType() + ")");
}
} else {
System.out.println("Not found");
}
} else {
System.out.println(xpath + ": null");
}