模組:Conversion rule extractor/Matcher
外观

子模块:负责将字词转换规则与目标文本内容进行匹配。 使用Trie树算法来高效地查找文本中是否存在规则中定义的源词语。
公共函数
[编辑]filterRules
[编辑]筛选规则列表,只保留那些源文本能在目标页面内容或指定文本中找到的规则。
参数:
- rulesList: (必须) table,包含待筛选的、已规范化的规则字符串的列表。
- targetPageTitleOrText: (必须) string|table,匹配目标。可以是:
- 页面标题 (string): 将获取该页面的内容进行匹配。
- 直接文本内容 (string): 直接在该文本上进行匹配。
- MediaWiki title 对象 (table): 将获取该对象对应页面的内容进行匹配。
返回值: table: 一个新的列表,仅包含源文本在目标中匹配成功的规则字符串。 如果输入 rulesList 为空、无法获取目标内容或目标内容为空,返回空列表 `{}`。
filterRulesAgainstTitleText
[编辑]筛选规则列表,只保留那些源文本能在指定页面的标题文本(不含名字空间)中找到的规则。
参数:
- rulesList: (必须) table,包含待筛选的、已规范化的规则字符串的列表。
- pageTitle: (必须) string,目标页面的标题。
返回值: table: 一个新的列表,仅包含源文本在页面标题文本中匹配成功的规则字符串。 如果输入 rulesList 为空、页面标题无效或标题文本为空,返回空列表 `{}`。
内部函数
[编辑]extractRuleSources
[编辑]从单个规范化的规则字符串中提取所有可能的源文本(即需要被匹配的词语)。 处理单向规则 (A=>B 提取 A) 和双向规则 (lang:A 提取 A)。
参数:
- ruleString: (字符串) 已规范化的规则字符串。
返回值: table: 包含从规则中提取出的所有源文本字符串的列表(已去重)。
buildRuleTrie
[编辑]根据规则列表构建一个Trie树,用于快速匹配。 Trie 的节点代表字符,叶子节点存储包含以该路径为源文本的规则在原始列表中的索引。
参数:
- rulesList: (table) 包含规则字符串的列表。
返回值: table: 构建好的Trie树。
matchTextWithTrie
[编辑]使用构建好的 Trie 树在给定的文本中查找所有匹配的规则。
参数:
- text: (字符串) 要在其中进行查找的文本内容。
- trie: (table) 使用buildRuleTrie构建的Trie树。
返回值: table: 一个集合(表),其键是匹配到的规则在原始列表中的索引,值为true。
-- Module:Conversion_rule_extractor/Matcher
-- 子模块:负责匹配规则与目标页面内容
local Matcher = {}
-- 工具函数:从规则字符串中提取需要匹配的源文本
-- 例如:'zh-cn:图尔库;zh-tw:土庫;' -> {"图尔库", "土庫"}
-- 例如:'巨集=>zh-cn:宏;' -> {"巨集"}
local function extractRuleSources(ruleString)
local sources = {}
local sourceSet = {} -- 用于去重
-- 移除外层包裹(如果存在,尽管 Extractor 通常会清理掉)
ruleString = ruleString:match('^%-{.-|(.*)}%-$') or ruleString
for part in mw.text.gsplit(ruleString, ';') do
part = mw.text.trim(part)
if part ~= '' then
local source
local unidirectionalMatch = part:match('^([^=]-)=>') -- 检查单向规则 A=>B
local bidirectionalMatch = part:match('^%w+%-%w+:(.+)') -- 检查双向规则 lang:Text
local simpleBidirectionalMatch = part:match('^([^:]+):(.+)') -- 检查简单的双向规则 Text:Variant (不太标准,但可能存在)
local fallbackMatch = part:match('^([^=:]+)') -- 如果没有=>或:,取整个部分作为源?(可能不太安全,但作为后备)
if unidirectionalMatch then
source = mw.text.trim(unidirectionalMatch)
elseif bidirectionalMatch then
source = mw.text.trim(bidirectionalMatch)
elseif simpleBidirectionalMatch then
-- 对于 Text:Variant 格式,我们假设 Text 是要匹配的源
source = mw.text.trim(simpleBidirectionalMatch)
elseif fallbackMatch and not part:find('=') and not part:find(':') then
-- 只有在没有 => 和 : 时才考虑整个部分作为源,例如 "單純文字" 这种无效但可能存在的规则
source = mw.text.trim(fallbackMatch)
end
if source and source ~= '' and not sourceSet[source] then
table.insert(sources, source)
sourceSet[source] = true
-- mw.log('Extracted source:', source, 'from part:', part)
-- else
-- mw.log('Could not extract source from part:', part)
end
end
end
-- mw.logObject('Extracted sources for rule "' .. ruleString .. '":', sources)
return sources
end
-- 构建用于匹配的Trie树 (改编自 Module:NoteTA-lite)
-- 输入: rulesList - 一个包含规则字符串的列表
-- 输出: Trie树,叶子节点存储规则在 rulesList 中的索引列表
function Matcher.buildRuleTrie(rulesList)
local trie = {}
local ruleSourcesMap = {} -- 存储每个源文本对应的规则索引列表 { ["源文本"] = {idx1, idx2} }
for index, ruleString in ipairs(rulesList) do
local sources = extractRuleSources(ruleString)
for _, source in ipairs(sources) do
if not ruleSourcesMap[source] then
ruleSourcesMap[source] = {}
end
table.insert(ruleSourcesMap[source], index)
-- mw.log('Mapping source:', source, 'to index:', index)
end
end
-- 构建Trie
for source, indices in pairs(ruleSourcesMap) do
local currentNode = trie
-- 使用 mw.ustring 处理 UTF-8 字符
for i = 1, mw.ustring.len(source) do
local char = mw.ustring.sub(source, i, i)
currentNode[char] = currentNode[char] or {}
currentNode = currentNode[char]
end
-- 在叶子节点存储规则索引列表
currentNode.indices = indices
-- mw.log('Added indices to Trie node for source:', source, indices)
end
return trie
end
-- 使用Trie树在文本中查找匹配的规则 (改编自 Module:NoteTA-lite)
-- 输入: text - 要搜索的文本内容
-- 输入: trie - Matcher.buildRuleTrie 构建的Trie树
-- 输出: matchedIndices - 一个集合 (table),key 是匹配到的规则索引,value 是 true
function Matcher.matchTextWithTrie(text, trie)
local matchedIndices = {}
if not text or text == '' then return matchedIndices end
local len = mw.ustring.len(text)
for i = 1, len do
local currentNode = trie
for j = i, len do
local char = mw.ustring.sub(text, j, j)
if not currentNode[char] then
break -- 没有后续匹配
end
currentNode = currentNode[char]
-- 检查当前节点是否是某个源文本的结尾
if currentNode.indices then
-- mw.log('Match found ending at pos', j, 'for source ending with char', char)
for _, index in ipairs(currentNode.indices) do
if not matchedIndices[index] then
-- mw.log('Recording match for rule index:', index)
matchedIndices[index] = true
end
end
-- 继续检查更长的匹配
end
end
end
-- mw.logObject('Indices matched in text:', matchedIndices)
return matchedIndices
end
-- 主函数:筛选规则列表,只保留在目标页面内容中能匹配到的规则
-- 输入: rulesList - 包含规则字符串的列表
-- 输入: targetPageTitleOrText - 目标页面的标题字符串 或 直接的文本内容
-- 输出: filteredRules - 只包含匹配到的规则字符串的列表
function Matcher.filterRules(rulesList, targetPageTitleOrText)
local filteredRules = {}
if not rulesList or #rulesList == 0 then
return filteredRules
end
local textContent
if type(targetPageTitleOrText) == 'string' then
-- 检查是页面标题还是直接文本
local titleObj = mw.title.new(targetPageTitleOrText)
if titleObj and titleObj.exists then
-- 是有效的页面标题,获取内容
textContent = titleObj:getContent()
-- mw.log('Matching against content of page:', targetPageTitleOrText)
else
-- 认为是直接的文本内容
textContent = targetPageTitleOrText
-- mw.log('Matching against provided text string.')
end
else
-- 如果传入的是 title 对象
if targetPageTitleOrText and targetPageTitleOrText.getContent then
textContent = targetPageTitleOrText:getContent()
-- mw.log('Matching against content of provided title object:', targetPageTitleOrText.prefixedText)
else
-- mw.log('Invalid target provided for matching.')
return filteredRules -- 无法获取内容,返回空
end
end
if not textContent or textContent == '' then
-- mw.log('Target content is empty, no rules will match.')
return filteredRules -- 没有内容可匹配
end
local trie = Matcher.buildRuleTrie(rulesList)
local matchedIndices = Matcher.matchTextWithTrie(textContent, trie)
for index, rule in ipairs(rulesList) do
if matchedIndices[index] then
table.insert(filteredRules, rule)
-- mw.log('Rule matched and kept:', rule)
-- else
-- mw.log('Rule did not match:', rule)
end
end
return filteredRules
end
-- 专门用于匹配标题的函数,只使用标题文本进行匹配
function Matcher.filterRulesAgainstTitleText(rulesList, pageTitle)
local filteredRules = {}
if not rulesList or #rulesList == 0 then
return filteredRules
end
local titleObj = mw.title.new(pageTitle)
if not titleObj then
-- mw.log('Invalid title provided for title text matching:', pageTitle)
return filteredRules
end
local titleText = titleObj.text -- 获取不含名字空间的标题文本
-- mw.log('Matching rules against title text:', titleText)
if not titleText or titleText == '' then
-- mw.log('Title text is empty, no rules will match.')
return filteredRules
end
local trie = Matcher.buildRuleTrie(rulesList)
local matchedIndices = Matcher.matchTextWithTrie(titleText, trie)
for index, rule in ipairs(rulesList) do
if matchedIndices[index] then
table.insert(filteredRules, rule)
-- mw.log('Rule matched title text and kept:', rule)
end
end
return filteredRules
end
return Matcher