/** * Make a SOM expression more human readable */ function terseSOM(sSOM) { return sSOM.replace(/\[0\]/g, "").replace(/^xfa\.template\./, ""); } // A collection of the messages we generate // Each message is an object with three properties: CODE, SOM and TEXT var messgs = []; // A map of all tabstops indexed by their SOM expression var tabstops = {}; // A map of all container subforms indexed by their SOM expression var containrs = {}; /** * A message object -- properties separated so they're suitable for formatting later. */ function messg(sCode, vNode, sText) { this.CODE = sCode; this.SOM = terseSOM(vNode.somExpression); this.TEXT = sText; } /** * a tabstop is a JavaScript object that tracks the properties of an individual * object in the tabbing sequence. We create these objects during our scan through * the hierachy, and then we post-process them to discover places where tab order and * read order will diverge. */ function tabstop(vNode) { this.bVisited = false; this.node = vNode; this.next = null; this.ref = ""; var vTraverse = null; if (vNode.isPropertySpecified("traversal")) { var i; for (i = 0; i < vNode.traversal.nodes.length; i++) { if (vNode.traversal.nodes.item(i).operation === "next") { vTraverse = vNode.traversal.nodes.item(i); break; } } if (vTraverse !== null) { if (vTraverse.ref !== null) { var vRef = vNode.resolveNode(vTraverse.ref); if (vRef === null) { messgs.push(new messg("Warning", vNode, "Warning: Invalid next node reference: " + vTraverse.ref)); } this.next = vRef; this.ref = vTraverse.ref; } } } } /** * Verify that the traverse is also in the container object itself. */ function containerNeedsUpdate(vSubform, vFieldNode) { var needsUpdate = false; try { // Get the traverse ref from the field element. var vRef = null; var i; for (i = 0; i < vFieldNode.traversal.nodes.length; i++) { if (vFieldNode.traversal.nodes.item(i).operation === "next") { vRef = vFieldNode.traversal.nodes.item(i).ref; break; } } if (vRef !== null) { needsUpdate = true; if (vSubform.isPropertySpecified("traversal")) { // Check that field's traverse is in the traversal of the container. for (i = 0; i < vSubform.traversal.nodes.length; i++) { if (vSubform.traversal.nodes.item(i).operation === "next") { if (vRef === vSubform.traversal.nodes.item(i).ref) { needsUpdate = false; } break; } } } } } catch (e) { var sErr = "Script error: " + e.message + "\r\nline:" + e.line + "\r\n"; designer.alert(sErr); designer.println(sErr); } return needsUpdate; } function copyTraverseToContainer(vSubform, vFieldNode) { try { var vTraverseCopy = null; for (var i = 0; i < vFieldNode.traversal.nodes.length; i++) { if (vFieldNode.traversal.nodes.item(i).operation === "next") { vTraverseCopy = vFieldNode.traversal.nodes.item(i).clone(1); break; } } if (vTraverseCopy !== null) { if (!vSubform.isPropertySpecified("traversal")) { // Subform doesn't have a traversal element, so create one with copy of field's traverse. var vTraversal = xfa.template.createNode("traversal"); vTraversal.nodes.append(vTraverseCopy); vSubform.nodes.append(vTraversal); } else { // Add copy of field's traverse to the traversal in the subform vSubform.traversal.nodes.append(vTraverseCopy); } messgs.push(new messg("Modified", vSubform, "Added copy of 'traverse' element referencing next: '" + vTraverseCopy.ref + "'.\r\n")); } } catch (e) { var sErr = "Script error: " + e.message + "\r\nline:" + e.line + "\r\n"; designer.println(sErr); } } /** * a containr is a JavaScript object that tracks the contents of subforms to determine * how many exits there are in the current tab order. * Create these objects during the first pass through the hierarchy and then post process * them to discover the read-order errors. */ function containr(vNode) { this.node = vNode; this.explicitExitCount = 0; this.explExitTarget = null; this.explExitOrigin = null; } /** * Traverse the template hierarchy to create the objects needed to analyse the * tab order. */ function buildTree(vContainer) { // for objects that can contain fields, create our data structures for later analysis // and then drill into the child objects var i; var vChild; if (vContainer.className === "subform" || vContainer.className === "pageArea") { containrs[vContainer.somExpression] = new containr(vContainer); for (i = 0; i < vContainer.nodes.length; i++) { vChild = vContainer.nodes.item(i); if (vChild.isContainer) { buildTree(vChild); } } tabstops[vContainer.somExpression] = new tabstop(vContainer); } else if (vContainer.className === "field" || vContainer.className === "draw" || vContainer.className === "exclGroup") { // Deal with the objects that will comprise the tabstops in a tabbing sequence if (vContainer.className === "draw") { var vValueType = vContainer.value.oneOfChild.className; // Avoid content types that are not part of tab order if (vValueType === "arc" || vValueType === "line" || vValueType === "rectangle") { return; } } tabstops[vContainer.somExpression] = new tabstop(vContainer); } else { // subformSet or area or some other container // otherwise just continue recursing into the nested content. for (i = 0; i < vContainer.nodes.length; i++) { vChild = vContainer.nodes.item(i); if (vChild.isContainer) { buildTree(vChild); } } } } /** * Need to figure out the relation between two subforms * Knowing whether one is an ancestor of the other is important for understanding * where the tabbing sequence is going. e.g. tabbing into a child subform is not * an exit while tabbing to a sibling or parent is an exit. */ function isAncestor(vParent, vAncestor) { while (vParent.className !== "template") { if (vParent === vAncestor) { return true; } vParent = vParent.parent; } return false; } /** * */ function trackExplicitExits(vTabstop) { if (vTabstop.next !== null) { var vNextParent = vTabstop.next.parent; var vThisParent = vTabstop.node.parent; // if we've stayed within the same parent, we didn't exit if (vNextParent !== vThisParent) { // if the next parent is not a child, then we exited if (!isAncestor(vNextParent, vThisParent) && (vTabstop.ref !== "")) { // Find the highest container parent that we're exiting. var vHighestParent = vThisParent; var vParent = vThisParent; // Walk up the parent chain and see which is the highest parent that doesn't contain the referenced target. while (vParent.resolveNode("$." + vTabstop.ref) === null) { vHighestParent = vParent; // The highest parent that fails is the exiting parent. vParent = vParent.parent; } // Record Explicit Exit information in the highest parent container. containrs[vHighestParent.somExpression].explicitExitCount++; containrs[vHighestParent.somExpression].explExitTarget = vTabstop.next; containrs[vHighestParent.somExpression].explExitOrigin = vTabstop.node; } } } } function removeContainerNextTraverses() { /* * Remove 'next' traverses from all subforms. */ designer.println("Updating subforms...\r\n"); for (sSOM in containrs) { if (containrs.hasOwnProperty(sSOM)) { var vContainr = containrs[sSOM]; designer.println(" checking: " + vContainr.node.name + "\r\n"); var vSubform = vContainr.node; if (vSubform.isPropertySpecified("traversal")) { for (var i = 0; i < vSubform.traversal.nodes.length; i++) { if (vSubform.traversal.nodes.item(i).operation === "next") { designer.println("\tremoving traverse from subform: " + vSubform.name + " traverse:" + vSubform.traversal.nodes.item(i).name + " ref:" + vSubform.traversal.nodes.item(i).ref + "\r\n"); vSubform.traversal.nodes.remove(vSubform.traversal.nodes.item(i)); if (vSubform.traversal.nodes.length == 0) { /* If this was the only traverse, then delete the traversal. */ vSubform.nodes.remove(vSubform.traversal); } } } } } } } var bUpdateNeeded = false; try { var sVersion = designer.version; } catch (e) { bUpdateNeeded = true; }; if (bUpdateNeeded) { try { buildTree(xfa.template["#subform"]); /* * Scan the list of containers to remove existing 'next' traversals before * re-adding them. */ removeContainerNextTraverses(); var s = "done.\r\n"; /* * Iterate through all the tabstops, tracking all the explicit * exits, and tracking all the nodes that are explicitly * visited. */ for (var sSOM in tabstops) { if (tabstops.hasOwnProperty(sSOM)) { trackExplicitExits(tabstops[sSOM]); } } /* * Iterate through the containers, finding those that have 1 exit */ for (sSOM in containrs) { if (containrs.hasOwnProperty(sSOM)) { var vContainr = containrs[sSOM]; designer.println("checking container: " + vContainr.node.name + " explicitExitCount: " + vContainr.explicitExitCount + "\r\n"); if (vContainr.explicitExitCount === 1) { var vExitingTabstop = tabstops[vContainr.explExitOrigin.somExpression]; // Check if container needs the update just in case change was already made. if (containerNeedsUpdate(vContainr.node, vExitingTabstop.node)) { designer.println(" updating: " + vContainr.node.name + " tabstop: " + vExitingTabstop.node.name + "\r\n"); // Update the container to include copy of traverse from the tabstop node. copyTraverseToContainer(vContainr.node, vExitingTabstop.node); } } } } /* * We'll dump the report to the log area. */ var s = ""; s += "FixReadOrder - Report\r\n"; for (var i = 0; i < messgs.length; i++) { s += messgs[i].SOM + "' : " + messgs[i].TEXT + "\r\n"; } if (messgs.length === 0) { s += "No problems found.\r\n"; } else { s += "Done.\r\n"; } designer.showTextWindow(s); designer.println(s); } catch (e) { var sErr = "Script error: " + e.message + "\r\nline:" + e.line + "\r\n"; designer.println(sErr); } } else { designer.println("This macro is no longer needed in this version of Designer."); }