解决方案
好的,我目前正在从自由格式文本 (XML) 中提取数千个 DOI,我意识到 我以前的方法 有一些问题,即关于编码实体和尾随标点符号,所以我继续阅读 规格 这是我能得到的最好的。
DOI前缀应由目录指标组成,然后是注册守则。这两个组件应通过完整的停止(周期)分开。
目录指示符应为“10”。目录指标将整个字符字符串(前缀和后缀)的整个集合为分辨率系统中的数字对象标识符。
很简单,最初的 \b
阻止我们“匹配”不以以下开头的“DOI” 10.
:
$pattern = '\b(10[.]';
DOI 前缀的第二个元素是注册人代码。注册人代码是分配给注册人的唯一字符串。
此外,所有分配的注册人代码都是数字,且长度至少为 4 位,因此:
$pattern = '\b(10[.][0-9]{4,}';
如果需要,则可以将注册守则进一步分为子元素,以便管理方便。注册守则的每个子元素应在完整停止之前。
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*';
doi语法应由doi前缀和前斜线分隔的doi前缀组成。
然而,这并不是绝对必要的,第 2.2.3 节指出,不常见的后缀系统可能会使用其他约定(例如 10.1000.123456
代替 10.1000/123456
),但让我们放松一些。
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/';
DOI名称是不敏感的,可以从Unicode的法定图形字符中结合任何可打印字符。DOI后缀应由注册人选择的任何长度的字符串组成。每个后缀应在其前面的前缀元素中是唯一的。唯一的后缀可以是一个顺序数字,也可以合并基于另一个系统或基于另一个系统的标识符。
现在这就是事情变得更棘手的地方,从我处理过的所有 DOI 中,我看到了以下字符(除了 [0-9a-zA-Z]
当然)在他们的 后缀: .-()/:-
——所以,虽然 DOI 不存在 10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7
是完全合理的。
合理的选择是使用 \S
或者 [[:graph:]]
PCRE POSIX 类,所以让我们这样做:
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/\S+'; // or
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/[[:graph:]]+';
现在我们遇到一个难题, [[:graph:]]
类是一个超集 [[:punct:]]
类,其中包括在自由文本或任何标记语言中轻松找到的字符: "'&<>
除其他外。
现在让我们使用负前瞻过滤标记:
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])\S)+'; // or
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])[[:graph:]])+';
上面应该涵盖编码实体(&
), 属性引号 (["']
)和打开/关闭标签([<>]
).
与标记语言不同,自由文本通常不使用标点符号,除非它们至少以一个空格为界 或者 放在句末,例如:
这是一个很长的 DOI:
10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7
!!!
这里的解决方案是关闭我们的捕获组并断言另一个字边界:
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])\S)+)\b'; // or
$pattern = '\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])[[:graph:]])+)\b';
和 瞧, 这是一个演示.
其他提示
@Silas 健全性检查是个好主意。但是,正则表达式并不涵盖所有 DOI。第一个元素(当前)必须为 10,第二个元素(当前)必须为数字,但第三个元素几乎不受限制:
“合法字符是 Unicode 的合法图形字符。这特别排除了控制字符范围 0x00-0x1F 和 0x80-0x9F...”
这就是真正的问题所在。在实践中,我从未见过使用空格,但规范特别允许这样做。基本上,似乎没有一种明智的方法来检测 结尾 DOI 的。
CrossRef 有推荐, ,他们在 99.3% 的 DOI 上测试成功:
/^10.\d{4,9}/[-._;()/:A-Z0-9]+$/i
我确信目前这对OP来说并不是超级有帮助,但我想我应该发布我正在尝试的内容,以防像我这样的其他人偶然发现这一点:
(10.(\d)+/(\S)+)
这匹配:“10 点数字斜线,任何非空白字符”
但对于我的使用(抓取 HTML)来说,这是发现误报,所以我必须匹配上面的内容,加上去掉引号和大于/小于:
(10.(\d)+/([^(\s\>\"\<)])+)
我仍在测试这些,但到目前为止我感到充满希望。
这是我的做法:
(10[.][0-9]{4,}[^\s"/<>]*/[^\s"<>]+)
还有一些有效的边缘情况,这不会失败,但其他情况似乎会失败:
10.1007/978-3-642-28108-2_19
10.1007.10/978-3-642-28108-2_19
(虚构的例子,参见 @Ju9OR 评论)10.1016/S0735-1097(98)00347-7
10.1579/0044-7447(2006)35\[89:RDUICP\]2.0.CO;2
另外,正确丢弃一些虚假的 (X|HT)ML 内容,例如:
<geo coords="10.4515260,51.1656910"></geo>
这是一个非常古老且已回答的问题,但这是另一个潜在的替代品。
\b10\.(\d+\.*)+[\/](([^\s\.])+\.*)+\b
这假设空白不是 DOI 的一部分。
尚未对此进行误报测试,但它似乎能够找到本页中提到的所有边缘情况。
以下正则表达式应该可以完成这项工作(Perl 正则表达式语法):
/(10\.\d+\/\d+)/
您可以通过打开网址进行一些额外的健全性检查
http://hdl.handle.net/<doi>
和
http://dx.doi.org/<doi>
候选人 doi 在哪里,
并测试您 a) 获得 200 OK http 状态,b) 返回的页面不是该服务的“DOI 未找到”页面。