Home » Articles » Unique Product Features » An XPath Enabled Rule Engine

An XPath Enabled Rule Engine

By Mark Joseph - May 22, 2008 @ 9:05 am

This document was updated on 26 Jan 2014.

An excellent introduction to Rule Engines can be found at “Chapter1. The Rule Engine” . However, this description is a bit detailed. In general, a rule engine allows the user to define a set of conditional statements (such as “if x then y”), where when the condition (x) is true the result (y) is executed. Both the condition and execution are application specific. Rule engines have been applied to many applications some of which include ad server systems, email filtering, anti-SPAM software, and business management tools.

As an example in the advertising space, a typical rule could be: if “mother’s day” then “show image banner A”, where banner A is a gift for mom.

There is a lot of technical jargon used to describe how rule engines work two of which are important to understand the rest of this article. The first term is “stated facts” which are fixed facts that are given as input to the rule evaluation (e.g., the price of a typical car is $22,000). And the second term is “inferred facts” which are facts that are generated by the rule engine (these come from a rule’s actions, e.g., Henry bought an expensive car since it cost more than $22,000).

What follows are details of what makes our rule engine so powerful. (See our XJR SDK.) While we where designing our rule engine we knew that we needed an expression language to define a rule’s conditional logic. Luckily we already had built XPath 2.0 which is a powerful expression language that is embeddable into XML.

Examples of other Rule Engines include open Source Rule Engines in Java (Drools is a great example of this). Another popular rule engine is from ILOG namely JRules and ILOG Rules and how they are used in Business rule management.

PROPERTIES OF OUR RULE ENGINE

  1. Our rule engine implements forward chaining where all inferred facts are added to a DOM tree at location under “/P6R:infer/”. The benefit of this is that all inferred facts are accessible via XPath 2.0 expressions. (XPath access requires that the xmlns:P6R=’http://www.p6r.com/ruleengine’ namespace be defined in the top level ‘rulesets’ element.)
  2. Our XPath 2.0 supports stated facts defined in either XML or JSON , thus our Rule Engine is also JSON enabled.
  3. No software programming is required. Our rule engine is controlled by an XML based language we have defined. For example, a basic rule looks like:
    <rule name='abc' setname='rover' priority='5'>
       <if test='XPath expression'>
       <then>
          One or more actions (which can also include XPath expressions)
       </then>
    </rule>
  4. Rules can be added, deleted, modified any time between evaluation calls.
  5. The rule engine is easily extensible by any application. This is done via XPath. XPath calls the enclosing application for any function it does not recognize as a predefined XPath function. For example,
    <rule name='hotcar' setname='over1'>
       <if test='$g2'/>
       <then>
          <call func="setclickurl( 'www.p6r.com' )" />
          <set-fact name='12345' select="'Hi Henry'" location='/P6R:infer/mealitem' />
       </then>
    </rule>

    the ‘setclickurl’ is not a defined XPath built-in function but is instead defined by the application. Also the variable ‘$g2′ is defined by the application. Our XPath component allows an application to register a special handler object which defines these external functions and variables.

  6. Any number of rule sets, rules, and actions in a rule are allowed.
  7. Our rule engine has a large set of built-in functionality via a well engineered XML rule language (some of which is presented below and we document in detail in our SDK).
  8. Our rule engine has a built-in logging facility. Also XPath 2.0 defines several tracing functions. Both the internal working of the rule engine logic as well as the XPath engine can be traced. For example, XPath 2.0 supports regular expressions. When the XPath log is turned on any regular expression being evaluated is also traced.
  9. Rules are compiled so any errors are returned as error codes to the user before the rules are run.
  10. Our rule engine is its own object and thus any number of separate rule engine instances can be created for a single or multiple applications. Each instance can be controlled by a different set of rules and can generate a different set of inferred facts.
  11. As with all P6R software, our rule engine runs on several platforms which currently include (as of this writting) Linux, Solaris and Windows.
  12. P6R’s rule engine is written in C++, but also has the following language bindings: Perl, Php, Python.

The following sections are excerpts from our rule engine manual. These small excerpts provide a view into the full expressive power of our engine.

THE RULE ENGINE LANGUAGE

The basic building block is the rule and it has the following form:

<rule name='abc' setname='rover' priority='5'>
     <if test='XPath expression' >
     <then>
           One or more actions
     </then>
</rule>

Every rule must have an ‘if’ and ‘then’ element. The ‘if’ element defines the conditions when the rule is true and thus can be executed. The ‘then’ element defines one or more actions which are evaluated when a rule is executed.

Rules can be grouped in to rule sets where each set is defined by a unique name. A rule defines which set it belongs do by the use of the optional attribute ‘setname’. If no ‘setname’ attribute is defined then a rule is placed in the default rule set which is named by ‘#default’. (Note, that the rule set name ‘#default’ is reserved.)

Rules are grouped under a parent element of ‘<rulesets> … </rulesets>’, allowing multiple rules and rule sets to be defined by a single XML document.

Rules can also use the optional attribute of ‘priority’ which defines the relative importance of a rule with all the other rules in the same rule set. If no ‘priority’ attribute is used then a priority of zero (the lowset level) is the default.

In the ‘if’ element, value of the test attribute can be any valid XPath 2.0 expression. These expressions are evaluated against the XML (or JSON) ‘fact’ tree created by a call to the startFacts() API call. This method allows the application to define ‘stated’ facts to be used in the evaluation of rules.

When a rules ‘if’ element evaluates to true and it is selected for execution each action defined in its ‘then’ element is executed in order of appearance. There are several possible actions, each is defined in the next section.

The Rule ‘Then’ Element

  1. <call func=’XPath Expression’ />The ‘func’ parameter is required. The func expression should contain a call to a function that is either a built in XPath 2.0 function, or one defined by the calling application via the rule engine setExternalFunctions() method. For example, the XPath expression could be something like “setclick( ‘abc’, $g1 )”, which would be a function defined by the application with two parameters being passed to it. Note, that XPath 2.0 allows any number of parameters to be passed to an application defined function, the parameters to that function must be valid XPath types (e.g., strings, integers, XML node sequences). This action is a good way to call out to application defined functions to execute an external action when a specific condition is satisified (i.e., when the ‘if’ element condition is true).
  2. <set-variable name=’$g1′ select=’XPath expression’ />The ‘name’ and ‘select’ parameters are required. This action will first evaluate the XPath expression defined in the select parameter. The result of that expression will be assigned to the global variable designated in the ‘name’ parameter. The rule set that this action is defined in is the rule set used to lookup the global variable (e.g., “rule setname=”example1″ …”, then the ‘example1′ rule set would be used for this action).
  3. <set-focus setname=’over’ />The ‘setname’ parameter is required. This action changes the rule set that is currently being evaluated. The change will occur after the current rule is executed. Thus any actions that follow the set-focus action in the same rule will not be affected. If the rule set identified by the setname parameter does not exist then a fatal error will occur and the evaluate() API call will fail and rule execution will stop immediately.
  4. <set-fact name=’rover’ select=’XPath expression’ location=’/P6R;infer …’ />The ‘name’ parameter is required and ‘select’ and ‘location’ are optional. This action adds one or more inferred facts onto the inferred fact XML tree under “/P6R:infer”. The value of the ‘name’ parameter defines the XML node to be added so if “name=’rover’” the path to the new inferred fact will be “/P6R:infer/rover”.However, if the ‘location’ parameter is also defined, for example “location=’/P6R:infer/part1′”, then the new inferred fact will be added at the path “/P6R:infer/part1/rover”, assuming that “part1″ is already in the tree. The ‘select’ parameter will evaluate to a valid XPath type value and that will get added to the location in the inferred fact tree. Thus its the value of the select parameter that determines the value of the inferred fact.The action <set-fact name=’section1′ /> will simply add a new XML node at the path “/P6R:infer/section1″. This is useful for different rule sets to create separate subtrees in the inferred fact tree that is just for that rule set to use. Also the action”<set-fact name=’paragraph44′ location=’/P6R:infer/section1′ />”,would create the following node in the inferred fact tree “/P6R:infer/section1/paragraph44″. Once this path is created, any number of facts can be “hung off of” any part of that tree (i.e., at the ‘section1′ branch or at the ‘paragraph44′ branch).
  5. <clear-facts />This action results in the entire inferred fact tree (i.e., “/P6R:infer”) to be deleted. After this action new inferred facts can be added to a new inferred fact tree. The result of this action is identical to the result of the rule engine API call reset( P6RULE_CLEAR_FACTS ).
  6. <delete-fact location=’/P6R:infer …’ />This action removes all the inferred facts defined in the inferred fact tree located at the path defined by the ‘location’ parameter. After this action, new inferred facts can always be added back onto the tree at the same location that was deleted.7) <halt />There are no parameters to this action. This action stops rule engine evaluation immediately. If there are any additional actions that are defined after the ‘halt’ they are ignored. This action results in a successful return of the rule engine’s evaluate() API call (i.e., it does not represent an error condition).

API LANGUAGE BINDINGS

All P6R software expose C++ API calls. In addition, our rule engine has language bindings for Python, Perl, and Php. However, if the user wishes to extend the rule engine language and functionality via an extension to XPath, then those extensions must be written in a C++ plug-in module.

A SIMPLE EXAMPLE

This example comes directly out of a unit test for our rule engine:

<?xml version='1.0' encoding='ISO-8859-1'?>
<rulesets version='1.0'
   xmlns:P6R='http://www.p6r.com/ruleengine/extensions'>

<variable name='$g1' select='/menu/dessert/item'/>           

-- 'matches' is an XPath 2.0 function implementing Perl Regular Expressions
<rule name='Irvin' prioirty='5'>
   <if test="matches( $g1, '\d\d-\d\d-\d\d', 'i') />
   <then>
     <set-fact name='infer1' select='menu has blueberry tart' />
  </then>
</rule>

<rule name='stoprules'>
<if test='true()'/>
<then>
     <halt/>
</then>
</rule>
</rulesets>

And the stated facts that was input to the above rule set was:

<?xml version='1.0' encoding='UTF-8' ?>
<menu>
   <maincourse>
         <meat>Whole Chiken</meat>
         <soup>green pee</soup>
   </maincourse>
   <dessert id='5'>
       <icecream>chocolate</icecream>
       <item>blueberry 44-34-29 tart</item>
       <item>strawberry 3 layer cake</item>
       <item>vanilla cream pie</item>
   </dessert>
</menu>

Or if your stated facts are in JSON, this input will produce the same result:

 { "menu": {
   "maincourse": {
       "meat": "Whole Chiken",
        "soup": "green pee"
     },
   "dessert": {
        "id": 5,
        "icecream": "chocolate",
        "item": [
             "blueberry 44-34-29 tart",
             "strawberry 3 layer cake",
             "vanilla cream pie"
        ]  } } };

Here are final results when the rules above are evaluated:

<?xml version="1.0" encoding="utf-8" ?>
<P6R:infer xmlns:P6R="http://www.p6r.com/ruleengine">
  <infer1>menu has blueberry tart</infer1>
</P6R:infer>

SET OPERATIONS IN OUR RULE ENGINE

XPath has the following built-in set functions: intersect(), except() (that is set difference), and union(). However, these functions work at the XML node level not at the contents of the node. For example, two sets intersect if they both contain the exact same nodes no matter what is in those nodes (not for example all nodes with the value “green”). This set functionality is insufficient for a rule engine so we added P6R set extensions to our XPath 2.0 implementation.

We defined the set functionss: P6R2:intersect(), P6R2:except(), and P6R2:union(), where the ‘P6R2′ namespace is defined as http://www.p6r.com/XPath/extensions. These extension functions work on an XML node’s value. Below we give a detailed example of how this works. First the rule set that uses the new set functions.

<?xml version='1.0' encoding='ISO-8859-1'?>                  
<rulesets version='1.0'                                         
    xmlns:P6R='http://www.p6r.com/ruleengine/extensions'        
    xmlns:P6R2='http://www.p6r.com/XPath/extensions'>         
                                                           
<variable name='$set1' select='/colors/set1/color'/>          
<variable name='$set2' select='/colors/set2/color'/>          
<variable name='$set3' select='/colors/set3/color'/>          
                                                           
<rule name='setList'>                                         
    <if test='true()' />                                      
    <then>                                                    
       <set-fact name='set1' separator=',' select='$set1' />  
       <set-fact name='set2' separator=',' select='$set2' />  
       <set-fact name='set3' separator=',' select='$set3' />  
    </then>                                                   
</rule>                                                      
<rule name='setIntersect'>                                   
    <if test='true()' />                                      
    <then>                                                   
       <set-fact name='intersect12' separator=',' select='P6R2:intersect( $set1, $set2 )' />
       <set-fact name='intersect13' separator=',' select='P6R2:intersect( $set1, $set3 )' /> 
       <set-fact name='intersect23' separator=',' select='P6R2:intersect( $set2, $set3 )' /> 
    </then>                                                  
</rule>                                                      
<rule name='setDifference'>                                  
    <if test='true()' />                                      
    <then>                                                    
       <set-fact name='except12' separator=',' select='P6R2:except( $set1, $set2 )' /> 
       <set-fact name='except13' separator=',' select='P6R2:except( $set1, $set3 )' /> 
       <set-fact name='except23' separator=',' select='P6R2:except( $set2, $set3 )' /> 
    </then>                                                   
</rule>                                                       
<rule name='setUnion'>                                       
    <if test='true()' />                                      
    <then>                                                   
       <set-fact name='union12' separator=',' select='P6R2:union( $set1, $set2 )' />
       <set-fact name='union13' separator=',' select='P6R2:union( $set1, $set3 )' /> 
       <set-fact name='union23' separator=',' select='P6R2:union( $set2, $set3 )' /> 
    </then>                                                   
</rule>                                                       
<rule name='stoprules'>                                         
    <if test='true()'/>                                        
    <then>                                                      
       <halt/>                                                  
    </then>                                                     
 </rule>                                                         
</rulesets>

Next the stated facts we use to test our rules:

<?xml version='1.0' encoding='UTF-8' ?>
<colors>                                  
   <set1>                                    
      <color>green</color>                      
      <color>blue</color>                       
      <color>red</color>                        
      <color>orange</color>                     
      <color>yellow</color>                     
   </set1>                                   
   <set2>                                    
      <color>purple</color>                     
      <color>brown</color>                      
      <color>blue</color>                       
   </set2>                                   
   <set3>                                    
      <color>yellow</color>                     
   </set3>                                   
</colors>

Finally the results of running the above set of rules:

<P6R:infer>
   <set1>green,blue,red,orange,yellow</set1>
   <set2>purple,brown,blue</set2>
   <set3>yellow</set3>
   <intersect12>blue</intersect12>
   <intersect13>yellow</intersect13>
   <intersect23/>
   <except12>green,red,orange,yellow</except12>
   <except13>green,blue,red,orange</except13>
   <except23>purple,brown,blue</except23>
   <union12>green,blue,red,orange,yellow,purple,brown</union12>
   <union13>green,blue,red,orange,yellow</union13>
   <union23>purple,brown,blue,yellow</union23>
</P6R:infer>

"An XPath Enabled Rule Engine" was published on May 22nd, 2008 and is listed in Unique Product Features.

Follow comments via the RSS Feed | Leave a comment | Trackback URL

Comments on "An XPath Enabled Rule Engine": 6 Comments

  1. On June 5th, 2008 at 10:26 am Vlad Mangeym said,

    Is it (Rule Engine) something that you have available?
    Is it an open source or commercial product?

    Thanks.

  2. On June 5th, 2008 at 2:51 pm Mark Joseph said,

    Our Rule Engine is a commercial product.
    It is full featured and is “programmed” solely via an XML language and can take fact data either in XML or JSON.
    Please refer to our platform description at:
    http://www.p6r.com/software.html

  3. On September 17th, 2008 at 7:51 pm Compiled Rule Sets | Project 6 Research - Articles said,

    [...] a developer wishes to use the P6R rule engine (reference An XPath Enabled Rule Engine), he can simply create a single instance of the rule engine component, load it with a set of rules [...]

  4. On December 17th, 2008 at 10:31 am Rule Throttling | Project 6 Research - Articles said,

    [...] an advanced feature of P6R’s rule engine. An introduction to that rule engine can be found at An XPath Enabled Rule Engine. The current article addresses the issue “how effective are my [...]

  5. On May 18th, 2009 at 11:17 pm santhosha said,

    I want to use this An XPath Enabled Rule Engine

    How can i use that , from where i can download the same , is it free , or we have to pay.

  6. On May 20th, 2009 at 12:13 pm Mark Joseph said,

    Sign up for the next beta (coming later this summer) via email to [email protected]. When version 1.0 is released (soon after the next beta) the software is available for a fee per development seat. Anyone wanting more detailed info again please send inquires to [email protected]


Leave Your Comment