首页 > 解决方案 > 使用 Google Docs API 检索 namedRange 中的文本

问题描述

使用带有 Node 的 Google Docs/Drive API,我成功地创建了一个服务,它可以生成“模板”样式的文档,该文档具有命名范围供其他用户写入。我想使用 Google Docs API 来读取在这些范围内输入的文本,但看不到这样做的干净方式。鉴于我有每个范围的开始和结束索引,我认为这将非常简单!不幸的是,我看不到任何内置的方法吗?

目前看来我将不得不请求整个谷歌文档,并且对于我正在观看的每个范围,比较每个节点的开始/结束索引并递归遍历树直到它们匹配。没有更好的方法吗?

干杯

编辑:

下面的 Tanaike 解决方案更简洁,但我已经有了一个适用于我的 Firebase 功能的版本,所以我想我不妨分享一下。此代码检索具有给定 ID 的 Google Doc,并将 namedRanges 的内容作为字符串存储在 Firebase 实时数据库中,通过“BBCode”样式标签保持图像和表格的完整性。下面的相关代码(请注意,我知道每个 namedRange 都在一个表格单元格内,这使得找到它们更容易):

async function StoreResponses(oauth2Client, numSections, documentId, meetingId, revisionId, roomId) 
{
    var gdocsApi = google.docs({version: 'v1', auth: oauth2Client});

    return gdocsApi.documents.get({ "documentId": documentId })
    .then((document) => {
        
        var ranges = document.data.namedRanges;
        var docContent = document.data.body.content;

        var toStore = [];

        for(var i = 0; i < numSections; i++)
        {
            var range = ranges[`zoomsense_section_${i}`].namedRanges[0].ranges[0]
            
            // loop through document contents until we hit the right index
            for(var j = 0; j < docContent.length; j++)
            {
                if(docContent[j].startIndex <= range.startIndex && docContent[j].endIndex >= range.endIndex)
                {
                    // we know that the ranges are inside single table cells
                    var sectionContents = docContent[j].table.tableRows[0].tableCells[0].content;

                    toStore.push(readStructuralElementsRecursively(document, sectionContents));
                }
            }
        }

        return db.ref(`/data/gdocs/${meetingId}/${roomId}/${documentId}/revisions/${revisionId}/responses`).set(toStore);
    })
    .catch((exception) => {
        console.error(exception)
        res.status(500).send(exception);
    })
}
// uses https://developers.google.com/docs/api/samples/extract-text
function readStructuralElementsRecursively(document, elements)
{
    var text = "";
    elements.forEach(element => {
        if(element.paragraph)
        {
            element.paragraph.elements.forEach(elem => {
                text += readParagraphElement(document, elem);
            });
        }
        else if(element.table)
        {
            // The text in table cells are in nested Structural Elements, so this is recursive
            text += "[table]"
            element.table.tableRows.forEach(row => {
                text += "[row]"
                row.tableCells.forEach(cell => {
                    text += `[cell]${readStructuralElementsRecursively(document, cell.content)}[/cell]`;
                })
                text += "[/row]"
            })
            text+= "[/table]"
        }
    });

    return text;
}
// handle text and inline content
function readParagraphElement(document, element)
{
    if(element.textRun)
    {
        // standard text
        return element.textRun.content;
    }
    if(element.inlineObjectElement)
    {
        var objId = element.inlineObjectElement.inlineObjectId;
        var imgTag = "\n[img]404[/img]"

        try
        {
            var embeddedObj = document.data.inlineObjects[objId].inlineObjectProperties.embeddedObject;
            if(embeddedObj.imageProperties)
            {
                // this is an image
                imgTag = `[img]${embeddedObj.imageProperties.contentUri}[/img]`
            }
            else if(embeddedObj.embeddedDrawingProperties)
            {
                // this is a shape/drawing
                // can't find any way to meaningfully reference them externally,
                // so storing the ID in case we can do it later
                imgTag = `[drawing]${objId}[/drawing]`
            }
        }
        catch(exception)
        {
            console.log(exception)
        }
         
        return imgTag;
    }
}

标签: node.jsgoogle-apps-scriptgoogle-docs-apinamed-ranges

解决方案


我相信你的目标如下。

  • 您想从 Google 文档的命名范围中检索值。
  • 在您的 Google 文档中,已设置命名范围。
  • 您想使用 Node.js 来实现这一点。
    • 不幸的是,根据您的问题,我无法确认您正在使用的库是否用于使用 Docs API。

为了实现上述目标,我想提出以下解决方法。

问题和解决方法:

不幸的是,在当前阶段,没有直接从 Google Docs API 中的命名范围检索值的方法。我相信将来可能会添加这种方法,因为 Docs API 现在正在增长。因此,作为当前使用 Docs API 的解决方法,需要执行以下流程。

  1. 使用 Docs API 中的 documents.get 方法检索 Google Document 对象。
  2. 检索startIndexendIndex使用命名范围的名称。
  3. startIndex使用和检索值endIndex

您的问题中已经提到了这一点。使用 Google Docs API 时,在当前阶段,需要使用此方法。但是当使用 Google Document 服务时,命名范围的值可以直接通过命名范围的名称和/或 ID 来检索。在这个答案中,我想将此方法作为另一种解决方法。

用法:

请执行以下流程。

1. 新建 Google Apps Script 项目。

Web Apps 的示例脚本是 Google Apps 脚本。所以请创建一个 Google Apps Script 项目。为了使用 Document 服务,在这种情况下,Web Apps 被用作包装器。

如果要直接创建,请访问https://script.new/。在这种情况下,如果您没有登录 Google,则会打开登录屏幕。所以请登录谷歌。至此,Google Apps Script 的脚本编辑器被打开。

2. 准备脚本。

请将以下脚本(Google Apps 脚本)复制并粘贴到脚本编辑器中。此脚本适用于 Web 应用程序。

function doGet(e) {
  Object.prototype.getText = function() {return this.getRange().getRangeElements().map(e => e.getElement().asText().getText().slice(e.getStartOffset(), e.getEndOffsetInclusive() + 1))};
  const doc = DocumentApp.openById(e.parameter.id);
  let res;
  if (e.parameter.name) {
    const ranges = doc.getNamedRanges(e.parameter.name);
    res = ranges.length > 0 ? ranges[0].getText() : [];
  } else if (e.parameter.rangeId) {
    const range = doc.getNamedRangeById(e.parameter.rangeId.split(".")[1]);
    res = range ? range.getText() : [];
  } else {
    res = [];
  }
  return ContentService.createTextOutput(JSON.stringify(res));
}

3. 部署 Web 应用程序。

  1. 在脚本编辑器上,通过“发布”->“部署为 Web 应用程序”打开一个对话框。
  2. “将应用程序执行为:”选择“我” 。
    • 这样,脚本作为所有者运行。
  3. “谁有权访问应用程序:”选择“任何人,甚至匿名” 。
    • 在这种情况下,不需要请求访问令牌。我认为我建议使用此设置来测试您的目标。
    • 当然,您也可以使用访问令牌。届时,请将此设置为“仅我自己”“任何人”。并请包括访问令牌的范围https://www.googleapis.com/auth/drive.readonly和范围。https://www.googleapis.com/auth/drive这些范围是访问 Web 应用程序所必需的。
  4. 单击“部署”按钮作为新的“项目版本”。
  5. 自动打开“需要授权”对话框。
    1. 单击“查看权限”。
    2. 选择自己的帐户。
    3. 在“此应用未验证”中单击“高级”。
    4. 点击“转到###项目名称###(不安全)”
    5. 单击“允许”按钮。
  6. 单击“确定”。
  7. 复制 Web 应用程序的 URL。就像https://script.google.com/macros/s/###/exec
    • 当您修改 Google Apps 脚本时,请重新部署为新版本。这样,修改后的脚本就会反映到 Web 应用程序中。请注意这一点。

4. 使用 Web Apps 运行该功能。

您可以使用以下脚本从 Google 电子表格中检索值。

const request = require("request");
const url = "https://script.google.com/macros/s/###/exec";  // Please set the URL of Web Apps.
let qs = {
  id: "###",  // Please set the Document ID.
  name: "###",  // Please set the name of named range.
  // rangeId: "kix.###",  // Please set the ID of named range.
};
let options = {
  url: url,
  qs: qs,
  method: "get",
};
request(options, (err, res, result) => {
  if (err) {
    console.log(err);
    return;
  }
  console.log(result);
});
  • 在这种情况下,结果作为包含值的数组返回。
  • 在上述 Web 应用程序中,可以使用命名范围的名称和/或 ID 检索值。当您想使用命名范围的名称时,请使用let qs = {id: "###", name: "###"};. 当您想使用命名范围的 ID 时,请使用let qs = {id: "###", rangeId: "kix.###"};.

笔记:

  • 当您修改 Web Apps 的脚本时,请将 Web Apps 重新部署为新版本。这样,最新的脚本就会反映到 Web 应用程序中。请注意这一点。

参考:


推荐阅读