Skip to content

Commit

Permalink
Uplift OpenrosaXpathEvaluatorBinding
Browse files Browse the repository at this point in the history
  • Loading branch information
jkuester committed Aug 27, 2021
1 parent 95614cd commit f735118
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 25 deletions.
4 changes: 2 additions & 2 deletions webapp/custom-webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
module.exports = {
resolve: {
alias: {
// TODO Do we also need mapping for enketo/translator, enketo/dialog? Probably only if we have custom impls.
// TODO Do we also need mapping for enketo/dialog? Probably only if we have custom impls.
'enketo/config': 'src/js/enketo/config.js',
'enketo/widgets': 'src/js/enketo/widgets',
'enketo/xpath-evaluator-binding': 'src/js/enketo/OpenrosaXpathEvaluatorBinding',
'enketo/file-manager': 'src/js/enketo/file-manager',
'extended-xpath': 'node_modules/openrosa-xpath-evaluator/src/extended-xpath',
'openrosa-extensions': 'node_modules/openrosa-xpath-evaluator/src/openrosa-extensions',
// translator for enketo's internal i18n
'translator': 'src/js/enketo/translator',
'enketo/translator': 'src/js/enketo/translator',
// enketo currently duplicates bootstrap's dropdown code. working to resolve this upstream
// https://github.com/enketo/enketo-core/issues/454
'../../js/dropdown.jquery': 'node_modules/bootstrap/js/dropdown',
Expand Down
68 changes: 46 additions & 22 deletions webapp/src/js/enketo/OpenrosaXpathEvaluatorBinding.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
const ExtendedXpathEvaluator = require('extended-xpath');
const ExtendedXPathEvaluator = require('extended-xpath');
const openrosaExtensions = require('openrosa-extensions');
const medicExtensions = require('./medic-xpath-extensions');
const translator = require('./translator');
const XPR = require('node_modules/openrosa-xpath-evaluator/src/xpr');
const {asString, asBoolean, asNumber} = require('node_modules/openrosa-xpath-evaluator/src/utils/xpath-cast');

module.exports = function() {
// re-implement XPathJS ourselves!
const evaluator = new XPathEvaluator();
this.xml.jsCreateExpression = function() {
return evaluator.createExpression.apply( evaluator, arguments );
};
this.xml.jsCreateNSResolver = function() {
return evaluator.createNSResolver.apply( evaluator, arguments );
};
this.xml.jsEvaluate = function(e, contextPath, namespaceResolver, resultType, result) {
const extensions = openrosaExtensions(translator.t);
extensions.func = Object.assign(extensions.func, medicExtensions.func);
extensions.process = Object.assign(extensions.process, medicExtensions.process);
const evaluator = new ExtendedXpathEvaluator(contextPath.ownerDocument, extensions);
return evaluator.evaluate(e, contextPath, namespaceResolver, resultType, result);
const cast = {
string: asString,
boolean: asBoolean,
number: asNumber,
};

module.exports = function( ) {
const ore = openrosaExtensions();
ore.func = Object.assign(ore.func, medicExtensions.func);
ore.process = Object.assign(ore.process, medicExtensions.process);
const evaluator = new ExtendedXPathEvaluator(new XPathEvaluator(), ore);

evaluator.customXPathFunction = {
add: (name, { fn, args:_args, ret }) => {
if(Object.prototype.hasOwnProperty.call(ore.func, name)) {
throw new Error(`There is already a function with the name: '${name}'`);
}

const argTypes = _args.map(a => a.t);
const allowedArgTypes = Object.keys(cast);
const unsupportedArgTypes = argTypes.filter(t => !allowedArgTypes.includes(t));
if(unsupportedArgTypes.length) {
const quoted = unsupportedArgTypes.map(t => `'${t}'`);
throw new Error(`Unsupported arg type(s): ${quoted.join(',')}`);
}

const allowedRetTypes = Object.keys(XPR);
if(!allowedRetTypes.includes(ret)) {
throw new Error(`Unsupported return type: '${ret}'`);
}

ore.func[name] = (...args) => {
if(args.length !== argTypes.length) {
throw new Error(`Function "${name}" expected ${argTypes.length} arg(s), but got ${args.length}`);
}

const convertedArgs = argTypes.map((type, idx) => cast[type](args[idx]));
return XPR[ret](fn(...convertedArgs));
};
},
};
window.JsXPathException =
window.JsXPathExpression =
window.JsXPathNSResolver =
window.JsXPathResult =
window.JsXPathNamespace = true;

this.xml.jsEvaluate = evaluator.evaluate;
return evaluator;
};
2 changes: 1 addition & 1 deletion webapp/src/js/enketo/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
require( 'enketo-core/src/widget/geo/geopicker' ),
require( 'enketo-core/src/widget/table/tablewidget' ),
require( 'enketo-core/src/widget/radio/radiopicker' ),
require( 'enketo-core/src/widget/date/datepicker-extended' ),
require( 'enketo-core/src/widget/time/timepicker-extended' ),
require( 'enketo-core/src/widget/columns/columns' ),
require( 'enketo-core/src/widget/file/filepicker' ),
require( './widgets/datepicker-widget' ),
require( './widgets/horizontal-choices' ),
Expand Down

0 comments on commit f735118

Please sign in to comment.