In Part 3, we covered the basics of writing a BREX content rule, including highlighting common mistakes one may commit. In this Part, we are going to employ what we have learned, and expand upon it, by taking a real-work BR received from a customer and develop a context rule that accurately represents the BR. In addition, we provide tips on improving the effectiveness of context rules, look beyond simple path-like expressions, and summarize what we have learned.
Customer’s Business Rule
A customer was having problems coming up with a context rule that properly enforces one of their BRs. The business rule was as follows:
If the install location does not have an
installationLocationType
attribute value it must have a zone reference.
A fairly specific BR, but this is not uncommon. The customer was familiar with XML and has worked with S1000D for a long time, but even with such knowledge and experience, writing a BREX context rule can be challenging if new to the experience. As with any skill, it takes time and practice.
The customer even provided their attempt at writing the context rule:
<structureObjectRule>
<objectPath allowedObjectFlag="1">
not(//installationLocation/@installationLocationType)
and
//installationLocation/zoneRef
</objectPath>
</structureObjectRule>
Can you spot the problems with this attempt?
The problems are:
Data modules without an
<installationLocation>
element will always fail due to the pitfalls of usingallowedObjectFlag=”1”
. The BR indicates the restriction imposed is only applicable if an<installationLocation>
element is present (the actual BR text is not explicit about that, but that is the intent based on customer’s confirmation).Context is missing. The structures relevant to the BR does not occur for all data module types. Therefore, to avoid false failures, context needs to be added.
The XPath expression does not accurately reflect the schemas. When writing XPath expressions, awareness of the schemas is very useful.
The return types of the XPath expression is boolean (true or false). The S1000D specification is ambiguous if this allowed, and in practice, most BREX checkers support the boolean return type. However, for purposes of failure reporting, it is highly recommended your expressions return node types (elements or attributes) whenever possible.
Corrective Attempt #1
In my attempt to correct the problems of the customer-provided attempt, I applied the following techniques:
Include context
Inverse the logic: Match what is not allowed.
Apply schema-structure awareness
With these techniques, I came up with the following:
<structureObjectRule>
<objectPath allowedObjectFlag="0">
/dmodule/content//workAreaLocationGroup
[workLocation/installationLocation
[not(@installationLocationType)] and
not(zoneRef)]
</objectPath>
<objectUse>
If an install location does not have an installationLocationType
attribute, a zone reference is required.
</objectUse>
</structureObjectRule>
Corrective Attempt #2
The context rule defined by Attempt #1 is logically valid and will enforce the customer’s BR. However, when writing context rules, when at all possible, it is best to have your expression return the closest node of interest. Some BREX checkers, like IrisCheck, will provide location information of a failure based on the nodes returned from the expression. In Attempt #1, the node returned will be to workAreaLocationGroup
, but the BR focuses on the installationLocation
element.
With this in mind, I rewrote the rule as follows:
<structureObjectRule>
<objectPath allowedObjectFlag="0">
/dmodule/content//workAreaLocationGroup/
workLocation/installationLocation
[not(@installationLocationType) and
not(../../zoneRef)]
</objectPath>
<objectUse>
If an install location does not have an
installationLocationType attribute, a zone reference
is required.
</objectUse>
</structureObjectRule>
Returning the installationLocation
element allows the data module author to more quickly know where to go to correct the problem. Sometimes it may be more challenging to write an expression to return the node of interest, but the efficiency benefits for authors can be significant.
Closest Node of Interest
It is easy to overlook the benefits of returning the nodes that will help authors make corrective actions. Some BREX checkers (especially older ones) fail to provide location information when reporting failures, where only the contents of objectUse
is provided. Therefore, you may encounter BREX data modules that do not follow the closest-node-of-interest practice, including the S1000D default BREX data modules!
For example, take the following XPath expression from the S1000D default BREX:
//dmodule[
(descendant-or-self::dmAddress[
descendant-or-self::issueInfo[attribute::inWork="00"]]) and
(descendant-or-self::changeInline or
descendant-or-self::*[attribute::changeMark or attribute::changeType]
and not(descendant-or-self::dmStatus[attribute::issueType="changed" or
attribute::issueType="rinstate-changed" or
attribute::issueType="status" or
attribute::issueType="rinstate-status"]))]
If the associated rule that contains this expression fails, the return node is to the root element of the data module: dmodule
. However, the closest node of interest is the dmStatus
element that contains the issueType
attribute the rule checks.
The expression can be rewritten as follows:
/dmodule/identAndStatusSection/dmStatus[
(../dmAddress/dmIdent/issueInfo/@inWork eq "00") and
(not(@issueType = ("changed","rinstate-changed","status","rinstate-status"))
and (/dmodule//changeInline or /dmodule//*[@changeMark or @changeType]))]
This version does take advantage of some features and operators of the XPath language vis-à-vis the original expression, but the main takeaway is this expression returns the dmStatus
element and not the root dmodule
element.
Complex Example
The XPath language is quite powerful, where you can do more than just simple path-like expression. Exploring the capabilities of XPath may have you discover that BRs you thought could not be expressed as a BREX context rule, can be. For example, let’s taking the following business rule:
Enumeration values specified for a product or condition attribute with a value type of '
real
' must conform to real number syntax.
This BR actually reflects what the S1000D specification demands. However, the S1000D default BREX does not enforce this rule, but by leveraging some features and functions of XPath, such a rule can be written:
<structureObjectRule>
<objectPath allowedObjectFlag="0">
//element()[self::productAttribute or self::condType]
[@valueDataType='real']/
enumeration[(for $v in tokenize(@applicPropertyValues, "\||~")
return if (number($v) = number($v))
then 1 else 0) = 0]
</objectPath>
<objectUse>
Enumeration values specified for a product or condition attribute
with a value type of 'real' must conform to real number syntax.
</objectUse>
</structureObjectRule>
It is likely such a rule does not exist in the default BREX as there is some confusion of what versions of the XPath language is allowed (along with knowing the full range of capabilities XPath supports). Many processing systems may still only support XPath 1.0. In the expression above, to make it compatible with 1.0, will likely need to replace the use of element()
with *
.1
What Have We Learned?
Let’s summarize some useful tips in building a better BREX:
Do not use the
rulesContext
attribute: Context can be included in the XPath expression itself. There may be some rare exceptions to this rule, but make sure you have exhausted all other possibilities before using the attribute.Match what is not allowed: Apply inverse logic when translating BRs into context rules. The effect of
allowedObjectFlag=”1”
is widely misunderstood, so do not use it unless you fully understand its effect.Know the schemas: Knowing what is and is not allowed by the schemas will help you write better expressions.
Return the closest node of interest: Helps authors on where to go to correct failures.
TEST YOUR RULES! Especially test for expected failures. There is a tendency to focus on what passes, overlooking the scenarios where a failure should be reported. Proper testing can catch mistakes that are easy to overlook.
I plan on covering the topic of XPath languages versions in relation to the S1000D specification in a future article.