.NET正则表达式,用于检测法语维基百科缩略图说明文字中是否缺少句号。

编程语言 2026-07-07

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

并且我们看到没有匹配项。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章