跳转到主要内容

Python中ISIS文件的I/O

项目描述

IOISIS - 转换Python中ISIS数据的I/O工具

这是一个Python库,具有命令行界面(CLI),旨在访问ISIS数据库文件并转换不同文件格式之间的数据。

CLI中可用的转换器有

命令 描述
bruma-mst2csv 基于Bruma的MST+XRF到CSV
bruma-mst2jsonl 基于Bruma的MST+XRF到JSON Lines
csv2iso CSV到ISO2709
csv2jsonl CSV到JSON Lines
csv2mst CSV到ISIS/FFI主文件格式
iso2csv ISO2709到CSV
iso2jsonl ISO2709到JSON Lines
jsonl2csv JSON Lines到CSV
jsonl2iso JSON Lines到ISO2709
jsonl2mst JSON Lines到ISIS/FFI主文件格式
mst2csv ISIS/FFI主文件格式到CSV
mst2jsonl ISIS/FFI主文件格式到JSON Lines

注意bruma-* 命令和 bruma 模块通过 JPype 使用了特定预编译版本的 Bruma,这需要 JVM(Java 虚拟机)。isomst 模块,以及其他模块和 CLI 命令不需要 Bruma。Bruma 仅在其首次使用时下载。

Bruma 的基于 Python 的替代品是从头创建的,它基于 Construct,这是一个 Python 库,允许声明式地实现解析和构建的二元文件结构。目前,可以使用该库解析/构建 ISO(基于 ISO2709 的文件格式)、MST(ISIS/FFI 主文件格式)和 XRF(ISIS/FFI 交叉引用文件格式)文件格式,但 Bruma 不可独立库/CLI 使用或构建 XRF 文件。

关于解析/构建过程的几乎所有细节都可以在库和 CLI 中进行配置,包括 CISIS 特定的 MST 文件的各种变体。CISIS 的序列化行为取决于架构和编译标志,但 ioisis 可以处理大多数(可能所有)由某些特定 CISIS 版本生成/读取的不同的 MST "文件格式"。

ioisis 中的所有内容都是平台无关的,其中大多数默认值基于 lindG4 版本的 CISIS,以及 isis2jsonMongoDB 类型 1-mt1)输出。一些 CLI 命令的 --xylose 选项将 JSONL 默认值切换为 Xylose 所期望的字典结构。

安装和测试

它需要 Python 3.6+,并已准备使用 toxpytest 在每个 Python 版本中进行测试。

# Installation
pip install ioisis

# Testing (one can install tox with "pip install tox")
tox                      # Test on all Python versions
tox -e py38 -- -k scanf  # Run "scanf" tests on Python 3.8

命令行界面 (CLI)

要使用 CLI 命令,请使用 ioisispython -m ioisis。示例

# Convert file.mst to a JSONL in the standard output stream
ioisis mst2jsonl file.mst

# Convert file.iso in UTF-8 to an ASCII file.jsonl
ioisis iso2jsonl --ienc utf-8 --jenc ascii file.iso file.jsonl

# Convert file.jsonl to file.iso where the JSON lines are like
# {"tag": ["field", ...], ...}
ioisis jsonl2iso file.jsonl file.iso

# Convert big-endian lindG4 MST data to CSV (one line for each field)
# ignoring noise in the MST file that might appear between records
# (it can access data from corrupt MST files)
ioisis mst2csv --ibp ignore --be file.mst file.csv

# Convert active and logically deleted records from file.mst
# to filtered.mst, selecting records and filtering out fields with jq,
# using a "v" prefix to the field tags,
# reseting the MFN to 1, 2, etc. while keeping its order
# instead of using the in-file order, besides enforcing a new encoding,
# with a file that might already have some records partially in UTF-8
ioisis bruma-mst2jsonl --all --ftf v%z --menc latin1 --utf8 file.mst \
| jq -c 'select(.v35 == ["PRINT"]) | del(.v901) | del(.v540)'
| ioisis jsonl2mst --ftf v%z --menc latin1 - filtered.mst

默认情况下,输入和输出是标准流,但某些命令需要文件名,而不是管道/流。由于 XRF 将基于它找到(仅 bruma-* 命令需要 XRF),Bruma 需要MST 输入为文件名。由于第一个记录(控制记录)包含一些仅在生成整个文件后才可用的信息(即它在文件末尾创建),因此需要随机访问,*2mst 命令需要 MST 输出文件名。

所有命令都有一个别名:它们的名称只有扩展名(或 bruma-b)的第一个字符。尝试 ioisis --help 获取有关所有命令的更多信息,并尝试 ioisis csv2mst --help 获取 csv2mst 的具体帮助(每个命令都有自己的帮助)。

所有文件编码均通过 --_enc 选项显式指定,其中 _ 应替换为文件扩展名的第一个字母,因此 --menc 有 MST 编码,--cenc 有 CSV 编码,等等。对于 bruma-* 命令,--menc 在 Java 中处理,所有其他编码选项都在 Python 中处理。--utf8 选项强制将输入处理为 UTF-8,并且只有不在该编码中的部分才由特定文件格式编码处理,即 --_enc 选项成为 UTF-8 的后备。这有助于从具有混合编码数据的数据库中加载数据。

JSON/CSV 模式,字段和子字段处理

CLI 命令还有其他几个选项旨在自定义处理过程,其中最重要的选项可能是 -m/--mode,它涉及 JSONL 文件中的字段和记录格式(以及 -M/--cmode,它对 CSV 文件执行相同的操作)。它的有效值是

  • field默认值):使用原始字段值字符串(忽略子字段解析选项)
  • pairs:将字段字符串拆分为 [key, value] 子字段对的数组
  • nest:将字段字符串按{key: value}对象分割,当键出现多次时保留最后一个子字段的值
  • inest:类似于CISIS子字段嵌套处理,与nest类似,但保留键的第一个条目而不是最后一个(仅在--no-number时有所不同)
  • tidy:表格格式,记录被分割,每个字段被视为一条单独的JSON行,例如:{"mfn": mfn, "index": index, "tag": field_key, "data": value}
  • stidy:子字段整理格式,与tidy格式类似,但字段本身被分割,每个子字段被视为结果中的单独JSON行,包括子字段键在结果的"sub"键中

当与--no-number一起使用时,fieldpairsnest模式分别类似于isis2json-mt1-mt2-mt3选项。inest模式在isis2json中不可用,它遵循CISIS在子字段查询上的行为。对于CSV,只有tidystidy格式可用,因为其余格式不是表格形式。

--ftf是一个期望处理字段标记的字段标记格式化器模板的选项,它对JSON/CSV输出(渲染/构建)和输入(解析)都适用。这些是解释后的序列

  • %d:标记号
  • %r:原始格式下的标记字符串。
  • %z:与%r相同,但移除了ISO标记的前导零
  • %i:记录中字段索引号,从零开始
  • %%%字符的转义符

注意%d%i选项可能在中间有数字参数,类似于printf%d(例如,回忆Python中的"%03d" % 15)。

对于子字段处理,有几种选项可用

  • --prefix:字段文本中开始新子字段的字符/字符串
  • --length:子字段键/标记的大小(字符数)
  • --lower/--no-lower:用于子字段键/标记规范化的切换,通过简单地将其转换为小写来实现
  • --first:在第一个前缀出现之前由前导字段数据使用的子字段键/标记
  • --empty/--no-empty:切换以显示/隐藏没有任何字符的子字段(除了子字段键/标记)
  • --number/--no-number:通过在第一个重复时添加数字后缀来处理重复的子字段键,从1开始,此选项切换此行为(是否添加后缀)
  • --zero/--no-zero:选择每个子字段键在每个字段中的第一次出现是否应有0后缀,以遵循前一个选项中描述的编号(当--no-number时没有效果)
  • --sfcheck/--no-sfcheck(仅适用于JSONL/CSV输入):检查之前参数中给出的子字段解析/反解析规则规范是否可以精确地重新生成所有输入字段(仅在JSONL/CSV输入时使用)

--xylose选项只是使用"--mode=inest --ftf=v%z"的另一种方法。为了更类似于isis2json的输出,同时仍然利用Xylose期望的格式,您应该使用"--mode=nest --no-number --ftf=v%z"。

常见的MST/ISO输入选项

MST和ISO记录都具有STATUS标志,该标志回答了这个问题:此记录是否逻辑上已删除?STATUS等于1表示True(已删除),0表示False(活动)。

在MST文件结构中的每个记录都有一个MFN,这是记录在数据库中的序列号/ID。bruma-mst2*命令和mst2*命令之间的一个主要区别在于它们处理MFN的方式:Bruma总是通过XRF文件访问MST文件,通过跳转地址来迭代记录,按MFN排序,而Python实现则按照块/偏移量顺序获取记录(即它们在输入文件中出现的顺序)。对于ISO文件,没有存储MFN,但ioisis可以生成它(从1开始,如常见的MST记录),如果需要的话(例如,创建CSV文件)。

这些选项在从MST或ISO文件读取时适用于多个命令

  • --only-active/--all:标志,用于选择是否将STATUS=1的记录(逻辑上已删除的记录)包含在输出中
  • --prepend-mfn/--no-mfn:在每条记录的开头添加一个名为mfn的伪字段,其中包含记录MFN的字符串(尽管它始终是数字)
  • --prepend-status/--no-status:在每条记录的开头添加一个名为status的伪字段,其中包含记录的状态字符串(尽管它通常是零或一)

ISO特定选项

可以将ISO文件视为一系列粘合在一起的记录。每个记录包含3部分:一个引导、一个目录字段值。引导部分包含一些元数据,其中大多数只能通过库访问,而不能通过CLI(只有状态由CLI使用)。目录是一个由常规模块组成的序列(目录项),每个项代表一个单独的字段(其标签、其值长度和其相对偏移量),它与记录最后部分中的相应值相匹配。

在ISO文件内部,在目录之后和每个字段值之间,有一个<强>字段终止符。在记录的末尾,有一个<强>字段终止符,最后还有一个<强>记录终止符。默认情况下,CISIS使用"#"作为终止符,对于字段和记录都是相同的,这也是ioisis的默认设置。但对于输入/输出文件,情况并不总是如此。例如,在MARC21规范中,字段终止符是"\x1e"字符,记录终止符是"\x1d"字符。

这些是ISO I/O命令的选项

  • --ft:ISO字段终止符
  • --rt:ISO记录终止符
  • --line:分割记录的行长度(不计EOL)
  • --eol:行末(EOL)字符或字符串,如果--line=0则忽略

它们的默认值是CISIS的值,目的是使ISO文件看起来像一个普通的文本文件。默认情况下,每个ISO记录(原始字节)被分成80字节的行,并在记录终止符后打印EOL,因此两个记录不会共享同一行。行分割是CISIS特定的行为,这是打开它导出的ISO文件所必需的,并且可能会使调试更容易。使用"--line=0"禁用此行为,将所有内容合并成一行。终止符可能包含多个字符,以及EOL,这三个参数(如帮助中显示的其他BYTES输入)由CLI解析,所以"\t"被视为制表符,"\n"被视为LF(换行符)。

MST特定选项(Python/construct)

这里显示的选项与MST文件格式构建器/解析器的Python实现相关,这些选项对bruma-*命令不可用。

ISIS/FFI 主文件格式(MST 文件)结构是一个以记录连接方式划分的二进制文件。其整体结构在《迷你微型 CDS/ISIS:参考手册(版本 2.3)》的附录 G 中进行了记录,但内容不完整。为了使数据库能够存储更多数据,对文件结构进行了多项增强。尽管如此,MST 文件仍然是一个以记录连接方式组成的文件,其中每个记录包含 3 个区块:领导者、目录和字段值。它与 ISO 文件类似,具有空字段和记录终止符,但领导者和目录项是二进制的,元数据不相同,填充、对齐和大小难以准确掌握。

这是 MST 文件单个记录中的领导者和目录项的内部结构(不适用于控制记录)

                   -------------------------------------------------
                  |    Format | ISIS     ISIS     FFI      FFI      |
                  | Alignment | 2        4        2        4        |
 -----------------------------+-------------------------------------|
|         Leader size (bytes) | 18       20       22       24       |
| Directory item size (bytes) | 6        6        10       12       |
|-----------------------------+-------------------------------------|
|           |      00-01      | MFN.1    MFN.1    MFN.1    MFN.1    |
|           |      02-03      | MFN.2    MFN.2    MFN.2    MFN.2    |
|           |      04-05      | MFRL     MFRL     MFRL.1   MFRL.1   |
|           |      06-07      | MFBWB.1  (filler) MFRL.2   MFRL.2   |
|           |      08-09      | MFBWB.2  MFBWB.1  MFBWB.1  MFBWB.1  |
|  Leader   |      10-11      | MFBWP    MFBWB.2  MFBWB.2  MFBWB.2  |
|           |      12-13      | BASE     MFBWP    MFBWP    MFBWP    |
|           |      14-15      | NVF      BASE     BASE.1   (filler) |
|           |      16-17      | STATUS   NVF      BASE.2   BASE.1   |
|           |      18-19      |          STATUS   NVF      BASE.2   |
|           |      20-21      |                   STATUS   NVF      |
|           |      22-23      |                            STATUS   |
|-----------+-----------------+-------------------------------------|
|           |      00-01      | TAG      TAG      TAG      TAG      |
|           |      02-03      | POS      POS      POS.1    (filler) |
| Directory |      04-05      | LEN      LEN      POS.2    POS.1    |
|   item    |      06-07      |                   LEN.1    POS.2    |
|           |      08-09      |                   LEN.2    LEN.1    |
|           |      10-11      |                            LEN.2    |
 -----------+-----------------+-------------------------------------|
            |  Offset (bytes) |              Structure              |
             -------------------------------------------------------

这些结构名称遵循《迷你微型 CDS/ISIS 参考手册》,其中 ".1" 和 ".2" 后缀用于表示字段为 4 字节,否则字段为 2 字节。每个字段的起始偏移量必须是对齐数的整数倍,因此需要填充。字节序不会改变这些字段的任何位置,它只是改变字段本身的 2 或 4 字节顺序(在 CISIS 中称为 "交换",意味着数据的最后一个字节位于地址/偏移量 最低 的位置)。到目前为止,大多数这种结构可以通过三个参数进行控制:格式、记录内对齐和字节序。以下是两种可能的格式:

  • ISIS 文件格式:参考手册中记录的原始标准
  • FFI 文件格式:一种替代方案,以克服 16 字节(MFRL)的记录大小,将其加倍,以及与记录内部偏移量相关的所有其他字段

以下是控制 MST 文件记录主要结构的 MST 特定选项

  • --end:指示每个字节的字节序是 big 还是 little,其中 --le--be 是这些的简称
  • --format:选择 isisffi 文件格式,其中 --isis--ffi 是这些的简称
  • --packed/--unpacked:这些控制领导者/目录对齐,packed 表示它们的对齐是 2,而 unpacked 表示它们的对齐是 4。

MST 文件有一个名为 控制记录 的起始记录,其 MFN(主文件号,这里的 file 表示记录)为零。它具有这个 32 字节的结构(除了 CISIS 中的 32 字节填充以外)

 -----------------------------
|  Offset (bytes) | Structure |
|-----------------+-----------|
|      00-01      | CTLMFN.1  |
|      02-03      | CTLMFN.2  |
|      04-05      | NXTMFN.1  |
|      06-07      | NXTMFN.2  |
|      08-09      | NXTMFB.1  |
|      10-11      | NXTMFB.2  |
|      12-13      | NXTMFP    |
|      14-15      | TYPE      |
|      16-17      | RECCNT.1  |
|      18-19      | RECCNT.2  |
|      20-21      | MFCXX1.1  |
|      22-23      | MFCXX1.2  |
|      24-25      | MFCXX2.1  |
|      26-27      | MFCXX2.2  |
|      28-29      | MFCXX3.1  |
|      30-31      | MFCXX3.2  |
 -----------------------------

其中最重要的字段是上面显示的 TYPE,在 CDS/ISIS 参考手册中写作 MFTYPE,但实际上 TYPE 包含两个单字节字段,这两个字段的顺序是唯一的多字段场景,它取决于字节序

  • MSTXL (最高有效字节):所有 XRF 条目中的 偏移量 移位(将讨论)
  • MFTYPE (最低有效字节):主文件类型(用户数据库文件应始终为零)

我们已经看到了不同 MST 文件格式之间的记录内差异,但整体结构本身也存在差异。对于整体 MST 文件结构来说,一个非常重要的参数是 记录间对齐。有关整体文件结构和对齐的一些细节如下:

  • 文件被划分为 512 字节的 ,最后一个块应填充到末尾
  • 第一条记录必须是控制记录
  • 记录简单地一个接一个堆叠,但有对齐约束
    • 记录的 BASE 和 MFN 字段必须在同一块中
    • 记录本身应该有2个字节的对齐(字对齐,ISIS默认的记录间对齐方式)

“位移”名称来源于XRF文件结构,该结构仅用32字节来存储块、偏移和一些标志。XRF应该能够指向MST文件中每个记录的地址,因此必须进行一些“位操作”才能支持更大的MST文件。这通过MSTXL字段实现,它代表“位移”,表示我们必须将偏移量向右移动多少位。这样做我们会丢失最低有效位,因此我们的偏移量应该总是对齐到“2的位移次方”(即位移的2次方)。这是我们主要的记录间对齐约束。

这些是关于记录间对齐的MST特定选项

  • --control-len:控制记录的长度(以字节为单位),用于控制第一个填充大小
  • --shift(仅MST文件输出):MSTXL值,指示记录间对齐至少应为2的MSTXL次方字节
  • --shift4is3/--shift4isnt3:切换文件中的MSTXL是否等于3或在--shift中视为4,这是CISIS的历史行为
  • --min-modulus:最小记录间对齐,以字节为单位(默认为2)。此选项使得绕过标准字对齐成为可能,“--min-modulus=1 --shift=0”将使MST文件具有字节对齐(即没有记录间填充/填充物)

ISIS中有三种锁定机制可能存储在MST文件中

  • EWLOCK (独占写锁):这是一个标志,存储在MFCXX3(控制记录)中
  • DELOCK (数据条目锁):这是一个计数器,存储在MFCXX2(控制记录)中,表示一次锁定了多少个记录
  • RLOCK (记录锁):这是MFRL(记录长度)每个记录的符号(记录大小实际上是MFRL的绝对值)

通常当ISIS只是一个静态文件且没有进程修改时,这些没有区别,ioisis CLI忽略EWLOCK和DELOCK(尽管它们可以通过ioisis作为库访问)。ioisis中有一个选项用于启用/禁用所有这些锁的解释,并且它已暴露给CLI,因为它会影响RLOCK

  • --lockable/--no-locks:控制MFRL是否应签名(可锁定)或未签名(无RLOCK,加倍记录长度限制)

由于多个对齐约束,MST文件中可能出现多个填充(填充字符)。MST文件的另一个问题是它没有为所有这些情况使用单个填充,并且某些特定架构的工具可能表现不同。由于解析器是严格的(即,它检查对齐和填充),在加载MST文件之前可能需要调整这些,以下是一些可能的命令

  • --filler:未设置填充选项的默认填充,但记录填充
  • --record-filler:用于最后一条记录数据,在最后一个字段值之后(默认为空白字符)
  • --control-filler:用于控制记录的尾部字节
  • --slack-filler:在--unpacked时用于引导/目录
  • --block-filler:用于512字节块中不属于任何记录的最后字节(文件末尾或由于“MFN+BASE在同一个块中”的约束)

上述填充选项只有一个参数,它应该始终是2个字符的字符串,表示填充字节的十六进制代码。

最后,有时输入的MST文件损坏,无法加载,例如,因为块填充不干净,或者因为MFRL小于实际记录数据。由于整体记录结构有一些内部约束(大小和偏移/地址),ioisis可以继续忽略作为新记录没有意义的几个字节。为此,应该使用无效块填充选项(--ibp)调用它,其值可以是

  • check(默认):严格的操作,当在应该有记录的某些偏移处出现无效数据时,ioisis会崩溃
  • ignore:静默跳过无效数据
  • store:将尾随信息放入输出中的伪ibp字段,以十六进制形式

库中表示单个记录的常见数据结构是有序的标签值对列表,或简称tl。它与tidy/stidy JSONL/CSV模式无关,只是存储数据避免原始记录容器中分散的结构。要使用库加载数据

from ioisis import bruma, iso, mst, fieldutils

# In the mst module, you must create a StructCreator instance
mst_sc = mst.StructCreator(ibp="store")
with open("file.mst", "rb") as raw_mst_file:
    for raw_tl in mst_sc.iter_raw_tl(raw_mst_file):
        tl = fieldutils.nest_decode(raw_tl, encoding="cp1252")
        ...

# For bruma.iter_tl the input must be a file name
for tl in bruma.iter_tl("file.mst", encoding="cp1252"):
    raw_tl = fieldutils.nest_encode(raw_tl, encoding="utf-8")
    ...

# The idea is similar for an ISO file, but ...
for raw_tl in iso.iter_raw_tl("file.iso"):
    tl = utf8_fix_nest_decode(raw_tl, encoding="latin1")
    ...

# ... for ISO files, you can always use either a file name
# or any file-like object open in "rb" mode
with open("file.iso", "rb") as raw_iso_file:
    for tl in iso.iter_tl(raw_iso_file, encoding="latin1"):
        ...

以下生成器函数和方法是上述示例中出现过的

  • mst.StructCreator.iter_raw_tl:以字节字符串形式读取MST
  • iso.iter_raw_tl:以字节字符串形式读取ISO
  • bruma.iter_tl:读取已解码内容的MST
  • iso.iter_tl:读取已解码内容的ISO

值得注意的是,以下来自fieldutils模块的函数允许一次编码/解码所有记录字段/子字段

  • nest_encode
  • nest_decode
  • utf8_fix_nest_decode

后者与nest_decode相同,但使用给定的编码作为后备,首先尝试将所有内容解码为UTF-8。

单个解码的tl的内容是什么?它是一个[标签,值]对的列表(作为列表或元组),如下所示

[["5", "S"],
 ["6", "c"],
 ["10", "br1.1"],
 ["62", "Example Institute"]]

可以从一个tl生成单个ISO记录

>>> from ioisis import iso, fieldutils
>>> tl = [["1", "test"], ["8", "it"]]
>>> raw_tl = fieldutils.nest_encode(tl, encoding="utf-8")
>>> raw_tl
[[b'1', b'test'], [b'8', b'it']]
>>> con = fieldutils.tl2con(raw_tl, ftf=iso.DEFAULT_ISO_FTF)
>>> con
{'dir': [{'tag': b'001'}, {'tag': b'008'}], 'fields': [b'test', b'it']}
>>> iso.DEFAULT_RECORD_STRUCT.build(con)
b'000580000000000490004500001000500000008000300005#test#it##\n'

创建记录的过程是将它们转换为内部[构造]容器格式(或简单地称为con),这通过fieldutils.tl2con完成。要创建MST文件,可以使用mst.StructCreatorbuild_stream方法,其第一个参数应该是con实例的生成器,第二个是可寻址的文件对象。

还有一种第三种格式,称为记录字典格式,它基于JSONL "--mode=field"输出格式。与上述替代方案相比,在库内部可用的资源较少,但在某些情况下可能更容易使用

>>> iso.dict2bytes({"1": ["testing"], "8": ["it"]})
b'000610000000000490004500001000800000008000300008#testing#it##\n'

# The same, but from the tl
>>> tl = [["1", "testing"], ["8", "it"]]
>>> record = fieldutils.tl2record(tl)
>>> iso.dict2bytes(record)
b'000610000000000490004500001000800000008000300008#testing#it##\n'

要从brumaiso加载ISIS数据,您也可以使用相应模块的iter_records函数,但如果您使用fieldutils转换函数,则它更可定制

  • record2tl
  • tl2record
  • tl2con

了解库的行为的最简单方法可能是使用CLI并检查调用命令的代码。

模块

ioisis包中可用的模块是

模块 内容
bruma 基于Bruma的所有MST文件处理
ccons 自定义构造类
fieldutils 字段/子字段处理函数和类
iso 基于构造的ISO解析/构建工具
java 基于JPype1的Java接口资源
mst 基于构造的MST/XRF解析/构建工具
streamutils 用于精确文件/管道处理的类
__main__ CLI(命令行界面)

默认情况下,您只需从ioisis中使用isomstbrumafieldutils模块即可将其用作库,其余模块可以视为内部内容。

mst模块默认不使用/创建XRF文件。可以使用mst.StructCreator.create_xrf_struct方法创建/加载XRF数据。

ISO容器构造(底层数据访问Python API)

iso模块使用Construct库,这使得它能够创建一个可声明的“结构”对象,可以执行双向构建/解析字节串(bytes实例)或流(以"rb"模式打开的文件)从/到构造容器(字典)。

构建和解析单个记录

此底层数据访问不执行任何字符串编码/解码,因此用于构建某些ISO数据的输入字典中的每个都应是一个原始字节串。同样,解析器不会解码编码的字符串(标签、字段和元数据),保持结果中的字节串。

以下是一个示例,其中包含ISO构建器所期望的“最小”格式的记录。值是字节串,每个目录条目与其索引匹配的字段值。

>>> lowlevel_dict = {
...     "dir": [{"tag": b"001"}, {"tag": b"555"}],
...     "fields": [b"a", b"test"],
... }

# Build a single ISO record bytestring from a construct.Container/dict
>>> iso_data = iso.DEFAULT_RECORD_STRUCT.build(lowlevel_dict)
>>> iso_data
b'000570000000000490004500001000200000555000500002#a#test##\n'

# Parse a single ISO record bytestring to a construct.Container
>>> con = iso.DEFAULT_RECORD_STRUCT.parse(iso_data)

# The construct.Container instance inherits from dict.
# The directory and fields are instances of construct.ListContainer,
# a class that inherits from list.
>>> [directory["tag"] for directory in con["dir"]]
[b'001', b'555']
>>> con.fields  # Its items can be accessed as attributes
ListContainer([b'a', b'test'])
>>> len(con.fields) == con.num_fields == 2  # A computed attribute
True

# This function directly converts that construct.Container object
# to a dictionary of already decoded strings in the the more common
# {tag: [field, ...], ..} format (default ISO encoding is cp1252):
>>> iso.con2dict(con).items()  # It's a defaultdict(list)
dict_items([('1', ['a']), ('555', ['test'])])

其他记录字段

每个ISO记录分为3部分

  • 引导(24字节头,包含元数据)
  • 目录(每个字段值的元数据,主要是其3字节标签
  • 字段(字段值本身,作为字节串)

引导包含

  • 单个字符元数据(状态类型编码
  • 两个数字元数据(指示器计数标识符长度),其范围仅从0到9
  • 为“厂商特定”内容预留空间,作为字节串:custom_2custom_3,其中数字是它们的大小(以字节为单位)
  • 一个条目映射,即目录中每个字段的长度:len_lenpos_lencustom_len,其范围仅从0到9
  • 一个单字节,reserved,字面意义上是为将来使用预留的
>>> con.len_len, con.pos_len, con.custom_len
(4, 5, 0)

实际上,reserved是条目映射的一部分,但在此处没有特定含义,它不需要是数字。除了条目映射和未包含的长度/地址字段外,这些元数据在读取ISO内容时都没有任何意义,并且默认情况下都填充为零(当它们是字符串时为ASCII零)。

>>> con.status, con.type, con.coding, con.indicator_count
(b'0', b'0', b'0', 0)

记录中存储的长度和位置字段(total_lenbase_addrdir.lendir.pos)在构建时计算并在解析时检查。我们不需要担心这些字段,但如果我们需要,可以读取它们。例如,一个目录记录(字典)有这个

>>> con.dir[1]
Container(tag=b'555', len=5, pos=2, custom=b'')

由于默认的dir.custom字段长度为零,它对大多数用例实际上并不太有用。考虑到这一点,我们已经看到了单个记录的低级ISO表示中的所有字段。

调整字段长度

ISO2709规范告诉我们,目录条目应恰好有12字节,这意味着len_len + pos_len + custom_len应为9。然而,这并不是此库的实际限制,所以我们不需要担心这一点,只要条目映射有正确的信息即可。

让我们自定义长度,以获得包含目录中的custom字段的一些数据的更小的ISO,使用一个8字节的目录

>>> dir8_dict = {
...     "len_len": 1,
...     "pos_len": 3,
...     "custom_len": 1,
...     "dir": [{"tag": b"001", "custom": b"X"}, {"tag": b"555"}],
...     "fields": [b"a", b"test"],
... }
>>> dir8_iso = iso.DEFAULT_RECORD_STRUCT.build(dir8_dict)
>>> dir8_iso
b'0004900000000004100013100012000X55550020#a#test##\n'
>>> dir8_con = iso.DEFAULT_RECORD_STRUCT.parse(dir8_iso)
>>> dir8_con.dir[0]
Container(tag=b'001', len=2, pos=0, custom=b'X')
>>> dir8_con.dir[1]  # The default is always zero!
Container(tag=b'555', len=5, pos=2, custom=b'0')
>>> dir8_con.len_len, dir8_con.pos_len, dir8_con.custom_len
(1, 3, 1)

如果我们尝试从一个不符合给定大小的字典中构建会发生什么?

>>> invalid_dict = {
...     "len_len": 1,
...     "pos_len": 9,
...     "dir": [{"tag": b"555"}],
...     "fields": [b"a string with more than 9 characters"],
... }
>>> iso.DEFAULT_RECORD_STRUCT.build(invalid_dict)
Traceback (most recent call last):
  ...
construct.core.StreamError: Error in path (building) -> dir -> len
bytes object of wrong length, expected 1, found 2

ISO文件、换行符和分隔符

ISO文件通常包含多个记录。然而,这些文件是通过简单地连接ISO记录来创建的。就这么简单:连接两个ISO文件应该得到另一个有效的ISO文件,其中包含两个文件的所有记录。

尽管这不是ISO2709规范的一部分,但iso.DEFAULT_RECORD_STRUCT解析器/构建器对象假设

  • 给定记录的除最后一行外的所有行必须正好有80个字节,并且在该行之后必须包含换行符(\x0a);
  • 每一行必须属于单个记录;
  • 单个记录的最后一行必须以\x0a结束。

这是 iso.LineSplitRestreamed 的行为,它内部“包装”记录结构以提供这种“行拆分”行为,但可以通过在创建自定义记录结构时将 line_len 设置为 None 或零来避免。

使用有意义的行断字符解析/构建数据

假设我们想存储这些值

>>> newline_info_dict = {
...     "dir": [{"tag": b"SIZ"}, {"tag": b"SIZ"}, {"tag": b"SIZ"}],
...     "fields": [b"linux^c\n^s1", b"win^c\r\n^s2", b"mac^c\r^s1"],
... }

作为一个例子,这是一个具有三个 SIZ 字段的 ISO 记录,每个字段有三个子字段,其中第二个子字段是某些环境的默认换行符,第三个子字段是其大小。尽管可以使用 DEFAULT_RECORD_STRUCT(行尾永远不会与内容混合)来构建它,但我们事先知道我们的值包含换行符,我们可能希望有一个没有那种“包装”行断行为的选择性结构

>>> breakless_struct = iso.create_record_struct(line_len=0)
>>> newline_info_iso = breakless_struct.build(newline_info_dict)
>>> newline_info_iso
b'000950000000000610004500SIZ001200000SIZ001100012SIZ001000023#linux^c\n^s1#win^c\r\n^s2#mac^c\r^s1##'
>>> newline_info_con = breakless_struct.parse(newline_info_iso)
>>> newline_info_simple_dict = dict(iso.con2dict(newline_info_con))
>>> newline_info_simple_dict
{'SIZ': ['linux^c\n^s1', 'win^c\r\n^s2', 'mac^c\r^s1']}
>>> newline_info_iso == iso.dict2bytes(
...     newline_info_simple_dict,
...     record_struct=breakless_struct,
... )
True

使用自定义行断和分隔符解析/构建

单条记录的默认构建器/解析器是用以下方式创建的

DEFAULT_RECORD_STRUCT = iso.create_record_struct(
    field_terminator=iso.DEFAULT_FIELD_TERMINATOR,
    record_terminator=iso.DEFAULT_RECORD_TERMINATOR,
    line_len=iso.DEFAULT_LINE_LEN,
    newline=iso.DEFAULT_NEWLINE,
)

我们可以使用其他值创建一个自定义对象。要使用它,我们将在调用函数时传递该对象作为 record_struct 关键字参数。

>>> simple_data = {
...     "OBJ": ["mouse", "keyboard"],
...     "INF": ["old"],
...     "SIZ": ["34"],
... }
>>> custom_struct = iso.create_record_struct(
...     field_terminator=b";",
...     record_terminator=b"@",
...     line_len=20,
...     newline=b"\n",
... )
>>> simple_data_iso = iso.dict2bytes(
...     simple_data,
...     record_struct=custom_struct,
... )
>>> from pprint import pprint
>>> pprint(simple_data_iso.decode("ascii"))
('00096000000000073000\n'
 '4500OBJ000600000OBJ0\n'
 '00900006INF000400015\n'
 'SIZ000300019;mouse;k\n'
 'eyboard;old;34;@\n')
>>> simple_data_con = custom_struct.parse(simple_data_iso)
>>> simple_data == iso.con2dict(simple_data_con)
True

计算的大小不计额外的行断字符

>>> simple_data_con.total_len, simple_data_con.base_addr
(96, 73)

项目详情


下载文件

下载您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分发

ioisis-0.4.0.tar.gz (72.9 kB 查看哈希)

上传时间

支持