XPath for uniqueness check in XForm binding constraint - xpath

I'm trying to enforce a uniqueness constraint in an XForm binding. The relevant XML instance snippet is:
<PortAssignments>
<PortAssignment>
<PortReference IDREF="A">
<PortReference IDREF="1">
<Value>FOO</Value>
</PortAssignment>
<PortAssignment>
<PortReference IDREF="A">
<PortReference IDREF="1">
<Value>BAR</Value>
</PortAssignment>
<PortAssignment>
<PortReference IDREF="B">
<PortReference IDREF="2">
<Value>FOO2</Value>
</PortAssignment>
</PortAssignments>
The constraint is that each port can only be connected to one other unique port, so in this case, A must always be connected to 1, and B must always be connected to 2. But, because they don't have to be unique in total, I can't simply check that there are no repeats, so a binding like the following doesn't work.
<xf:bind ref="PortReference">
<xf:bind ref="#IDREF" required="true" type="xs:string" constraint="not(. = ../../preceding-sibling::*/PortReference/#IDREF) and not(. = ../../following-sibling::*/PortReferenence/#IDREF)"/>
</xf:bind>
The other thing I've tried is comparing the second PortReference in the binding to the second PortReference in the PortAssignment with the matching IDREF, but I can't figure out a way to include the context from the current node. An example of that type of lookup is:
<xf:bind ref="PortReference">
<xf:bind ref="#IDREF" required="true" type="xs:string" constraint="not(../../../PortAssignment[PortReference/#IDREF = {binding node's IDREF}][rest of comparison])"/>
</xf:bind>

The equivalent of {binding node's IDREF} in your XPath is the context() function.
You could also ensure uniqueness counting the number of ocurrences of a value (the context() function references the value)
<xf:bind nodeset="/PortAssignments/PortAssignment/PortReference[1]/#IDREF"
constraint="count( /PortAssignments/PortAssignment[PortReference[1]/#IDREF = context()] ) lt 2" />

After seeing Bill's answer talking about the context() function, I looked around and found this discussion and realized I could use the current() function for my {binding node's IDREF}. This lead to the following XPath, which worked for any number of matching PortReferences.
<xf:bind ref="PortAssignment/PortReference/#IDREF" constraint="not(boolean(../../preceding-sibling::*[PortReference[1]/#IDREF = current() and PortReference[2]/#IDREF != current()/../../PortReference[2]/#IDREF])) and not(boolean(../../following-sibling::*[PortReference[1]/#IDREF = current() and PortReference[2]/#IDREF != current()/../../PortReference[2]/#IDREF]))"/>

Related

Dynamic domain apply while other field's value changed - odoo

class procurement(models.Model)
_name="procurement"
procurement_line_ids = fields.One2many(comodel_name='procurement.line', inverse_name='procurement_id', string='Procurement Lines')
global_procurement = fields.Boolean("Global Procurement",default=True)
class procurement_line(models.Model)
_name="procurement.line"
procurement_id = fields.Many2one(comodel_name='procurement', string='Procurement')
warehouse_id = fields.Many2one(comodel_name='stock.warehouse', string='Warehouse')
class stock_warehouse(models.Model)
_name="stock.warehouse"
is_default_warehouse = fields.Boolean(string="Is Default Warehouse?",default=False)
If global_procurement is True then I want to load only default warehouses in procurement lines otherwise I want to load all warehouses. So how could I do this.
We may try with following way.
Pass value in context. For example:
<field name="warehouse_id"
context="{'global_procurement': parent.global_procurement}"/>
Check context value name_search() of stock.warehouse object. For example:
#api.model
def name_search(self, name, args=None, operator='ilike', limit=100):
if self._context and self._context.get('global_procurement'):
default_list = [1,2,3] # set your logic to search list of default warehouse
return self.browse(default_list).name_get()
return super(Warehouse, self).name_search(name=name, args=new_args, operator=operator, limit=limit)
I have written answer in air. I didn't try it.
I have done it by just defining domain in field (idea is taken from then #Odedra's answer).
<field name="warehouse_id" required="1" domain="[('field_name','=',parent.global_procurement)]" options="{'no_create': True, 'no_quick_create':True, 'no_create_edit':True}" />

How to refer to another instance in the iterate of the XForms action element?

I am using an XForms action along with iterate. The iterate selects a set (using XPath) of nodes and repeats the action for it.The problem is I have multiple conditions for selecting the node set.
There should not be a readOnly node.
Should not be part of the ignoreProperties list (this list is in another instance).
Code:
<xf:action ev:event="setValues" iterate="
instance('allProps')/props/prop[
not(readOnly) and
not(instance('ignoreProperties')/ignoredProperties/property[text() = name]
]
">
The first condition not(readOnly) works. But the second condition does not work. I feel there is some problem with the context of the XPath nodes.
How should I replace the second condition to achieve the result ?
The target XML is a simple ignoredProperties document:
<ignoredProperties>
<property>c_name</property>
<property>c_tel_no</property>
</ignoredProperties>
This should work:
<xf:action ev:event="setValues" iterate="
instance('allProps')/props/prop[
not(readOnly) and
not(name = instance('ignoreProperties')/ignoredProperties/property)
]
">
The = operator works against multiple nodes, returning all the ones that match. With not() you can express that you don't want a match.
Explicitly selecting .../property/text() will not be necessary.
There seems to be something wrong with your calls to instance(). If you have:
<xf:instance id="ignoredProperties">
<ignoredProperties>
<property>c_name</property>
<property>c_tel_no</property>
</ignoredProperties>
</xf:instance>
Then instance('ignoredProperties') returns the <ignoredProperties> element. So you should write:
<xf:action ev:event="setValues" iterate="
instance('allProps')/prop[
not(readOnly) and
not(instance('ignoreProperties')/property[text() = name])
]
">
This also assumes your allProps instance has a <props> root element.
Further, the second condition appears wrong, as already shown in another answer. Write instead:
not(name = instance('ignoreProperties')/property)
In XPath 2, you could clarify that your not() are testing on node existence by using empty() instead:
<xf:action ev:event="setValues" iterate="
instance('allProps')/prop[
empty(readOnly) and
not(name = instance('ignoreProperties')/property)
]
">

XPath: Default to "Master" node, select current node if specified (in third node)

My problem:
I need to select the value in the "Master_Node" only if the "Sub_Node_Checker" is set to "false".
If "Sub_Node_Checker" is set to "true" then the value must be set to "Sub_Node".
Current node is "Sub_Node"
I am using InfoPath 2010.
Here is my sample XML:
<my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xml:lang="en-us">
<my:Master_Node>123456</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
</my:myFields>
Refer to the following forum thread to download my XSN template.
Here is the XPath that I have been attempting to use, to no avail (line breaks added for legibility):
//my:Master_Node[../my:Sub_Node_Checker = "false"]
|
../my:Sub_Node[../my:Sub_Node_Checker = "true"]
This does not seem to return anything whatsoever, and I'm not sure why.
The following question accompanies "Sub_Node_Checker" in my XML form: "Does the sub node differ from the master node?"
If the user selects "Yes" (true) then the Sub_Node field should be set to its own value.
If the user selects "No" (false) then the Sub_Node field should default to the Master_Node.
Edit & Additional XML
My Repeater section repeats (as per the name) and seems to cause additional chaos with the XPath selectors.
<my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xml:lang="en-us">
<my:Master_Node>123123</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
</my:myFields>
A much cleaner solution would be to move the predicate to the <my:myFields/> element.
/my:myFields[my:Repeater/my:Sub_Node_Checker = "false"]/my:Master_Node
If you insist on your approach, you're missing a / to jump over the <my:Repeater/> element or reference that:
//my:Master_Node[..//my:Sub_Node_Checker = "false"]
//my:Master_Node[../my:Repeater/my:Sub_Node_Checker = "false"]
Relating to your xpath question you may try something like this.
(self::*[../my:Sub_Node_Checker = "true"]
|
//my:Master_Node)[last()]
Which should work if Master_Node is always before Sub_Node (in document order).
Ok, I got this one worked out a bit differently than my original approach. The following article on MSDN has an example of using the substring() function to return different values based on outside conditions. Also have to thank Hilary Stoupa at InfoPath Dev for helping me come to the solution.
I will also mention that I could not use the "current" node and had to create a third node which housed my XPath expression and evaluated the conditions to return the appropriate value.
Here is the source XML roughly as InfoPath might interpret it (note the "location" of the xpath expression in the 3rd repeater group - this is how InfoPath evaluates default values):
<my:myFields xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003" xml:lang="en-us">
<my:Master_Node>123456</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>true</my:Sub_Node_Checker>
<my:Sub_Node>9870</my:Sub_Node>
<my:Sub_Node_Stored>9870</my:Sub_Node_Stored>
</my:Repeater>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
<my:Sub_Node_Stored>123456</my:Sub_Node_Stored>
</my:Repeater><
my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
<my:Sub_Node_Stored>concat(substring(../../my:Master_Node, 1, (../my:Sub_Node_Checker != "true") * string-length(../../my:Master_Node)), substring(../my:Sub_Node, 1, (../my:Sub_Node_Checker != "false") * string-length(../my:Sub_Node)))</my:Sub_Node_Stored>
</my:Repeater>
</my:myFields>
Note that the following XPath expression was instrumental in causing the appropriate node to be selected:
concat(substring(../../my:Master_Node, 1, (../my:Sub_Node_Checker != "true") * string-length(../../my:Master_Node)), substring(../my:Sub_Node, 1, (../my:Sub_Node_Checker != "false") * string-length(../my:Sub_Node)))
The substring function returns the number of characters from the target string as specified by the user. When a boolean value is evaluated inside the substring function (at the location given for number of characters to return) it returns either a "1" or a "0".
When multiplied by the length of the target string this boolean check causes any conditions that would exempt a particular target node from selection to return a length of "0" characters from that node. (0*X=0) This effectively allows for different default values within the node without the use of the | operator.

Inserting a child node when list is empty (XForms)

My problem is the following :
I usually have those data:
<structures>
<structure id="10">
<code>XXX</code>
</structure>
</structures>
so the table I display (single columns : code) is ok.
But in some cases, the data is the result a a query with no content, so the data is:
<structures/>
resulting in my table not displaying + error.
I am trying to insert, in the case of an empty instance, a single node so that the data would look like:
<structures>
<structure id="0"/>
</structures>
I am trying something like that :
<xforms:action ev:event="xforms-submit-done">
<xforms:insert if="0 = count(instance('{./instance-name}')/root/node())" context="instance('{./instance-name}')/root/node()" origin="xforms:element('structure', '')" />
</xforms:action>
but no node inserted when I look at the data in the inspector in the page.
Any obvious thing I am doing wrong?
There seems to be erros in your XPath if and context expressions:
if="0 = count(instance('{./instance-name}')/root/node())"
context="instance('{./instance-name}')/root/node()"
You are a using curly brackets { and }, I assume to have the behavior of attribute value templates (AVTs). But the if and context expressions are already XPath expressions, so you cannot use AVTs in them. Try instead:
if="0 = count(instance(instance-name)/root/node())"
context="instance(instance-name)/root/node()"
Also, the instance-name path is relative to something which might not be clear when reading or writing the expression. I would suggest using an absolute path for example instance('foo')/instance-name to make things clearer.
You don't provide the structure of the other instances, so I can tell for sure, but you'll expression above suppose that they have the form:
<xf:instance id="foo">
<some-root-element>
<root>
<structure/>
</root>
<some-root-element>
</xf:instance>
I don't know if that's what you intend.
Finally, you could replace count(something) = 0, with empty(something).

XPath: Selecting a node based on another nodes value

I am trying to use a single XPath expression to select a node that has a child node which matches another node in the document.
A match would mean that ALL attributes of the node are the same. So if a node was being compared with several attributes doing individual attribute comparisons would be unmaintainable.
As an example given the following:
<Network>
<Machines>
<Machine Name = "MyMachine">
<Services>
<ServiceDetails Description="MyService" Executable="c:\Myservice.exe" DisplayName="My Service" Version="5"/>
</Services>
</Machine>
...
</Machines>
<Services>
<Service Name = "Service1">
<ServiceDetails Description="MyService" Executable="c:\Myservice.exe" DisplayName="My Service" Version="5"/>
</Service>
...
</Services>
</Network>
I want to get the service node from Services based on the ServiceDetails listed under MyMachine.
I thought it would look something like:
//Services/Service[ServiceDetails = //Machines/Machine[#Name='MyMachine']/ServiceDetails]
but it doesn't seem to work. I suspect the '=' operator isn't handling the node comparison correctly. I think there are some XPath 2.0 Methods that might work but I am using .NET 4.0 (System.XML namespace) I do not know if I can use them. If XPath 2.0 methods would help here I would really appreciate an explanation on how to use them in .Net 4.0.
Thanks
Use:
/*/Services/Service
[ServiceDetails/#Description
=
/*/Machines/Machine[#Name = "MyMachine"]
/Services/ServiceDetails/#Description
]
Try this will validate all attribute values are equal in both the elements then it is true:
/Network[(descendant::ServiceDetails/#Description = /Network//Machine[#Name = "MyMachine"]/Services/ServiceDetails/#Description) and (descendant::ServiceDetails/#Executable = /Network//Machine[#Name = "MyMachine"]/Services/ServiceDetails/#Executable) and (descendant::ServiceDetails/#DisplayName = /Network//Machine[#Name = "MyMachine"]/Services/ServiceDetails/#DisplayName) and (descendant::ServiceDetails/#Version = /Network//Machine[#Name = "MyMachine"]/Services/ServiceDetails/#Version)]

Resources