MarcLoader简介
MarcQuery 函数库增加了一个类 MarcLoader: https://github.com/DigitalPlatform/chord/blob/master/DigitalPlatform.MarcQuery/MarcLoader.cs
这个类用于遍历 ISO2709 文件里面的记录。可以用两种方式遍历:一种是遍历 ISO2709 原始 byte []。这个可能仅仅具有调试观察意义。另外一种是遍历时候返回 MARC 机内格式字符串。
机内格式字符串是非常方便可以直接装入 MarcQuery 函数库的主要对象 MarcRecord 的。 其实函数库提供 MarcLoader 这个类,就是给函数库增加从外界导入 MARC 数据的手段。
以前是假定使用这个函数库的人一定能自己搞定从 ISO2709 文件到机内格式这个过程的。聚焦在机内格式字符串如何处理方面。但其实现在要推广这个函数库,一般使用者对于从 ISO2709 文件中读入数据也是头疼的,所以最好把这些功能包含到 MarcQuery 函数库中,让它更加实用。
以前在 dp2 系统中是 MarcUtil 这个类在管辖这些文件 IO 的任务,不过写得比较乱,代码经过很多年的慢慢积累,风格也前后差异很大。这次考虑到函数库建设问题,自然是要把这些环节都理一理
后面还要开发一个 MarcWriter 类,用来负责向 ISO2709 文件写入内容,或者说用来创建 ISO2709 文件。 其实这些功能十几年来反复写。但这次依然是继续加强整理,胜在函数组织方面。
MarcLoader 类这次做得比较彻底,它是一个枚举器,直接用 foreach(...) 就可以遍历, 非常符合初学开发的人的偏好。简单,是没有止境的,越简单越好。
TestZClient 小前端里面加了一段测试验证这个 MarcLoader 类功能的代码。不过在打开文件的时候,还没有界面可以指定文件的编码方式,还是默认 GB2312。这里似乎需要一个专用于打开 ISO2709 文件的对话框,其实 dp2 项目里面是有的。但是否复制到 chord 里面来,还没有想好。
其实这些功能主要是要找到一个合适的函数库存放。
函数库的功能组织要合理。所谓合理就是,用它的时候它存在,不用它的时候就不连接它。最好功能成组。不要出现因为需要一个函数库里面的一点点功能,就连接整个函数库的局面。这通常说明函数库内的功能组织不是太合理。如果合理的话,相近的功能都是在一起的,只要引用少量函数库就可以获得必要的功能。
比如 MarcQuery 是处理 MARC 的,那么相关的功能都聚集在这里原则上没有错。但另外一个维度,要考虑函数库是否和 UI 有关,比如是否和 Windows Form 有关。和 UI 无关的函数库,一般是默默用在服务器模块,服务器不需要 UI。有 UI 的函数组织在一起,和无 UI 的分开。这样函数库可以视为用于底层的或者服务器的,和用于前端的。 有 UI 的函数库,也可以细分为,为 Windows Form 服务的,为 WPF 服务的,为 IE 浏览器控件服务的,几类。也需要按类单独聚集。
所以现在拿着这个 MARC 文件打开专用对话框,要考虑考虑放在什么函数库里面。
放在 MarcQuery 函数库里面,违背了这个函数库不包含 UI 组件的原则。
MarcLoade 类还可以挂接回调函数,在遍历过程中触发回调函数显示装载进度。注意它并不去显示,而是仅仅触发函数。
MarcLoader使用例子
看这个代码: https://github.com/DigitalPlatform/chord/blob/master/TestZClient/Form1.cs
这段代码简单得令人发指。
首先构造 MarcLoader 的时候,注意有个回调函数稍微洋派一点,不要被迷惑了,它不过是提供进度条显示用。如果对回调函数不理解,回调函数这里使用 null 也行。
然后就是一个 foreach,注意截图的底部。就是这个 foreach 就完成了对文件里面记录的遍历。哪怕文件有一亿个记录,也可以遍历,而且不多费内存。
目前这个 MarcLoader 还比较简单,构造的时候是给它一个文件名字符串。后面我还会增加给它一个 stream 用于构造的版本,因为有的开发者喜欢这么用。
您可以重点看看它是如何实现 IEnumberable 接口的,如何写庞大集合(如果全部装入内存会导致内存溢出)的 yield 枚举过程的。这个 yield 是 C# 后来的语法糖,是我们常说如何如何超过 Java 的重要部分。
这些和 await 什么加在一起,构成了“现代”的 C#。其实很多人写的老式 C# 和这种现代的差异很大,几乎是不同的两种“语言”了。
最后那个截图,为啥 foreach 要包在一个 Task.Run() 以内并且 await,那就是要让 Windows Form 界面在长操作中不至于冻结。而且,要全程控制这个 foreach,比如能用 try catch 包住,可以捕获异常。那这个 await 写法就几乎是唯一的了。
如果用 Task.Run() 启动了一个 Task 但是不 await,Windows Form 界面也做到了不冻结,但 _click() 函数这里对循环过程失控了,因为循环在一个不相干的单独线程里面去执行了,飞走了。比如万一抛出异常就麻烦,没有人去捕获和处理。
这是 2018 年了,所以写这些东西要求要高一些。如果是十年前,可以用 Application.DoEnvents() 来对付一下,让界面不至于冻结,但这个方法问题很多,具体表现就是 dp2catalog 经常会抛一点异常什么的,始终不太容易写完美和干净。
MarcQuery 函数库增加了从一个 ISO2709 byte[] 转换到机内格式 string 的函数
用户问题:
为什么我这会出现字段前面都少内容的结果
是不是我直接用marc的string初始化marcrecord不对
数字平台谢:
构造 MarcRecord 的时候,用的是一个机内格式的字符串。这个字符串前面 24 个 char 是头标区,后面依次为各个字段。每个字段末尾都有字段结束符 char,注意这个 char 的内码是 30(十进制)。字段内容中可能有子字段符号,内码是 31。整个记录结束可用也可用不用记录结束符,记录结束符是 29。
node.Content 是字段或者子字段内容。要看这个 node 是具体什么类型了。node.Name 是字段或者子字段名。注意子字段名是一个字符的字符串。
字段的 node.Content 里面可能有子字段符号,您显示出来的时候小心,有些环境不一定能显示这个字符。可以在显示前替换一下,确保显示出来能被看见。node.Text 一般是等于 node.Name 连接 node.Indicator 连接 node.Content。
一般关于编程的交流,可以在 GitHub 上建立一个自己的 repo,然后把写好的代码 Push 上去,这样讨论编程问题的时候,一个 URL 就可以让对方看到全部代码。如果必要也可以 Clone 到本地进行编译调试。这样交流就直接了。
MarcQuery 函数库增加了从一个 ISO2709 byte[] 转换到机内格式 string 的函数,非常方便。先转换为机内格式字符串,再用于构造 MarcRecord 就好了。不过 MarcQuery 的 NuGet 包还没有来得及更新,chord 最新源代码里面是有这个函数的。
看这个代码:
https://github.com/DigitalPlatform/chord/blob/master/DigitalPlatform.MarcQuery/MarcLoader.cs
如果代码的实现您觉得繁琐,可以不看,但可以了解一下函数的参数。这个很简单,就是输入一个 byte[],输出一个 string。还要指明 Encoding。这都是设计阶段可以想象的参数。
上面介绍的是如果你手握一个 byte [],可以这么去处理,先把它变换为(机内格式的) string,然后再用于构造 MarcRecord对象。但,容我多说一句。您这个 byte [] 从哪儿来的?
一般来说,是两种途径。一种是从 Z39.50 服务器获取来的。另外一种是从一个 ISO2709 文件读入进来的。
如果是第二种情况,您大概还费了一点功夫,判断 ISO2709 的记录结束符 29,弄清楚了记录边界然后读入的。但这个编程属于基本套路了,很常见,如果每个人都去重复编写一下也是很累的,还容易出错,因为国内出现了一些变种的 ISO2709 文件,每个记录末尾有回车符号一对。
所以 MarcLoader 这个枚举类本身是解决从 ISO2709 文件一个一个读入,遍历 ISO2709 记录而且连带把 ISO2709 转换为机内格式 string 这些工作都做了。