Check for duplicated attribute data in sibling elements - Schematron - validation

I'm trying to write a check in Schematron that will ensure no elements contain duplicated attribute data. These elements are at a specific location in the XML document, I have the XPATH that locates them.
For example:
should fail because it has duplicate foo and bar attribute values.
<id foo="test1" bar="abc" />
<id foo="test1" bar="abc" />
This should pass as the foo attributes are not the same.
<id foo="test1" bar="abc" />
<id foo="test2" bar="abc" />
I'm not sure if this is too complicated for Schematron.
Any thoughts?

I don't know Schematron, but if you're able to use XPath 2.0 (which is possible at least with some implementations), deep-equal($val1, $val2) will come in handy.
not(deep-equal(<id foo="test1" bar="abc" />, <id foo="test1" bar="abc" />)) (: false :)
not(deep-equal(<id foo="test1" bar="abc" />, <id foo="test2" bar="abc" />)) (: true :)
If not, there should be a solution using XSLT 1.0, but you will have to construct the recursive comparisons on your own (and I don't know XSLT well enough to do so).

I would do it this way in Schematron (checked with XML ValidatorBuddy):
<iso:pattern id="unique name attributes">
<iso:rule context="id">
<iso:assert test="count(id) = count(id[not(#foo=preceding-sibling::person/#foo)])">
Not all foo attributes of the id elements are unique
</iso:assert>
</iso:rule>
</iso:pattern>
You can also add a check for the bar attribute here.

Related

XMLUNIT 2 using comparison with ignore element order with diffbuilder and namespaces fails

I am trying to use DiffBuilder to ignore XML elements order when comparing two .xml files but it fails. I have tried every possible combination and read many articles before posting this question.
For example:
<Data:Keys>
<Data:Value Key="1" Name="Example1" />
<Data:Value Key="2" Name="Example2" />
<Data:Value Key="3" Name="Example3" />
</Data:Keys>
<Data:Keys>
<Data:Value Key="2" Name="Example2" />
<Data:Value Key="1" Name="Example1" />
<Data:Value Key="3" Name="Example3" />
</Data:Keys>
I want these two treated as same XML. Notice that elements are empty, they have only attributes.
What I did so far:
def diff = DiffBuilder.compare(Input.fromString(xmlIN))
.withTest(Input.fromString(xmlOUT))
.ignoreComments()
.ignoreWhitespace()
.checkForSimilar()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.conditionalBuilder()
.whenElementIsNamed("Data:Keys").thenUse(ElementSelectors.byXPath("./Data:Value",
ElementSelectors.byNameAndText))
.elseUse(ElementSelectors.byName)
.build()))
But it fails every time. I don't know if the issue is the namespace, or that the elements are empty.
Any help will be appricated. Thank you in advance.
if you aim to match tags Data:Value by their attributes together, you should start with this:
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.conditionalBuilder()
.whenElementIsNamed("Data:Value")
and since that tag doesn't have any text, the byNameAndText won't work. You can only work on names and attributes. My advice is to do it like this:
.thenUse(ElementSelectors.byNameAndAttributes("Key"))
or
.thenUse(ElementSelectors.byNameAndAllAttributes())
//equivalent
.thenUse(ElementSelectors.byNameAndAttributes("Key", "Name"))
As of issues with namespaces, checkForSimilar() should output SIMILAR, this means they are not DIFFERENT, so this is what you need. If you didn't use checkForSimilar() the differences in namespaces would be outputed as DIFFERENT.

XPath remove single node (via Saxon CLI)

I want to remove a node from an XML file (using SaxonHE9-8-0-11J):
<project name="Build">
<property name="src" value="src/main/resources" />
<property name="target" value="target/classes" />
<condition property="target.exists">
<available file="target" />
</condition>
</project>
Apparently there are 2 ways I can do this.
XPath1: using a not function
XPath2: using an except clause. But both simply return the entire node-set.
With a not function:
saxonb-xquery -s:test.xml -qs:'*[not(local-name()="condition")]'
With an except clause:
saxonb-xquery -s:test.xml -qs:'* except condition'
With -explain switch the queries are:
<query>
<body>
<filterExpression>
<axis name="child" nodeTest="element()"/>
<operator op="ne (on empty return true())">
<functionCall name="local-name">
<dot/>
</functionCall>
<literal value="condition" type="xs:string"/>
</operator>
</filterExpression>
</body>
</query>
and
<query>
<body>
<operator op="except">
<axis name="child" nodeTest="element()"/>
<path>
<root/>
<axis name="descendant" nodeTest="element(condition, xs:anyType)"/>
</path>
</operator>
</body>
</query>
In general, XPath select nodes from one or more input documents, it doesn't allow you to construct new ones, for that you need XSLT or XQuery. And removing the condition child of the project root, if that is what you want to achieve, is something you need XSLT or XQuery for, with XPath, even if you use /*/(* except condition), you then get all children except the condition element, but as a sequence, not wrapped into a a root.
So with XQuery you could use
/*/element {node-name()} { * except condition }
as a compact but generic way to reconstruct any root with all child elements except the condition: https://xqueryfiddle.liberty-development.net/948Fn5b
Whether you get such an expression through a command line shell is a different problem, on Windows with a Powershell window and the cmd shell it works for me to use
-qs:"/*/element {node-name()} { * except condition }"

XACML rule check between resource and subject with XPath

I can't figure out how to write a rule that would solve this requirement :
Let's assume I have this request :
<Request>
<Attributes Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject">
<Content>
<Categories>
<Category name="cat1">
<CategoryValue>A</CategoryValue>
<CategoryValue>B</CategoryValue>
<CategoryValue>C</CategoryValue>
</Category>
<Category name="cat2">
<CategoryValue>B</CategoryValue>
<CategoryValue>E</CategoryValue>
<CategoryValue>F</CategoryValue>
</Category>
</Categories>
</Content>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource">
<Content>
<Categories>
<Category name="cat1">
<CategoryValue>A</CategoryValue>
</Category>
<Category name="cat2">
<CategoryValue>A</CategoryValue>
<CategoryValue>E</CategoryValue>
<CategoryValue>F</CategoryValue>
<CategoryValue>G</CategoryValue>
</Category>
</Categories>
</Content>
</Attributes>
</Request>
I want to write a policy that contains a rule with a Permit effect when for each of the Category elements of the resource, the subject has a Category with the same #name and if both of these Category elements has at least one common CategoryValue.
In this Example above :
Resource has "cat1" with "A" - Subject has "cat1" with one value that is A : Permit
Resource has "cat2" with "A", "E", "F", "G" - Subject has "cat2" with value E (or F) : Permit
Final result of the rule : Permit
My question is not on which functionId I should use, but how can I combine these conditions so that the rule behaves the way I described ? How to compare the GenericValue elements of nodes that has the same #name ?
I think I will have to use the string-at-least-one-member-of function between the values of the subject and resource "cat1", then between the subject and resource "cat2", but the real difficulty is that the PDP has no idea of the #name of the Category elements, so I can't hardcode it directly in the rule and I don't know how to select them in particular to perform the check.
Any idea on this ?
First of all, your request is invalid. You are missing some elements e.g.
ReturnPolicyIdList="true"
CombinedDecision="true"
Secondly, I would recommend you do not use XPath in XACML. It makes your policies hard to write (hence your question), hard to maintain, and hard to read (audit). It defeats the purpose of XACML in a way. Let the PEP do the heavy XML processing and send in attributes with attribute values rather than XML content.
In addition, you cannot control the iteration over the different elements / attribute values in the XML in XACML. I can implement your use case with a specific #name value but I cannot manage to do it over an array of values.
Assuming a single value, you would have to implement a condition as follows:
<xacml3:Rule RuleId="axiomatics-example-xacml30" Effect="Permit" xmlns:xacml3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17">
<xacml3:Target/>
<xacml3:Condition >
<xacml3:Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of">
<xacml3:AttributeSelector Path="/Categories/Category[#name='cat1']/CategoryValue/text()" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"/>
<xacml3:AttributeSelector Path="/Categories/Category[#name='cat1']/CategoryValue/text()" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"/>
</xacml3:Apply>
</xacml3:Condition>
</xacml3:Rule>
But you cannot really iterate over the different values

Determine if any element with a given name has a particular value

Given this XML fragment (I've removed superfluous fluff):
<Event name="DataComplete">
<Task id="d20a0053-7678-43ba-bc8a-ece24dcff15b"/>
<DataItems>
<DataItem name="Survey" type="task">
<Value status="NotStarted" taskId="00000000-0000-0000-0000-000000000000" />
</DataItem>
<GroupDataItem name="CT_Visit"> --- this may repeat
<ItemGroup id="1" >
<DataItem name="Special Contractor" type="string">Yes</DataItem>
What xPath expression will determine if any DataItem with name="Special Contractor" has the value "Yes".
I'm trying something like this:
Yes = /Event/Task/DataItems/GroupDataItem/ItemGroup/DataItem/#[normalize-space() = 'Special Contractor']
and many variations usually resulting in "invalid xPath expression".
Any clues most welcome. Thanks!
[EDIT]
Thanks for the answers Jiri and Will. Will was close, but as my question states, I'm trying to determine if any* element has the value Yes. I should have been more explicit in saying that I need a boolean, true or false. Adapting Will's answer led me to this:
"Yes" = //Event/Task/DataItems/GroupDataItem/ItemGroup/DataItem[#name='Special Contractor']
This returns a simple Boolean='true' or Boolean='false'.
Thanks guys!
/Event/DataItems/GroupDataItem/ItemGroup/DataItem[#name = "Special Contractor"][. = "Yes"]
Returns the DataItem in question. Note that this will be a sequence of matching DataItem elements if there are more than one. If you just want a boolean:
exists(/Event/DataItems/GroupDataItem/ItemGroup/DataItem[#name = "Special Contractor"][. = "Yes"])
(as an aside; I removed Task from the xpath, since it's not actually an ancestor of the DataItem in the XML fragment you posted, even though the indentation makes it look like it is.)
Use this xpath
/Event/Task/DataItems/GroupDataItem/ItemGroup/DataItem[#name='Special Contractor']
for following xml:
<Event name="DataComplete">
<Task id="d20a0053-7678-43ba-bc8a-ece24dcff15b">
<DataItems>
<DataItem name="Survey" type="task">
<Value status="NotStarted" taskId="00000000-0000-0000-0000-000000000000" />
</DataItem>
<GroupDataItem name="CT_Visit"> --- this may repeat
<ItemGroup id="1" >
<DataItem name="Special Contractor" type="string">Yes</DataItem>
</ItemGroup>
</GroupDataItem>
</DataItems>
</Task>
...
</Event>
If the task is really non-pair element, then omit it from the xpath expression.

XPATH expression that Matches on the attribute value "true"

I have some XML like this:
<engine-set>
<engine host-ref="blah1.com">
<property name="foo" value="true"/>
<property name="bar" value="true"/>
</engine>
<engine host-ref="blah2.com">
<property name="foo" value="true"/>
<property name="bar" value="false"/>
</engine>
</engine-set>
I want to match on all engine elements that have a child node property with a name equal to "bar" and and value equal to "true". I'm finding the fact that "true" appears in my XML is causing my condition to always evaluate to true in an XPath expression. Is there a way around? I'm using Python and lxml.
EDIT:
My xpath expression is (that isn't working) is:
//engine[(property/#name='bar' and property/#value="true")]
Thanks,
I want to match on all engine elements
This is:
//engine
that have a child node property
Now this becomes:
//engine[property]
with a name equal to "bar"
Still more specific:
//engine[property[#name = 'bar']]
and and value equal to "true".
Finally:
//engine[property[#name = 'bar' and #value = 'true']]
So you're saying
//engine[property[#name='bar' and #value='true']]
gives you too many results? Because for me it gives just one.
What XPath expression did you try?
The following seems to work well in getting "blah1.com" but not "blah2.com":
//engine[property[#value="true"][#name="bar"]]
Remember that you need to encase your parameter test values in quotes.

Resources