Word加载项CustomXMLParts数据建模和/或性能优化

时间:2018-04-10 23:13:25

标签: ms-word office-js office-addins officedev

环境:Mac 10.12.6,Word 2016(16.11.1),@ microsoft / office-js“^ 1.1.4”

我想知道在如何使用CustomXMLParts以最大化读/写性能方面是否有任何指导方针或最佳实践,或者是否存在为了相同目的在XML部件中建模数据的“理想”方法

我正在编写一个加载项,我需要在可见文档之外保留一些数据,但在docx文件中。

例如,我存储了一张发票清单(可能是100-200张发票),每张发票都有典型的结构化数据(名称,ID,日期,工作清单)和一个可以包含的自由风格备注部分高达5-10kb的文本,说明等等。

我拿这些发票,在Word文档中渲染其中一些,然后在任务窗格中对其余数据进行一些可视化分析 - 用户可以将注释写入(并保存)回自定义XML部件(进入他们正在查看的发票。)

现在......这里我有点困惑......我不确定将每张发票作为单独的CustomXMLPart存储在文件中是否更好(例如每张发票1个XML文件),或者如果更好的话将所有发票存储在单个大型CustomXMLPart中,或者如果存在中间地板(例如,每个XML部分10个发票)。如上所述,用例是回读所有发票,然后偶尔更新10-20%的发票中的数据。

现在,我正在为每个XML部件存储1个发票,当我加载我的插件并进行批量读取以将所有内容都放入内存时,每个发票需要250-500平方英寸才能读取它们,并行(因此,250-500ms * 100-200发票)。顺序地,它需要更长的时间(2-3倍长)。使用performance.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)
}

我使用Promises手动包装Office-JS回调,但我已经使用async / await,/ 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),这样我不只是进行完全删除并重新保存?

1 个答案:

答案 0 :(得分:2)

很好地使用Promises,我正在为非承诺officejs函数做同样的包装。使用基于promise的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.');
         }
      });
   }