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 虚拟机)。iso
和 mst
模块,以及其他模块和 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,以及 isis2json 的 MongoDB 类型 1(-mt1
)输出。一些 CLI 命令的 --xylose
选项将 JSONL 默认值切换为 Xylose 所期望的字典结构。
安装和测试
它需要 Python 3.6+,并已准备使用 tox 和 pytest 在每个 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 命令,请使用 ioisis
或 python -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
一起使用时,field
、pairs
和nest
模式分别类似于isis2json
的-mt1
、-mt2
和-mt3
选项。inest
模式在isis2json
中不可用,它遵循CISIS在子字段查询上的行为。对于CSV,只有tidy
和stidy
格式可用,因为其余格式不是表格形式。
--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
:选择isis
或ffi
文件格式,其中--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
:以字节字符串形式读取MSTiso.iter_raw_tl
:以字节字符串形式读取ISObruma.iter_tl
:读取已解码内容的MSTiso.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.StructCreator
的build_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'
要从bruma
或iso
加载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
中使用iso
、mst
、bruma
和fieldutils
模块即可将其用作库,其余模块可以视为内部内容。
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_2
和custom_3
,其中数字是它们的大小(以字节为单位) - 一个条目映射,即目录中每个字段的长度:
len_len
、pos_len
和custom_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_len
、base_addr
、dir.len
、dir.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 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 637b8acdac872368ea6843131503757f5da62138fad3df08110b6a9c40722a2c |
|
MD5 | 420193ebd383792945d8d089236ee36e |
|
BLAKE2b-256 | 154504147b8646cc3d29e5f59f82a0b8f77006fab9420270492f2a7915b8ac67 |