LemmaLoom用户指南 PDF

自定义分析器

自定义分析器是一段简短的 JavaScript 程序。它读取文档的语法分析,从中找出你需要的段落。你在窗口 ▸ 分析器中编写脚本,LemmaLoom 在为你所有项目中所选语言的文档建立索引时运行它。分析器找到的内容与各个文档关联存储。之后,你可以通过分析器的透镜查看这些发现,为它们自动编码,也可以在查询中统计和比较它们。

有些问题取决于语法,而内置透镜又无法以你需要的形式回答,分析器就用于这类问题。只回答一个问题时,查询更快(见第 15 章“构建查询”);一般来说,如果你要在文档(包括现有的文档和以后导入的文档)中反复查找同一种模式,并且既要在阅读器中看到标注,又要能统计结果,就值得编写一个分析器。

本章以示例项目“气候辩论”为例,这个项目通过文件 ▸ 导入 ▸ 示例项目导入(见第 1 章“简介”中的示例项目)。具体来说,本章考察“气候”在小句中是充当语法主语(如“气候并没有等我们”),还是充当宾语(如“保护孩子们将要继承的气候”)。词典会找出“气候”这个词的每一次出现,不管它处在什么语境中;分析器则能根据语法分析中记录的功能,区分主语和宾语。示例分析器“气候角色”用一种颜色标注每个作主语的“气候”,用另一种颜色标注每个作宾语的“气候”。编写第一个分析器详细介绍了如何自己编写这个分析器。

分析器是 LemmaLoom 中唯一要求你具备编程技能的部分。脚本相对简短,也不复杂,但会用到变量、列表、循环、if 语句,以及用 => 语法定义的匿名函数。如果你从没写过程序,MDN Web Docs 的免费 JavaScript 课程会介绍这些基础知识。如果你对编程不感兴趣,可以构建一个查询来取得所需的数据,再把这个查询转换为分析器,由它自动生成脚本(见第 18 章“使用查询结果”中的把查询转换为分析器)。生成的脚本也是很好的学习范例。

关键术语

在 LemmaLoom 中,分析器是在建立索引时运行、为文档添加发现的程序。(关于 LemmaLoom 如何建立索引,详见第 21 章“分析与索引”。)LemmaLoom 自带三个分析器,即衔接、元话语和修辞。自定义分析器可以由你自己编写,也可以由 LemmaLoom 根据你的查询自动生成。每个自定义分析器都属于你的资料库,所以都会在所有项目的文档上运行。

脚本用 JavaScript 编写,必须包含一个名为 analyze 的函数。LemmaLoom 会把文档的分析交给这个函数并执行它,你的分析器则必须把发现返回给 LemmaLoom。

文档对象是传入你的函数的 JavaScript 对象(这里的对象取编程上的意思,即一组带名称的字段,例如 text 和 lemma)。这个对象包含若干字段,分别表示文档的各个方面,包括语言、句子及其中的词符、短语、名词块,以及内置分析器的输出。

词符是单个的词形和标点符号。每个词符都带有它的词典形式(即词元)、词类标记、它与所依存的词(即它的中心词)之间的依存关系,以及数、时态等形态特征。

发现是分析器报告的一段文字,由同一句子中一串相邻的词符和这条发现的键组成。

键表示发现的种类,例如 subject、object 等。图例是你的键的集合,并为每个键指定显示时所用的标签、颜色和样式。分析器透镜的图例就由它生成。

位置可以对自定义分析器做什么
窗口 ▸ 分析器编写、测试、存储、修改和删除分析器
导入文档对话框和文档信息选择分析器是否在某个文档上运行;再次运行它或移除它的输出
阅读器的透镜菜单在打开的文档中标注发现
文档 ▸ 自动编码…为发现编码
查询查找分析 · 发现,在规则中检验它们,并统计和比较它们

分析器窗口

选取窗口 ▸ 分析器打开这个窗口。窗口分为三栏。

  • 左侧第一栏是你的分析器列表,按名称排序。每一行列出分析器运行的语言,例如 de en。点按列表下方的添加分析器按钮即可新建分析器。
  • 第二栏显示当前选中的分析器。顶部是名称、语言复选框英语、德语和中文,以及存储按钮。下面依次是图例、脚本编辑器和试一试,你可以在试一试中用几个句子测试分析器。
  • 第三栏是参考面板,列出辅助函数、具名常量和文档对象的成员。
已选中“气候角色”的分析器窗口,“试一试”下方的测试句中显示了它的发现。已选中“气候角色”的分析器窗口,“试一试”下方的测试句中显示了它的发现。
已选中“气候角色”的分析器窗口,“试一试”下方的测试句中显示了它的发现。

窗口打开时,中间一栏显示一个尚未存储的新分析器,它的示例脚本会找出所有名词。要修改已有的分析器,请在左侧列表中点按它。起初,试一试窗格只在这一栏底部占据很小的高度;测试时,向上拖动分隔线即可把它拉高。LemmaLoom 会记住这个高度,下次启动时沿用。

编写脚本时,编辑器会为各行编号,用不同颜色显示脚本的各个部分,并提示名称。输入 POS. 之后,自动补全弹出菜单会列出所有词类;同样,输入 LL. 之后,弹出菜单会列出辅助函数。编辑器还会在你输入时给某些错误加下划线,例如引用了未定义的常量。要在脚本中插入断点,请点按相应行号左侧的空白处(见本章后文断点与单步执行)。

“分析器”窗口中的编辑器,在新的最后一行输入 POS. 之后,提示出各个词类。“分析器”窗口中的编辑器,在新的最后一行输入 POS. 之后,提示出各个词类。
“分析器”窗口中的编辑器,在新的最后一行输入 POS. 之后,提示出各个词类。

编写第一个分析器

分析器“气候角色”只有两个键,subject 和 object。每个“气候”取哪个键,取决于这个词在句子结构中充当主语还是宾语。完整的脚本如下:

const CLIMATE = ['climate', 'klima', '气候'];
function analyze(doc) {
  const findings = [];
  for (const sentence of doc.sentences) {
    sentence.tokens.forEach((token, index) => {
      if (CLIMATE.includes(token.lemma.toLowerCase())) {
        const role = token.derived.function;
        if (role === FUNCTION.SUBJECT) {
          findings.push({ sentenceIndex: sentence.index, start: index, end: index + 1, key: 'subject' });
        }
        if (role === FUNCTION.OBJECT) {
          findings.push({ sentenceIndex: sentence.index, start: index, end: index + 1, key: 'object' });
        }
      }
    });
  }
  return findings;
}

“气候辩论”中的文档大多是中文,“时间不多了 (英语)”则是英文,所以“气候角色”在这两种语言上运行。它的图例把两个键分别称为“气候作主语”和“气候作宾语”。

要新建分析器:

  1. 选取窗口 ▸ 分析器。如果中间一栏显示的是你已存储的分析器,请点按列表下方的添加分析器。
  2. 在名称中输入名称。
  3. 勾选分析器要处理的文档语言。英语一开始就已勾选。
  4. 点按编辑器,按 ⌘A 选中示例脚本,然后输入或粘贴你的脚本来替换它。
  5. 在图例中为每个键设置标签,LemmaLoom 会在这里列出它在脚本中找到的键(见本章后文键与图例)。
  6. 在试一试下测试脚本(见本章后文用“试一试”测试)。
  7. 点按存储。

测试“气候角色”时,最好用一个同时包含两种用法的句子,例如 The climate is changing faster than our politics, so we must do more for the climate(气候变化得比我们的政治还快,所以我们必须为气候多做一些)。在旁边的语言菜单中选取 en 后点按运行,第一个 climate 会以蓝色标为 subject,第二个以红色标为 object。

存储之后,新分析器会加入列表,LemmaLoom 随即在后台对你所选语言的文档运行它。这些语言的每个文档的透镜菜单中会立即出现这个分析器;分析器在某个文档上运行完毕后,它的标注就会出现在那里。

解读脚本

如果你熟悉 JavaScript,可以直接跳到下文的分析器接收的文档。请注意,下文提到的行号是代码粘贴进去后编辑器显示的行号。

第 1 行(const CLIMATE = …)定义哪些词元算作“气候”。const 为一个值命名,方括号定义一个数组。英语 climates 的词元是 climate,德语 Klimas(如 des Klimas)的词元是 Klima。这个数组列出这个词的英语、德语和中文形式。请注意,德语词写成小写的 klima,因为第 6 行在匹配词元时会把每个词元转成小写。

第 2 行(function analyze(doc) {)定义 LemmaLoom 要调用的函数,它有一个名为 doc 的参数。这个参数就是 LemmaLoom 传入的文档对象。第 3 行(const findings = [])声明一个数组变量 findings,用来存放发现。

第 4 行(for (const sentence of doc.sentences) {)遍历文档的每个句子,每次都执行这个代码块中的操作。同样,第 5 行(sentence.tokens.forEach(…))遍历当前句子中的词符。(token, index) => { … } 是一个匿名函数;forEach 每次调用它,传入当前词符和它在句中的位置(从 0 开始)作为参数。

如果第 6 行(if (CLIMATE.includes(…)) {)的条件为真,也就是 includes 在列表 CLIMATE 中找到了词符的词元,这个 if 的花括号中的语句就会执行。

第 7 行(const role = token.derived.function;)读取当前词符的语法功能值,也就是语法功能透镜显示的值。第 8 行(role === FUNCTION.SUBJECT)和第 11 行(role === FUNCTION.OBJECT)用 === 检验是否相等。两个操作数相等时,相等运算符返回真。这里,脚本检查词符的语法功能是 FUNCTION.SUBJECT 还是 FUNCTION.OBJECT(见本章后文常量)。

条件为真时,脚本在第 9 行和第 12 行(findings.push(…))把表示发现的对象添加到数组 findings 中;注意对象用花括号括起来。每个对象有四个字段,分别表示来源句子、这段文字的起始位置(start,即当前词符的 index)、紧接在这段文字最后一个词符之后的位置(end,这里是 index + 1)以及键。因此,每条发现只对应一个词,它的起始位置和结束位置是两个相邻的数。

最后,第 17 行(return findings;)把结果交还给 LemmaLoom。

宾语是语法功能中一个宽泛的类别,也包括介词的宾语。因此,这个脚本既会统计 protect the climate 这样的例子,也会统计 do more for the climate。被动句的主语,如 the climate is affected by our emissions 中的 climate,属于 FUNCTION.SUBJECT 这一语法功能类别,所以算作主语。如果要把被动句主语和宾语算在一起,可以把第 7 行改为 const role = token.dep === DEP.NSUBJPASS ? FUNCTION.OBJECT : token.derived.function;。借助 ? 和 :,这一行根据词符是不是被动句主语,在 FUNCTION.OBJECT 和词符本身的功能之间选择。请记住,这种办法只在英语中完全适用;德语的语法分析给被动句主语的标注与主动句主语相同,中文的语法分析只给部分被字句的主语加上这个标签。

这个脚本可以从语法上追踪任何一个词,例如“政府”或“产业”。把第 1 行中的词元换成你要追踪的词的小写形式即可。

分析器接收的文档

函数 analyze 接收一个参数,下文称为 doc。这个参数有三个字段:

字段内容
doc.language分析所用的语言,即 "en"、"de" 或 "zh"
doc.sentences各个句子,按顺序排列
doc.builtins内置分析器衔接、元话语和修辞的结果

分析器只能根据文档的分析返回结果。如果要把分析器的发现与你的编码实例、备忘录、链接、书签、属性、文档信息或其他自定义分析器的结果结合起来,请考虑为此构建一个查询(见本章后文使用发现)。

对于非常大的文档,LemmaLoom 会分段建立索引,每段文本最长半兆字节(约 80,000 个英语单词)。LemmaLoom 对每一段调用一次 analyze 函数,句子编号也重新从 0 开始。一般长度的文档作为一个整体建立索引。

句子

doc.sentences 中的每一项包含以下字段:

字段内容
index句子的编号,从 0 开始。它也是句子在 doc.sentences 中的位置。
text句子的文本
tokens句子中的词符,按顺序排列
root句子根词符的位置,根通常是主要动词;句子没有句法分析时为 -1
constituents句子中的短语(见短语与名词块)
nounChunks句子中的名词块

词符

词符在每个句子中从 0 开始编号,脚本中的位置指的都是词符的编号。词符可以是词,也可以是标点符号。每个词符包含以下字段:

字段内容示例
text词符在文本中的原样targets
whitespace词符后面的空白" "
lemma词典形式;中文与 text 相同target
pos通用词类,各语言使用相同的代码NOUN
tag该语言自身标记集中的细粒度标记NNS
dep与中心词之间的依存关系nsubjpass
head中心词的位置;根指向自身4
index词符自身在句中的位置2
morph形态特征;中文为空{ Number: "Plur" }
isStop对 the、of 这类非常常见的词为 truefalse
derivedLemmaLoom 根据上述字段得出的三个类别(见下一节)

表中的示例分析基于句子 The new targets were announced last year.(新的目标已于去年公布)。这个句子的分析大致如下:

indextextlemmapostagdepheadderived.function
0ThetheDETDTdet2adjectival
1newnewADJJJamod2adjectival
2targetstargetNOUNNNSnsubjpass4subject
3werebeAUXVBDauxpass4connector
4announcedannounceVERBVBNROOT4mainClause
5lastlastADJJJamod6adjectival
6yearyearNOUNNNnpadvmod4adverbial
7..PUNCT.punct4unknown

可以把 head 一列看作从每个词指向它所依存的词的箭头。targets 作为被动句主语,依存于位置 4 的 announced;the 和 new 则依存于 targets。announced 是根词,所以它的中心词就是它自己,sentence.root 为 4。

标记和关系标签因语言而异。英语的标记遵循 Penn Treebank,关系标签是 nsubj、dobj 这样的英语依存关系。德语的标记遵循 STTS,关系标签遵循 TIGER,例如 sb 表示主语,oa 表示宾格宾语。中文的标记遵循 Chinese Treebank,关系标签是英语的关系标签加上 aux:asp、nmod:assmod 等中文标签。pos 字段和 derived 各字段的取值在三种语言中完全相同,所以只检验这些字段的脚本适用于所有语言。参考面板按名称列出了标记和标签(见本章后文参考面板)。

派生类别

token.derived 包含 LemmaLoom 为每个词符计算出的三个类别。每个类别对应一个透镜,在每种语言中含义都相同。

字段透镜取值
derived.function语法功能subject、object、predicative、complement、adjectival、adverbial、appositive、agent、conjunct、connector、mainClause、unknown
derived.posCategory词类noun、verb、adjective、adverb、pronoun、determiner、preposition、conjunction、number、interjection、other
derived.phraseCategory短语nounPhrase、verbPhrase、prepPhrase、adjPhrase、advPhrase、conjPhrase、interjection、other,或空字符串

LemmaLoom 根据词符的依存关系推断它的功能。不过,对于几种关系,推断时还要考虑词符的中心词。如果中心词是名词、代词或数词,像 advmod 这样的修饰关系算作 adjectival,否则算作 adverbial。带子类型的关系(用冒号标出)按冒号前的部分归类,只有 obl:agent 例外,它算作 agent。根的功能是 mainClause,unknown 指其余所有关系,例如标点。

短语类别是包含该词符的最小短语(小句除外)的类别。只属于小句一级短语的词符(例如句号),短语类别为空字符串。

短语与名词块

sentence.constituents 给出句子中各个短语组成的数组,依据的是短语透镜和句子图示中直观显示的短语结构树。每个短语包含其词符的起始位置和结束位置(start 和 end),以及短语标签的数组 labels,例如名词短语为 ['NP']。如果几个短语覆盖的词符完全相同,它们会合为一个对象,标签按从外到内的嵌套顺序排列。

sentence.nounChunks 给出句子中的名词块。名词块是一个名词连同它前面从属于它的词,例如 the new targets。名词块包含起始位置和结束位置。

对于 The new targets were announced last year.,这两个数组可能是这样的:

constituents: [
  { start: 0, end: 8, labels: ['S'] },
  { start: 0, end: 3, labels: ['NP'] },
  { start: 3, end: 7, labels: ['VP'] },
  { start: 4, end: 7, labels: ['VP'] },
  { start: 5, end: 7, labels: ['NP'] },
]
nounChunks: [
  { start: 0, end: 3 },
]

S 表示句子,VP 表示动词短语。last year 是名词短语,但不是名词块。在英语中,名词块只出现在充当主语、宾语或句中类似成分的名词或代词周围,而这里的 year 实际上是状语修饰语。两个列表都可能为空,例如只有一个词的回答。编写程序代码时,要考虑到这些情况。

内置分析器的结果

你的自定义分析器最后在文档上运行。三个内置分析器先运行,并把结果放在 doc.builtins 中。即使你先前删除了某个内置分析器的结果,你的自定义分析器仍然可以使用这些结果。

字段每一项包含
doc.builtins.cohesion每句一项,包含 sentenceIndex、tie(句子如何与前文衔接)、theme 和 rheme(分别是句子开头和句子重心所在的词符范围,形式为 [start, end];句子没有相应部分时,该字段不存在)以及 tieWord(构成衔接的词)
doc.builtins.metadiscourse分析器找到的每个词一项,包含 sentenceIndex、local(词符的位置)以及 category(例如 hedge)
doc.builtins.rhetoric每个修辞手法一项,包含 device(例如 passiveVoice)、sentenceIndex、range(词符范围,形式为 [start, end])以及 note

tie 有七个取值(opening、pronoun、possessive、demonstrative、repetition、zero 和 broken)。category 有五个可能的取值(hedge、booster、attitude、connective 和 engagement);此外,德语的 category 还可能是 modalParticle,中文的还可能是 finalParticle。最后,device 有十三个可能的取值,从 passiveVoice 到 correlative。参考面板列出了每个字段的所有可能取值,以及透镜中定义的名称。关于各个分析器分析什么,详见第 6 章“透镜”。

辅助函数

LL 提供了几个函数,用来处理编写脚本时的常见操作。

函数返回
LL.isWord(token)词符不是标点或 $ 之类的符号时为 true
LL.isNominal(pos)对 NOUN、PROPN 和 PRON 为 true
LL.isVerbal(pos)对 VERB 和 AUX 为 true
LL.children(sentence, i)以词符 i 为中心词的各个词符的位置
LL.subtree(sentence, i){ start, end },即词符 i 周围由它和直接或间接依存于它的词符组成的连续范围
LL.root(sentence)句子根的位置,与 sentence.root 相同
LL.text(sentence, start, end)从 start 到 end 之前各词符的文本,连同其间的空白
console.log(...)在试一试下方写出一行;列表和对象会完整写出

在示例句中,LL.children(sentence, 4) 返回数组 [2, 3, 6, 7],即依存于 announced 的词符。调用 LL.subtree(sentence, 2) 会得到一个有两个字段的对象 { start: 0, end: 3 },它覆盖文本 The new targets;调用 LL.text(sentence, 0, 3) 则得到文本 "The new targets"。

console.log 调用只用于调试;分析器在你的文档上运行时,它的输出会被丢弃。

常量

绝大多数字段存放的是 NOUN、nsubjpass 这样的简短代码。代码可以写成带引号的字符串,也可以写成具名常量;表达式 token.pos === POS.NOUN 与 token.pos === 'NOUN' 含义相同。请尽可能使用具名常量。编辑器会弹出具名常量的建议,并给拼错的常量加下划线。带引号的字符串则无论拼写是否正确,都会原样接受。运行时,像 POS.NOUM 这样拼错的常量求值为 undefined,脚本照常运行,所以下划线是唯一的提醒。

常量对应的取值示例
POStoken.posPOS.NOUN 即 'NOUN'
TAGtoken.tag,三种语言都有TAG.NNS 即 'NNS'
DEPtoken.dep,三种语言都有DEP.NSUBJPASS 即 'nsubjpass'
PHRASE成分的 labelsPHRASE.NP 即 'NP'
MORPHtoken.morph 中特征的名称MORPH.TENSE 即 'Tense'
FUNCTIONtoken.derived.functionFUNCTION.SUBJECT 即 'subject'
CATEGORYtoken.derived.posCategoryCATEGORY.NOUN 即 'noun'
PHRASE_CATEGORYtoken.derived.phraseCategoryPHRASE_CATEGORY.NOUN_PHRASE 即 'nounPhrase'
COHESIONdoc.builtins.cohesion 中的 tieCOHESION.REPETITION 即 'repetition'
METADISCOURSEdoc.builtins.metadiscourse 中的 categoryMETADISCOURSE.HEDGE 即 'hedge'
RHETORICdoc.builtins.rhetoric 中的 deviceRHETORIC.PASSIVE_VOICE 即 'passiveVoice'
KEY本分析器图例中的键KEY.SUBJECT 即 'subject'

LemmaLoom 中的常量名称就是大写的代码。如果代码中小写字母后面紧跟大写字母,就在两者之间加一个下划线,例如 nounPhrase 写作 NOUN_PHRASE,mainClause 写作 MAIN_CLAUSE。不能出现在名称中的非字母符号(例如冒号)都换成下划线。

MORPH 常量是特征代码的名称。特征的取值请写成字符串,例如 token.morph[MORPH.TENSE] === 'Past',或者直接写成 token.morph.Tense === 'Past'。

常量名称适用于大多数代码,但有四种情况不同:

  • DEP.ROOT 的值是 'root',但分析中根的标签是 ROOT。要判断一个词符是不是根,请比较位置,如 index === sentence.root,或者使用 'ROOT'。
  • MORPH 常量下列出十个常见特征,即 Tense、Number、Person、Mood、VerbForm、Case、Degree、Aspect、Gender 和 PronType。MORPH 常量中没有的特征,请直接使用它的字符串形式,如 token.morph.Definite === 'Def'。
  • 只出现在中文中的短语标签,例如 IP、DNP 和 LCP,需要在名称外加引号。
  • 完全由标点构成的标记,例如英语的句号(.)和逗号(,)或德语的 $,,也需要加引号。

不要使用已经属于常量、LL 或 console 的名称。脚本如果自己定义了 const KEY,运行时就会因出错而停止。

参考面板

窗口右侧的参考面板列出文档对象的各个部分、辅助函数和常量。API 下列出 doc.language、doc.sentences[i].tokens[j]、doc.builtins 中的全部三个列表,以及各个辅助函数。每个组成部分的声明后面都注明它的值类型,例如 String、Bool。句子、词符和发现对象的所有字段,可以在本章的表格中查看。API 下面的各部分列出常量,每个常量都附有一个通俗的名称:

  • 词类(POS)、细粒度标记(TAG)、依存关系(DEP)、成分标签(PHRASE)和形态特征(MORPH);
  • 语法功能(FUNCTION)、词类别(CATEGORY)和短语类别(PHRASE_CATEGORY);
  • 修辞手法(RHETORIC)、衔接手段(COHESION)和元话语类别(METADISCOURSE)。

以上各部分默认都是收起的。点按某一部分的标题,即可展开或收起这一部分。在搜索中输入某一项的一部分文字,即可查找它。查找常量时,会比对它的名称、代码或通俗名称;查找 API 中的项时,会搜索 API 声明的原文。面板会把所有部分中匹配的项合并列出,或者显示无匹配项。点按任何一项,即可把它插入编辑器中光标所在的位置。

在“搜索”中输入 subject 后的参考面板。在“搜索”中输入 subject 后的参考面板。
在“搜索”中输入 subject 后的参考面板。

发现与图例

发现

analyze 必须返回一个列表,其中每个元素都是一条发现,包含以下字段:

字段内容
sentenceIndex句子的 index
start这段文字第一个词符的位置
end紧接在最后一个词符之后的位置
key图例中的某个键
note一段你自己写的简短文本;可选

以 The new targets were announced 为例,第一个词符在位置 0,最后一个在位置 4,所以这条发现取 start: 0 和 end: 5。因此,词符数总是等于 end - start,这里是 5。

一条发现只能位于一个句子之内。发现可以相互重叠,因此一个词符可以带有多个键;这时阅读器会在它上面呈现所有这些键的样式。

注释字段供查询使用。你可以在查询中用字段注释检验它们(见本章后文使用发现)。

键与图例

发现用到的每个键,在图例中都必须有对应的一行。编辑器上方的图例窗格为每个键显示一行,包含以下几部分:

  • 键显示脚本中写的键。
  • 标签是透镜图例中显示的名称,查询也使用它。标签最初就是键名本身。
  • 颜色池设定这个键的发现所用的颜色。新键会从八种颜色中依次取色。
  • 样式菜单设定阅读器的呈现方式,选项有高亮、下划线、文本颜色和粗体。新添加的键最初选择的是高亮。

你编写脚本时,脚本中每个用引号写出的键(key: '...')都会在图例中生成一行,顺序与它们在脚本中出现的顺序相同。从脚本中删除一个键,图例中相应的行也会删除。如果修改了键名,原来那一行就会删除,换成一个新行,以修改后的键为标签,并使用默认颜色。因此,最好在确定键名之后再设置颜色和标签。

你可以随时修改任何一行,包括它的标签、颜色和样式。存储之后,对图例行所做的修改会显示在启用了该透镜的每个文档中。

由脚本计算出的键

键也可以根据分析本身生成。用下面这一行替换分析器“气候角色”中的两条 if 语句及其中的 push 语句,语法功能就会成为键:

findings.push({ sentenceIndex: sentence.index, start: index, end: index + 1, key: token.derived.function });

作主语的“气候”得到键 subject,作宾语的“气候”得到 object,climate policy(气候政策)的第一部分得到 adjectival,依此类推,涵盖本章前文派生类别中列出的全部十二个取值。

这些键通过试运行进入图例。点按存储时,LemmaLoom 会在一个由三个词组成的示例句(英语为 Cats sleep soundly)上运行脚本,并自动添加运行中产生的键。这些行会保留各自的标签和颜色;脚本不再产生的键会在下次存储时移除。

试运行没有产生的键(如本例中的键)需要手动添加:

  1. 点按图例下方的添加键。
  2. 在新行的键栏中输入键,写法要与脚本产生的完全一致(例如 adjectival)。
  3. 填写标签,并设置颜色和样式。

你添加的行末尾有一个减号按钮,用来移除这一行;无论脚本中写了什么,这一行都会保留在图例中。存储时,LemmaLoom 会略去键为空的行;两行的键相同时,只保留第一行。

“气候角色”的“图例”窗格,用“添加键”新增了一行,样式菜单已打开。“气候角色”的“图例”窗格,用“添加键”新增了一行,样式菜单已打开。
“气候角色”的“图例”窗格,用“添加键”新增了一行,样式菜单已打开。

图例中有某个键,KEY 中就有对应的常量。图例中列有键 subject 时,你可以写 key: KEY.SUBJECT。如果你只以 KEY 常量的形式写出某个键,就需要用添加键添加它。以数字开头的键(如 1st),或包含 a 到 z 以外字母的键(如 über 或 主语),要用引号括起来。

支持的语言

复选框英语、德语和中文决定分析器在哪些文档上运行。LemmaLoom 为这三种语言的文档建立索引。只有所选语言的文档,才会在透镜菜单中提供这个分析器,并在文档信息中显示它。至少要选择一种语言。

请确保你的脚本适用于所选的语言。“气候角色”适用于全部三种语言,不过“气候辩论”只需要中文和英语。“气候角色”的列表包含英语、德语和中文的词元,检验的是 token.derived.function,这个字段在所有语言中取值都相同。检验 token.tag 或 token.dep 的脚本只适用于其标签所属的语言。一个脚本可以借助 doc.language 处理多种语言:

let subjectLabel = 'nsubj';
if (doc.language === 'de') {
  subjectLabel = 'sb';
}

即使考虑到派生字段,每种语言的分析在细节上也会有所不同,所以请测试你选择的每种语言。在英语中,do more for the climate 会产生一条发现,因为分析把 climate 视为介词 for 的宾语。而在德语中,介词短语 für das Klima 中的名词通常标为 nk(名词短语的一部分),因而算作 adjectival,这个句子就不会产生发现。

要测试另一种语言,请在试一试下的语言菜单中选取它,再输入用这种语言写的句子。菜单中只能选择你勾选的语言,并以代码列出:en 表示英语,de 表示德语,zh 表示中文。

存储时,LemmaLoom 会从已取消勾选的语言的文档中删除这个分析器的发现,并在新勾选的语言的文档上运行它。

用“试一试”测试

试一试在你输入的文本上运行编辑器中当前的脚本,无论脚本是否已存储。编写时请经常使用试一试,并加入结构与最初例句不同的句子,因为分析器必须应对你的文档中的每一种句子结构。

  1. 在试一试下的文本框中输入一个或多个句子。
  2. 在文本框旁边的菜单中选取文本的语言。
  3. 点按运行。

LemmaLoom 用与文档相同的方法对你的文本进行语法分析,然后运行内置分析器,再运行你的脚本。你的文本会出现在文本框下方,发现按图例中指定的颜色和样式标注出来。对某种语言第一次运行之前,LemmaLoom 可能需要几秒钟为这种语言准备语法分析。文本框为空时,运行已停用(未输入文本);运行期间,按钮的位置会显示一个旋转图标。

运行之后的“试一试”,第一个 climate 被识别为主语,第二个被识别为宾语。运行之后的“试一试”,第一个 climate 被识别为主语,第二个被识别为宾语。
运行之后的“试一试”,第一个 climate 被识别为主语,第二个被识别为宾语。

标注文本的下方有一个小控制台,显示脚本用 console.log 写出的所有内容,每次调用占一行,每行前面都有 ›。脚本什么也没找到时,控制台显示0 个结果。如果想知道分析如何解读某个句子,可以把相应的值写出来。把下面这一行放在遍历句子的循环中,它会写出每个词符及其依存关系:

console.log(sentence.tokens.map((token) => token.text + '/' + token.dep));

对于 The new targets were announced last year.,它写出 ["The/det","new/amod","targets/nsubjpass","were/auxpass","announced/ROOT","last/amod","year/npadvmod","./punct"]。

只要窗口开着,测试文本就会保留在文本框中,即使切换分析器也是如此,所以你可以在同一段文本上运行多个分析器。

出现错误时

如果运行失败,LemmaLoom 会停止执行你的脚本,在编辑器下方以红色显示错误信息;如果错误出在某一行,还会在编辑器中给这一行加下划线。发现本身有任何错误时,编辑器会给第 1 行加下划线。

第 6 行写成 token.lema 而不是 token.lemma 时,点按“运行”后出现的错误。第 6 行写成 token.lema 而不是 token.lemma 时,点按“运行”后出现的错误。
第 6 行写成 token.lema 而不是 token.lemma 时,点按“运行”后出现的错误。
信息含义
The script must define a function named analyze(doc).没有名为 analyze 的函数,可能是因为函数名拼错了。
analyze(doc) must return an array of findings.函数没有返回任何内容,或者返回的不是列表。请检查 return 这一行。
Finding 3 must have sentenceIndex, start, end, and key.某条发现缺少四个字段之一,某个字段名拼错了,或者某个值的类型不对(位置不是数字,或者键不是文本)。像 KEY.SUBJCT 这样拼错的常量没有值,所以也会导致这条信息。发现从 0 开始计数,所以这里指的是第四条。
Finding 3: sentenceIndex 12 is out of range.没有这个编号的句子。
Finding 3: token range 4..4 is out of range for sentence 0.end 不大于 start,start 小于 0,或者 end 超出了句子末尾。
Finding 3: key “objekt” is not in the legend.这个键在图例中没有对应的行。请改正脚本,或者用添加键添加这个键。
以 TypeError、ReferenceError 或 SyntaxError 开头的信息JavaScript 本身的错误。编辑器会给相应的行加下划线。

TypeError 常常表示某个字段名拼错了。假设“气候角色”的第 6 行写的是 token.lema.toLowerCase(),而不是 token.lemma.toLowerCase()。这个错误会让运行在第 6 行因 TypeError 而停止,因为 token.lema 不存在,也就无法转成小写。

陷入无限循环的脚本会一直执行下去。在试一试下,旋转的圆圈会一直占据运行的位置。存储按钮也会执行脚本,所以也可能造成同样的问题。由于退出会丢失未存储的修改,你需要先用 ⌘A 选中整个脚本,再用 ⌘C 拷贝它。然后退出 LemmaLoom(⌘Q),重新打开,再把脚本粘贴回去。陷入无限循环的脚本通常含有一个条件永远不会变为假的 while 循环。如果脚本中有遍历列表的 for … of 或 forEach 以外的循环,请在存储之前先用一小段文本测试它。

断点与单步执行

如果标注出的文本不符合预期,你可以在测试运行中途暂停执行,查看脚本中变量的当前状态。

  1. 点按编辑器中行号左侧的空白处,或点按行号本身。出现一个红点,表示这一行设有断点。再点按一次即可移除。
  2. 点按试一试下的运行。

脚本会在即将执行带断点的那一行时暂停。这一行会高亮显示,测试文本下方的一个框中会显示它的行号(例如 ● L7),以及脚本中各个变量的当前值。此外,把鼠标指针悬停在脚本编辑器中的某个变量上,也会显示它的值。

“气候角色”停在第 7 行的断点处,测试文本下方显示 doc、sentence、token、findings 和 index 的值。“气候角色”停在第 7 行的断点处,测试文本下方显示 doc、sentence、token、findings 和 index 的值。
“气候角色”停在第 7 行的断点处,测试文本下方显示 doc、sentence、token、findings 和 index 的值。

这时,三个按钮取代了运行:

  • 播放按钮会越过当前断点,继续运行到下一个断点或运行结束。
  • 弯箭头会前进到下一个开始一条语句的行,包括进入循环语句和交给 forEach 的函数。因此,单步执行“气候角色”会依次经过每个词符。
  • 方块会提前结束运行。

LemmaLoom 只显示 doc、sentence、token、findings、finding、marker、span、clause、i 和 index 这些变量,而且只在它们当前存在于脚本中时才显示。本章的示例尽可能使用这些名称。其他变量,例如“气候角色”中的 role,可以用 console.log 写出来。较长的值会被截断。列表显示为它的长度(Array(3)),文本在前 80 个字符之后截断,对象在 120 个字符之后截断。

以“气候角色”和本章前文介绍的测试句为例,在第 7 行(const role = …)设置断点后,执行会在第一个 climate 处暂停,此时 index 为 1,findings 为 Array(0)。点按播放按钮后,执行在第二个 climate 处暂停,此时 index 为 16,findings 为 Array(1),因为第一个 climate 的发现已在两次暂停之间添加进去。

断点只会让脚本停在开始一条语句的行上。因此,设在只有右花括号和右圆括号的行上(例如“气候角色”中的 } 或 });)、以 else 开头的行上或上一条语句的接续行上的断点会被跳过。对于少数几种代码排版,LemmaLoom 只能从一个断点跟到下一个断点;在另外少数几种排版下,脚本会一直执行到底,不在任何断点处停下。断点只能在试一试中使用。

存储、修改和删除

存储分析器

要存储分析器,请点按中间一栏顶部的存储,或者在编辑名称或某个标签栏时按下 Return 键。名称为空(名称为空)或没有勾选任何语言(未选择语言)时,存储已停用。

存储分析器之前,LemmaLoom 会检查它的脚本。LemmaLoom 先提取图例的键,再针对每种所选语言,在一个由三个词组成的示例句上运行你的脚本(Cats sleep soundly、Katzen schlafen gern 和“猫 喜欢 睡觉”)。这些示例句的短语、名词块、形态特征和内置分析器结果都是空的,所以这项检查也能发现脚本在处理空列表时出现的问题。脚本有问题时,LemmaLoom 会在脚本编辑器下方告诉你(见本章前文出现错误时)。另有两条信息只在存储时显示。The script is empty. 表示编辑器中没有写任何脚本,No keys found in the script. 表示图例表格为空。

接着,LemmaLoom 把检查过的脚本应用到你所选语言的所有文档上。导航栏和窗口 ▸ 活动(⌥⌘0)中会显示进度环。所需时间取决于你有多少文档,以及你的 Mac 的性能。

修改分析器

在列表中选择一个分析器,修改它,然后点按存储。根据修改的性质,结果会有所不同:

  • 如果修改的是名称、标签、颜色或样式,修改会立即在启用了它的透镜的所有地方生效。
  • 如果修改的是分析器脚本本身、它所用的语言,或者它用于分析的键,LemmaLoom 会在它指定语言的所有文档上重新运行整个分析器,包括你刚勾选的语言。在分析器处理到某个文档之前,这个文档的文档信息中该分析器的行显示已过期。

新建、修改和删除分析器,都是分析器窗口撤销历史中的条目。分析器窗口处于活动状态时,选取编辑 ▸ 撤销编辑分析器之类的命令,就会回到分析器的上一个版本。在你再次从列表中选择这个分析器之前,中间一栏仍然显示你存储的内容;如果在此之前存储,就会再次存储较新的版本。关于各个窗口中的撤销,详见第 23 章“撤销与重做”中的步骤属于哪个窗口。

删除分析器

要删除分析器,请在列表中右键点按它,然后选取删除分析器。你也可以先选择这个分析器,再选取编辑 ▸ 删除分析器(⌘⌫)。这个快捷键在列表获得焦点时有效,选择分析器之后就是如此。LemmaLoom 会立即删除这个分析器。

分析器由所有项目共享,所以删除后它会立即从所有项目中消失。它的透镜在所有已打开的文档中停用,LemmaLoom 也会从所有文档中移除它的发现。编辑 ▸ 撤销删除分析器(⌘Z)会恢复它并重新运行。如果你只是不想让分析器在某些文档上运行,请为这些文档停用它(见本章后文按文档设置分析器)。

分析器何时执行

自定义分析器在建立索引的最后阶段,即在每个文档的语法分析和三个内置分析器之后,在你的文档上执行。适用于文档语言的每个自定义分析器,只要在这个文档上处于启用状态,就会依次执行。它的发现与文档本身一起存储,可以通过透镜、自动编码…和查询访问。

在以下情况下,你的分析器会在文档上执行:

  • 初次建立索引时,即导入项目之后或通过立即建立索引;
  • 之后重新建立索引时,例如更改了文档的文本或语言设置之后;
  • 存储新分析器时,或者存储对分析器脚本、所选语言或键的修改时;
  • 在文档的文档信息中点按运行时。

修改现有的分析器之后,LemmaLoom 通常会沿用之前对文档进行的语法分析,只重新执行各个分析器,而不是一切从头再来。尽管如此,分析器仍要处理它所适用语言的每个文档。脚本写得效率低下,就会拖慢所有文档的处理。更糟的是,脚本如果陷入无限循环,就会一直运行下去,直到你退出 LemmaLoom。遇到这种情况,请先在窗口 ▸ 分析器中改正或删除出问题的分析器,再退出并重新打开 LemmaLoom。

脚本可能在某个文档上执行出错。也许这个文档包含某种词语或句法组合,而编写脚本时的测试句里没有出现过。LemmaLoom 会丢弃该分析器在这个文档上的发现,并在文档信息中把它标为未运行或已过期。你可以在那里点按运行重试。如果问题依旧,分析器脚本再次失败,LemmaLoom 会在文档信息中把它标为失败,鼠标指针悬停时显示错误信息。要找出确切的问题,请把可疑的句子放进试一试,在那里运行脚本。

“时间不多了”的“文档信息”中“索引”部分的“分析器”列表,“气候角色”标记为“失败”,将鼠标指针悬停时显示其错误信息。“时间不多了”的“文档信息”中“索引”部分的“分析器”列表,“气候角色”标记为“失败”,将鼠标指针悬停时显示其错误信息。
“时间不多了”的“文档信息”中“索引”部分的“分析器”列表,“气候角色”标记为“失败”,将鼠标指针悬停时显示其错误信息。

按文档设置分析器

分析器会在其所支持语言的每个文档上运行,除非你为某个文档停用了它。不适合某个文档的分析器,你可以在导入这个文档时停用,也可以之后随时停用。第 21 章“分析与索引”中的为文档选择分析器介绍了如何为文档选择分析器。

导入文档时,你可以在导入文档对话框(网页则为从网页导入对话框)中使用分析器列表。列表先显示内置分析器,再按名称顺序显示你的分析器。列表中的每一项都有一个复选框,取消勾选就能阻止这个分析器在该文档上执行。语言设为自动时,列表显示所有分析器;选择了某种语言时,只显示适用于这种语言的分析器。此外,为语法分析建立索引未勾选时,这个列表会停用。详见第 4 章“导入资料”中的分析器与建立索引。

对于已在项目中的文档,分析器列表位于文档信息中:

  1. 在导航栏中右键点按文档,选取信息。
  2. 在索引部分找到分析器列表。

适用于该文档语言的每个分析器都有自己的一行,显示它是完成、已过期、未运行、运行中还是失败。

  • 要从文档中移除某个分析器的发现,让它不再在这个文档上运行,请点按它的废纸篓按钮,即移除输出。分析器在这个文档上运行过之后,这个按钮才会出现。
  • 要执行未运行、已过期或已从文档中移除的分析器,请点按运行;这样也会在这个文档上重新启用已移除的分析器。

文档尚未建立索引(尚未索引)或已为文档关闭索引(索引已关闭)时,运行已停用。移除和执行都会立即生效。

移除分析器和执行已移除的分析器,都是主窗口撤销历史中的操作;编辑 ▸ 撤销移除输出会恢复并重新运行这个分析器。

“时间不多了”的文档信息中“索引”部分的“分析器”列表。“时间不多了”的文档信息中“索引”部分的“分析器”列表。
“时间不多了”的文档信息中“索引”部分的“分析器”列表。

从某个文档中移除分析器后,它就不再出现在这个文档的透镜菜单中,自动编码…和涉及它的查询也会跳过这个文档。如果你之后修改了在某个文档上停用的分析器,它在那里仍然保持停用,所以新版本只在启用了它的文档上运行。

使用发现

在阅读器中

你可以在透镜菜单中逐个开关分析器,直接在阅读器中查看它们对发现的标注。要启用某个分析器的标注,请打开标题栏右侧的透镜菜单,你的分析器在这里归为一组。透镜显示标注,它的图例显示标签名称。清除分析器会立即停用所有分析器透镜。关于开关单个键,以及用仅显示透镜标注的句子只查看带标注的句子,详见第 6 章“透镜”中的分析器透镜。

启用了“气候角色”透镜的“时间不多了”。启用了“气候角色”透镜的“时间不多了”。
启用了“气候角色”透镜的“时间不多了”。

为发现编码

启用分析器的透镜后,你就可以使用文档 ▸ 自动编码…(⇧⌘A),为这个分析器的发现编码。范围可以是当前打开的文档、一个文档集,甚至整个项目。发现中的每个键都成为自动编码对话框中的一行。每一行以相应的标签命名,可以归入已有的编码(例如在“气候辩论”中,把“气候作宾语”这一行归入“责任”),也可以变成新的编码(例如用“气候角色”的两行新建两个编码)。对于文档集或项目,LemmaLoom 会对其中所有与分析器语言相同、已建立索引并启用了该分析器的文档执行同样的操作。请注意,语法分析的每个错误也会变成一条编码实例。关于这个对话框,详见第 7 章“编码”。

在查询中

查找分析 · 发现的查询会搜索各个分析器找到的内容,包括 LemmaLoom 自带的分析器和你创建的自定义分析器。有三个字段专门与发现有关。分析器检验发现是由哪个分析器找到的,发现检验记录发现时所用的键(按标签列出),注释检验发现的注释。查询中的其他检验也都适用,包括发现所带的编码,以及它所在文档的属性。查找资料库 · 分析器的查询则列出分析器本身,可以检验它们的名称、语言和图例键(见第 15 章“构建查询”中的选择要查找的内容)。

在“气候辩论”中,每个文档都有一个名为“立场”的属性,取值为“行动派”“怀疑派”或“中立”。因此,按键和“立场”统计分析器“气候角色”的发现,就能看出行动派和怀疑派呈现气候这一话题的方式是否有差异。处理你自己的材料时,请选择能把你的文档区分开来的属性来分组。

要按组统计分析器的发现:

  1. 打开项目的查询视图。
  2. 在查找中选取分析 · 发现。
  3. 在条件中点按添加,然后选取添加规则。
  4. 在新规则的字段菜单中,从发现部分选取分析器。
  5. 比较方式保持为是以下任一,并选择这个分析器作为规则的值。
  6. 在显示中选取数量。
  7. 点按分组依据,在新行的字段菜单中选取发现。
  8. 再次点按分组依据,在第二行的字段菜单中选取属性,例如属性 ▸ 立场。
  9. 在显示命中的菜单中选取每万词。

对于“气候辩论”中的“气候角色”,最终会得到一张表,列出“立场”每个取值下的主语和宾语。各组的文本量不同,所以你得到的实际上是一张比率表。这里的比率是指在每组文档的文本中,每 10,000 词出现多少条发现(见第 16 章“查询结果与统计”中的度量)。例如,假设行动派的文档共有 4,000 词,其中有 3 条“气候作宾语”(这里取整数只为举例),比率就是 。

“气候辩论”项目中的查询,按发现和“立场”统计“气候角色”的发现,比率为每 10,000 词。“气候辩论”项目中的查询,按发现和“立场”统计“气候角色”的发现,比率为每 10,000 词。
“气候辩论”项目中的查询,按发现和“立场”统计“气候角色”的发现,比率为每 10,000 词。

如第 16 章“查询结果与统计”中的比较各组所述,如果要检验各组之间是否存在显著差异,请在显示中选取比较,然后从分组依据中删除发现这一行,只保留“立场”,最后添加规则发现 是以下任一“气候作宾语”。三篇中立的文档“2024 热浪报告”“碳税之争”和“电台辩论”作为单独的一组参加比较。

在归档中

LemmaLoom 归档(.llz)包含归档中的文档所存发现对应的分析器,以及这些发现本身。归档还包含归档中已存储查询所用到的分析器,以及由这些查询转换而来的分析器。导入归档时,如果某个分析器与你资料库中已有的分析器同名但并无关联,它会作为另一个分析器导入,与已有的分析器并列。详见第 22 章“交换与导出”。

由查询生成的分析器

在查询的存储菜单中选取存储为分析器…,即可把查询存储为分析器。查询必须查找词、模式匹配、句子、短语或名词块,并且只检验它们的词和语法,否则无法转换为分析器。新生成的分析器会和其他分析器一起出现在窗口 ▸ 分析器中,所以和其他分析器一样,你可以查看它的脚本,用试一试测试运行,也可以直接修改它。生成分析器时所用的键,就是查询中的类别;查询没有类别时,只有一个键 hit,标签为匹配。这些键在图例中显示为带键栏和减号按钮的行,与用添加键添加的行相同。

新生成的分析器会链接到它所来自的已存储查询。如果这个查询修改后再次存储,列表中该分析器的名称旁边就会显示已过期。更新方法如下:

  1. 在列表中点按这个分析器。
  2. 点按存储左边的更新分析器。

LemmaLoom 根据已存储的查询写出新脚本时,会保留分析器的名称和语言,但替换它的脚本和图例,所以你对分析器的标签、颜色和样式所做的任何修改都会被新生成的分析器覆盖。检查新脚本找到的内容是否与查询相同,这一步只在使用存储为分析器…时进行。如果新写出的脚本有错误,错误会显示在编辑器下方。如果已存储的查询在当前状态下无法转换为分析器,更新分析器会停用,并说明无法更新的原因;编辑 ▸ 撤销更新分析器会把分析器恢复到更新之前的状态。

编辑并存储脚本后,已存储查询与分析器之间的链接就会解除,此后这个分析器算作你自己的分析器。只修改名称、语言或图例则会保留链接。关于处理由查询生成的分析器的更多方法,见第 18 章“使用查询结果”中的让分析器与查询保持同步。

© 2026 Ryan S. Cooper. 保留所有权利。
本版本借助人工智能根据英文原版翻译而成,译文可能存在错误或不准确之处。如有疑问,请以英文版为准。