XFA - Save filled data in the pdf when downloading the file (Bug 1716288)
- when binding (after parsing) we get a map between some template nodes and some data nodes; - so set user data in input handlers in using data node uids in the annotation storage; - to save the form, just put the value we have in the storage in the correct data nodes, serialize the xml as a string and then write the string at the end of the pdf using src/core/writer.js; - fix few bugs around data bindings: - the "Off" issue in Bug 1716980.
This commit is contained in:
parent
d7fdb72a3f
commit
429ffdcd2f
@ -963,6 +963,13 @@ class PDFDocument {
|
|||||||
this.xfaFactory.setFonts(pdfFonts);
|
this.xfaFactory.setFonts(pdfFonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async serializeXfaData(annotationStorage) {
|
||||||
|
if (this.xfaFactory) {
|
||||||
|
return this.xfaFactory.serializeData(annotationStorage);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
get formInfo() {
|
get formInfo() {
|
||||||
const formInfo = {
|
const formInfo = {
|
||||||
hasFields: false,
|
hasFields: false,
|
||||||
|
@ -77,6 +77,10 @@ class BasePdfManager {
|
|||||||
return this.pdfDocument.loadXfaFonts(handler, task);
|
return this.pdfDocument.loadXfaFonts(handler, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serializeXfaData(annotationStorage) {
|
||||||
|
return this.pdfDocument.serializeXfaData(annotationStorage);
|
||||||
|
}
|
||||||
|
|
||||||
cleanup(manuallyTriggered = false) {
|
cleanup(manuallyTriggered = false) {
|
||||||
return this.pdfDocument.cleanup(manuallyTriggered);
|
return this.pdfDocument.cleanup(manuallyTriggered);
|
||||||
}
|
}
|
||||||
|
@ -564,8 +564,9 @@ class WorkerMessageHandler {
|
|||||||
|
|
||||||
handler.on(
|
handler.on(
|
||||||
"SaveDocument",
|
"SaveDocument",
|
||||||
function ({ numPages, annotationStorage, filename }) {
|
function ({ isPureXfa, numPages, annotationStorage, filename }) {
|
||||||
pdfManager.requestLoadedStream();
|
pdfManager.requestLoadedStream();
|
||||||
|
|
||||||
const promises = [
|
const promises = [
|
||||||
pdfManager.onLoadedStream(),
|
pdfManager.onLoadedStream(),
|
||||||
pdfManager.ensureCatalog("acroForm"),
|
pdfManager.ensureCatalog("acroForm"),
|
||||||
@ -573,19 +574,21 @@ class WorkerMessageHandler {
|
|||||||
pdfManager.ensureDoc("startXRef"),
|
pdfManager.ensureDoc("startXRef"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
|
if (isPureXfa) {
|
||||||
promises.push(
|
promises.push(pdfManager.serializeXfaData(annotationStorage));
|
||||||
pdfManager.getPage(pageIndex).then(function (page) {
|
} else {
|
||||||
const task = new WorkerTask(`Save: page ${pageIndex}`);
|
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
|
||||||
startWorkerTask(task);
|
promises.push(
|
||||||
|
pdfManager.getPage(pageIndex).then(function (page) {
|
||||||
return page
|
const task = new WorkerTask(`Save: page ${pageIndex}`);
|
||||||
.save(handler, task, annotationStorage)
|
return page
|
||||||
.finally(function () {
|
.save(handler, task, annotationStorage)
|
||||||
finishWorkerTask(task);
|
.finally(function () {
|
||||||
});
|
finishWorkerTask(task);
|
||||||
})
|
});
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(function ([
|
return Promise.all(promises).then(function ([
|
||||||
@ -596,15 +599,23 @@ class WorkerMessageHandler {
|
|||||||
...refs
|
...refs
|
||||||
]) {
|
]) {
|
||||||
let newRefs = [];
|
let newRefs = [];
|
||||||
for (const ref of refs) {
|
let xfaData = null;
|
||||||
newRefs = ref
|
if (isPureXfa) {
|
||||||
.filter(x => x !== null)
|
xfaData = refs[0];
|
||||||
.reduce((a, b) => a.concat(b), newRefs);
|
if (!xfaData) {
|
||||||
}
|
return stream.bytes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const ref of refs) {
|
||||||
|
newRefs = ref
|
||||||
|
.filter(x => x !== null)
|
||||||
|
.reduce((a, b) => a.concat(b), newRefs);
|
||||||
|
}
|
||||||
|
|
||||||
if (newRefs.length === 0) {
|
if (newRefs.length === 0) {
|
||||||
// No new refs so just return the initial bytes
|
// No new refs so just return the initial bytes
|
||||||
return stream.bytes;
|
return stream.bytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || [];
|
const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || [];
|
||||||
@ -652,6 +663,7 @@ class WorkerMessageHandler {
|
|||||||
newRefs,
|
newRefs,
|
||||||
xref,
|
xref,
|
||||||
datasetsRef: xfaDatasets,
|
datasetsRef: xfaDatasets,
|
||||||
|
xfaData,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -123,12 +123,7 @@ function computeMD5(filesize, xrefInfo) {
|
|||||||
return bytesToString(calculateMD5(array));
|
return bytesToString(calculateMD5(array));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateXFA(datasetsRef, newRefs, xref) {
|
function writeXFADataForAcroform(str, newRefs) {
|
||||||
if (datasetsRef === null || xref === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const datasets = xref.fetchIfRef(datasetsRef);
|
|
||||||
const str = datasets.getString();
|
|
||||||
const xml = new SimpleXMLParser({ hasAttributes: true }).parseFromString(str);
|
const xml = new SimpleXMLParser({ hasAttributes: true }).parseFromString(str);
|
||||||
|
|
||||||
for (const { xfa } of newRefs) {
|
for (const { xfa } of newRefs) {
|
||||||
@ -148,7 +143,17 @@ function updateXFA(datasetsRef, newRefs, xref) {
|
|||||||
}
|
}
|
||||||
const buffer = [];
|
const buffer = [];
|
||||||
xml.documentElement.dump(buffer);
|
xml.documentElement.dump(buffer);
|
||||||
let updatedXml = buffer.join("");
|
return buffer.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateXFA(xfaData, datasetsRef, newRefs, xref) {
|
||||||
|
if (datasetsRef === null || xref === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xfaData === null) {
|
||||||
|
const datasets = xref.fetchIfRef(datasetsRef);
|
||||||
|
xfaData = writeXFADataForAcroform(datasets.getString(), newRefs);
|
||||||
|
}
|
||||||
|
|
||||||
const encrypt = xref.encrypt;
|
const encrypt = xref.encrypt;
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
@ -156,12 +161,12 @@ function updateXFA(datasetsRef, newRefs, xref) {
|
|||||||
datasetsRef.num,
|
datasetsRef.num,
|
||||||
datasetsRef.gen
|
datasetsRef.gen
|
||||||
);
|
);
|
||||||
updatedXml = transform.encryptString(updatedXml);
|
xfaData = transform.encryptString(xfaData);
|
||||||
}
|
}
|
||||||
const data =
|
const data =
|
||||||
`${datasetsRef.num} ${datasetsRef.gen} obj\n` +
|
`${datasetsRef.num} ${datasetsRef.gen} obj\n` +
|
||||||
`<< /Type /EmbeddedFile /Length ${updatedXml.length}>>\nstream\n` +
|
`<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` +
|
||||||
updatedXml +
|
xfaData +
|
||||||
"\nendstream\nendobj\n";
|
"\nendstream\nendobj\n";
|
||||||
|
|
||||||
newRefs.push({ ref: datasetsRef, data });
|
newRefs.push({ ref: datasetsRef, data });
|
||||||
@ -173,8 +178,9 @@ function incrementalUpdate({
|
|||||||
newRefs,
|
newRefs,
|
||||||
xref = null,
|
xref = null,
|
||||||
datasetsRef = null,
|
datasetsRef = null,
|
||||||
|
xfaData = null,
|
||||||
}) {
|
}) {
|
||||||
updateXFA(datasetsRef, newRefs, xref);
|
updateXFA(xfaData, datasetsRef, newRefs, xref);
|
||||||
|
|
||||||
const newXref = new Dict(null);
|
const newXref = new Dict(null);
|
||||||
const refForXrefTable = xrefInfo.newRef;
|
const refForXrefTable = xrefInfo.newRef;
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
$hasSettableValue,
|
$hasSettableValue,
|
||||||
$indexOf,
|
$indexOf,
|
||||||
$insertAt,
|
$insertAt,
|
||||||
|
$isBindable,
|
||||||
$isDataValue,
|
$isDataValue,
|
||||||
$isDescendent,
|
$isDescendent,
|
||||||
$namespaceId,
|
$namespaceId,
|
||||||
@ -87,12 +88,12 @@ class Binder {
|
|||||||
// data node (through $data property): we'll use it
|
// data node (through $data property): we'll use it
|
||||||
// to save form data.
|
// to save form data.
|
||||||
|
|
||||||
|
formNode[$data] = data;
|
||||||
if (formNode[$hasSettableValue]()) {
|
if (formNode[$hasSettableValue]()) {
|
||||||
if (data[$isDataValue]()) {
|
if (data[$isDataValue]()) {
|
||||||
const value = data[$getDataValue]();
|
const value = data[$getDataValue]();
|
||||||
// TODO: use picture.
|
// TODO: use picture.
|
||||||
formNode[$setValue](createText(value));
|
formNode[$setValue](createText(value));
|
||||||
formNode[$data] = data;
|
|
||||||
} else if (
|
} else if (
|
||||||
formNode instanceof Field &&
|
formNode instanceof Field &&
|
||||||
formNode.ui &&
|
formNode.ui &&
|
||||||
@ -103,13 +104,11 @@ class Binder {
|
|||||||
.map(child => child[$content].trim())
|
.map(child => child[$content].trim())
|
||||||
.join("\n");
|
.join("\n");
|
||||||
formNode[$setValue](createText(value));
|
formNode[$setValue](createText(value));
|
||||||
formNode[$data] = data;
|
|
||||||
} else if (this._isConsumeData()) {
|
} else if (this._isConsumeData()) {
|
||||||
warn(`XFA - Nodes haven't the same type.`);
|
warn(`XFA - Nodes haven't the same type.`);
|
||||||
}
|
}
|
||||||
} else if (!data[$isDataValue]() || this._isMatchTemplate()) {
|
} else if (!data[$isDataValue]() || this._isMatchTemplate()) {
|
||||||
this._bindElement(formNode, data);
|
this._bindElement(formNode, data);
|
||||||
formNode[$data] = data;
|
|
||||||
} else {
|
} else {
|
||||||
warn(`XFA - Nodes haven't the same type.`);
|
warn(`XFA - Nodes haven't the same type.`);
|
||||||
}
|
}
|
||||||
@ -496,6 +495,12 @@ class Binder {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!child[$isBindable]()) {
|
||||||
|
// The node cannot contain some new data so there is nothing
|
||||||
|
// to create in the data node.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let global = false;
|
let global = false;
|
||||||
let picture = null;
|
let picture = null;
|
||||||
let ref = null;
|
let ref = null;
|
||||||
|
82
src/core/xfa/data.js
Normal file
82
src/core/xfa/data.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* Copyright 2021 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
$getAttributes,
|
||||||
|
$getChildren,
|
||||||
|
$nodeName,
|
||||||
|
$setValue,
|
||||||
|
$toString,
|
||||||
|
$uid,
|
||||||
|
} from "./xfa_object.js";
|
||||||
|
|
||||||
|
class DataHandler {
|
||||||
|
constructor(root, data) {
|
||||||
|
this.data = data;
|
||||||
|
this.dataset = root.datasets || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(storage) {
|
||||||
|
const stack = [[-1, this.data[$getChildren]()]];
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const last = stack[stack.length - 1];
|
||||||
|
const [i, children] = last;
|
||||||
|
if (i + 1 === children.length) {
|
||||||
|
stack.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = children[++last[0]];
|
||||||
|
const storageEntry = storage.get(child[$uid]);
|
||||||
|
if (storageEntry) {
|
||||||
|
child[$setValue](storageEntry);
|
||||||
|
} else {
|
||||||
|
const attributes = child[$getAttributes]();
|
||||||
|
for (const value of attributes.values()) {
|
||||||
|
const entry = storage.get(value[$uid]);
|
||||||
|
if (entry) {
|
||||||
|
value[$setValue](entry);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = child[$getChildren]();
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
stack.push([-1, nodes]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = [
|
||||||
|
`<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">`,
|
||||||
|
];
|
||||||
|
if (this.dataset) {
|
||||||
|
// Dump nodes other than data: they can contains for example
|
||||||
|
// some data for choice lists.
|
||||||
|
for (const child of this.dataset[$getChildren]()) {
|
||||||
|
if (child[$nodeName] !== "data") {
|
||||||
|
child[$toString](buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.data[$toString](buf);
|
||||||
|
buf.push("</xfa:datasets>");
|
||||||
|
|
||||||
|
return buf.join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DataHandler };
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import { $globalData, $toHTML } from "./xfa_object.js";
|
import { $globalData, $toHTML } from "./xfa_object.js";
|
||||||
import { Binder } from "./bind.js";
|
import { Binder } from "./bind.js";
|
||||||
|
import { DataHandler } from "./data.js";
|
||||||
import { FontFinder } from "./fonts.js";
|
import { FontFinder } from "./fonts.js";
|
||||||
import { warn } from "../../shared/util.js";
|
import { warn } from "../../shared/util.js";
|
||||||
import { XFAParser } from "./parser.js";
|
import { XFAParser } from "./parser.js";
|
||||||
@ -23,7 +24,9 @@ class XFAFactory {
|
|||||||
constructor(data) {
|
constructor(data) {
|
||||||
try {
|
try {
|
||||||
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
|
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
|
||||||
this.form = new Binder(this.root).bind();
|
const binder = new Binder(this.root);
|
||||||
|
this.form = binder.bind();
|
||||||
|
this.dataHandler = new DataHandler(this.root, binder.getData());
|
||||||
this.form[$globalData].template = this.form;
|
this.form[$globalData].template = this.form;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn(`XFA - an error occured during parsing and binding: ${e}`);
|
warn(`XFA - an error occured during parsing and binding: ${e}`);
|
||||||
@ -70,6 +73,10 @@ class XFAFactory {
|
|||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serializeData(storage) {
|
||||||
|
return this.dataHandler.serialize(storage);
|
||||||
|
}
|
||||||
|
|
||||||
static _createDocument(data) {
|
static _createDocument(data) {
|
||||||
if (!data["/xdp:xdp"]) {
|
if (!data["/xdp:xdp"]) {
|
||||||
return data["xdp:xdp"];
|
return data["xdp:xdp"];
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
$clean,
|
$clean,
|
||||||
$cleanPage,
|
$cleanPage,
|
||||||
$content,
|
$content,
|
||||||
|
$data,
|
||||||
$extra,
|
$extra,
|
||||||
$finalize,
|
$finalize,
|
||||||
$flushHTML,
|
$flushHTML,
|
||||||
@ -32,9 +33,9 @@ import {
|
|||||||
$getSubformParent,
|
$getSubformParent,
|
||||||
$getTemplateRoot,
|
$getTemplateRoot,
|
||||||
$globalData,
|
$globalData,
|
||||||
$hasItem,
|
|
||||||
$hasSettableValue,
|
$hasSettableValue,
|
||||||
$ids,
|
$ids,
|
||||||
|
$isBindable,
|
||||||
$isCDATAXml,
|
$isCDATAXml,
|
||||||
$isSplittable,
|
$isSplittable,
|
||||||
$isTransparent,
|
$isTransparent,
|
||||||
@ -572,7 +573,7 @@ class BooleanElement extends Option01 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML](availableSpace) {
|
[$toHTML](availableSpace) {
|
||||||
return valueToHtml(this[$content] === 1);
|
return valueToHtml(this[$content] === 1 ? "1" : "0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,17 +951,31 @@ class CheckButton extends XFAObject {
|
|||||||
let type;
|
let type;
|
||||||
let className;
|
let className;
|
||||||
let groupId;
|
let groupId;
|
||||||
let id;
|
const field = this[$getParent]()[$getParent]();
|
||||||
const fieldId = this[$getParent]()[$getParent]()[$uid];
|
const items =
|
||||||
const container = this[$getParent]()[$getParent]()[$getParent]();
|
(field.items.children.length &&
|
||||||
|
field.items.children[0][$toHTML]().html) ||
|
||||||
|
[];
|
||||||
|
const exportedValue = {
|
||||||
|
on: (items[0] || "on").toString(),
|
||||||
|
off: (items[1] || "off").toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = (field.value && field.value[$text]()) || "off";
|
||||||
|
const checked = value === exportedValue.on || undefined;
|
||||||
|
const container = field[$getParent]();
|
||||||
|
const fieldId = field[$uid];
|
||||||
|
let dataId;
|
||||||
|
|
||||||
if (container instanceof ExclGroup) {
|
if (container instanceof ExclGroup) {
|
||||||
groupId = container[$uid];
|
groupId = container[$uid];
|
||||||
type = "radio";
|
type = "radio";
|
||||||
className = "xfaRadio";
|
className = "xfaRadio";
|
||||||
id = `${fieldId}-radio`;
|
dataId = container[$data] && container[$data][$uid];
|
||||||
} else {
|
} else {
|
||||||
type = "checkbox";
|
type = "checkbox";
|
||||||
className = "xfaCheckbox";
|
className = "xfaCheckbox";
|
||||||
|
dataId = field[$data] && field[$data][$uid];
|
||||||
}
|
}
|
||||||
|
|
||||||
const input = {
|
const input = {
|
||||||
@ -969,14 +984,13 @@ class CheckButton extends XFAObject {
|
|||||||
class: [className],
|
class: [className],
|
||||||
style,
|
style,
|
||||||
fieldId,
|
fieldId,
|
||||||
|
dataId,
|
||||||
type,
|
type,
|
||||||
|
checked,
|
||||||
|
xfaOn: exportedValue.on,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) {
|
|
||||||
input.attributes.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
input.attributes.name = groupId;
|
input.attributes.name = groupId;
|
||||||
}
|
}
|
||||||
@ -1022,25 +1036,36 @@ class ChoiceList extends XFAObject {
|
|||||||
const children = [];
|
const children = [];
|
||||||
|
|
||||||
if (field.items.children.length > 0) {
|
if (field.items.children.length > 0) {
|
||||||
const displayed = field.items.children[0][$toHTML]().html;
|
const items = field.items;
|
||||||
const values = field.items.children[1]
|
let displayedIndex = 0;
|
||||||
? field.items.children[1][$toHTML]().html
|
let saveIndex = 0;
|
||||||
: [];
|
if (items.children.length === 2) {
|
||||||
|
displayedIndex = items.children[0].save;
|
||||||
|
saveIndex = 1 - displayedIndex;
|
||||||
|
}
|
||||||
|
const displayed = items.children[displayedIndex][$toHTML]().html;
|
||||||
|
const values = items.children[saveIndex][$toHTML]().html;
|
||||||
|
|
||||||
|
const value = (field.value && field.value[$text]()) || "";
|
||||||
for (let i = 0, ii = displayed.length; i < ii; i++) {
|
for (let i = 0, ii = displayed.length; i < ii; i++) {
|
||||||
children.push({
|
const option = {
|
||||||
name: "option",
|
name: "option",
|
||||||
attributes: {
|
attributes: {
|
||||||
value: values[i] || displayed[i],
|
value: values[i] || displayed[i],
|
||||||
},
|
},
|
||||||
value: displayed[i],
|
value: displayed[i],
|
||||||
});
|
};
|
||||||
|
if (values[i] === value) {
|
||||||
|
option.attributes.selected = true;
|
||||||
|
}
|
||||||
|
children.push(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectAttributes = {
|
const selectAttributes = {
|
||||||
class: ["xfaSelect"],
|
class: ["xfaSelect"],
|
||||||
fieldId: this[$getParent]()[$getParent]()[$uid],
|
fieldId: field[$uid],
|
||||||
|
dataId: field[$data] && field[$data][$uid],
|
||||||
style,
|
style,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1272,11 +1297,13 @@ class DateTimeEdit extends XFAObject {
|
|||||||
// When the picker is host we should use type=date for the input
|
// When the picker is host we should use type=date for the input
|
||||||
// but we need to put the buttons outside the text-field.
|
// but we need to put the buttons outside the text-field.
|
||||||
const style = toStyle(this, "border", "font", "margin");
|
const style = toStyle(this, "border", "font", "margin");
|
||||||
|
const field = this[$getParent]()[$getParent]();
|
||||||
const html = {
|
const html = {
|
||||||
name: "input",
|
name: "input",
|
||||||
attributes: {
|
attributes: {
|
||||||
type: "text",
|
type: "text",
|
||||||
fieldId: this[$getParent]()[$getParent]()[$uid],
|
fieldId: field[$uid],
|
||||||
|
dataId: field[$data] && field[$data][$uid],
|
||||||
class: ["xfaTextfield"],
|
class: ["xfaTextfield"],
|
||||||
style,
|
style,
|
||||||
},
|
},
|
||||||
@ -1976,6 +2003,10 @@ class ExclGroup extends XFAObject {
|
|||||||
this.setProperty = new XFAObjectArray();
|
this.setProperty = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$isBindable]() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
[$hasSettableValue]() {
|
[$hasSettableValue]() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1988,17 +2019,7 @@ class ExclGroup extends XFAObject {
|
|||||||
field.value = nodeValue;
|
field.value = nodeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeBoolean = new BooleanElement({});
|
field.value[$setValue](value);
|
||||||
nodeBoolean[$content] = 0;
|
|
||||||
|
|
||||||
for (const item of field.items.children) {
|
|
||||||
if (item[$hasItem](value)) {
|
|
||||||
nodeBoolean[$content] = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
field.value[$setValue](nodeBoolean);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2312,6 +2333,10 @@ class Field extends XFAObject {
|
|||||||
this.setProperty = new XFAObjectArray();
|
this.setProperty = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$isBindable]() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
[$setValue](value) {
|
[$setValue](value) {
|
||||||
_setValue(this, value);
|
_setValue(this, value);
|
||||||
}
|
}
|
||||||
@ -2906,15 +2931,6 @@ class Items extends XFAObject {
|
|||||||
this.time = new XFAObjectArray();
|
this.time = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
[$hasItem](value) {
|
|
||||||
return (
|
|
||||||
this.hasOwnProperty(value[$nodeName]) &&
|
|
||||||
this[value[$nodeName]].children.some(
|
|
||||||
node => node[$content] === value[$content]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML]() {
|
||||||
const output = [];
|
const output = [];
|
||||||
for (const child of this[$getChildren]()) {
|
for (const child of this[$getChildren]()) {
|
||||||
@ -3182,11 +3198,13 @@ class NumericEdit extends XFAObject {
|
|||||||
[$toHTML](availableSpace) {
|
[$toHTML](availableSpace) {
|
||||||
// TODO: incomplete.
|
// TODO: incomplete.
|
||||||
const style = toStyle(this, "border", "font", "margin");
|
const style = toStyle(this, "border", "font", "margin");
|
||||||
|
const field = this[$getParent]()[$getParent]();
|
||||||
const html = {
|
const html = {
|
||||||
name: "input",
|
name: "input",
|
||||||
attributes: {
|
attributes: {
|
||||||
type: "text",
|
type: "text",
|
||||||
fieldId: this[$getParent]()[$getParent]()[$uid],
|
fieldId: field[$uid],
|
||||||
|
dataId: field[$data] && field[$data][$uid],
|
||||||
class: ["xfaTextfield"],
|
class: ["xfaTextfield"],
|
||||||
style,
|
style,
|
||||||
},
|
},
|
||||||
@ -4151,6 +4169,10 @@ class Subform extends XFAObject {
|
|||||||
this.subformSet = new XFAObjectArray();
|
this.subformSet = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$isBindable]() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
*[$getContainedChildren]() {
|
*[$getContainedChildren]() {
|
||||||
// This function is overriden in order to fake that subforms under
|
// This function is overriden in order to fake that subforms under
|
||||||
// this set are in fact under parent subform.
|
// this set are in fact under parent subform.
|
||||||
@ -4924,11 +4946,13 @@ class TextEdit extends XFAObject {
|
|||||||
// TODO: incomplete.
|
// TODO: incomplete.
|
||||||
const style = toStyle(this, "border", "font", "margin");
|
const style = toStyle(this, "border", "font", "margin");
|
||||||
let html;
|
let html;
|
||||||
|
const field = this[$getParent]()[$getParent]();
|
||||||
if (this.multiLine === 1) {
|
if (this.multiLine === 1) {
|
||||||
html = {
|
html = {
|
||||||
name: "textarea",
|
name: "textarea",
|
||||||
attributes: {
|
attributes: {
|
||||||
fieldId: this[$getParent]()[$getParent]()[$uid],
|
dataId: field[$data] && field[$data][$uid],
|
||||||
|
fieldId: field[$uid],
|
||||||
class: ["xfaTextfield"],
|
class: ["xfaTextfield"],
|
||||||
style,
|
style,
|
||||||
},
|
},
|
||||||
@ -4938,7 +4962,8 @@ class TextEdit extends XFAObject {
|
|||||||
name: "input",
|
name: "input",
|
||||||
attributes: {
|
attributes: {
|
||||||
type: "text",
|
type: "text",
|
||||||
fieldId: this[$getParent]()[$getParent]()[$uid],
|
dataId: field[$data] && field[$data][$uid],
|
||||||
|
fieldId: field[$uid],
|
||||||
class: ["xfaTextfield"],
|
class: ["xfaTextfield"],
|
||||||
style,
|
style,
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import { getInteger, getKeyword, HTMLResult } from "./utils.js";
|
import { getInteger, getKeyword, HTMLResult } from "./utils.js";
|
||||||
import { shadow, warn } from "../../shared/util.js";
|
import { shadow, warn } from "../../shared/util.js";
|
||||||
|
import { encodeToXmlString } from "../core_utils.js";
|
||||||
import { NamespaceIds } from "./namespaces.js";
|
import { NamespaceIds } from "./namespaces.js";
|
||||||
import { searchNode } from "./som.js";
|
import { searchNode } from "./som.js";
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ const $extra = Symbol("extra");
|
|||||||
const $finalize = Symbol();
|
const $finalize = Symbol();
|
||||||
const $flushHTML = Symbol();
|
const $flushHTML = Symbol();
|
||||||
const $getAttributeIt = Symbol();
|
const $getAttributeIt = Symbol();
|
||||||
|
const $getAttributes = Symbol();
|
||||||
const $getAvailableSpace = Symbol();
|
const $getAvailableSpace = Symbol();
|
||||||
const $getChildrenByClass = Symbol();
|
const $getChildrenByClass = Symbol();
|
||||||
const $getChildrenByName = Symbol();
|
const $getChildrenByName = Symbol();
|
||||||
@ -50,12 +52,12 @@ const $getParent = Symbol();
|
|||||||
const $getTemplateRoot = Symbol();
|
const $getTemplateRoot = Symbol();
|
||||||
const $global = Symbol();
|
const $global = Symbol();
|
||||||
const $globalData = Symbol();
|
const $globalData = Symbol();
|
||||||
const $hasItem = Symbol();
|
|
||||||
const $hasSettableValue = Symbol();
|
const $hasSettableValue = Symbol();
|
||||||
const $ids = Symbol();
|
const $ids = Symbol();
|
||||||
const $indexOf = Symbol();
|
const $indexOf = Symbol();
|
||||||
const $insertAt = Symbol();
|
const $insertAt = Symbol();
|
||||||
const $isCDATAXml = Symbol();
|
const $isCDATAXml = Symbol();
|
||||||
|
const $isBindable = Symbol();
|
||||||
const $isDataValue = Symbol();
|
const $isDataValue = Symbol();
|
||||||
const $isDescendent = Symbol();
|
const $isDescendent = Symbol();
|
||||||
const $isSplittable = Symbol();
|
const $isSplittable = Symbol();
|
||||||
@ -78,6 +80,7 @@ const $setSetAttributes = Symbol();
|
|||||||
const $setValue = Symbol();
|
const $setValue = Symbol();
|
||||||
const $text = Symbol();
|
const $text = Symbol();
|
||||||
const $toHTML = Symbol();
|
const $toHTML = Symbol();
|
||||||
|
const $toString = Symbol();
|
||||||
const $toStyle = Symbol();
|
const $toStyle = Symbol();
|
||||||
const $uid = Symbol("uid");
|
const $uid = Symbol("uid");
|
||||||
|
|
||||||
@ -101,6 +104,8 @@ const _validator = Symbol();
|
|||||||
|
|
||||||
let uid = 0;
|
let uid = 0;
|
||||||
|
|
||||||
|
const NS_DATASETS = NamespaceIds.datasets.id;
|
||||||
|
|
||||||
class XFAObject {
|
class XFAObject {
|
||||||
constructor(nsId, name, hasChildren = false) {
|
constructor(nsId, name, hasChildren = false) {
|
||||||
this[$namespaceId] = nsId;
|
this[$namespaceId] = nsId;
|
||||||
@ -161,6 +166,10 @@ class XFAObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$isBindable]() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
[$setId](ids) {
|
[$setId](ids) {
|
||||||
if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
|
if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
|
||||||
ids.set(this.id, this);
|
ids.set(this.id, this);
|
||||||
@ -207,10 +216,6 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[$hasItem]() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[$indexOf](child) {
|
[$indexOf](child) {
|
||||||
return this[_children].indexOf(child);
|
return this[_children].indexOf(child);
|
||||||
}
|
}
|
||||||
@ -599,6 +604,7 @@ class XFAObject {
|
|||||||
shadow(clone, $symbol, this[$symbol]);
|
shadow(clone, $symbol, this[$symbol]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clone[$uid] = `${clone[$nodeName]}${uid++}`;
|
||||||
clone[_children] = [];
|
clone[_children] = [];
|
||||||
|
|
||||||
for (const name of Object.getOwnPropertyNames(this)) {
|
for (const name of Object.getOwnPropertyNames(this)) {
|
||||||
@ -720,6 +726,7 @@ class XFAAttribute {
|
|||||||
this[$nodeName] = name;
|
this[$nodeName] = name;
|
||||||
this[$content] = value;
|
this[$content] = value;
|
||||||
this[$consumed] = false;
|
this[$consumed] = false;
|
||||||
|
this[$uid] = `attribute${uid++}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$getParent]() {
|
[$getParent]() {
|
||||||
@ -730,6 +737,11 @@ class XFAAttribute {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
value = value.value || "";
|
||||||
|
this[$content] = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
[$text]() {
|
[$text]() {
|
||||||
return this[$content];
|
return this[$content];
|
||||||
}
|
}
|
||||||
@ -765,6 +777,44 @@ class XmlObject extends XFAObject {
|
|||||||
this[$consumed] = false;
|
this[$consumed] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toString](buf) {
|
||||||
|
const tagName = this[$nodeName];
|
||||||
|
if (tagName === "#text") {
|
||||||
|
buf.push(encodeToXmlString(this[$content]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prefix = this[$namespaceId] === NS_DATASETS ? "xfa:" : "";
|
||||||
|
buf.push(`<${prefix}${tagName}`);
|
||||||
|
for (const [name, value] of this[_attributes].entries()) {
|
||||||
|
buf.push(` ${name}="${encodeToXmlString(value[$content])}"`);
|
||||||
|
}
|
||||||
|
if (this[_dataValue] !== null) {
|
||||||
|
if (this[_dataValue]) {
|
||||||
|
buf.push(` xfa:dataNode="dataValue"`);
|
||||||
|
} else {
|
||||||
|
buf.push(` xfa:dataNode="dataGroup"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this[$content] && this[_children].length === 0) {
|
||||||
|
buf.push("/>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push(">");
|
||||||
|
if (this[$content]) {
|
||||||
|
if (typeof this[$content] === "string") {
|
||||||
|
buf.push(encodeToXmlString(this[$content]));
|
||||||
|
} else {
|
||||||
|
this[$content][$toString](buf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const child of this[_children]) {
|
||||||
|
child[$toString](buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.push(`</${prefix}${tagName}>`);
|
||||||
|
}
|
||||||
|
|
||||||
[$onChild](child) {
|
[$onChild](child) {
|
||||||
if (this[$content]) {
|
if (this[$content]) {
|
||||||
const node = new XmlObject(this[$namespaceId], "#text");
|
const node = new XmlObject(this[$namespaceId], "#text");
|
||||||
@ -808,6 +858,10 @@ class XmlObject extends XFAObject {
|
|||||||
return this[_children].filter(c => c[$nodeName] === name);
|
return this[_children].filter(c => c[$nodeName] === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$getAttributes]() {
|
||||||
|
return this[_attributes];
|
||||||
|
}
|
||||||
|
|
||||||
[$getChildrenByClass](name) {
|
[$getChildrenByClass](name) {
|
||||||
const value = this[_attributes].get(name);
|
const value = this[_attributes].get(name);
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
@ -882,6 +936,11 @@ class XmlObject extends XFAObject {
|
|||||||
return this[$content].trim();
|
return this[$content].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
value = value.value || "";
|
||||||
|
this[$content] = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
[$dump]() {
|
[$dump]() {
|
||||||
const dumped = Object.create(null);
|
const dumped = Object.create(null);
|
||||||
if (this[$content]) {
|
if (this[$content]) {
|
||||||
@ -993,6 +1052,7 @@ export {
|
|||||||
$finalize,
|
$finalize,
|
||||||
$flushHTML,
|
$flushHTML,
|
||||||
$getAttributeIt,
|
$getAttributeIt,
|
||||||
|
$getAttributes,
|
||||||
$getAvailableSpace,
|
$getAvailableSpace,
|
||||||
$getChildren,
|
$getChildren,
|
||||||
$getChildrenByClass,
|
$getChildrenByClass,
|
||||||
@ -1007,11 +1067,11 @@ export {
|
|||||||
$getTemplateRoot,
|
$getTemplateRoot,
|
||||||
$global,
|
$global,
|
||||||
$globalData,
|
$globalData,
|
||||||
$hasItem,
|
|
||||||
$hasSettableValue,
|
$hasSettableValue,
|
||||||
$ids,
|
$ids,
|
||||||
$indexOf,
|
$indexOf,
|
||||||
$insertAt,
|
$insertAt,
|
||||||
|
$isBindable,
|
||||||
$isCDATAXml,
|
$isCDATAXml,
|
||||||
$isDataValue,
|
$isDataValue,
|
||||||
$isDescendent,
|
$isDescendent,
|
||||||
@ -1034,6 +1094,7 @@ export {
|
|||||||
$setValue,
|
$setValue,
|
||||||
$text,
|
$text,
|
||||||
$toHTML,
|
$toHTML,
|
||||||
|
$toString,
|
||||||
$toStyle,
|
$toStyle,
|
||||||
$uid,
|
$uid,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
|
@ -2790,6 +2790,7 @@ class WorkerTransport {
|
|||||||
saveDocument() {
|
saveDocument() {
|
||||||
return this.messageHandler
|
return this.messageHandler
|
||||||
.sendWithPromise("SaveDocument", {
|
.sendWithPromise("SaveDocument", {
|
||||||
|
isPureXfa: !!this._htmlForXfa,
|
||||||
numPages: this._numPages,
|
numPages: this._numPages,
|
||||||
annotationStorage: this.annotationStorage.serializable,
|
annotationStorage: this.annotationStorage.serializable,
|
||||||
filename: this._fullReader?.filename ?? null,
|
filename: this._fullReader?.filename ?? null,
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class XfaLayer {
|
class XfaLayer {
|
||||||
static setupStorage(html, fieldId, element, storage, intent) {
|
static setupStorage(html, id, element, storage, intent) {
|
||||||
const storedData = storage.getValue(fieldId, { value: null });
|
const storedData = storage.getValue(id, { value: null });
|
||||||
switch (element.name) {
|
switch (element.name) {
|
||||||
case "textarea":
|
case "textarea":
|
||||||
if (storedData.value !== null) {
|
if (storedData.value !== null) {
|
||||||
@ -25,36 +25,22 @@ class XfaLayer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
html.addEventListener("input", event => {
|
html.addEventListener("input", event => {
|
||||||
storage.setValue(fieldId, { value: event.target.value });
|
storage.setValue(id, { value: event.target.value });
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "input":
|
case "input":
|
||||||
if (element.attributes.type === "radio") {
|
if (
|
||||||
if (storedData.value) {
|
element.attributes.type === "radio" ||
|
||||||
|
element.attributes.type === "checkbox"
|
||||||
|
) {
|
||||||
|
if (storedData.value === element.attributes.exportedValue) {
|
||||||
html.setAttribute("checked", true);
|
html.setAttribute("checked", true);
|
||||||
}
|
}
|
||||||
if (intent === "print") {
|
if (intent === "print") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
html.addEventListener("change", event => {
|
html.addEventListener("change", event => {
|
||||||
const { target } = event;
|
storage.setValue(id, { value: event.target.getAttribute("xfaOn") });
|
||||||
for (const radio of document.getElementsByName(target.name)) {
|
|
||||||
if (radio !== target) {
|
|
||||||
const id = radio.id;
|
|
||||||
storage.setValue(id.split("-")[0], { value: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
storage.setValue(fieldId, { value: target.checked });
|
|
||||||
});
|
|
||||||
} else if (element.attributes.type === "checkbox") {
|
|
||||||
if (storedData.value) {
|
|
||||||
html.setAttribute("checked", true);
|
|
||||||
}
|
|
||||||
if (intent === "print") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
html.addEventListener("input", event => {
|
|
||||||
storage.setValue(fieldId, { value: event.target.checked });
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (storedData.value !== null) {
|
if (storedData.value !== null) {
|
||||||
@ -64,7 +50,7 @@ class XfaLayer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
html.addEventListener("input", event => {
|
html.addEventListener("input", event => {
|
||||||
storage.setValue(fieldId, { value: event.target.value });
|
storage.setValue(id, { value: event.target.value });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -80,9 +66,9 @@ class XfaLayer {
|
|||||||
const options = event.target.options;
|
const options = event.target.options;
|
||||||
const value =
|
const value =
|
||||||
options.selectedIndex === -1
|
options.selectedIndex === -1
|
||||||
? null
|
? ""
|
||||||
: options[options.selectedIndex].value;
|
: options[options.selectedIndex].value;
|
||||||
storage.setValue(fieldId, { value });
|
storage.setValue(id, { value });
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -96,7 +82,7 @@ class XfaLayer {
|
|||||||
attributes.name = `${attributes.name}-${intent}`;
|
attributes.name = `${attributes.name}-${intent}`;
|
||||||
}
|
}
|
||||||
for (const [key, value] of Object.entries(attributes)) {
|
for (const [key, value] of Object.entries(attributes)) {
|
||||||
if (value === null || value === undefined || key === "fieldId") {
|
if (value === null || value === undefined || key === "dataId") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +101,8 @@ class XfaLayer {
|
|||||||
|
|
||||||
// Set the value after the others to be sure overwrite
|
// Set the value after the others to be sure overwrite
|
||||||
// any other values.
|
// any other values.
|
||||||
if (storage && attributes.fieldId !== undefined) {
|
if (storage && attributes.dataId) {
|
||||||
this.setupStorage(html, attributes.fieldId, element, storage);
|
this.setupStorage(html, attributes.dataId, element, storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -67,6 +67,7 @@
|
|||||||
!issue8229.pdf
|
!issue8229.pdf
|
||||||
!issue8276_reduced.pdf
|
!issue8276_reduced.pdf
|
||||||
!issue8372.pdf
|
!issue8372.pdf
|
||||||
|
!xfa_filled_imm1344e.pdf
|
||||||
!issue8424.pdf
|
!issue8424.pdf
|
||||||
!issue8480.pdf
|
!issue8480.pdf
|
||||||
!bug1650302_reduced.pdf
|
!bug1650302_reduced.pdf
|
||||||
|
71171
test/pdfs/xfa_filled_imm1344e.pdf
Normal file
71171
test/pdfs/xfa_filled_imm1344e.pdf
Normal file
File diff suppressed because one or more lines are too long
@ -930,6 +930,14 @@
|
|||||||
"link": true,
|
"link": true,
|
||||||
"type": "load"
|
"type": "load"
|
||||||
},
|
},
|
||||||
|
{ "id": "xfa_filled_imm1344e",
|
||||||
|
"file": "pdfs/xfa_filled_imm1344e.pdf",
|
||||||
|
"md5": "0576d16692fcd8ef2366cb48bf296e81",
|
||||||
|
"rounds": 1,
|
||||||
|
"enableXfa": true,
|
||||||
|
"lastPage": 2,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "xfa_bug1717681",
|
{ "id": "xfa_bug1717681",
|
||||||
"file": "pdfs/xfa_bug1717681.pdf",
|
"file": "pdfs/xfa_bug1717681.pdf",
|
||||||
"md5": "435b1eae7e017b1a932fe204d1ba8be5",
|
"md5": "435b1eae7e017b1a932fe204d1ba8be5",
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"writer_spec.js",
|
"writer_spec.js",
|
||||||
"xfa_formcalc_spec.js",
|
"xfa_formcalc_spec.js",
|
||||||
"xfa_parser_spec.js",
|
"xfa_parser_spec.js",
|
||||||
|
"xfa_serialize_data_spec.js",
|
||||||
"xfa_tohtml_spec.js",
|
"xfa_tohtml_spec.js",
|
||||||
"xml_spec.js"
|
"xml_spec.js"
|
||||||
]
|
]
|
||||||
|
@ -88,6 +88,7 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/writer_spec.js",
|
"pdfjs-test/unit/writer_spec.js",
|
||||||
"pdfjs-test/unit/xfa_formcalc_spec.js",
|
"pdfjs-test/unit/xfa_formcalc_spec.js",
|
||||||
"pdfjs-test/unit/xfa_parser_spec.js",
|
"pdfjs-test/unit/xfa_parser_spec.js",
|
||||||
|
"pdfjs-test/unit/xfa_serialize_data_spec.js",
|
||||||
"pdfjs-test/unit/xfa_tohtml_spec.js",
|
"pdfjs-test/unit/xfa_tohtml_spec.js",
|
||||||
"pdfjs-test/unit/xml_spec.js",
|
"pdfjs-test/unit/xml_spec.js",
|
||||||
].map(function (moduleName) {
|
].map(function (moduleName) {
|
||||||
|
73
test/unit/xfa_serialize_data_spec.js
Normal file
73
test/unit/xfa_serialize_data_spec.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/* Copyright 2021 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $uid } from "../../src/core/xfa/xfa_object.js";
|
||||||
|
import { DataHandler } from "../../src/core/xfa/data.js";
|
||||||
|
import { searchNode } from "../../src/core/xfa/som.js";
|
||||||
|
import { XFAParser } from "../../src/core/xfa/parser.js";
|
||||||
|
|
||||||
|
describe("Data serializer", function () {
|
||||||
|
it("should serialize data with an annotationStorage", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<foo>bar</foo>
|
||||||
|
<xfa:data>
|
||||||
|
<Receipt>
|
||||||
|
<Page>1</Page>
|
||||||
|
<Detail PartNo="GS001">
|
||||||
|
<Description>Giant Slingshot</Description>
|
||||||
|
<Units>1</Units>
|
||||||
|
<Unit_Price>250.00</Unit_Price>
|
||||||
|
<Total_Price>250.00</Total_Price>
|
||||||
|
</Detail>
|
||||||
|
<Page>2</Page>
|
||||||
|
<Detail PartNo="RRB-LB">
|
||||||
|
<Description>Road Runner Bait, large bag</Description>
|
||||||
|
<Units>5</Units>
|
||||||
|
<Unit_Price>12.00</Unit_Price>
|
||||||
|
<Total_Price>60.00</Total_Price>
|
||||||
|
</Detail>
|
||||||
|
<Sub_Total>310.00</Sub_Total>
|
||||||
|
<Tax>24.80</Tax>
|
||||||
|
<Total_Price>334.80</Total_Price>
|
||||||
|
</Receipt>
|
||||||
|
</xfa:data>
|
||||||
|
<bar>foo</bar>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const data = root.datasets.data;
|
||||||
|
const dataHandler = new DataHandler(root, data);
|
||||||
|
|
||||||
|
const storage = new Map();
|
||||||
|
for (const [path, value] of [
|
||||||
|
["Receipt.Detail[0].Units", "12&3"],
|
||||||
|
["Receipt.Detail[0].Unit_Price", "456>"],
|
||||||
|
["Receipt.Detail[0].Total_Price", "789"],
|
||||||
|
["Receipt.Detail[1].PartNo", "foo-bar😀"],
|
||||||
|
["Receipt.Detail[1].Description", "hello world"],
|
||||||
|
]) {
|
||||||
|
storage.set(searchNode(root, data, path)[0][$uid], { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
const serialized = dataHandler.serialize(storage);
|
||||||
|
const expected = `<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><foo>bar</foo><bar>foo</bar><xfa:data><Receipt><Page>1</Page><Detail PartNo="GS001"><Description>Giant Slingshot</Description><Units>12&3</Units><Unit_Price>456></Unit_Price><Total_Price>789</Total_Price></Detail><Page>2</Page><Detail PartNo="foo-bar😀"><Description>hello world</Description><Units>5</Units><Unit_Price>12.00</Unit_Price><Total_Price>60.00</Total_Price></Detail><Sub_Total>310.00</Sub_Total><Tax>24.80</Tax><Total_Price>334.80</Total_Price></Receipt></xfa:data></xfa:datasets>`;
|
||||||
|
|
||||||
|
expect(serialized).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user