使用 Google Docs API 检索 namedRange 中的文本

GS_*_*Dan 1 named-ranges google-docs-api node.js google-apps-script

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

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

干杯

编辑:

下面 Tanaike 的解决方案更简洁,但我已经有一个版本可以在我的 Firebase 函数上运行,所以我想我不妨分享一下。此代码检索具有给定 ID 的 Google 文档,并将 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);
    })
}
Run Code Online (Sandbox Code Playgroud)
// 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;
}
Run Code Online (Sandbox Code Playgroud)
// 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Tan*_*ike 5

我相信你的目标如下。

  • 您想从 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 项目。为了使用文档服务,在这种情况下,使用 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));
}
Run Code Online (Sandbox Code Playgroud)

3. 部署 Web 应用程序。

  1. 在脚本编辑器上,通过“发布”->“部署为 Web 应用程序”打开一个对话框。
  2. “将应用程序执行为:”选择“我
    • 通过这种方式,脚本以所有者身份运行。
  3. “谁有权访问应用程序:”选择“任何人,甚至匿名
    • 在这种情况下,不需要请求访问令牌。我认为我推荐使用此设置来测试您的目标。
    • 当然,您也可以使用访问令牌。届时,请将其设置为“仅我自己”“任何人”。并且请包括访问令牌的范围https://www.googleapis.com/auth/drive.readonlyhttps://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 Apps 中。请注意这一点。

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);
});
Run Code Online (Sandbox Code Playgroud)
  • 在这种情况下,结果作为包含值的数组返回。
  • 在上述 Web 应用程序中,可以使用命名范围的名称和/或 ID 检索值。当您想使用命名范围的名称时,请使用let qs = {id: "###", name: "###"};. 当您想使用命名范围的 ID 时,请使用let qs = {id: "###", rangeId: "kix.###"};.

笔记:

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

参考: