Open DigitalPlatform opened 6 years ago
这里记载一些设计思路,供参考。以后稳定下来再改写为 wiki。
这是函数库的两个主要的类。
ZChannel 基本对等于 .NET 的 TcpClient 类型,包装了一下,实现 TCP 通讯。它负责发送和接收 Z39.50 的通讯包。通讯包不是这个类负责构造的,另有类来解决构造通讯包问题,这个类只是负责发送和接收构造好的包。但它在接收包的时候,调用了一个 IsBerComplete() 函数来检查包是否完整到达。
ZClient 实现了 Z39.50 协议的 API,是应用本函数库时候直接使用的类。每个 ZClient 对象包含一个 ZChannel 对象用以实现通讯功能。但 ZClient 对象不包含 Z39.50 服务器信息存储结构,这个需要调主自行用 TargetInfo 对象来管理,包括管理持久化问题。
可以理解为一个 ZClient 对象代表了和一个 Z39.50 服务器进行通讯的通道结构。函数库的使用者要自行处理它的持久性问题。
ZClient 类里面提供了三种 Z39.50 请求的相关 API: 1) Initialize。初始化 2) Search。检索,命中结果放入指定的服务器结果集。 3) Present。获得检索结果集内的记录
初始化是每次检索或者获取结果之前必须要进行的,也就是每个通道的第一个动作。为了确保初始化不会被遗漏,函数库提供了一个 TryInitialize() 函数,从名字就可以看出它是尝试性地进行初始化,也就是说如果先前初始化过了、并且主要特征(服务器名、端口号、通道状态等)没有变化的情况下,不会再次初始化。
TargetInfo 类是表现一个 Z39.50 服务器配置信息的类。它存储了服务器地址、端口号、数据库名列表、用户名和密码等必要信息。这些成员非常多,结构比较复杂,但一般使用的时候调主只需要设置和关注其中的一些主要字段即可。
类中也有少数成员,是可能被 Z39.50 Initialize() API 调用过程所修改的,例如字符集协商过程。所以每当一个 TargetInfo 调用了 Initialize() API 以后,最好持久存储它,以便在多轮请求之间保持参数一致。
后面等 Z39.50 站点数据库功能有了以后,TargetInfo 对象会增加从站点数据记录中填充内容的功能。
尝试执行 Z39.50 初始化请求
public async Task<InitialResult> TryInitialize(TargetInfo targetinfo)
唯一的参数是 TargetInfo 对象。这个结构后面会单独介绍,它存储了一个 Z39.50 服务器的所有配置参数。
所返回的 InitialResult 结构如下:
public class InitialResult : Result
{
// 说明初始化结果的文字
public string ResultInfo { get; set; }
}
可以看出,它比 Result 结构多了一个成员 ResultInfo,这是用于存储初始化结果描述文字的成员。初始化成功的时候这个成员会被填充好。
执行 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() 并不直接返回命中的记录,但返回了命中记录的总条数。这样在后面获取结果集内记录的阶段,就可以知道这个结果集的记录规模了,便于安排获取记录的循环。
执行 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 协议规定,这些记录里面可能会返回诊断记录。诊断记录并不是真正承载数据记录内容,而是承载了错误类型和错误信息。
用于解析或者构建 BER 包的工具类。这个类把一个 BER 包的结构抽象为一棵树,所以名为 BerTree。它运用了 BerNode 描述 BER 树的节点。
用于描述 BER 树的一个节点。
描述 BIB-1 集合。也就是检索途径定义集合。
描述 BIB-1 的 一个 Use 事项
实用工具类,用于处理 ISBN-10 和 ISBN-13 的互相转换,和去除横杠的操作。 在对某些 Z39.50 服务器进行 ISBN 检索的时候,由于这些服务器能力不足,只能按照请求的字符串原样一丝不差地去匹配数据中的 ISBN 字符串,所以会导致 ISBN-13 字符串检索不到数据中的实质相同的 ISBN-10 内容,或者反之,的遗憾结果。(正确做法应该是对数据和检索词在 Z39.50 服务器内做归一化,然后再匹配) 本类就是为了缓解这个问题而出现的。
是一种特殊的 BerNode。为了方便构造 type-1 或者 type-101 检索式结构而出现的。
为了方便处理 Present 请求返回的记录集合而出现的类。
检索目标(也就是 Z39.50 服务器)信息结构
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
下面是 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>
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 服务器访问和服务器参数特殊定义。