openrosa-xpath-evaluator
Version:
Wrapper for browsers' XPath evaluator with added support for OpenRosa extensions.
368 lines (348 loc) • 11.6 kB
JavaScript
const {
assertBoolean,
assertNumberValue,
assertStringValue,
assertTrue,
initDoc,
} = require('./helpers');
describe('predicates with function calls', () => {
it('should handle deep example 1', () => {
// given
initDoc(`
<model>
<instance>
<data>
<PROCEDURE>
<PROC_GRID>
<PROC>6</PROC>
</PROC_GRID>
</PROCEDURE>
</data>
</instance>
</model>
`);
// expect
assertBoolean(
' /model/instance[1]/data/PROCEDURE/PROC_GRID[position() = 1]/PROC = 6 or /model/instance[1]/data/PROCEDURE/PROC_GRID[position() = 2]/PROC = 6',
true
);
assertStringValue(
' /model/instance[1]/data/PROCEDURE/PROC_GRID[position() = 1]/PROC = 6 or /model/instance[1]/data/PROCEDURE/PROC_GRID[position() = 2]/PROC = 6',
'true'
);
});
it('should handle deep example 2', () => {
// given
initDoc(`
<model>
<instance>
<new_cascading_selections_inside_repeats id="cascading_select_inside_repeats">
<group1>
<country/>
<city/>
<neighborhood/>
</group1>
<meta>
<instanceID/>
</meta>
</new_cascading_selections_inside_repeats>
</instance>
<instance id="cities">
<root>
<item>
<itextId>static_instance-cities-0</itextId>
<country>nl</country>
<name>ams</name>
</item>
<item>
<itextId>static_instance-cities-1</itextId>
<country>usa</country>
<name>den</name>
</item>
<item>
<itextId>static_instance-cities-2</itextId>
<country>usa</country>
<name>nyc</name>
</item>
</root>
</instance>
</model>
`);
// expect
assertBoolean(
'/model/instance[@id="cities"]/root/item[country=/model/instance[1]/new_cascading_selections/group4/country4 and name=/model/instance[1]/new_cascading_selections/group4/city4]',
false
);
assertStringValue(
'/model/instance[@id="cities"]/root/item[country=/model/instance[1]/new_cascading_selections/group4/country4 and name=/model/instance[1]/new_cascading_selections/group4/city4]',
''
);
});
describe('little predicates', () => {
[
['//*[@id="3"] and /data/*[@id="1"]', false],
['/data/*[@id="3"] and /data/*[@id="1"]', false],
['/data/c[@id="3"] and /data/a[@id="1"]', false],
['/data/*[@id="1"] and //*[@id="3"]', false],
['/data/*[@id="3"] or /data/*[@id="2"]', true],
['/data/*[@id="1"] and //*[@id="2"]', true],
['/data/*[@id="3"] or /data/*[@id="4"]', false],
].forEach(([expr, expected]) => {
it(`should evaluate ${expr} as ${expected}`, () => {
initDoc(`
<data>
<a id="1">aa</a>
<b id="2">bb</b>
</data>
`);
assertBoolean(expr, expected);
});
});
});
describe('fiendishly complicated examples #2', () => {
const namespaceResolver = {
lookupNamespaceURI: (prefix) => {
const namespaces = {
OpenClinica: 'http://openclinica.com/odm',
enk: 'http://enketo.org/xforms',
};
return namespaces[prefix];
},
};
[
[`/*[1]/item/a/number`, 'siete'],
[`/data/item/a/number`, 'siete'],
[`/data/item/a/number/@OpenClinica:this`, 'seven'],
[`/data/item/a/number[@OpenClinica:this="three"]`, 'tres'],
[
`normalize-space(/data/item/a[../number[@OpenClinica:this="three"]])`,
'cc dd ee',
],
[
`/data/item/a[../number[@OpenClinica:this="three"]]/name[@enk:that='something']/last[@id='d']/@Value`,
'ddd',
],
[
`concat( selected( /data/item/a[../number[@OpenClinica:this='three']]/name[@enk:that="something"]/last/@Value, 'ccc' ), 'ing', '-', sin( pi() div 2))`,
'trueing-1',
],
].forEach(([expr, expected]) => {
it(`should evaluate ${expr} as ${expected}`, () => {
initDoc(
`
<data xmlns:OpenClinica="http://openclinica.com/odm" xmlns:enk="http://enketo.org/xforms">
<item>
<a>
<number OpenClinica:this="seven">siete</number>
<name>
<last>aa</last>
</name>
</a>
</item>
<item>
<a>
<number OpenClinica:this="three">tres</number>
<number OpenClinica:this="four"/>
<name enk:that="something else">
<last>bb</last>
</name>
</a>
</item>
<item>
<number OpenClinica:this="three"/>
<a>
<name enk:that="something">
<last id="c" Value="ccc">cc</last>
<last id="d" Value="ddd">dd</last>
<last id="e" Value="eee">ee</last>
</name>
</a>
</item>
<meta>
<instanceID>a</instanceID>
</meta>
</data>
`,
namespaceResolver
);
assertStringValue(expr, expected);
});
});
});
describe('nested predicates', () => {
[
['/data/item/number/@this', 'seven'],
['/data/item/number[@this]', 'siete'],
['/data/item/number[@this="four"]', 'cuatro'],
['/data/item/name[../number[@this="four"]]/last', 'bb'],
['/data/item/name[../number[./@this="four"]]/last', 'bb'],
[
'/data/item/name[../number[string-length(./@this) = 1]]/last',
'cc',
],
[
'/data/item/name[../number[string-length(./@this) < pi()]]/last',
'cc',
],
].forEach(([expr, expected]) => {
it(`should evaluate ${expr} as ${expected}`, () => {
initDoc(`
<data>
<item>
<number>entruchón</number>
<name>decoy</name>
</item>
<item>
<number this="seven">siete</number>
<name>
<last>aa</last>
</name>
</item>
<item>
<number this="three">tres</number>
<number this="four">cuatro</number>
<name>
<last>bb</last>
</name>
</item>
<item>
<number this="o">la letra o</number>
<name>
<last>cc</last>
</name>
</item>
</data>
`);
assertStringValue(expr, expected);
});
});
});
describe('with native functions', () => {
[
['count(/data/item[true()]) = 2', assertTrue],
['count(/data/b[round(2.44) = 2])', assertNumberValue, 2],
['/data/item[true()]/number', assertNumberValue, 4],
['/data/item[2]/number', assertNumberValue, 6],
['/data/item[true()]/number + 1', assertNumberValue, 5],
['/data/item[true()]/number + 1', assertStringValue, '5'],
[
'/data/item[string-length("a") = 1]/number + 2',
assertNumberValue,
6,
],
[
'/data/item[string-length("]") = 1]/number + 2',
assertNumberValue,
6,
],
[
`/data/item[string-length(']') = 1]/number + 2`,
assertNumberValue,
6,
],
['/data/item[2]/number + 3', assertNumberValue, 9],
[
'/data/item[string-length(./number)=1]/number + 3',
assertNumberValue,
7,
],
[
'/data/item[string-length(./number) = 1]/number + 3',
assertNumberValue,
7,
],
[
'/data/item[(./number div 3.14) > 1.9]/number',
assertNumberValue,
6,
],
].forEach(([expr, assertion, ...extraArgs]) => {
it(`should evaluate ${expr} as expected`, () => {
initDoc(`
<data>
<item>
<number>4</number>
</item>
<item>
<number>6</number>
</item>
<b/>
<b/>
</data>
`);
assertion(expr, ...extraArgs);
});
});
});
describe('with extended functions', () => {
[
['pi()', assertNumberValue, 3.141592653589793],
['/data/item[1]/number', assertNumberValue, 4],
['/data/item[true()]/number', assertNumberValue, 4],
['/data/item[pi() > 3]/number', assertNumberValue, 4],
['/data/item[tan(./number) > 1]/number', assertNumberValue, 4],
['/data/item[tan(./number) <= 1]/number', assertNumberValue, 6],
[
'/data/item[(./number div pi()) > 1.9]/number',
assertNumberValue,
6,
],
[
'/data/item[(./number div pi()) <= 1.9]/number',
assertNumberValue,
4,
],
].forEach(([expr, assertion, ...extraArgs]) => {
it(`should evaluate ${expr} as expected`, () => {
initDoc(`
<data>
<item>
<number>4</number>
</item>
<item>
<number>6</number>
</item>
</data>
`);
assertion(expr, ...extraArgs);
});
});
});
// I put this one separate as it has a different 'too many args' error, and there may be multiple causes for failure
it('with the #selected function', () => {
initDoc(`
<data>
<a>a</a>
<a>b</a>
<a>c</a>
</data>
`);
// assertTrue('selected("a b", "a")');
assertNumberValue('count(/data/a[selected("a b", "a")])', 3);
});
it('should deal with a fiendishly complicated example', () => {
initDoc(`
<data>
<item>
<number>2</number>
<name>
<first>1</first>
<last>bb</last>
</name>
<result>incorrect</result>
</item>
<item>
<number>3</number>
<name>
<first>1</first>
<last>b</last>
</name>
<result>correct</result>
</item>
</data>`);
assertStringValue(
'/data/item/number[../name/first = string-length(../name/last)]/../result',
'correct'
);
});
});