本文作为 Extension:Cargo 官方文档以及《MediaWiki教程之Cargo篇》的补充,从另外的角度来谈谈这款扩展。主要内容译自Fandom帮助并做了适当修改,水平有限,望见谅。


Cargo是MediaWiki中的扩展,主要用于页面信息之间的“交流”。想象一下,你有一张包含游戏数据的电子表格,它有很多页面。一个页面上写的是物品;另一个是关于装备的;还有一个是关于敌人boss的数据;也许还有一个是关于不同的城镇居民和他们的对话台词。你可以想象这些可能有一些列——物品有购买成本、销售价格、成分、额外护甲。敌方boss有HP、防御、弱点、强项、特殊技能值。市民有问候、告别、职业和房子。Cargo 允许你以“Cargo 表”的形式实际创建“虚拟电子表格”。

当你使用信息框制作wiki页面时,你已经在以这种方式考虑你的数据。你可以在以下问题中看到相似之处:

信息框电子表格类比Cargo
我需要什么类型的信息框?我应该制作哪些 Excel 工作簿?我应该声明哪些表?
此信息框包含哪些字段?我应该在这个 Excel 表中添加哪些列?我应该在声明中包含哪些字段?
我应该在这个信息框中输入什么值/我应该如何格式化它们?我对该列中的单元格应用什么类型的格式?(日期、小数、百分比等)我应该为这些字段分配什么类型?(日期时间、布尔值、字符串等)

为什么是Cargo?

使用Cargo的原因是你可以在wiki的多个页面上重复使用相同的数据。例如,如果你有一个关于游戏的维基,并且想要在单个物品页面上显示物品统计信息,同时在通用的“物品”概览页面上显示可排序的表格。或者,如果你有一个带有配方的制作系统,Cargo可以帮助你管理物品和原料之间的关系。当添加或修改新配方时,更改会在整个维基中反映,确保信息在所有相关页面上都是最新且一致的。
如果你有带有统计信息的物品,也许你想在物品页面的信息框中显示统计信息,但也希望在“物品”概览页面上的可排序表格中显示它们。或者,也许你想在一个页面上显示一个城镇中每个人的所有对话行列表,并在另一个页面上显示每个铁匠的对话选项列表——但你不想每次都因为新扩展了一个铁匠之后还要更新两个页面。或许你的游戏有一个制作系统,并且你知道匕首配方是2个铁矿石。在另一页上,你要展示每一个至少需要一个铁矿石的项目。不应该有某种方式可以让第二页中展示出匕首吗?是的,使用Cargo绝对是可能的。

注:实现以上功能的不仅仅是Cargo,还有大名鼎鼎的SMW(Semantic MediaWiki)。只是相对于后者来说,Cargo更加轻量和简便,但功能上可能也会略显不足。具体可参见Cargo and Semantic MediaWiki一文。而在另一篇Semantic MediaWiki vs Wikibase vs Cargo的文章中,作者似乎对Cargo的代码库颇有微词,不过既然Fandom都在使用,那还是足以说明其成长空间的巨大。

术语表

术语等效于电子表格的功能定义
表(Table)Excel表类似类型的数据对象及其属性的集合
声明(Declare)创建新文件告诉 wiki 你希望它创建一个新表
Write to保存将数据保存到表中
Read from打开从表中获取数据
Store 添加新行并保存
"Where" 条件过滤列需要所有返回的字段都满足才能包含在结果中的条件

我该如何开始?

由于Cargo不是默认扩展,因此第一步是在你的wiki上安装Cargo。(注:Cargo 的安装并不复杂,且没有特别的依赖,具体安装步骤可参考官方网站,本站不做详细介绍)。

第一步:声明(Declare)

在Excel类比中,这就像打开一个新工作簿、创建列标签以及对每一列应用单元格格式,仅此而已。还没有存储数据,也没有检索任何内容,你只是告诉wiki当你最终存储数据时会期望用到哪些字段。

Cargo的声明可能如下所示:

{{#cargo_declare:_table=WikiAbbreviations 
|Platform=String<!-- Gamepedia or Fandom --> 
|Abbreviation=String 
|Full=String 
|Definition=Text 
}}

首先,我们将表格命名为“WikiAbbreviations”。然后我们会说,“这张表中的内容包含4条我们需要知道的信息:首先是平台(Platform),即Gamepedia或Fandom。接下来的事情是我们正在谈论的缩写(Abbreviation)。然后我们想知道缩写代表什么,我们称之为“全称(Full)”。最后,缩写的含义是什么?我们将其存储在一个名为“定义(Definition)”的字段中。”
声明表格后,你需要再执行一个步骤才能使用它——如果你是维基站的管理员,你可以在声明并保存代码之后单击模板页面上的“创建数据表”按钮。如果没有,你可以请维基管理员或其他管理员帮忙。每次添加或删除字段,或更改其中一个字段的类型时,你都必须重新创建表。这基本上以相同的方式完成——只不过按钮显示的不是“创建”而是“重新创建”。同样,你需要成为管理员或向维基管理员寻求帮助。

有关重新创建表的更多信息,请参阅重新创建表。

字段类型

你需要查看MediaWiki.org上的文档以对此进行全面讨论。但这里有几个你想要使用的常见字段类型:

  • String - 默认类型
  • Wikitext string - 与字符串相同,但wikitext格式将保留在查询中
  • Integer - 不能有任何小数/小数部分的数字
  • Float - 能够具有小数/小数部分的数字
  • Text - 一大段文本,你永远不会想在“where”或“join”条件下使用(稍后会详细介绍!)——如果有疑问,不要使用它!
  • Wikitext - 与文本相同,但wikitext格式将保留在查询中。

索引

本节解释了为什么我说你应该避免在“where”或“join”条件下使用文本和维基文本。如果你相信我的话,你可以跳过这一段。
与你可能会做的其他一些事情相比,从Cargo表中查找数据是一个缓慢的过程。为了在你提供“where”条件(即过滤器)时更快地找到你要查找的行,一些字段是会“被索引的”。但是,索引一个字段首先会使得存储速度变慢。因此,为了加快存储过程,Cargo为你提供了对“text”和“wikitext”的选项,即很长字段不会被索引。将这些查询显示是可以的,但你绝对不该基于它们的值进行过滤,因为没有索引会让查询过程变得非常慢。如果你对此感到困惑,你可以简单地避免使用“Text”和“Wikitext”类型。如果你认为需要它们,请向维基管理员寻求帮助!

这是一个示例,显示了一个可以正常工作的查询示例,以及一种不能正常工作的类型。假设我们有一张市民表,字段GreetingText类型,因为有些问候可能很长。其他字段,包括City都是String类型。我们以“where”充当过滤器进行查询。

妥当的:

|where= City="Whiterun" 
|fields= Name, Occupation, Greeting
基于City(一个字符串字段)使用“where”进行过滤会比较好。
欠妥的:

|where= Greeting="What do you get when you cross an arrow with a knee?" 
|fields=Name, Occupation, City
基于Greeting(一个文本字段)使用“where”进行过滤将使得查询变慢,应该避免。

我在哪里声明一个表?

你可以在存储数据的模板中<noinclude></noinclude>部分执行此操作。或者,你可以在其他任何地方都不使用的独立模板上执行此操作。这取决于你——唯一的要求是它必须在模板命名空间中完成。
如果声明模板与用于存储的模板不是同一个,则需要在存储模板的noinclude 部分中使用解析器函数{{#cargo_attach:_table=TABLE_NAME}}。这告诉 wiki在使用附加模板的页面上查找行。

第二步:存储数据

好的,现在我们基本上打开了一个新工作簿并添加了一些列标签和类型。接下来是什么?好吧,如果没有添加一些数据行,它就毫无用处了。所以让我们添加一些数据!通常,你希望存储来自信息框模板的数据。

这是我们示例商店{{WikiAbbreviation}}的样例:

{{#cargo_store:_table=WikiAbbreviations
|Platform={{{platform|}}}
|Abbreviation={{{1|}}}
|Full={{{full|}}}
|Definition={{{definition}}}
}}

看似很容易......用户输入|platform=,|full=|definition=, 以及一个未命名的第一个参数,即Abbreviation。我们只是直接将所有这些存储到表中,而不对用户输入的内容进行任何更改。
这是一个稍微复杂的示例,来自{{WikiDiscordLink}}:

{{#cargo_store:_table=WikiDiscords
|Invite={{{invite|}}}
|WikiName={{{name|}}}
|WikiURL={{{url|}}}
|Language={{#language:{{{lang|en}}}|en}}
|Platform={{#if:{{#pos:{{{url|}}}|.gamepedia.com}}|Gamepedia|Fandom}}
}}

在这里,我们将数据存储到一个名为“WikiDiscords”的表中。该表包含有关属于各种wiki的Discord服务器的信息。在这里,我们让用户输入wiki的URL,并将其存储起来。但是,我们还想存储一个名为“Platform”的字段,它可以是“Fandom”或“Gamepedia”。你的第一个想法可能是,“好吧,上次我们制作了一个|platform=field,为什么不再做一次呢?” 好吧,因为我们也再请求|url=,实际上我们可以通过它弄清楚平台是什么!如果我们在|url=找到字符串.gamepedia.com,我们肯定它在Gamepedia wiki上,如果没有,则在Fandom wiki上。因此,让我们先弄清楚然后相应地存储Fandom或Gamepedia。

如果我们能够从URL中找出平台,为什么还需要平台字段?好吧,这将我们带到了最后一个阶段……查询数据。

第三步:查询数据

所以现在我们有了一个漂亮的数据电子表格。它有一个名字,它有一堆列,还有一堆包含数据的行。这很好,但是……我们实际上还没有做任何事情。所以让我们做点什么吧!

“做点什么”是指使用函数{{#cargo_query:}}。让我们尝试制作一张只有 Gamepedia缩写的表格。请注意,与declare、store和attach不同,此解析器函数接受table参数和而非_table参数。你也可以使用tables代替table

{{#cargo_query:table=WikiAbbreviations
|fields=Abbreviation,Full,Definition
|where=Platform="Gamepedia"
}}

没有结果
如果我们只想显示Fandom缩写呢

{{#cargo_query:table=WikiAbbreviations
|fields=Abbreviation,Full,Definition
|where=Platform="Fandom"
}}

没有结果
事实证明,这很容易做到!想象一下,在你的wiki中,如果你显示的是一列物品及其所有属性,或者是城镇居民及其所有对话的列表,或者……你能做的事情是无穷无尽的。

高级查询

如果你只想要带有标记的表,请参阅自定义表以获取有关向|format=table查询添加标记的更多信息。

也许你注意到我们对输出的外观没有太多控制。我们所能看到的只是一个包含所有值的表格。如果我们只想显示项目图标列表以列出一个项目所构建的所有内容,该怎么办?如果我们想用每个英雄的缩略图和该英雄页面的链接制作一个网格,并将其显示在wiki首页上,该怎么办?事实证明,你可以通过设置|format=template(当你在查询中没有设置任何|format=时,默认为|format=table)来完成所有这些以及更多操作。
由于Cargo的一个bug,实际使用|format=template有时会显示错误。我们有一个解决方法 - 使用与|format=template查询完全相同的语法,但用{{CargoQuery}}代替实际的#cargo_query解析器函数。如果你需要使用它,请确保在你的wiki上启用了Extension:Scribunto 。如果不确定该怎么做,请咨询你的Wiki管理员。此解决方法不需要你使用任何Lua。
当你设置|format=template时,你可以创建一个模板来控制输出中每个“行”的格式。因此,你可以显示一个列表,或者只是一堆图标,而不是一个表格——任何你想要的!以下是你可以选择做的几件事:

让模板输出wikitable的一行,但每行都有更高级的样式。这将要求你定义表头和表尾,使用|intro=|outro=或仅包括围绕查询的额外元素。
让模板显示其中一个值及其名称的缩略图,以创建导航网格
使用#vardefine#expr计算总数,然后在最后打印它们(请参阅使用数字)
有关更多信息,请参阅MediaWiki Cargo文档。

先进的东西

Cargo表可以相互连接。语法是|join on=,或者只是在Lua中加入join =
在Lua中有一个专门的函数mw.ext.cargo.query,但是如果你想从Lua中声明或存储,你需要使用frame:callParserFunction。 另请参阅Cargo Lua 示例。
Cargo表可以通过action=cargoquery的MediaWiki api进行公开,这是非常有用的。
如果你想要调用一个名为_pageData的表,请让维基管理员创建它。
Path of Exile wikiLeaguepedia都广泛使用了Cargo和Lua。除非你非常熟悉编程,否则这两个wiki都不应该被模拟,但是如果你想查看可能的示例,可以查看这两个wiki。

处理数字

通常你希望Cargo返回一些东西。这里有一些建议。

SQL命令

你可以使用COUNT(*)获取与WHERE匹配的条目总数
你可以使用COUNT(DISTINCT FieldName)获取字段中与WHERE匹配的行中不同条目的总数
存在SUM, MAX,MIN等。有关更多信息,请参阅主Cargo文档中的使用 SQL函数。

使用 Cargo 中的数字

  • 人们经常遇到的一个问题是#cargo_query返回带有千位分隔符的整数/浮点数。如果使用#expr或其他解析器函数,则围绕TRIM()字段(例如|fields=TRIM(GoldCost)=GoldCost)以删除千位分隔符。
  • 另一个问题是Cargo在输出中添加了一些额外的HTML。如果你想执行`{{#expr:{{#cargo_query:QUERY
    HERE}} * 100这样的操作来显示百分比,则需要将|no
    html参数添加到查询中。这将抑制包装HTML的输出并仅返回数字,以便#expr`可以理解它。
  • 如果你正在用{{CargoQuery}}进行查询,则不会出现上述问题。

其他常见问题

  • HOLDS仅在查询单个条件时才有效。要解决此问题,field__full LIKE/RLIKE请显式使用子表或将子表连接到父表,例如Child._rowID=Parent._ID,然后写出=而不是HOLDS
  • 如前所述,|format=template有bug;使用{{CargoQuery}}代替。
  • 当使用HOLDS时,Lu 中给出的错误通常与实际问题完全不正确。理想情况下,不要使用HOLDS周期。如果需要,请忽略错误文本(如果有的话)。
  • 与千位分隔符和数字的问题类似,你可能还需要将通过{{PAGENAME}}魔术字存储的字段包装在TRIM()。(_pageName是默认字段,但也许你用{{#titleparts:}}作为字段。)
  • 每个模板只能附加1个表。因此,如果你需要一个模板来写入多个表,你可能必须创建“假”模板,这些模板除了附加表之外什么都不做,然后被模板嵌入到存储中的每个页面上。

    • 同样,一个模板只能声明一个表。
    • 但是,模板可以同时声明和附加最多2个表,而没有任何变通方法。
    • 有关详细信息,请参见附加表。
  • unique崩了,不要用
最后修改:2023 年 12 月 22 日
如果觉得我的文章对你有用,请随意赞赏