Skip to content

OFD 自定义标引与公文节点

本节介绍自定义标引(CustomTags / CustomTag)、标引树节点(TagTreeNode)以及公文语义节点(OfficeNode)的访问与编辑。这些能力用于公文、档案、电子证照等场景下的语义标引与结构树,不属于 图元与图层 章节(图元遍历请参阅该章)。

任务场景

  • 通过 CustomTag 维护标引类型,并将外部文件与标引条目绑定(SetFile / ExtractFile)。
  • 使用 OFDDoc::InitializeTagTreeNode 等接口构建或读取票据/证照类标引树(TagTreeNode),并关联页面图元。
  • 从 XML 导入或流式构建公文语义结构(CreateOfficeNodeByXmlBufferBeginOfficeNode / EndOfficeNode)。
  • 手动维护 OfficeNode 子节点树,将页面图元挂载到公文节点,并设置权限与 Schema 属性。

API 概览

功能C++ API(foxit::ofd核心参数 / 描述
文档入口OFDDoc::CreateCustomTagsGetCustomTags创建或获取文档级 CustomTags 集合
自定义标引条目CustomTagGetType / SetTypeSetFile / ExtractFile 绑定或导出外部附件
标引集合与公文入口CustomTagsGetCustomTagCountAddCustomTagGetCustomTag
CreateOfficeNodeGetOfficeNode;XML 导入与流式构建
标引树(文档级写入)OFDDoc::InitializeTagTreeNodeBeginTagTreeNode
AddTagTreeNodeMetaDataEndTagTreeNode
type 初始化标引树;InitializeCertTagTreeNode 为电子证照快捷入口
标引树(读取)OFDDoc::GetTagTreeRoot按类型字符串(如 "Cert""od")获取根 TagTreeNode
标引树节点TagTreeNodeGetKeyGetText;递归 GetTagTreeNodeGetGraphicsObject 关联 graphics::OFDGraphicsObject
公文语义节点OfficeNodeAddOfficeNodeGetOfficeNodeAddGraphicsObject(page, rect)AddGraphicsObject(page_id, obj_id)SetPermissionSetOfficeSchema
公文 XML 导出OFDDoc::ExportOfficeDoc将公文语义导出为 XML 文件

自定义标引与外部文件

通过 OFDDoc::CreateCustomTags 创建标引集合,使用 AddCustomTag 添加条目(索引 -1 表示追加到末尾)。SetType 设置标引类型,SetFile / ExtractFile 用于绑定或导出关联的外部文件。

c++
#include "ofd/fs_ofdpackage.h"
#include "ofd/fs_ofddoc.h"
#include "ofd/fs_ofdcustomtag.h"

using namespace foxit;
using namespace foxit::ofd;

bool ConfigureCustomTag(OFDDoc& doc, const char* type, const char* payload_path) {
  if (doc.IsEmpty()) {
    return false;
  }

  CustomTags custom_tags = doc.CreateCustomTags();
  if (custom_tags.IsEmpty()) {
    return false;
  }

  // index = -1 表示在列表末尾追加
  CustomTag custom_tag = custom_tags.AddCustomTag(-1);
  if (custom_tag.IsEmpty()) {
    return false;
  }

  if (!custom_tag.SetType(type)) {
    return false;
  }

  return custom_tag.SetFile(payload_path);
}
java
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.CustomTag;
import com.foxit.sdk.ofd.CustomTags;

public class OFDCustomTagExample {
    public static boolean configureCustomTag(OFDDoc doc, String type, String payloadPath)
            throws Exception {
        if (doc.isEmpty()) {
            return false;
        }

        CustomTags customTags = doc.createCustomTags();
        if (customTags.isEmpty()) {
            return false;
        }

        CustomTag customTag = customTags.addCustomTag(-1);
        if (customTag.isEmpty()) {
            return false;
        }

        if (!customTag.setType(type)) {
            return false;
        }

        return customTag.setFile(payloadPath);
    }
}

从 XML 构建公文结构

公文语义节点挂在 CustomTags 下。常见方式包括:

  1. 一次性导入CreateOfficeNodeByXmlBufferCreateOfficeNodeByXmlFile,传入符合规范的公文 XML。
  2. 流式构建CreateOfficeNode 创建根节点后,成对调用 BeginOfficeNode / EndOfficeNode 模拟 XML 层级(标签名需正确嵌套)。
  3. 手动建树GetOfficeNode 取得根节点后,通过 OfficeNode::AddOfficeNode 添加子节点。
c++
#include "ofd/fs_ofdcustomtag.h"

using namespace foxit;
using namespace foxit::ofd;

bool ImportOfficeNodeFromXml(CustomTags& custom_tags, const char* xml_path) {
  if (custom_tags.IsEmpty()) {
    return false;
  }

  // 须先存在至少一条 CustomTag(与 SDK 单测用法一致)
  if (custom_tags.GetCustomTagCount() <= 0) {
    custom_tags.AddCustomTag(-1);
  }

  if (!custom_tags.CreateOfficeNodeByXmlFile(xml_path)) {
    return false;
  }

  OfficeNode root = custom_tags.GetOfficeNode();
  return !root.IsEmpty();
}

void BuildOfficeNodeByStream(CustomTags& custom_tags) {
  if (custom_tags.IsEmpty()) {
    return;
  }

  if (custom_tags.GetCustomTagCount() <= 0) {
    custom_tags.AddCustomTag(-1);
  }

  custom_tags.CreateOfficeNode();

  // Begin/End 成对调用,形成嵌套结构(类似 XML 标签栈)
  custom_tags.BeginOfficeNode("root");
  custom_tags.BeginOfficeNode("section");
  custom_tags.EndOfficeNode("section");
  custom_tags.EndOfficeNode("root");

  OfficeNode root = custom_tags.GetOfficeNode();
}
java
import com.foxit.sdk.ofd.CustomTags;
import com.foxit.sdk.ofd.OfficeNode;

public class OFDOfficeNodeXmlExample {
    public static boolean importOfficeNodeFromXml(CustomTags customTags, String xmlPath)
            throws Exception {
        if (customTags.isEmpty()) {
            return false;
        }

        if (customTags.getCustomTagCount() <= 0) {
            customTags.addCustomTag(-1);
        }

        if (!customTags.createOfficeNodeByXmlFile(xmlPath)) {
            return false;
        }

        OfficeNode root = customTags.getOfficeNode();
        return !root.isEmpty();
    }

    public static void buildOfficeNodeByStream(CustomTags customTags) throws Exception {
        if (customTags.isEmpty()) {
            return;
        }

        if (customTags.getCustomTagCount() <= 0) {
            customTags.addCustomTag(-1);
        }

        customTags.createOfficeNode();
        customTags.beginOfficeNode("root");
        customTags.beginOfficeNode("section");
        customTags.endOfficeNode("section");
        customTags.endOfficeNode("root");
    }
}

XML 结构示例:

xml
<?xml version="1.0" encoding="UTF-8"?>
<officeRoot xmlns:od="http://www.officedocument.org">
  <section>CustomTagUT</section>
</officeRoot>

构建与遍历标引树

标引树的 写入OFDDoc 上完成:先 InitializeTagTreeNode(或 InitializeCertTagTreeNode),再通过 BeginTagTreeNode / AddTagTreeNodeMetaData / EndTagTreeNode 添加节点。读取时使用 GetTagTreeRoot(type) 取得根 TagTreeNode,再递归访问子节点与关联图元。

c++
#include "ofd/fs_ofddoc.h"
#include "ofd/fs_ofdtagtreenode.h"
#include "ofd/graphics/fs_ofdgraphicsobject.h"

using namespace foxit;
using namespace foxit::ofd;
using namespace foxit::ofd::graphics;

void BuildCustomTagTree(OFDDoc& doc) {
  if (doc.IsEmpty()) {
    return;
  }

  const char* tree_type = "UTCustomTree";
  doc.ClearTagTreeNode();

  doc.InitializeTagTreeNode(tree_type, "urn:example:tagtree", "示例标引树",
                            "TagTree", true);
  doc.BeginTagTreeNode("invoice");
  doc.AddTagTreeNodeMetaData("DocNo", "INV-001");
  doc.AddTagTreeNodeMetaData("Issuer", "Foxit");
  doc.EndTagTreeNode("invoice");
}

void TraverseTagTreeNode(TagTreeNode& node) {
  if (node.IsEmpty()) {
    return;
  }

  String key = node.GetKey();
  String text = node.GetText();

  // 读取与本节点关联的页面图元
  int graphics_count = node.GetGraphicsObjectCount();
  for (int i = 0; i < graphics_count; ++i) {
    OFDGraphicsObject obj = node.GetGraphicsObject(i);
    if (!obj.IsEmpty()) {
      ObjectType type = obj.GetType();
    }
  }

  // 递归子节点
  int child_count = node.GetTagTreeNodeCount();
  for (int i = 0; i < child_count; ++i) {
    TagTreeNode child = node.GetTagTreeNode(i);
    TraverseTagTreeNode(child);
  }
}

void ReadTagTreeRoot(OFDDoc& doc, const char* tree_type) {
  TagTreeNode root = doc.GetTagTreeRoot(tree_type);
  if (!root.IsEmpty()) {
    TraverseTagTreeNode(root);
  }
}
java
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.TagTreeNode;
import com.foxit.sdk.ofd.graphics.OFDGraphicsObject;

public class OFDTagTreeExample {
    public static void buildCustomTagTree(OFDDoc doc) throws Exception {
        if (doc.isEmpty()) {
            return;
        }

        String treeType = "UTCustomTree";
        doc.clearTagTreeNode();

        doc.initializeTagTreeNode(treeType, "urn:example:tagtree", "示例标引树",
                "TagTree", true);
        doc.beginTagTreeNode("invoice");
        doc.addTagTreeNodeMetaData("DocNo", "INV-001");
        doc.addTagTreeNodeMetaData("Issuer", "Foxit");
        doc.endTagTreeNode("invoice");
    }

    public static void traverseTagTreeNode(TagTreeNode node) throws Exception {
        if (node.isEmpty()) {
            return;
        }

        String key = node.getKey();
        String text = node.getText();

        int graphicsCount = node.getGraphicsObjectCount();
        for (int i = 0; i < graphicsCount; i++) {
            OFDGraphicsObject obj = node.getGraphicsObject(i);
            if (!obj.isEmpty()) {
                int type = obj.getType();
            }
        }

        int childCount = node.getTagTreeNodeCount();
        for (int i = 0; i < childCount; i++) {
            TagTreeNode child = node.getTagTreeNode(i);
            traverseTagTreeNode(child);
        }
    }

    public static void readTagTreeRoot(OFDDoc doc, String treeType) throws Exception {
        TagTreeNode root = doc.getTagTreeRoot(treeType);
        if (!root.isEmpty()) {
            traverseTagTreeNode(root);
        }
    }
}

操作公文节点

取得 CustomTags::GetOfficeNode 后,可手动添加组节点或元素节点,并将页面图元绑定到节点:

  • AddGraphicsObject(OFDPage, RectF):按页面与矩形区域关联图元(由 SDK 在区域内查找匹配)。
  • AddGraphicsObject(page_id, obj_id):按页面 ID 与图元 ID 精确绑定。
c++
#include "ofd/fs_ofdcustomtag.h"
#include "ofd/fs_ofdofficenode.h"
#include "ofd/fs_ofdpage.h"

using namespace foxit;
using namespace foxit::ofd;

void ConfigureOfficeNode(CustomTags& custom_tags, OFDPage& page) {
  if (custom_tags.IsEmpty() || page.IsEmpty()) {
    return;
  }

  if (custom_tags.GetCustomTagCount() <= 0) {
    custom_tags.AddCustomTag(-1);
  }

  OfficeNode root = custom_tags.CreateOfficeNode();
  if (root.IsEmpty()) {
    return;
  }

  root.SetOfficeSchema("xmlns:od", "http://www.officedocument.org", "officeRoot");
  root.SetPermission(true, true, true, "replace-text", true);

  // group = true 表示组节点,可包含子节点
  OfficeNode section = root.AddOfficeNode("section", true);
  if (!section.IsEmpty()) {
    section.SetAttributeValue("Version", "1.0");

    // 方式一:按页面 ID + 图元 ID 精确绑定
    section.AddGraphicsObject(static_cast<uint32>(page.GetID()), 0);

    // 方式二:按页面与矩形区域绑定
    section.AddGraphicsObject(page, RectF(0.0f, 0.0f, 500.0f, 500.0f));
  }
}
java
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.ofd.CustomTags;
import com.foxit.sdk.ofd.OFDPage;
import com.foxit.sdk.ofd.OfficeNode;

public class OFDOfficeNodeExample {
    public static void configureOfficeNode(CustomTags customTags, OFDPage page)
            throws Exception {
        if (customTags.isEmpty() || page.isEmpty()) {
            return;
        }

        if (customTags.getCustomTagCount() <= 0) {
            customTags.addCustomTag(-1);
        }

        OfficeNode root = customTags.createOfficeNode();
        if (root.isEmpty()) {
            return;
        }

        root.setOfficeSchema("xmlns:od", "http://www.officedocument.org", "officeRoot");
        root.setPermission(true, true, true, "replace-text", true);

        OfficeNode section = root.addOfficeNode("section", true);
        if (!section.isEmpty()) {
            section.setAttributeValue("Version", "1.0");
            section.addGraphicsObject(page.getID(), 0);
            section.addGraphicsObject(page, new RectF(0.0f, 0.0f, 500.0f, 500.0f));
        }
    }
}

注意事项

  • 数据模型分离OfficeNode / TagTreeNode 描述语义结构,与页面内容流中的图元遍历是不同模型,请勿在 图元与图层 章节混用。
  • CustomTag 前置条件:操作 OfficeNode(创建、XML 导入、流式构建)前,建议先通过 AddCustomTag 添加至少一条自定义标引,与 SDK 用法保持一致。
  • 对象有效性:调用任何方法前使用 IsEmpty() 检查 CustomTagCustomTagsTagTreeNodeOfficeNode 是否有效。
  • 标引树类型参数GetTagTreeRootInitializeTagTreeNodetype 须与初始化时一致(如 "Cert""od" 或自定义类型字符串)。
  • XML 格式CreateOfficeNodeByXmlBuffer / CreateOfficeNodeByXmlFile 要求 XML 符合 OFD 公文语义规范;格式错误将返回失败(单测中对畸形 XML 验证为 false)。
  • Begin/End 配对BeginOfficeNodeEndOfficeNodeBeginTagTreeNodeEndTagTreeNode 须按层级成对调用,标签名与嵌套顺序需与目标结构一致。
  • 持久化:标引与公文节点修改均为内存操作,须通过 OFDPackage::SaveSaveAs 写回文件,请参阅 OFD 文件包