XMLReader

文档中心 › 组件索引 › XMLReader

XMLReader Controller Service

类型全限定名:cn.org.acal.lynxus.xml.XMLReader

所属 Bundle:cn.org.acal.lynxus:lynxus-record-serialization-services-nar:2.11.8

描述

读取 XML 内容并创建 Record 对象。 记录应位于 XML 数据的第二级中,嵌入在封闭的根标记中。

标签

xmlrecordreaderparser

属性

下表中,加粗的属性为必填项;其余为可选项。表格同时标注默认值、是否支持表达式语言,以及是否为敏感属性(值会被加密)。

名称默认值可选值描述
Schema Access Strategyinfer-schema
  • 使用 'Schema Name' 属性 — 要使用的 Schema 名称由 'Schema Name' 属性指定。此属性的值用于在配置的 Schema Registry 服务中查找 Schema。
  • 使用 'Schema Text' 属性 — Schema 本身的文本由 'Schema Text' 属性指定。此属性的值必须是有效的 Avro Schema。如果使用表达式语言,则 'Schema Text' 属性的值在替换表达式后必须有效。
  • Schema 引用读取器 — Schema 引用信息将通过配置的 Schema 引用读取器服务实现提供。
  • 推断 Schema — 在读取数据时将自动推断数据的 Schema。有关如何推断 Schema 的信息,请参阅组件的使用说明和其他详细信息。
指定如何获取用于解释数据的架构。
Schema Registry控制器服务 API:
SchemaRegistry
cn.org.acal.lynxus-lynxus-standard-services-api-nar-2.11.8
指定用于架构Registry的Controller Service
Schema Name${schema.name}指定要在架构Registry属性中查找的架构名称
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
Schema Version指定在 Schema Registry 中查找 Schema 的版本。如果未指定,则检索 Schema 的最新版本。
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
Schema Branch指定在 Schema Registry 属性中查找 Schema 时使用的分支名称。如果选择的 Schema Registry 不支持分支,则此值将被忽略。
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
Schema Text${avro.schema}Avro 格式的架构文本
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
Schema Reference Reader控制器服务 API:
SchemaReferenceReader
cn.org.acal.lynxus-lynxus-standard-services-api-nar-2.11.8
服务实现负责读取 FlowFile 属性或内容以确定架构参考标识符
Parse XML Attributestrue
  • true
  • false
当 'Schema Access Strategy' 为 'Infer Schema' 且此属性为 'true' 时,XML 属性将被解析并作为新字段添加到记录中。当 Schema 被推断但此属性为 'false' 时,XML 属性及其值将被忽略。
Schema Inference Cache控制器服务 API:
RecordSchemaCacheService
cn.org.acal.lynxus-lynxus-standard-services-api-nar-2.11.8
指定在推断 Schema 时使用的 Schema 缓存。如果未填充缓存,每次都会推断 Schema。但是,如果指定了缓存,将首先查询缓存,如果能找到适用的 Schema,则使用该 Schema 而不是推断。
Expect Records as Arrayfalse
  • false — 每个 FlowFile 将由单条记录组成,不含任何形式的 "包装器"。
  • true — 每个 FlowFile 将由零条或多条记录组成。最外层的 XML 元素应是一个 "包装器",并将被忽略。
  • 使用属性 'xml.stream.is.array' — 是否将 FlowFile 视为单个 Record 或多个 Record 的数组由 'xml.stream.is.array' 属性的值决定。如果属性值为 'true'(不区分大小写),XML Reader 将把 FlowFile 视为一系列 Record 并忽略外层元素。如果属性值为 'false'(不区分大小写),则 FlowFile 被视为单个 Record,不假定有包装元素。如果属性缺失或其值不是 'true' 或 'false',则将抛出异常且不解析任何记录。
此属性定义 Reader 是否期望 FlowFile 由单个记录或一系列带有 "wrapper element" 的记录组成。因为 XML 不提供从流中直接读取一系列 XML 文档的方式,所以通常通过将多个 XML 文档连接在一起并用 "wrapper element" 包裹整个 XML 块来合并。此属性指示 Reader 是否期望 FlowFile 由单个记录或一系列带有将被忽略的 "wrapper element" 的记录组成。
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
Attribute Prefix如果设置此属性,则在将属性添加到记录时,属性名称将在前面添加前缀。
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
内容字段名如果带有内容的标签(例如 <field>content</field>)在架构中定义为嵌套记录,则标签名将用作记录名,此属性的值将用作字段名。如果带有内容的标签应与属性一起解析(例如 <field attribute="123">content</field>),则它们必须定义为记录。在这种情况下,标签名将用作记录名,此属性的值将用作保存原始内容的字段名。属性名将用于创建新的记录字段,其值为属性值。有关更多信息,请参阅 XMLReader 控制器服务文档的 'Additional Details...' 部分。
支持表达式语言:是(使用 FlowFile 属性与环境/系统变量求值)
Date Format指定读取/写入日期字段时使用的格式。如果未指定,日期字段将被视为自纪元(1970 年 1 月 1 日午夜 GMT)以来的毫秒数。如果指定,值必须匹配 Java java.time.format.DateTimeFormatter 格式(例如,MM/dd/yyyy 表示两位数字的月份,后跟两位数字的日期,后跟四位数字的年份,全部以 '/' 字符分隔,如 01/01/2017)。
Time Format指定读取/写入时间字段时使用的格式。如果未指定,时间字段将被视为自纪元(1970 年 1 月 1 日午夜 GMT)以来的毫秒数。如果指定,值必须匹配 Java java.time.format.DateTimeFormatter 格式(例如,HH:mm:ss 表示 24 小时格式的两位数字的小时,后跟两位数字的分钟,后跟两位数字的秒,全部以 ':' 字符分隔,如 18:04:15)。
Timestamp Format指定读取/写入时间戳字段时使用的格式。如果未指定,时间戳字段将被视为自纪元(1970 年 1 月 1 日午夜 GMT)以来的毫秒数。如果指定,值必须匹配 Java java.time.format.DateTimeFormatter 格式(例如,MM/dd/yyyy HH:mm:ss 表示两位数字的月份,后跟两位数字的日期,后跟四位数字的年份,全部以 '/' 字符分隔;然后后跟 24 小时格式的两位数字的小时,后跟两位数字的分钟,后跟两位数字的秒,全部以 ':' 字符分隔,如 01/01/2017 18:04:15)。

状态管理

该组件不存储状态。

受限 (Restricted)

该组件不受限。

提供的 Service API

  • RecordReaderFactory (cn.org.acal.lynxus-lynxus-standard-services-api-nar-2.11.8)

详细说明

XMLReader

XMLReader Controller Service 读取 XML 内容并创建 Record 对象。必须为该 Controller Service 配置一个描述 XML 数据结构的 schema。XML 数据中未在 schema 中定义的字段会被跳过。根据 "Expect Records as Array" 属性设为 "false" 还是 "true",Reader 会期望每个 FlowFile 是单条记录或一个记录数组。

示例:单条记录

<record>
    <field1>content</field1>
    <field2>content</field2>
</record>

记录数组必须被一个根标签包裹。示例:记录数组

<root>
    <record>
        <field1>content</field1>
        <field2>content</field2>
    </record>
    <record>
        <field1>content</field1>
        <field2>content</field2>
    </record>
</root>

示例:简单字段

XML 数据中最简单的类型是只包含内容的标签/字段(没有属性、没有嵌套标签)。 它们可以用简单类型(如 INT、STRING 等)在 schema 中描述。

<root>
    <record>
        <simple_field>content</simple_field>
    </record>
</root>

该记录可以用一个包含单个字段(例如 string 类型)的 schema 描述。提供该 schema 后,Reader 期望记录中出现零次或一次 "simple_field"。

{
  "namespace": "lynxus",
  "name": "test",
  "type": "record",
  "fields": [
    {
      "name": "simple_field",
      "type": "string"
    }
  ]
}

示例:由简单字段组成的数组

数组在 XML 数据中被视为重复出现的标签/字段。对于以下 XML 数据, "array_field" 被视为包裹简单字段的数组,而 "simple_field" 被视为不在 数组中的简单字段。

<record>
    <array_field>content</array_field>
    <array_field>content</array_field>
    <simple_field>content</simple_field>
</record>

该记录可以用以下 schema 描述:

{
  "namespace": "lynxus",
  "name": "test",
  "type": "record",
  "fields": [
    {
      "name": "array_field",
      "type": {
        "type": "array",
        "items": "string"
      }
    },
    {
      "name": "simple_field",
      "type": "string"
    }
  ]
}

如果 schema 中的字段嵌在数组中,Reader 期望该字段在记录中出现零次、一次 或多次。字段 "array_field" 原则上也可以定义为简单字段,但那样该字段的 第二次出现会替换记录对象中的第一次出现。反过来,字段 "simple_field" 也 可以定义为数组,此时 Reader 会把它作为只有一个元素的数组放入记录对象。

示例:带属性的标签

XML 字段往往不仅包含内容,还包含属性。以下记录包含一个带有属性 "attr" 和内容的字段:

<record>
    <field_with_attribute attr="attr_content">content of field</field_with_attribute>
</record>

要把字段 "field_with_attribute" 的内容与属性 "attr" 一起解析,需要 满足两个要求:

  • 在 schema 中,该字段必须定义为 record。
  • 必须设置 "Field Name for Content" 属性。
  • 可选地,也可以设置 "Attribute Prefix" 属性。

对于上面的例子,假设属性设置如下:

属性名 属性值
Field Name for Content field_name_for_content
Attribute Prefix prefix_

Schema 可以定义如下:

{
  "name": "test",
  "namespace": "lynxus",
  "type": "record",
  "fields": [
    {
      "name": "field_with_attribute",
      "type": {
        "name": "RecordForTag",
        "type": "record",
        "fields": [
          {
            "name": "attr",
            "type": "string"
          },
          {
            "name": "field_name_for_content",
            "type": "string"
          }
        ]
      }
      ]
    }

注意,字段 "field_name_for_content" 不仅要在属性配置中定义,还要在 schema 中定义;而属性的前缀不属于 schema 的一部分。当在 XML 数据的相应 位置发现名为 "attr" 的属性时,会追加前缀并添加到记录中。上面示例的记录 对象结构如下:

Record (
    Record "field_with_attribute" (
        RecordField "prefix_attr" = "attr_content",
        RecordField "field_name_for_content" = "content of field"
    )

原则上,字段 "field_with_attribute" 也可以定义为简单字段,此时属性会被 直接忽略。反过来,上面示例 1 中的简单字段也可以定义为 record(前提是设置 了 "Field Name for Content" 属性)。

也可能不显式提供 schema 而使用 schema 推断。关于 XML 属性与 schema 推断的详情,参见下文"示例:带属性的标签与 Schema 推断"。

示例:标签中嵌套标签

XML 数据经常是嵌套的。此时标签包裹着其他标签:

<record>
    <field_with_embedded_fields attr="attr_content">
        <embedded_field>embedded content</embedded_field>
        <another_embedded_field>another embedded content</another_embedded_field>
    </field_with_embedded_fields>
</record>

无论外层字段是否包含要解析的属性,都必须定义为 record。本例中,标签 "field_with_embedded_fields" 包裹着 "embedded_field" 和 "another_embedded_field" 两个简单字段。Schema 可以定义如下:

{
  "name": "test",
  "namespace": "lynxus",
  "type": "record",
  "fields": [
    {
      "name": "field_with_embedded_fields",
      "type": {
        "name": "RecordForEmbedded",
        "type": "record",
        "fields": [
          {
            "name": "attr",
            "type": "string"
          },
          {
            "name": "embedded_field",
            "type": "string"
          },
          {
            "name": "another_embedded_field",
            "type": "string"
          }
        ]
      }
      ]
    }

注意,这种情况不需要设置 "Field Name for Content" 属性,它只在标签同时 包含属性和内容时才需要。

示例:记录数组(Array of records)

为进一步说明该 Reader 的逻辑,演示一个记录数组的例子。以下记录包含重复出现的 字段 "array_field",每个字段包含两个嵌套字段。

``xml <array_field> <embedded_field>embedded content 1</embedded_field> <another_embedded_field>another embedded content 1</another_embedded_field> </array_field> <array_field> <embedded_field>embedded content 2</embedded_field> <another_embedded_field>another embedded content 2</another_embedded_field> </array_field>


该 XML 数据的解析方式与"标签中嵌套标签"示例类似。不过,该示例中定义的
record 必须嵌入一个数组。

``json
{
  "namespace": "lynxus",
  "name": "test",
  "type": "record",
  "fields": [
    {
      "name": "array_field",
      "type": {
        "type": "array",
        "items": {
          "name": "RecordInArray",
          "type": "record",
          "fields": [
            {
              "name": "embedded_field",
              "type": "string"
            },
            {
              "name": "another_embedded_field",
              "type": "string"
            }
          ]
        }
      }
    }
  ]
}

示例:记录中的数组(Array in record)

在 XML 数据中,数组经常被标签包裹:

``xml <field_enclosing_array> content 1 content 2 </field_enclosing_array> <field_without_array>content 3</field_without_array>


对于 schema,嵌套标签必须用 record 描述。因此,字段 "field\_enclosing\_array"
是一个嵌入了数组字段的 record,该数据可以用以下 schema 处理:

``json
{
  "namespace": "lynxus",
  "name": "test",
  "type": "record",
  "fields": [
    {
      "name": "field_enclosing_array",
      "type": {
        "name": "EmbeddedRecord",
        "type": "record",
        "fields": [
          {
            "name": "element",
            "type": {
              "type": "array",
              "items": "string"
            }
          }
        ]
      }
    },
    {
      "name": "field_without_array",
      "type": "string"
    }
  ]
}

示例:Map

Map 是嵌入了不同名称字段的字段:

``xml <map_field> content content ... </map_field> <simple_field>content</simple_field>


该数据可以用以下 schema 处理:

``json
{
  "namespace": "lynxus",
  "name": "test",
  "type": "record",
  "fields": [
    {
      "name": "map_field",
      "type": {
        "type": "map",
        "items": string
      }
    },
    {
      "name": "simple_field",
      "type": "string"
    }
  ]
}

Schema 推断

虽然 Lynxus 的 Record API 要求每条记录都有 schema,但基于数据中的值来 推断 schema 往往比手动创建更方便。做法是把 "Schema Access Strategy" 属性设为 "Infer Schema"。使用该策略时,Reader 会先解析 FlowFile 中的 所有数据,记录遇到的所有字段及每个字段的类型。所有数据解析完成后,会 形成一个涵盖所有已遇到字段的 schema。

推断 schema 时的一个常见顾虑是如何处理两个值类型不同的情况。例如,考虑 包含以下两条记录的 FlowFile:

<root>
    <record>
        <name>John</name>
        <age>8</age>
        <values>N/A</values>
    </record>
    <record>
        <name>Jane</name>
        <age>Ten</age>
        <values>8</values>
        <values>Ten</values>
    </record>
</root>

显然 "name" 字段会被推断为 STRING 类型。但 "age" 字段该如何处理?应该是 INT 和 STRING 之间的 CHOICE 吗?应该优先用 LONG 而不是 INT 吗?直接用 STRING 吗?该字段应视为可为 null 吗?

为帮助理解该 Record Reader 如何推断 schema,以下是推断逻辑所遵循的规则:

  • 所有字段都被推断为可为 null。
  • 当两条不同记录中同一字段遇到两个值(或 ARRAY 类型遇到两个值)时, 推断引擎倾向于使用"更宽"的数据类型,而不是 CHOICE 类型。当且仅当数据 类型 "A" 除了包含 "B" 的所有值外还包含其他值时,才称 "A" 比 "B" 宽。 例如,LONG 类型比 INT 宽,但不比 BOOLEAN 宽(BOOLEAN 也不比 LONG 宽)。 INT 比 SHORT 宽。STRING 类型被认为比除 MAP、RECORD、ARRAY、CHOICE 之外的所有类型都宽。
  • 当两条不同记录中同一字段遇到两个值(或 ARRAY 类型遇到两个值), 但两个值的类型互不比对方宽时,使用 CHOICE 类型。在上面的例子中, "values" 字段会被推断为 STRING 或 ARRAY 之间的 CHOICE。
  • 如果配置了 "Time Format"、"Timestamp Format" 或 "Date Format" 属性, 任何原本会被视为 STRING 类型的值,都会先与配置的格式比对,看是否匹配。 如果值匹配 Timestamp Format,视为 Timestamp 字段;匹配 Date Format 视为 Date 字段;匹配 Time Format 视为 Time 字段。如果值碰巧匹配多个 配置的格式,按 Timestamp、Date、Time 的顺序匹配。也就是说,如果一个值 同时匹配 Timestamp Format 和 Date Format,推断出的类型是 Timestamp。 由于解析日期和时间开销较大,如果不预期出现日期、时间和时间戳,或者可以 接受按 STRING 处理,建议不要配置这些格式。对于需要此功能的用例, 推断引擎足够智能,会先检查若干非常廉价的条件来优化解析。例如,先检查 字符串长度是否过长或过短、不可能匹配模式。这比尝试把每个字符串值都 解析为时间戳要高效得多。
  • MAP 类型永远不会被推断,而是使用 RECORD 类型。
  • 如果存在两个同名且同父的元素(即两个兄弟元素同名),该字段会被推断为 ARRAY 类型。
  • 如果字段存在但所有值都是 null,该字段被推断为 STRING 类型。

推断 Schema 的缓存

该 Record Reader 要求:如果要推断 schema,必须读取所有记录,以确保推断出的 schema 适用于 FlowFile 中的所有记录。但这可能开销很大,尤其是数据要经过 很多不同转换时。为降低推断成本,可以通过设置同名属性为该 Record Reader 配置 "Schema Inference Cache"。这是一个可以在 Record Reader 和 Record Writer 之间共享的 Controller Service。

每当使用 Record Writer 写出数据时,如果它配置了 "Schema Cache",也会把 schema 添加到 Schema Cache 中。这会使该 schema 的标识符作为 Attribute 添加到 FlowFile 上。

每当使用 Record Reader 读取数据时,如果它配置了 "Schema Inference Cache", 会先在 FlowFile 上查找 "schema.cache.identifier" Attribute。如果该 Attribute 存在,就用它的值在 schema 缓存中查找 schema。如果能在缓存中 找到该标识符对应的 schema,就直接使用,而不必读取、解析和分析数据来 推断。如果 FlowFile 上没有该 Attribute,或缓存中没有对应标识符的 schema,Record Reader 会按上述方式推断。

最终效果是:用户可以串联许多不同的 Processor 来处理面向 Record 的数据。 通常只有链中的第一个 Processor 需要承担推断 schema 的"代价"。对链中的 其他所有 Processor,Record Reader 可以直接按标识符在 Schema Cache 中 查找。这使得 Record Reader 既能准确推断(基于 FlowFile 中的所有数据), 又能高效执行(无论多少 Processor 处理数据,通常只推断一次)。