Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,67 @@ fn.showConfirmBulkActionModal = function (confirmMessage, confirmActionFn) {
$modal.modal('show');
};

fn.showPathCorrectionModal = function (corrections, formXML, options) {
var _this = this,
$modal;

function toAbsolutePath(path) {
return path.replace(/^#form/, '/data');
}

function applyCorrections() {
var fixedXML = formXML;
_.each(corrections, function (c) {
fixedXML = fixedXML.split(toAbsolutePath(c.wrongPath))
.join(toAbsolutePath(c.correctPath));
});
_this.closeModal();
_this.data.core.parseWarnings = [];
_this.loadXML(fixedXML, options);
delete _this.data.core.parseWarnings;
}

function buildCorrectionList() {
var $list = $('<ul></ul>');
_.each(corrections, function (c) {
var wrong = toAbsolutePath(c.wrongPath),
correct = toAbsolutePath(c.correctPath),
leafName = wrong.split('/').pop();
$list.append(
$('<li></li>').append(
document.createTextNode(gettext("Path ")),
$('<code></code>').text(wrong),
document.createTextNode(util.format(
gettext(" was not found. A data node named " +
"\"{name}\" exists at "),
{name: leafName})),
$('<code></code>').text(correct),
document.createTextNode(".")
)
);
});
return $list;
}

$modal = this.generateNewModal(gettext("Broken Paths Detected"), [
{
title: gettext('Fix and Reload'),
cssClasses: "btn-primary",
action: applyCorrections
}
], gettext("Ignore"));

var $body = $modal.find('.modal-body');
$body.append(
$('<p></p>').text(
gettext("The following paths in this form's XML do not match " +
"any data nodes. Saving without fixing may result in data loss.")
),
buildCorrectionList()
);
$modal.modal('show');
};

fn._init_extra_tools = function () {
var _this = this,
menuItems = this.getToolsMenuItems();
Expand Down Expand Up @@ -1411,6 +1472,10 @@ fn.loadXML = function (formXML, options) {
_this._populateTree(selectedHashtag);
}

if (form.pathCorrections && form.pathCorrections.length) {
_this.showPathCorrectionModal(form.pathCorrections, formXML, options);
}

form.on('question-type-change', function (e) {
_this.jstree("set_type", e.mug.ufid, e.qType);

Expand Down
33 changes: 33 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ function parseControlElement(form, $cEl, parentMug) {
path = getPathFromControlElement($cEl, form, parentMug);
}
mug = form.getMugByPath(path);
if (!mug && path) {
var match = findDataNodeByLeafName(form, path);
if (match) {
if (!form.pathCorrections) {
form.pathCorrections = [];
}
form.pathCorrections.push({
wrongPath: path,
correctPath: match.absolutePath
});
}
}
}
mug = adapt(mug, form);
var node = form.tree.getNodeFromMug(mug);
Expand Down Expand Up @@ -531,6 +543,27 @@ function parseBoolAttributeValue (attrString, undefined) {
}
}

/**
* Search for an unclaimed data node whose nodeID matches the last
* segment of the given path. Used to recover from hand-edited XML
* where a control element's ref/nodeset has the wrong path.
*
* @param form - the form object
* @param path - the broken path (e.g., "/data/members_repeat")
* @returns - the matching DataBindOnly mug, or null
*/
function findDataNodeByLeafName(form, path) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if multiple data nodes are matched? It doesn't know which is the correct one. As implemented, it chooses the last match.
Imagine a scenario where two different mugs are missing data nodes, and both have the same node leaf name. I think both would be assigned the same data node, which would be incorrect.

var leafName = path.replace(/^#form\//, '').split('/').pop();
var match = null;
_.each(form.mugMap, function (mug) {
if (mug.p.nodeID === leafName &&
mug.__className === 'DataBindOnly') {
match = mug;
}
});
return match;
}

/**
* Figures out what the xpath is of a control element
* by looking at the ref or nodeset attributes.
Expand Down