.NET正则表达式,用于检测法语维基百科缩略图说明文字中是否缺少句号。
fr.wikipedia.org上某些缩略图说明文字的末尾没有句点,但在法语排版的多数情况下这是强制性的(参见fr:WP:TYPO#POINTS,法语)。
我想要一个.NET正则表达式(用于AutoWikiBrowser)来帮助检测它们。
它应该匹配:
[[File:example.jpg|thumb|Caption]](最简单的语法)[[Fichier:example.png|Caption|vignette]](法语参数)[[ Image:example.svg | vignette| This [[is]] a [[Caption]] |link = Example |upright 0.5 ]](多余的空格,说明文字不是最后一个参数)
并且不匹配以句点结尾的说明文字。
当前的尝试有很多误报:
\[\[.*?(?:thumb|thumbnail|vignette)[^\[]*?[^\.]\s*\]\]匹配没有嵌套内部链接的简单情况(但只有当说明文字处于最后一个位置时)\[\[(?:File|Fichier|Image):(?:(?!\]\]).)*\|(?!(?:thumb|thumbnail|vignette|left|right|center|centre|none|gauche|droite|frame|frameless|border|baseline|sub|super|top|middle|bottom|mini|upright(?:=[^|\]]*)?|[\w-]+=[^|\]]*)\]\])([^|\]]*[^.!?…\s\]])\]\]匹配后面没有句点的嵌套内部链接(这是我们不想要的)
两者都没有考虑到wiki缩略图参数的完整清单。 我在这里整理了我认为完整的清单:
bas|bas-texte|bas-txt|base|baseline|border|bordure|bottom|cadre|center|centre|centré|droite|encadre|encadré|enframed|exp|exposant|frame|framed|frameless|gauche|haut|haut-texte|haut-txt|ind|indice|left|ligne-de-base|middle|milieu|neant|non_encadre|non_encadré|none|néant|redresse|right|sans_cadre|sub|sup|super|text-bottom|text-top|thumb|thumbnail|top|upright|vignette
以及那些包含数值的参数:
$1px|alt=$1|classe=$1|class=$1|langue=$1|lang=$1|lien=$1|link=$1|lossy=$1|page=$1|page $1|vignette=$1|thumbnail=$1|thumb=$1|redresse=$1|redresse_$1|upright=$1|upright $1
参数之间与后续 = 之间不能有空格,但其他空格可以有任意长度,因此也许可以这样归一化:
\b.*\b\s*px|(?:alt=|classe=|class=|langue=|lang=|lien=|link=|lossy=|page=|vignette=|thumbnail=|thumb=|redresse=|redresse_|upright=|page |upright )\s*\b.*\b
这会导致相当长的正则表达式,且说明文字未必是最后一个参数。我猜需要在两边各有两个前瞻,并再加上匹配嵌套链接的部分。
我觉得不需要以 \[\[\s*(?:File|Fichier|Image) 开头正则,因为所有缩略图必然包含参数 thumb|thumbnail|vignette。
此外,上述正则还没有考虑管道符前后可能出现的空格,这应通过 \s* 进行检测。
还有许多其他潜在的误报(在法语环境下的讨论见 [[Wikipédia:Bot/Requêtes/2026/06#Point en fin de légende image]]),因此用机器人自动修正似乎非常不太可能,但如果可能的话,我仍想得到尽可能最佳的正则表达式。
再尝试一些:
我也许找到了一个能工作的正则表达式:[regex101.com/r/5YfueK/4]。我需要检查是否误报太多。上述正则在存在复杂链接时(例如 [[target|text]])不起作用。我做了一个似乎能工作的版本:[regex101.com/r/kVBMdp/3],但仅适用于无链接的说明文字,并且存在误报,因为它也能检测到并非缩略图的图片(我可能需要为 thumb|thumbnail|vignette 添加一个正向前瞻)。
解决方案
这个初步的Find / Replace方案在很大程度上依赖于你原来的正则表达式。 在说明文字部分注入了一个平衡分组段,因为看起来这是唯一需要的地方,但我也可能错了。
这里的前提是先使用前瞻来找到一个有效的参数。 然后它跳过任意参数,进入这些说明文字的Caption部分,只匹配末尾确实没有句点的那些。
另外一个值得注意的点是,在前瞻之后,当它可选地匹配参数时,这一部分本身完全被封装在一个原子组中。(?> (?:\| parameters)* ) 这是必要的,这样如果某个块只包含一个参数,它也不会为了满足一个 Caption 段而把它返回。这是一个重要的必需。
下面还有一些重构版本。
用 $1$2. 来修复。
演示 https://regex101.com/r/j990Zl/1/substitution
(\[\[\s*(?:File|Fichier|Image):[^|]+(?=(?:(?>(?:\[(?>[^\[\]]+|\[(?<Depth_1>)|\](?<-Depth_1>))*(?(Depth_1)(?!))\])|(?!\]\]).))*?\|\s*(?:bas|bas-texte|bas-txt|base|baseline|border|bordure|bottom|cadre|center|centre|centré|droite|encadre|encadré|enframed|exp|exposant|frame|framed|frameless|gauche|haut|haut-texte|haut-txt|ind|indice|left|ligne-de-base|middle|milieu|neant|non_encadre|non_encadré|none|néant|redresse|right|sans_cadre|sub|sup|super|text-bottom|text-top|thumb|thumbnail|top|upright|vignette|\b.*\s*px|(?:alt=|classe=|class=|langue=|lang=|lien=|link=|lossy=|page=|vignette=|thumbnail=|thumb=|redresse=|redresse_|upright=|page[ ]|upright[ ])\s*(?:\b.*\b|[^|]*)))(?>(?:\|\s*(?:bas|bas-texte|bas-txt|base|baseline|border|bordure|bottom|cadre|center|centre|centré|droite|encadre|encadré|enframed|exp|exposant|frame|framed|frameless|gauche|haut|haut-texte|haut-txt|ind|indice|left|ligne-de-base|middle|milieu|neant|non_encadre|non_encadré|none|néant|redresse|right|sans_cadre|sub|sup|super|text-bottom|text-top|thumb|thumbnail|top|upright|vignette|\b.*\s*px|(?:alt=|classe=|class=|langue=|lang=|lien=|link=|lossy=|page=|vignette=|thumbnail=|thumb=|redresse=|redresse_|upright=|page[ ]|upright[ ])\s*(?:\b.*\b|[^|]*))\s*)*)\|)((?:(?>(?:\[(?>[^\[\]]+|\[(?<Depth_2>)|\](?<-Depth_2>))*(?(Depth_2)(?!))\])|(?!\s*\.\s*(?:\||[\[\]])).))*?(?=(?:\||\]\])))
格式化
( # (1 start)
\[ \[ \s*
(?: File | Fichier | Image )
: [^|]+
(?=
(?:
(?>
(?:
\[
(?>
[^\[\]]+
| \[
(?<Depth_1> ) #_(3)
| \]
(?<-Depth_1> )
)*
(?(Depth_1)
(?!)
)
\]
)
| (?! \] \] )
.
)
)*?
\| \s*
(?:
bas
| bas-texte
| bas-txt
| base
| baseline
| border
| bordure
| bottom
| cadre
| center
| centre
| centré
| droite
| encadre
| encadré
| enframed
| exp
| exposant
| frame
| framed
| frameless
| gauche
| haut
| haut-texte
| haut-txt
| ind
| indice
| left
| ligne-de-base
| middle
| milieu
| neant
| non_encadre
| non_encadré
| none
| néant
| redresse
| right
| sans_cadre
| sub
| sup
| super
| text-bottom
| text-top
| thumb
| thumbnail
| top
| upright
| vignette
| \b .* \s* px
| (?:
alt=
| classe=
| class=
| langue=
| lang=
| lien=
| link=
| lossy=
| page=
| vignette=
| thumbnail=
| thumb=
| redresse=
| redresse_
| upright=
| page [ ]
| upright [ ]
)
\s*
(?: \b .* \b | [^|]* )
)
)
(?>
(?:
\| \s*
(?:
bas
| bas-texte
| bas-txt
| base
| baseline
| border
| bordure
| bottom
| cadre
| center
| centre
| centré
| droite
| encadre
| encadré
| enframed
| exp
| exposant
| frame
| framed
| frameless
| gauche
| haut
| haut-texte
| haut-txt
| ind
| indice
| left
| ligne-de-base
| middle
| milieu
| neant
| non_encadre
| non_encadré
| none
| néant
| redresse
| right
| sans_cadre
| sub
| sup
| super
| text-bottom
| text-top
| thumb
| thumbnail
| top
| upright
| vignette
| \b .* \s* px
| (?:
alt=
| classe=
| class=
| langue=
| lang=
| lien=
| link=
| lossy=
| page=
| vignette=
| thumbnail=
| thumb=
| redresse=
| redresse_
| upright=
| page [ ]
| upright [ ]
)
\s*
(?: \b .* \b | [^|]* )
)
\s*
)*
)
\|
) # (1 end)
( # (2 start)
(?:
(?>
(?:
\[
(?>
[^\[\]]+
| \[
(?<Depth_2> ) #_(4)
| \]
(?<-Depth_2> )
)*
(?(Depth_2)
(?!)
)
\]
)
| (?!
\s* \. \s*
(?: \| | [\[\]] )
)
.
)
)*?
(?=
(?: \| | \] \] )
)
) # (2 end)
重构的 Trie 版本
https://regex101.com/r/O7kv9O/1/substitution
(\[\[\s*(?:Fi(?:le|chier)|Image):[^|]+(?=(?>\[(?>[^\[\]]+|\[(?<Depth_1>)|\](?<-Depth_1>))*(?(Depth_1)(?!))\]|(?!\]\]).)*?\|\s*(?:b(?:as(?:-t(?:exte|xt)|e(?:line)?)?|o(?:rd(?:er|ure)|ttom))|c(?:adre|ent(?:er|r[eé]))|(?:droi|vignet)te|e(?:n(?:cadr[eé]|framed)|xp(?:osant)?)|frame(?:d|less)?|gauche|haut(?:-t(?:exte|xt))?|ind(?:ice)?|l(?:eft|igne-de-base)|mi(?:ddle|lieu)|n(?:[eé]ant|on(?:_encadr[eé]|e))|r(?:edresse|ight)|s(?:ans_cadre|u(?:b|p(?:er)?))|t(?:ext-(?:bottom|top)|humb(?:nail)?|op)|upright|\b.*\s*px|(?:(?:alt|vignette|classe?|l(?:ang(?:ue)?|i(?:en|nk)|ossy)|thumb(?:nail)?)=|(?:page|upright)[ =]|redresse[=_])\s*(?:\b.*\b|[^|]*)))(?>(?:\|\s*(?:b(?:as(?:-t(?:exte|xt)|e(?:line)?)?|o(?:rd(?:er|ure)|ttom))|c(?:adre|ent(?:er|r[eé]))|(?:droi|vignet)te|e(?:n(?:cadr[eé]|framed)|xp(?:osant)?)|frame(?:d|less)?|gauche|haut(?:-t(?:exte|xt))?|ind(?:ice)?|l(?:eft|igne-de-base)|mi(?:ddle|lieu)|n(?:[eé]ant|on(?:_encadr[eé]|e))|r(?:edresse|ight)|s(?:ans_cadre|u(?:b|p(?:er)?))|t(?:ext-(?:bottom|top)|humb(?:nail)?|op)|upright|\b.*\s*px|(?:(?:alt|vignette|classe?|l(?:ang(?:ue)?|i(?:en|nk)|ossy)|thumb(?:nail)?)=|(?:page|upright)[ =]|redresse[=_])\s*(?:\b.*\b|[^|]*))\s*)*)\|)((?>\[(?>[^\[\]]+|\[(?<Depth_2>)|\](?<-Depth_2>))*(?(Depth_2)(?!))\]|(?!\s*\.\s*[\[\]|]).)*?(?=(?:\||\]\])))
作为最终证明,我们取修复后的说明文字并用我们的修复正则进行测试。
https://regex101.com/r/7KJaeB/1/substitution
并且我们看到没有匹配项。