环境: Mac 10.12.6, Word 2016 (16.11.1), @microsoft/office-js "^1.1.4">
我想知道在如何使用 CustomXMLPart 以最大限度地提高读/写性能方面是否有任何指南或最佳实践,或者是否有一种"理想"的方式来对 XML 部件中的数据进行建模以达到相同的目的。
我正在编写一个加载项,我需要将一些数据保留在可见文档之外,但在 docx 文件内。
例如,我存储了一个发票列表(可能是 100-200 张发票),每个发票都有典型的结构化数据(名称、ID、日期、工作项列表)和一个自由式注释部分,其中最多可以包含 5-10kb 的文本、说明等。
我获取这些发票,在 Word 文档中呈现其中一些发票,然后在任务窗格中对其余发票中的数据进行一些可视化分析 - 用户可以在其中将注释写入(和保存)回自定义 XML 部件(到他们正在查看的发票中)。
现在。。。这就是我有点困惑的地方...我不确定是否最好将每张发票作为单独的 CustomXMLPart 存储在文件中(例如,每张发票 1 个 XML 文件),或者是否最好将所有发票存储在单个大型 CustomXMLPart 中,或者是否有中间立场(例如每个 XML 部分 10 张发票)。如前所述,用例是回读所有发票,然后偶尔更新 10-20% 的发票中的数据。
现在,我为每个 XML 部件存储 1 张发票,当我加载我的插件并进行批量读取以将所有内容放入内存时,每张发票平均需要 250-500 毫秒才能并行读回它们(因此,250-500 毫秒 * 100-200 张发票)。按顺序,它需要更长的时间(长 2-3 倍)。使用性能进行测试.now() 验证使用挂钟计时。
这似乎很长,所以我不知道我是否做错了什么 - 或者这只是打开并从这些文件中提取数据所需的时间?
// Sequential example - excluding error handling and type-safety
// Parallel equivalent is essentially a Promise.all with a .map
// Approx 50ms
let result = await this.xmlPartsHelper.getByNamespaceAsync(...);
for (const item of result.value) {
// Approx 150-200ms
result = await this.xmlPartsHelper.getByIdAsync(item.id);
// Approx 150-200ms
result = await this.xmlPartsHelper.getXmlAsync(result.value);
// Approx 5ms
const invoice = this.mapper.reverseMap(result.value);
invoices.push(invoice)
}
我用 Promise 手动包装了 Office-JS 回调,但我已经用 async/await、then/catch 和 office-js 回调测试了这个例子 - 结果都大致相同。
public getByNamespaceAsync(namespace: string): Promise<Office.AsyncResult> {
return new Promise<Office.AsyncResult>((resolve, reject) => {
Office.context.document.customXmlParts.getByNamespaceAsync(namespace, (result: Office.AsyncResult) => {
return resolve(result);
});
});
}
public getByIdAsync(id: string): Promise<Office.AsyncResult> {
return new Promise<Office.AsyncResult>((resolve, reject) => {
Office.context.document.customXmlParts.getByIdAsync(id, (result: Office.AsyncResult) => {
return resolve(result);
});
});
}
public getXmlAsync(xmlPart: Office.CustomXmlPart): Promise<Office.AsyncResult> {
return new Promise<Office.AsyncResult>((resolve, reject) => {
xmlPart.getXmlAsync((result: Office.AsyncResult) => {
return resolve(result);
});
});
}
更新
我不完全理解的一块拼图是CustomXMLNode - 也许这会有所帮助。似乎有一些方法专门在 CustomXMLPart (https://dev.office.com/reference/add-ins/shared/customxmlnode.customxmlnode) 的节点中获取/设置数据 - 所以也许这是一个中间选项,我可以将所有发票放入单个 CustomXMLPart 中(这样我只受到单个 CustomXMLPart 的文件系统的打击),然后我可以有选择地更新该 CustomXMLPart 的各个部分(使用 CustomXMLNode),这样我就不会只是完全删除并重新保存?
很好地使用了 Promises,我正在对非 promise officejs 函数进行相同的包装。使用基于承诺的 API,您现在可以利用 Promise.all() 进行并行操作。您可以同时启动所有操作并等待完成。这应该更快。
function getAllParts(ids) {
return Promise.all(ids.map(id => xmlPartsHelper.getByIdAsync(id)));
}
let namespaces = await this.xmlPartsHelper.getByNamespaceAsync(...);
getAllParts(namespaces.value).then((results) => {
console.log('invioces are', results);
});
在文档中存储数据的其他方法是Office.context.document.settings。您可以将其用作键/值存储,并将 JSON 作为您的值。也许尝试将所有发票放入一个数组中并将其写入相同的键。以下是执行此操作的帮助程序函数:
/** Set a document property. Properties are specific to the document and the Addin-ID.
* @param {string} propertyName Name of the property.
* @param {string} value Value of the property.
* @returns {Promise} A promise without content.
*/
function setDocumentProperty(propertyName, value) {
return new Promise((resolve, reject) => {
if (Office.context.document.settings) {
Office.context.document.settings.set(propertyName, value);
Office.context.document.settings.saveAsync((asyncResult) => {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
reject(`[ExcelApi] Property '${propertyName}=${value}' could not be saved. Error: ${asyncResult.error.message}`);
} else {
resolve(`[ExcelApi] Property '${propertyName}=${value}' saved.`);
}
});
} else {
reject('[ExcelApi] document.settings is not ready.');
}
});
}
/** Get a document property.
* @param {string} propertyName Name of the property. Properties are specific to the document and the Addin-ID.
* @returns {Promise<object>} A promise that contains the property value.
*/
function getDocumentProperty(propertyName) {
return new Promise((resolve, reject) => {
if (Office.context.document.settings) {
const result = Office.context.document.settings.get(propertyName);
if (result === null) reject(`[ExcelApi] Property '${propertyName}' not found.`);
resolve(result);
} else {
reject('[ExcelApi] document.settings is not ready.');
}
});
}