DigitalPlatform / chord

新一代分布式图书馆软件
Apache License 2.0
31 stars 18 forks source link

Z39.50 协议函数库 #170

Open DigitalPlatform opened 6 years ago

DigitalPlatform commented 6 years ago

chord 项目要有自己的新一代 Z39.50 协议函数库,通过改写和升级 dp2 v2 的 DigitalPlatform.Z39.50 函数库达成,名字还是沿用以前的。

这个函数库有如下目标: 1) 简化结构,便于一般开发者理解和使用。简化最初级方式的调用。 2) 用 C# 新特性 async await 来大幅度简化以前的异步通讯写法。 3) 采用 .NET Standard 2.0 的 DLL 形态,以便 .NET Core 项目可以调用它。能用于 Windows Form、WPF、.NET Core Server 等各种场合。 4) 尽量节省内存,提高一台服务器内允许的极限通道数。这是为了满足 Z39.50 群检的需求。 5) 支持双 IP 的 Z39.50 服务器访问和服务器参数特殊定义。

DigitalPlatform commented 6 years ago

设计思路

这里记载一些设计思路,供参考。以后稳定下来再改写为 wiki。

ZChannel 和 ZClient

这是函数库的两个主要的类。

ZChannel 基本对等于 .NET 的 TcpClient 类型,包装了一下,实现 TCP 通讯。它负责发送和接收 Z39.50 的通讯包。通讯包不是这个类负责构造的,另有类来解决构造通讯包问题,这个类只是负责发送和接收构造好的包。但它在接收包的时候,调用了一个 IsBerComplete() 函数来检查包是否完整到达。

ZClient 实现了 Z39.50 协议的 API,是应用本函数库时候直接使用的类。每个 ZClient 对象包含一个 ZChannel 对象用以实现通讯功能。但 ZClient 对象不包含 Z39.50 服务器信息存储结构,这个需要调主自行用 TargetInfo 对象来管理,包括管理持久化问题。

可以理解为一个 ZClient 对象代表了和一个 Z39.50 服务器进行通讯的通道结构。函数库的使用者要自行处理它的持久性问题。

ZClient 的三种 Z39.50 请求

ZClient 类里面提供了三种 Z39.50 请求的相关 API: 1) Initialize。初始化 2) Search。检索,命中结果放入指定的服务器结果集。 3) Present。获得检索结果集内的记录

初始化是每次检索或者获取结果之前必须要进行的,也就是每个通道的第一个动作。为了确保初始化不会被遗漏,函数库提供了一个 TryInitialize() 函数,从名字就可以看出它是尝试性地进行初始化,也就是说如果先前初始化过了、并且主要特征(服务器名、端口号、通道状态等)没有变化的情况下,不会再次初始化。

TargetInfo

TargetInfo 类是表现一个 Z39.50 服务器配置信息的类。它存储了服务器地址、端口号、数据库名列表、用户名和密码等必要信息。这些成员非常多,结构比较复杂,但一般使用的时候调主只需要设置和关注其中的一些主要字段即可。

类中也有少数成员,是可能被 Z39.50 Initialize() API 调用过程所修改的,例如字符集协商过程。所以每当一个 TargetInfo 调用了 Initialize() API 以后,最好持久存储它,以便在多轮请求之间保持参数一致。

后面等 Z39.50 站点数据库功能有了以后,TargetInfo 对象会增加从站点数据记录中填充内容的功能。

DigitalPlatform commented 6 years ago

函数介绍

ZClient::TryInitialize()

尝试执行 Z39.50 初始化请求

public async Task<InitialResult> TryInitialize(TargetInfo targetinfo)

唯一的参数是 TargetInfo 对象。这个结构后面会单独介绍,它存储了一个 Z39.50 服务器的所有配置参数。

所返回的 InitialResult 结构如下:

        public class InitialResult : Result
        {
            // 说明初始化结果的文字
            public string ResultInfo { get; set; }
        }

可以看出,它比 Result 结构多了一个成员 ResultInfo,这是用于存储初始化结果描述文字的成员。初始化成功的时候这个成员会被填充好。

ZClient::Search()

执行 Z39.50 检索。命中的记录会放入 Z39.50 服务器端的指定的结果集内。注意,本 API 并不直接返回检索命中的记录。

        public async Task<SearchResult> Search(
            string strQuery,
            Encoding queryTermEncoding,
            string[] dbnames,
            string strPreferredRecordSyntax,
            string strResultSetName)

参数提供了检索式,检索词编码方式,数据库名列表,推荐的记录语法(MARC格式类型),结果集名。

注意 strQuery 参数里面是一种特殊的检索式,可以表示与或非逻辑运算,后面会介绍这种检索式的格式。

所返回的 SearchResult 结构如下:

        public class SearchResult : Result
        {
            public int ResultCount { get; set; }
        }

可以看出,它比 Result 多了一个 ResultCount 成员。用于记载检索命中的结果集内的记录条数。虽然 Search() 并不直接返回命中的记录,但返回了命中记录的总条数。这样在后面获取结果集内记录的阶段,就可以知道这个结果集的记录规模了,便于安排获取记录的循环。

ZClient::Present()

执行 Z39.50 获取。从指定的结果集中取得一个范围的记录。

        public async Task<PresentResult> Present(
            string strResultSetName,
            int nStart,
            int nCount,
            int nPreferedEachCount, 
            string strElementSetName,
            string strPreferredRecordSyntax)

参数为结果集名,开始偏移(从 0 开始计算),数量,每次获取推荐的数量,元素集名("B"或"F"),推荐的记录语法(也就是 MARC 类型)。

这个函数其实并不是直接对应 Z39.50 获取操作,它略微做了一些包装,以确保调用后能返回 nCount 个记录,那么在调用中,它有可能发起多于一次的 Present 请求。

它调用 OncePresent() 实现其功能,OncePresent() 才是直接和一次 Present 请求对应的函数。

所返回的 PresentResult 结构如下:

        public class PresentResult : Result
        {
            public RecordCollection Records { get; set; }
        }

可以看出,它比 Result 多了一个 Records 成员。用于存储 Present 请求所返回的记录。按照 Z39.50 协议规定,这些记录里面可能会返回诊断记录。诊断记录并不是真正承载数据记录内容,而是承载了错误类型和错误信息。

DigitalPlatform commented 6 years ago

辅助性的类

BerTree

用于解析或者构建 BER 包的工具类。这个类把一个 BER 包的结构抽象为一棵树,所以名为 BerTree。它运用了 BerNode 描述 BER 树的节点。

BerNode

用于描述 BER 树的一个节点。

UseCollection

描述 BIB-1 集合。也就是检索途径定义集合。

Bib1Use

描述 BIB-1 的 一个 Use 事项

IsbnConvertInfo

实用工具类,用于处理 ISBN-10 和 ISBN-13 的互相转换,和去除横杠的操作。 在对某些 Z39.50 服务器进行 ISBN 检索的时候,由于这些服务器能力不足,只能按照请求的字符串原样一丝不差地去匹配数据中的 ISBN 字符串,所以会导致 ISBN-13 字符串检索不到数据中的实质相同的 ISBN-10 内容,或者反之,的遗憾结果。(正确做法应该是对数据和检索词在 Z39.50 服务器内做归一化,然后再匹配) 本类就是为了缓解这个问题而出现的。

PolandNode

是一种特殊的 BerNode。为了方便构造 type-1 或者 type-101 检索式结构而出现的。

RecordCollection

为了方便处理 Present 请求返回的记录集合而出现的类。

TargetInfo

检索目标(也就是 Z39.50 服务器)信息结构

DigitalPlatform commented 6 years ago

检索式

ZClient::Search() 这个 API 使用了一种特定的检索式。在 Z39.50 术语里面,叫做 type-1 或者 type-101 检索式。下面介绍这种检索式。

调试技巧

运行 TestZClient 小前端程序,它允许我们用比较方便的下拉列表指定检索途径,也允许我们直接使用上述 API 检索式。开始检索时,程序会很贴心地把实际转换为 API 特定检索式形态也显示在面板上,这样就方便我们学习参考了,也可以直接在这个现成的字符串上面修改使用。

一个检索词情况

例子如下: "小王子"/1=4

其中,双引号里面的部分是检索词,斜杠后面的 1 表示即将指明检索途径,等号后面的 4 代表检索途径为“题名”。这些数字的意思后面有表格说明。

再一个例子: "阿甲"/1=1003

这个式子是用“阿甲”检索著者这个途径。1003 代表著者。

多个检索词情况

多个检索词,可以用逻辑算符组合起来,实现逻辑运算。不过这个功能是否能奏效,要看 Z39.50 是否支持逻辑运算。

例子: "小王子"/1=4 OR "阿甲"/1=1003

这个式子表示用题名和作者进行或运算。即,无论是指定的题名,还是指定的著者检索命中,都进入结果集中。

例子2: "小王子"/1=4 AND "李懿芳"/1=1003

这个式子表示用题名和作者进行与运算。即,必须是题名和作者都同时满足要求的书目记录才会命中进入结果集。

由于阿甲没有翻译过“小王子”这本书,那么式子 "小王子"/1=4 AND "阿甲"/1=1003 检索就不会命中。

括号的使用

使用括号可以明确指定逻辑运算的先后顺序。

例子: ("小王子"/1=4 AND "李懿芳"/1=1003) OR "阿甲"/1=1003

这个式子是先进行 AND,后进行 OR 运算。OR 运算时是在先前 AND 运算的结果集再和 著者阿甲 之间进行的。

检索词中的转义字符

在检索式中,检索词部分可能由于检索词本身使用了一些敏感标点字符,例如双引号、斜撇、等号,那么这时需要对这些检索词进行转义保护以后才能用于检索式中。

StringUtil 函数库中 EscapeString() 函数可以用来做这个转义操作:

string strWord = StringUtil.EscapeString(strWord, "\"/=")

例子: 一本"好书" 转义后形如 一本%22好书%22

参考:BIB-1 集合定义

下面是 Z39.50 中常用的,用于指定检索途径的 BIB-1 集合的定义,可供构造上述检索式时参考:

<root>
  <item name = "Title" value="4" uni_name = "Title" comment="题名" />
  <item name = "Author" value="1003" uni_name = "Author-name" comment="著者"/>
  <item name = "ISBN" value="7" uni_name = "Identifier-ISBN"/>
  <item name = "ISSN" value="8" uni_name = "Identifier-ISSN"/>
  <item name = "Publisher" value="1018" uni_name = "Name-publisher" comment="出版者"/>
  <item name = "Personal name" value="1" uni_name = "Name-personal" comment="个人名称"/>
  <item name = "LC card number" value="9" uni_name = "Control number-LC" comment="国会图书馆卡片号"/>
  <item name = "Local number" value="12" uni_name = "Control number-local" comment="本地控制号"/>
  <item name = "Dewey classification" value="13" uni_name = "Classification-Dewey" comment="杜威分类号"/>
  <item name = "LC call number" value="16" uni_name = "Classification-LC" comment="国会图书馆索取号"/>
  <item name = "Local classification" value="20" uni_name = "Classification-local" comment="本地分类号"/>
  <item name = "Subject heading" value="21" uni_name = "Subject" comment="主题"/>
  <item name = "LC subject heading" value="27" uni_name = "Subject-LC" comment="LC主题标目"/>
  <item name = "Date" value="30" uni_name = "Date" comment="日期"/>
  <item name = "Date of publication" value="31" uni_name = "Date-publication" comment="出版日期"/>
  <item name = "CODEN" value="60" uni_name = "Identifier-CODEN"/>
  <item name = "Abstract" value="62" uni_name = "Abstract" comment="摘要"/>
  <item name = "Note" value="63" uni_name = "Note" comment="注释"/>
  <item name = "Name" value="1002" uni_name = "Name" comment="名称"/>
  <item name = "Any" value="1016" uni_name = "Any" comment="任意"/>
  <item name = "Editor" value="1020" uni_name = "Name-editor" comment="编辑"/>
</root>

完整的列表请参考:https://www.loc.gov/z3950/agency/defns/bib1.html