Cargo是什么?

Cargo是MediaWiki的扩展之一,能够以轻量的方式来存储和查询数据,这些数据一般通过信息框(infobox)等模板调用。 Cargo扩展被有意识地设计为模仿Semantic MediaWiki(SMW)的整个系统及其许多派生扩展,包括其语法选项和总体接口。但是,它也有许多优点,比如容易安装,容易使用。
从某种意义上来说,Cargo与SMW在三个主要方面有所不同:

  • Cargo将数据存储直接绑定到模板。在SMW中,语义值可以放在页面的任何位置,尽管在实践中它们通常被限制在模板中;但在Cargo中,是模板本身负责存储其数据。
  • Cargo以尽可能简单的方式存储其数据,使用标准数据库表保存表格数据;而SMW使用数据库来表示数据的“三元组(triples)”。
  • 尽管差异较小,但与SMW及其衍生扩展相比,Cargo的自定义性较差(主要是选择基于数据本身的显示设置)。

相对于SMW强大的生态系统,Cargo要显得简约得多,虽然在自定义方面有所欠缺,但贵在上手较为容易(SMW的复杂也就意味着学习过程不可能一蹴而就)。
关于Cargo和Semantic MediaWiki以及Wikibase的更多对比介绍请浏览:

好了,接下来我们就将开始Cargo的探索之旅。

Cargo是如何工作的?

假设你有一个关于世界各地公共艺术雕塑的维基。你已经尽了最大努力仔细地为成千上万的雕塑编目,现在,你突发奇想,想看看立陶宛考纳斯19世纪创作的所有雕塑的清单。在一个典型的维基上,无论是维基百科还是其他什么,你基本上有两个选择:你可以在某个维基页面上手工编辑这样一个列表,或者你可以用一个类似“立陶宛考纳斯的19世纪雕塑”的类别来标记所有这样的页面(假设每个雕塑都有一个单独的页面)。
这两种方法一直在维基百科上被采用,在许多其他维基上也是如此。然而,它们都有问题:第一个选项,手动编译列表,需要大量的工作,并且需要在每次添加属于该列表的新页面时(或者当发现一些错误时)去修改列表。在第二种情况下,列表(在类别页面上)是自动生成的,但是类别标签必须费力地添加到每个页面上。如果你希望用户这样做,他们需要得到关于如何添加类别以及类别应该如何命名的精确说明(应该是“立陶宛考纳斯”还是仅仅是“考纳斯”?)。还有在一般情况下,理想的数据结构应该是怎样的?维基中覆盖的每个城市都应该有一个“19世纪”的类别吗,即使是那些名字只有一两个雕塑的城市?反过来,有许多雕塑的城市是否应该进一步细分,比如说按照雕塑的艺术风格?还是应该用单独的类别来标记样式?这些都是很难的问题,不一定有什么好的答案。
Cargo为这个问题提供了一个解决方案。你可以定义一个单独的信息框模板放在雕塑页面上——既显示所有相关信息(城市、国家、年份、流派、主题等),而不是编辑列表或拥有过多的类别——并且以可查询的方式存储信息。因此,可以使数据结构保持简单,并将复杂性(如果有的话)转移到显示数据的查询中,而不必管理一大堆可能有些混乱的分类。
应该注意的是,你可能根本不需要创建任何查询来获得前面提到的特定列表,因为Cargo提供了一个自动钻取(drill-down)的界面,允许你单击不同方面/字段的值来查看你正在寻找的任何特定组合的结果。
Cargo不仅仅是简单地显示页面列表——例如,你可以在一个表格中看到所有的信息,你可以在地图或时间轴上显示雕像,你还可以按国家、年份、风格等聚合它们来显示它们的分类。
那么信息框呢?用户学习如何添加和填充这些信息框不是很困难吗?为此,我们将在之后的教程中介绍Page Forms扩展,它提供表单,使用户在创建和更改数据时不需要处理wikitext语法。

存储数据

数据结构的创建和数据的存储都是通过模板在Cargo中完成的。任何使用Cargo的模板都需要包含对解析器函数#cargo_declare#cargo_store的调用;或者,更罕见地调用#cargo_attach#cargo_store#cargo_declare定义数据表的字段,#cargo_store将数据存储在该表中,而#cargo_attach指定模板将其数据存储在别处定义的表中。

设置Cargo数据库

默认情况下,Cargo使用标准的MediaWiki数据库来保存其数据;它通过以“cargo__”开头来区分数据库表。相反,你可以让Cargo使用单独的数据库。这可能是一个好主意,主要有两个原因:它可能对主要的MediaWiki数据更安全(Cargo中没有已知的安全问题,但将来可能会发现问题),并且它可以防止Cargo缓慢的查询干扰Wiki的正常操作。
Cargo提供以下全局设置,允许你使用单独的数据库:$wgCargoDBtype$wgCargoDBserver$wgCargoDBname$wgCargoDBuser$wgCargoDBpassword$wgCargoDBprefix
要使用自定义数据库,只需在LocalSettings.php中设置前五个变量的值。(第6个前缀是可选的。)

声明一个数据表

在表中存储数据的模板也需要声明该表,或者将自己“附加(attach)”到别处声明的表。由于每个模板通常有一个表(反之亦然),因此大多数使用Cargo的模板将声明它们自己的表。声明是通过解析器函数#cargo_declare完成的。这个函数是用以下语法调用的:

{{#cargo_declare:
_table=table name
|field_1=field description 1
|field_2=field description 2
...etc.
}}

首先,注意表名和字段名都不能包含空格或破折号(-);相反,你可以使用下划线、驼峰命名法(CamelCase)等。下划线不能用于表和字段名的开头或结尾。
字段描述必须从字段的类型开始,在许多情况下,它只是类型。Cargo中预定义了以下类型:

  • Page - 保存维基中页面的名称(默认最大为300个字符)
  • String - 保存标准的non-wikitext文本(默认最大为300个字符)
  • Text - 保存标准的non-wikitext文本(用于更长的内容)
  • Integer - 保存整数
  • Float - 保存实数,即非整数
  • Date - 保存不含时间的日期
  • Start date, End date - 与Date类似,但用于保存某一持续时间的开始和结束。一个表可以不包含开始日期和结束日期字段,也可以同时包含两者
  • Datetime - 保存日期和时间
  • Start datetime, End datetime - 工作方式同Start date或End date,只是包括时间
  • Boolean - 保存一个布尔值,其值应该是1或0,yes或no
  • Coordinates - 保存地理坐标
  • Wikitext string - 保存要由MediaWiki解析器解析的短文本(默认最大为300个字符)
  • Wikitext - 保存要由MediaWiki解析器解析的较长文本
  • Searchtext - 保存可以使用“MATCHES”命令进行搜索的文本(需要MySQL 5.6+或MariaDB 5.6+)
  • File - 保存维基中上传的文件或图像的名称(类似于Page,但不需要指定“file:”的命名空间)(默认最大为300个字符)
  • URL - 保存网址(默认最大为300个字符)
  • Email - 保存Email地址(默认最大为300个字符)
  • Rating - 保存评级值,通常为1到5分的整数

指定的其他任何类型都将被简单地视为“String”类型。未索引类型的字段在查询或加入时明显较慢。
字段中也可以包含任何此类类型的列表。要定义这样的列表,类型值需要看起来像“List (分隔符) of type”。例如,要使一个名为“ Authors”的字段包含用逗号(,)分隔的文本值列表,你可以在#cargo_declare调用中使用以下参数:

|Authors=List (,) of String

备注:分隔符没有特定要求,并且在模板显示中是可以进行调整的,所以没有必要纠结使用哪种分隔符,尽量采用半角英文字符(中文字符虽然能够正常工作,但为避免在存储中出现错误还是建议采用英文字符)并且保证统一。

字段参数

描述字符串也可以有附加参数;这些都包含在类型标识符后的括号内,并用分号分隔。当前允许的参数有:

  • size= - 对于“Text”类型的字段,设置该字段的大小,即字符数;默认值由全局变量$wgCargoDefaultStringBytes设置,该变量的默认值为300(可以在LocalSettings.php中对其进行修改)。
  • hierarchy - 指定该字段包含值的层次结构,如“allowed values”参数中所定义(请参阅下一项)。
  • allowed values= - 字段可以具有的一组允许值(通常用于类型为“String”或“Page”的字段)。如果未指定“hierarchy”,则应该只是一组用逗号分隔的值。如果指定了“hierarchy”,则应使用项目符号列表的语法来定义值。简而言之:每个值应在其单独的行上,每行应至少以一个“”开头,第一行应以一个“”开头,并且“*”的数量应增加不超过一个一次。

例如,要定义一个名为“Color”的字段,它有三个允许的值,你可以使用以下声明:

|Color=String (size=10;allowed values=Red,Blue,Yellow)

同时,要定义一个称为“主要成分”的字段作为层次结构,你可以具有以下声明:

|Main_ingredient = String (hierarchy;allowed values=*Fruits
**Mangoes
**Apples
*Vegetables
**Root vegetables
***Carrots
***Turnips
**Peppers)
  • link text= - 对于“URL”类型的字段,设置将显示为该URL链接的文本。默认情况下,会显示整个URL。

    • hidden - 没有任何值。如果设置,则该字段不会在Special:Drilldown中列出,尽管它仍然可以查询。
    • mandatory - 没有任何值。如果设置,则将该字段声明为必填字段,即不允许使用空白值。
    • unique - 没有任何值。如果设置,则该字段的所有值都必须是唯一的——表中该字段已存在的值将不会保存。
    • regex= - 为此字段设置一个正则表达式,所有值都必须匹配。例如,如果设置了“regex=T\d+”,则该字段的值必须由字母“T”和一个或多个数字组成。
    • dependent on= - 输入此表中另一个字段的名称,以指定仅在用户为该字段选择值后才在Special:Drilldown中显示此字段。

其他的#cargo_declare参数

除了表的名称和字段之外,还可以将以下参数添加到#cargo_declare中:

  • _parentTables - 用于将一个或多个其他Cargo表设置为该表的“父表(parent tables)”。在Special:Drilldown中使用此功能,以使用户可以筛选与该表有某种联系的其他Cargo表中的字段。它采用以下语法:

    | _parentTables = tableName1(_localField = localFieldName,_remoteField = remoteFieldName,_alias = tableAlias);tableName2(...); ...

在这里,“tableName1”是要声明为父表的表的名称。“_localField”和“_remoteField”在两个表中指定需要连接的字段(两者的默认值为“_pageName”)。如果定义了“_alias”,则将在钻取中显示该名称,而不是父表的名称。

示例1

此钻取显示父表“Items”(列为“Item”)(这是模板)中的其他钻取字段

  • _drilldownTabs-用于在“Special:Drilldown”页面中设置自定义钻取选项卡。可以这样声明:

    |_drilldownTabs= Tab1(format=list;delimiter=\;;fields=A,B,C), Tab2(format=table; fields=A,C,D)

其中“Tab1”是选项卡的显示名称,“format”参数采用所需的格式名称——你可以添加该格式所需的所有参数,然后“fields”保留要显示的字段集。

示例2

此钻取显示还显示自定义标签(这是模板
#cargo_declare还显示了一个指向“Special:CargoTables”页面的链接,用于查看该数据库表的内容。

附于另一个表

在某些情况下,你可能希望多个模板将它们的数据存储到同一个Cargo表中。在这种情况下,只有一个模板应该声明该表,而其他模板应该简单地使用解析器函数#cargo_attach将自己“附加”到该表。使用以下语法调用该函数:

{{#cargo_attach: _table=table name }}

为了让模板向某个表添加行,实际上并不需要这个调用——通过模板或其他方式在任何地方调用#cargo_store将向表添加一行(假设调用有效)。但是,#cargo_attach允许你为该模板执行“重新创建数据”操作——请参阅本节后面的“创建或重新创建表”。
模板最多只能调用一次#cargo_attach(就此而言,最多只能调用一次#cargo_declare)。包含对#cargo_store的调用的任何模板也应调用#cargo_declare#cargo_attach

数据存储到表

声明表或附加到表的模板也应该在该表中存储数据。这是通过解析器函数#cargo_store完成的。#cargo_declare#cargo_attach应用于模板页面本身,因此应该进入模板的<noinclude>部分,而#cargo_store应用于调用该模板的每个页面,因此应该进入模板的<includeonly>部分。这个函数是用以下语法调用的:

{{#cargo_store: _table=table name |field 1=value 1 |field 2=value 2 ...etc. }}

字段名必须与模板其他地方的#cargo_declare调用中的字段名相匹配。这些值通常(但不总是)是模板参数,但从理论上来说,它们可以保存任何东西。

存储重复事件

存在用于存储重复发生的事件(如生日或每周会议等定期发生的事件)的特殊处理。解析函数#recurring_event就是为实现这些而准备的。它为一个循环事件接受一组参数(表示开始日期、频率等),并简单地打印出一个包含该事件日期列表的字符串。它应该在#cargo_store(用于定义为保存日期列表的字段)中调用,然后#cargo_store将相应地存储数据。#recurring_event使用以下语法调用:

{{#recurring_event:
start=start date
|end=end date
|unit=day, week, month or year
|period=some number, representing the number of "units" between event
instances (default is 1)
|include=list of dates, to be included in the list
|exclude=list of dates to exclude
|delimiter=delimiter for dates (default is ',')
}}

在这些参数中,只有“start=”和“unit=”是必需的。
默认情况下,如果没有设置结束日期,或者如果结束日期距离将来太远,则#recurring_event存储事件的50个实例。要更改此设置,你可以在LocalSettings.php中添加$wgCargoRecurringEventMaxInstances的值,将它写在Cargo的设置下。例如,要将数字设置为100,可以添加以下内容:

$wgCargoRecurringEventMaxInstances  =  100 ;

如果要处理周期性事件,请将字段的类型声明为List (;) of Date。

示例

你可以在这里这里看到两个使用#cargo_declare#cargo_store的模板。

创建或重新创建表

当保存一个包含#cargo_declare调用的模板页面时,实际上不会生成或修改任何数据。相反,必须在单独的进程中创建或重新创建数据。有两种方法可以做到这一点:

基于Web的选项卡

Cargo_recreate_data_interface.png
从模板页面中,选择名为“创建数据”或“重新创建数据”的选项卡操作。这将打开一个可能包含以下复选框的界面:“重建数据至替换表格,保持旧有数据用于查询”。(仅当有关Cargo表已经存在时,该复选框才会出现。)
单击“确定”,将发生以下情况之一:

  1. 如果选中此复选框,则将创建一个“替换表”,而当前表不受影响。任何人都可以查看此替换表,但其数据将不会在查询中使用(在数据库中,实际表的名称将类似于“cargo__tableName__NEXT”)。如果当你认为此替换表已准备好使用时,可以单击Special:CargoTables上的“切换以使用此表格”链接。该链接将删除当前的Cargo表并重命名替换表,使其成为正式表。相反,如果你不想使用替换表,则可以单击“删除它”的链接。
  2. 如果未选中该复选框,则将立即删除当前表,并创建一个新版本。
  3. 如果该复选框不存在,则表示这是一个新表。在这种情况下,将创建表。

在这三种情况下,MediaWiki作业都用于循环浏览所有相关页面并重新创建数据——为每个页面创建一个单独的作业。对于大型表,这可能是一个漫长的过程,这就是为什么建议对大型表使用“替换表”方法的原因——它避免了当表大部分为空时的“停机时间”。
根据MediaWiki版本和配置,对MediaWiki的runJobs.php脚本的调用对于这些作业的实际启动可能是有用的,甚至是必要的。
如果任何模板包含#cargo_attach,它们也将获得“创建数据”或“重新创建数据”选项卡。如果选择并激活了该选项卡,它将不会删除并重新创建数据库表本身;相反,它将只在表中重新创建那些来自调用该模板的页面的行。

权限

具有“recreatecargodata”权限的用户可以使用创建/重新创建数据的功能,默认情况下,该权限授予sysops。你可以将此权限授予其他用户——例如,要拥有一个具此功能的新用户组“cargoadmin”,你只需要在LocalSettings.php中添加以下内容:

$wgGroupPermissions['cargoadmin']['recreatecargodata'] = true;

一旦模板的表存在,任何包含一次或多次调用该模板的页面都将在重新保存时,刷新该表中的数据。而包含对该模板调用的新页面将在创建页面时添加它们的数据。

命令行脚本

如果可以访问命令行,你还可以通过调用位于Cargo/maintenance目录中的脚本cargoRecreateData.php来重新创建数据。它有两种调用方式:

  • php cargoRecreateData.php - 为系统中的所有Cargo表重新创建数据。
  • php cargoRecreateData.php –table tableName – 为指定的Cargo表重新创建数据。

此外,可以使用--quiet标志来调用脚本,该标志将关闭所有打印输出。有关完整的使用信息,请使用进行调用--help
备注:通过Web选项卡替换表之后的数据更新需要一定的时间,尤其是存在大量数据时更是如此(仅在刷新页面或者是进入到某个条目时更新某条数据),所以最好的方法就是在通过命令行脚本来重新创建数据。

额外的存储字段

创建或重新创建模板数据时,将在Cargo数据库中创建一个数据库表,该数据库表(通常)为每个指定字段创建一列。该表还将包含以下列:

  • _pageName - 保存存储该行值的页面的名称。
  • _pageTitle - 类似于_pageName,但如果有命名空间,则省略命名空间。
  • _pageNamespace - 保存存储该行值的页面的命名空间的数字ID。
  • _pageID - 保存该页面内部MediaWiki的ID。
  • _ID - 保存该行的唯一ID。

保存页面数据

你可以创建一个额外的Cargo表来保存“页面数据”:特定于维基中每个页面的数据,与信息框数据无关。然后,可以单独查询这些数据,或者将其与一个或多个“常规”Cargo表连接。该表名为“_pageData”,它为维基中的每个页面保存一行。必须指定数据表要存储的字段集;默认情况下,它只包含五个标准的Cargo字段,如_pageName(见上文)。要包含其他字段,请在LocalSettings.php配置文件中安装Cargo的行下面添加$wgCargoPageDataColumns数组。
还有7个字段可以添加到_pageData表中;下面是六个字段,以及添加每个字段的调用:

  • _creationDate - 页面的创建日期:

    $wgCargoPageDataColumns[] = 'creationDate';

  • _modificationDate - 上次修改页面的日期:

    $wgCargoPageDataColumns[] = 'modificationDate';

  • _creator - 创建该页面的用户的用户名:

    $wgCargoPageDataColumns[] = 'creator';

  • _fullText - 页面的(可搜索)全文:

    $wgCargoPageDataColumns[] = 'fullText';

  • _categories - 页面的类别(列表,可使用“HOLDS”查询):

    $wgCargoPageDataColumns[] = 'categories';

  • _numRevisions - 本页面已编辑的次数:

    $wgCargoPageDataColumns[] = 'numRevisions';

  • _isRedirect - 此页面是否为重定向:

    $wgCargoPageDataColumns[] = 'isRedirect';

一旦你指定了希望该表保留哪些字段,请转到Cargo/maintenance目录,并执行以下调用来创建或重新创建_pageData表:

php setCargoPageData.php

要用替换重新创建,请添加一个--replacement标志:

php setCargoPageData.php --replacement

然后可以使用该Special:CargoTables接口正常切换替换表。

如果你想删除此表,请调用以下方法:

php cargoRecreateData.php –delete

如果你打算重新创建表,则无需调用“ --delete”选项;只需调用setCargoPageData.php就会删除以前的版本。

存储文件数据

与页面数据类似,你也可以自动存储每个上传文件的数据。这些数据被放在一个名为“_fileData”的表中,每个文件对应一行。该表也有自己的设置数组,用来指定应该存储哪些列,称为$wgCargoPageDataColumns。目前可以设置五列:

  • _mediaType - 每个文件的媒体类型或MIME类型,如“image/png”:

    $wgCargoFileDataColumns[] = 'mediaType';

  • _path - 服务器上文件的目录路径:

    $wgCargoFileDataColumns[] = 'path';

  • _lastUploadDate - 上次上传文件的日期/时间:

    $wgCargoFileDataColumns[] = 'lastUploadDate';

  • _fullText - 文件的全文(这仅适用于PDF文件):

    $wgCargoFileDataColumns[] = 'fullText';

  • _numPages - 文件中的页数(这仅适用于PDF文件):

    $wgCargoFileDataColumns[] = 'numPages';

要存储PDF文件的全文,你需要在服务器上安装pdftotext应用,然后将以下内容添加到LocalSettings.php中:

$wgCargoPDFToText = '...path to file.../pdftotext';

pdftotext是几个不同软件包的一部分。如果你已经安装了PdfHandler扩展(并且正在运行),则可能已经安装了pdftotext。

查询数据

可以通过#cargo_query和#cargo_compound_query两个函数以及一个特殊页面special:ViewTable来查询Cargo内的数据。special:ViewTable页面提供了一个简单的接口,允许你为查询设置参数。实际上,#cargo_compound函数同时调用两个或多个查询,然后一起显示它们的结果。

#cargo_query

#cargo_query函数本质上是一个SQL的包装器,只做了一些修改。它使用以下语法调用:

{{#cargo_query:
tables=table1, table2, etc.
|join on=table1.fieldA=table2.fieldB, table2.fieldC=table3.fieldD, etc.
|fields=field1=alias1,field2=alias2, etc.
|where=table1.fieldE="some value" AND/OR etc.
|group by=table1.fieldG
|having=table1.fieldG="some value", etc.
|order by=table2.fieldF, etc.
|limit=some number
|intro=some text
|outro=some text
|default=some text
|more results text=some text
|format=format
...additional format-based parameters
}}

对于使用过SQL SELECT查询的人来说,前8个参数应该非常熟悉:

  • tables= - 对应于FROM子句;" table= "是它的别名。
  • join on= - 对应于JOIN...ON子句。
  • fields= - 对应于SELECT子句(可选的“alias”值对应于AS子句)。它的默认值是“_pageName”。
  • where= - 对应于WHERE子句。
  • group by= - 对应于GROUP BY子句。
  • having= - 对应于HAVING子句(类似于WHERE,但适用于为“groups”计算的值)。
  • order by= - 对应于ORDER BY子句。它的默认值是“_pageName ASC”。
  • limit= - 对应于LIMIT子句。

接下来的三个参数是有条件的,取决于是否有结果:

  • intro= - 设置位于查询结果之前的文本(仅在有结果时应用)。
  • outro= - 设置位于查询结果之后的文本(仅在有结果时应用)。
  • default= - 如果没有结果,则设置代替查询结果的文本。默认值是“无结果(No results)”,以用户的语言表示。若要不显示任何文本,只需设置“default=”。

注:具体示例可参见本站《Cargo插件查询结果的输出方式被我搞复杂了》一文。

最后一组参数是:

  • more results text= - 设置查询显示后的文本,以链接到其他结果。默认值为“更多……(More...)”,使用用户的语言。若要不显示任何文本,只需设置“more results text=”。
  • no html - 指定输出不包含HTML(在其他解析器函数中嵌入#cargo_query调用时非常有用,可以避免解析器错误)。
  • format= - 设置显示的格式(参见显示格式)。

还可以有其他允许的参数,特定于所选的显示格式。同样,请参阅“format”参数和这些附加参数的可能值的显示格式。在所有这些参数中,“tables=”是唯一需要的(如果"tables="包含多个表,"join on="也是必需的)。

示例

下面的查询将获取当前(虚构的)wiki上的所有城市及其人口,并将它们显示在一个表中:

{{#cargo_query:
tables=Cities
|fields=_pageName=City,Population
|format=table
}}

以下查询仅获取亚洲的城市,并在一个动态的、基于JavaScript的表中显示城市名称、国家名称和人口:

{{#cargo_query:
tables=Cities,Countries
|join on=Cities.Country=Countries._pageName
|fields=Cities._pageName=City, Countries._pageName=Country, Cities.Population
|where=Countries.Continent="Asia"
|format=dynamic table
}}

以下查询将获取维基中的所有国家,以及每一个在维基页面中拥有两个以上城市的国家,它所统计的城市数量,然后,它用条形图显示这组数字,开头是城市的最高数字:

{{#cargo_query:
tables=Cities,Countries
|join on=Cities.Country=Countries._pageName
|fields=Countries._pageName=Country, COUNT(*)
|group by=Countries._pageName
|having=COUNT(*) > 2
|order by=COUNT(*) DESC
|format=bar chart
}}

#cargo_compound_query

你可能希望在同一个显示中显示多个查询的结果——这可以使用#cargo_compound_query函数实现。这种“复合查询”的主要用途是在地图或日历上显示多个点集,每个点集有不同的图标(在地图中)或不同的颜色(在日历中);不过也可以使用它以更简单的格式(如表和列表)来显示多个结果集。#cargo_compound_query通过传入一个或多个对#cargo_query调用的参数集来调用,整个参数集由管道分隔,内部参数现在由分号分隔。
举个例子,假设你想展示一个城市中所有医院和银行的地图。在我们的地图上,医院会有一个红十字图标,而银行会有一个美元符号(我们会说这个城市在某个使用美元的国家)。更复杂的是,在维基上,医院页面有自己的类别“医院”,而银行页面是一般类别“业务”的一部分,字段“业务类型”的值为“银行”。图1展示了这样一个地图的例子。

SCQ map.png
使用#cargo_compound_query和'googlemaps'格式创建的地图

你需要做的第一件事是上传你想显示的图标的图像。假设你为图1所示的两个图标上传了图像,并将它们命名为“Red cross.jpg”和“Dollar sign.png”。
下面是一个示例调用,它将在地图中显示两组页面,每组页面有一个不同的图标:

{{#cargo_compound_query:
table=Hospitals; fields=_pageName,Address,Coords; icon=Red cross.jpg
|table=Businesses; where=Business type='Bank';
fields=_pageName,Address,Hours,Coordinates; icon=Dollar sign.png
|format=googlemaps
|height=200
|width=400
}}

本质上,每个子查询作为它自己的“参数”。这些子查询有它们自己的子参数,这些子参数是#cargo_query允许的参数的子集:只允许直接与查询相关的参数——“tables”、“join on”等——加上另外两个与日历和地图显示相关的参数:“color”和“icon”。除了子查询之外,#cargo_compound_query唯一允许的其他参数是“format”,以及所选格式的任何其他参数。
如果我们想在地图中为所有非银行业务添加第三组点,每个点都用一个建筑物的图片表示,该怎么办?幸运的是,#cargo_compound_query使得这一点很容易做到:被多个子查询覆盖的页面只由它们应用到的第一个子查询显示。因此,只要在常规查询之前包含更具体的查询,最后一个或多个查询就可以作为对之前不适合的所有内容的汇总。你可以这样做:

{{#cargo_compound_query:
table=Hospitals; fields=_pageName,Address,Coords; icon=Red cross.jpg
|table=Businesses; where=Business type='Bank'; fields=_pageName, Address,
Hours, Coordinates; icon=Dollar sign.png
|table=Businesses; fields=_pageName, Address, Hours, Coordinates; icon=Office
building.png
|format=googlemaps
|height=200
|width=400
}}

除了地图,Cargo复合查询的另一个常见应用是在日历上显示多种类型的事件,每种事件可以有不同的颜色编码。例如,要在同一日历上显示会议和任务截止日期,其中会议为蓝色,截止日期为橙色,你可以调用以下方法:

{{#cargo_compound_query:
table=Meetings; fields=_pageName, Date; color=blue
|table=Tasks; fields=_pageName, Deadline; color=orange
|format=calendar
|height=200
|width=400
}}

图2显示了这样的查询可以生成什么。
Cargo compound query calendar.png

“HOLDS”命令

不幸的是,SQL自己对包含列表/数组的字段的支持相当差。因此,#cargo_declare为每个包含值列表的字段创建一个额外的helper表。此外,#cargo_query在“join on=”和“where=”参数中支持它自己的类似于SQL的命令“HOLDS”,这使得查询此类数据更加容易。不必在每次这样的调用中手动包含helper表,你可以使用“HOLDS”来简化语法——它被称为真正的、更复杂的SQL的“语法糖(syntactic sugar)”。你可以在“where=”参数中使用“HOLDS”来查找列表字段包含某个值的所有行。为了使用前面的示例,如果我们有一个名为“Books”的表,其中包含一个字段“Authors”,它包含一个作者列表,我们可以使用下面的#cargo_query调用来获取列夫·托尔斯泰(Leo Tolstoy)编写的或与他人合作编写的所有图书:

{{#cargo_query:
tables=Books
|fields=_pageName=Book,Authors
|where=Authors HOLDS "Leo Tolstoy"
}}

这个调用类似于这个更复杂的调用:

{{#cargo_query:
tables=Books,Books__Authors
|join on=Books._ID=Books__Authors._rowID
|fields=_pageName=Book, Books.Authors__full=Authors
|where=Books__Authors._value = "Leo Tolstoy"
}}

同样,你可以在“join on=”中使用“HOLDS”,根据列表字段中的值将两个表连接在一起。例如,如果关于作者的信息存储在它自己的数据库表“Authors”中,并且你想要显示一个包含书籍、其作者和这些作者的出生日期的表,可以进行以下调用:

{{#cargo_query:
tables=Books,Authors
|join on=Books.Authors HOLDS Authors._pageName
|fields=Books._pageName, Books.Authors, Authors.Date_of_birth
}}

"HOLDS LIKE"

还有一个额外的命令“HOLDS LIKE”,它将SQL“LIKE”命令映射到所有的值列表中。它的工作原理就像“HOLDS”。例如,要获得任何以“Leo”为名字的人写的或合作写的书,你可以通过如下代码调用:

{{#cargo_query: tables=Books
|fields=_pageName=Book,Authors
|where=Authors HOLDS LIKE "%Leo%"
}}

“NEAR”命令

像数组一样,关系数据库并不完全支持坐标。因此,与数组类似,Cargo中的坐标对存储和查询都有特殊的处理。对于坐标的情况,如果你想查询它们,推荐的方法是使用“NEAR”命令,它像“HOLDS”一样是一个虚拟命令,由Cargo定义。“NEAR”查找指定距离内指定坐标集附近的所有点。坐标和距离必须放在括号内,用逗号隔开;距离必须以公里(指定为“kilometers”或“km”)或英里(指定为“miles”或“mi”)为单位。例如,如果有一个名为“Restaurants”的表,其中包含一个餐馆列表,它包含一个名为“Coords”的字段,其中包含每个字段的坐标,你可以调用以下查询来显示意大利圣马可广场10公里范围内的所有餐馆(以及一些相关信息):

{{#cargo_query:
tables=Restaurants
|fields=_pageName=Restaurant, Address, Rating, Coords
|where=Coords NEAR (45.434, 12.338, 10 km)
}}

使用SQL函数

你可以在#cargo_query中的“fields”、“join on”和“where”参数中包含你正在使用的任何数据库系统的本机函数。为了安全起见,允许的SQL函数的集合定义在一个全局变量$wgCargoAllowedSQLFunctions中,默认设置如下:

  • 数学函数:'COUNT', 'FLOOR', 'CEIL', 'ROUND', 'MAX', 'MIN', 'AVG', 'SUM', 'POWER', 'LN', 'LOG'
  • 字符串函数:'CONCAT', 'IF', 'LOWER', 'LCASE', 'UPPER', 'UCASE', 'SUBSTRING', 'TRIM', 'FORMAT'
  • 日期函数:'NOW', 'DATE', 'YEAR', 'MONTH', 'DAYOFMONTH', 'DATE_FORMAT', 'DATE_ADD', 'DATE_SUB', 'DATEDIFF'

如果需要一个集合中缺少的函数,你可以在包含Cargo之后,向LocalSettings.php中进行补充并启用它们,如下所示:

$wgCargoAllowedSQLFunctions[] = 'CURDATE';

关于函数的文档可以通过搜索相关文档获得。
下面是一些如何使用SQL函数的示例,所有示例均基于MySQL系统。

自定义链接文本

可以使用CONCAT()为内部和外部链接创建自定义链接文本。例如;

{{#cargo_query:table=Newspapers
|fields=CONCAT( '[[', _pageName, '|View page]]' ) = Newspaper, Circulation,
CONCAT( '[', URL, ' View URL]' ) = URL }}

删除页面链接

相反,对于“Page”类型的字段,可以使用CONCAT()删除指向值的链接。默认情况下,这些值显示为链接,但你可能希望将它们仅显示为字符串。CONCAT()函数可能是实现这一点的最简单方法。如果这里的“Author”字段的类型是“Page”,那么要想以字符串的形式显示Author值,可以调用如下函数:

{{#cargo_query:tables=Blog_posts
|fields=_pageName,CONCAT(Author) }}

这是因为,只要显示的不是简单的字段名称,#cargo_query就不会应用该字段的任何特殊处理。如果该字段包含一个值列表,你应该改为调用“CONCAT(fieldName__full)”。所以调用可以是这样的:

{{#cargo_query:tables=Blog_posts
|fields=_pageName,CONCAT(Topics__full) }}

日期筛选

你可以使用像DATEDIFF()这样的日期函数来获取日期在特定范围内的项。如下例:

{{#cargo_query:tables=Blog_posts
|fields=_pageName,Author,Date
|where=DATEDIFF(Date,NOW()) >= -7
|order by=Date DESC
}}

截取字符串

你可以使用诸如LEFT()或SUBSTRING()这样的字符串函数来修剪字符串。下面的示例还使用CONCAT()和IF()来附加省略号,但前提是字符串值(引号)已被截断。

{{#cargo_query:tables=Authors
|fields=_pageName=Author, CONCAT( LEFT( Quote, 200 ), IF( LENGTH( Quote ) >
200, "...", "" ) )=Quote
}}

显示硬解码(非Cargo)数据

你可以使用嵌套的IF()语句在查询结果中显示不是来自Cargo的自定义数据。例如:

{{#cargo_query:tables=Drinks
|fields=_pageName=Drink, Ingredients, IF( _pageName = 'Lemonade', 'One dollar', IF ( _pageName = 'Fruit punch', 'Two dollars', 'Three dollars' ) )=Price
}}

显示格式

参数"format="允许你设置显示结果的格式。如果没有指定格式,则“list”是结果中只显示单个字段的默认格式,而“table”是多于一个字段的默认格式。
Cargo扩展支持以下格式:

列表

  • list - 以分隔列表的形式显示结果。参数:

    • delimiter - 设置分隔符(默认为逗号)
  • ul - 以项目符号列表的形式显示结果。参数:

    • columns - 设置列数(默认为1)
  • ol - 以编号列表的形式显示结果。参数:

    • columns - 设置列数(默认为1)
  • category - 以MediaWiki分类的样式显示结果。参数:

    • columns - 设置列数(默认为3)

更复杂的文本显示

  • template - 使用格式化每行结果的MediaWiki模板显示结果。参数:

    • template - 指定要使用的模板的名称(必填)
    • named args - 如果设置为“yes”,则指定模板应该使用命名参数,其中参数名称是查询字段的名称(如果设置了别名的话也可以使用别名)

“template”格式提供了一种非常通用的方法,它允许你对单行查询结果的值应用自定义格式和文本。要应用模板,你需要创建一个接受值并对其应用一些格式的模板,然后向#cargo_query调用添加“|template=template name”。
以下是一个内容示例,如果附加的查询输出是针对艺术家、年份和流派的,则该模板可用于显示关于音乐专辑的信息:

* ''{{{1|}}}'', {{{2|}}} ({{{3||}}}) - genre: {{{4|}}}

如果模板名为“Album display”,那么调用该模板的查询可能是这样的:

{{#cargo_query:table=Albums
|fields=_pageName, Artist, Year, Genre
|format=template
|template=Album display
}}

这是结果显示的样子:

  • Computer World, Kraftwerk (1981) - genre: Electronic
  • Crescent, John Coltrane (1964) - genre: Jazz

你可以看到,即使是简单的格式化也可以使数据的显示更加清晰和便于读取。

  • embedded - 显示每个查询页面的全文(只使用查询的第一个字段)。
  • outline - 以大纲格式显示结果。参数:

    • outline fields - 保存以逗号分隔的查询字段列表——这些字段用于定义大纲,根据大纲将结果分组在一起(必须的)
  • tree - 以树状格式显示结果,其中单个字段定义了“子”页和“父”页之间的所有关系。参数:

    • parent field - 保存此连接器字段的名称(必须的)
  • table - 在表格中显示结果。参数:

    • merge similar cells - 如果设置为“yes”,则将同一列中包含相同值的相邻单元格合并在一起,以方便阅读。
  • dynamic table - 在一个“动态”表中显示结果,包括排序、分页和搜索。参数:

    • rows per page - 设置表的每个“页面”上最初显示的行数(默认为10)
    • searchable columns - 指定应该在其列上方有特殊搜索输入的字段
    • details fields - 指定字段不应该有列,而是应该包含在每一行下的特殊“详细信息部分”中
    • hidden fields - 指定默认情况下不应该包含在表中,但用户可以通过“切换列”链接添加的字段

Cargo dynamic table format.png

  • tag cloud - 以“标签云”格式显示结果,其中与每个字符串值对应的数字指示该字符串的字体大小。参数:

    • min size - 最小文本显示的正常字体大小的百分比(默认为80)
    • max size - 最大文本显示的正常字体大小的百分比(默认为200)
    • template - 指定用于显示每个结果的模板的名称

图像显示

  • gallery - 以MediaWiki中标记的样式显示一个图像库。图片必须是上传到维基的文件——它们可以是直接查询的页面(如果图像页面调用基于Cargo的模板),也可以是其他页面的“File”类型的字段。参数:

    • mode - 设置gallery的展示模式;可以是traditional(默认)、nolinespackedpacked-overlay或者packed-hover

      • traditional 是MediaWiki使用的原始图库类型。
      • nolines 与 traditional 相似,但不包含边框。
      • packed 使图像拥有相同的高度但是宽度不同,图像之间的空隙很小。 这个响应模式中的行根据屏幕的宽度自行组织。
      • packed-overlay 用一个半透明的白框将标题覆盖显示在图像上。
      • packed-hover 与 packed-overlay 相似,但图片标题和白框只在悬停时显示。
      • slideshow 用幻灯片形式展现图像。
    • show bytes - 如果设置为“0”,隐藏每个图像的文件大小(默认显示)
    • show filename - 如果设置为“0”,隐藏每个图像的名称(默认显示)
    • show dimensions - 如果设置为“0”,隐藏每个图像的尺寸(默认显示)
    • per row - 指定在每个行图像上显示的图像数量
    • image width - 指定显示每个图像的宽度(以像素为单位)
    • image height - 指定显示每个图像的高度(以像素为单位)
    • caption field - 指定查询字段的名称,该字段的值应用于每个图像标题
    • alt field - 指定一个查询字段的名称,该字段的值应该用作每个图像的“alt文本”
    • link field - 指定查询字段的名称,该字段的值应用作图像链接到的页面的名称
  • slideshow - 在页面上显示图像的“幻灯片”。参数:

    • slides per screen - 设置一次在屏幕上显示的并排图像的数量(默认值为1)
    • autoplay speed - 前进到下一张幻灯片之前等待的秒数(默认情况下,幻灯片根本不会自动播放,用户必须手动前进)
    • caption field - 指定查询字段的名称,该字段的值应该用于每个图像标题
    • link field - 指定查询字段的名称,该字段的值应用作图像链接到的页面的名称

关于图像显示的形式可以参考官方文档

时间显示

  • calendar - 使用FullCalendar的JavaScript库在日历中显示结果。参数:

    • width - 设置日历的宽度(默认为100%)
    • start date - 设置显示日历的日期(默认为当前日期)
    • view - 设置日历的开始显示——选项有“day”、“week”或“month”(默认为month)
    • color - 设置显示事件名称的颜色——在#compound_query中有用(默认值由FullCalendar库设置)
  • timeline - 以可滚动的时间轴显示结果。可选的“height”和“width”参数设置高度和宽度。

数值显示

  • bar chart - 以柱状图(带横向柱状图)显示结果。可选的“ height”和“ width”参数设置高度和宽度。
  • pie chart - 在饼图中显示结果。除了可选的“ height”和“ width”参数外,它还具有以下参数:

    • colors - 用于饼图“切片”的颜色列表(以逗号分隔)
    • hide legend - 如果设置为“yes”,则删除显示的图表图例

地图

  • googlemaps - 使用Google Maps服务在地图中显示结果。参数:

    • height - 设置地图的高度(默认为400px)
    • width - 设置地图的宽度(默认为700px)
    • icon - 设置用于显示点的自定义图标——value必须是已上传到Wiki的文件的名称。这在#compound_query中特别有用。
    • zoom - 设置缩放级别,即从0到20左右的整数值,随数字变大而放大(默认值基于显示的一组点的面积)。
  • leaflet - 使用Leaflet库在地图中显示结果。参数:与googlemaps相同,但有一个附加项:image,它允许你指定一个背景图像在地图中使用,以代替标准的世界地图。
  • openlayers - 使用OpenLayers服务在地图上显示结果。参数:与googlemaps相同。

更复杂的显示

  • exhibit - 在可浏览的基于JavaScript的界面中显示结果。此格式具有许多可选参数,尽管其默认行为通常可以正常工作。使用SIMILE Exhibit库/服务。

    • view - 设置将要显示的视图,如果不止一个,则以逗号分隔。有效值为map,tabular和timeline。如果未设置,则将根据查询中字段的类型来设置视图。
    • facets - 设置用于过滤器的字段,如果有多个,则以逗号分隔。最多允许三个。如果未设置,则使用结果的前三个字段。
    • datalabel - 设置引用数据的标签。默认值为“Item”。

对于“时间轴”视图:

  • end - 设置保存每个事件的结束时间的字段名称(如果有的话)
  • color - 设置用于对标记进行颜色编码的字段的名称(如果有的话)
  • topunit - 设置单位,用于顶部:millisecond,second,minute,hour,day,week,month,year,decade,century,millennium
  • toppx - 设置顶部中每个间隔的宽度(以像素为单位)
  • bottompx - 设置底部中每个间隔的宽度(以像素为单位)

导出

定义了五种基于导出的显示格式:csv,json,excel,bibtex和icalendar。

浏览数据

Cargo提供了许多方法来查看存储的数据——这些都是公开可用的,但有些是面向普通用户的,有些面向管理员的,还有一些是面向两者的。

钻取(Drill-down)界面

Cargo提供的用于浏览数据的主要机制是页面Special:Drilldown,该页面显示每个表及其内容的列表,以及一组用于向下钻取该信息的过滤器。过滤器是根据每个表的字段类型自动设置的。类型为String和Page的字段,加上所有数字和日期类型,变成过滤器,所使用的输入类型取决于字段类型;其他字段类型则没有。(类似地,任何标记为“隐藏”的字段都不会显示为过滤器。)
cargo-drilldown.png

全文搜索

从示例图中可以看到,除了数据过滤器之外,还可以在顶部显示一个文本搜索输入,可以将其与过滤器结合使用。如果满足以下任一条件,则会显示此搜索输入:

  • 页面文本的存储已启用
  • 要浏览的表具有一个或多个“文件”类型的字段,并且启用了文件文本存储

如果两者均成立,则文本搜索将同时搜索页面和文件的内容,并同时显示两者。

查询表格

如果转到Special:CargoQuery页面,你可以看到一个表单,该表单允许通过填写表单输入来运行查询(正如#cargo_query所做的)。这些输入提供自动完成和验证之类的帮助,以简化任务。

查看表格

Special:CargoTables页面显示了维基中所有表的列表,以及每个表的一些有用链接。该页面具有双重功能:对于用户和管理员,这是查看数据总体布局的便捷方法;对于管理员而言,它也是一种用于维护所有表的仪表。转到该页面将显示Cargo数据库中所有表的列表,并为每个表提供指向“视图(view)”和“钻取(drilldown)”的链接(对于管理员,为“重新创建数据”和“删除”)。“视图”链接将转到页面“ Special:CargoTables/tableName ”。单击任何“查看”链接将显示一个显示完整内容的表格(同样,“隐藏”字段除外)。

单个页面的值

如果单击任一页面侧栏底部的“页面值(Page values)”链接,你将看到为该页面定义的一组表行。

官网的案例

官网的快速手册中有一个案例也很值得借鉴,所以一并加入到本文中。

案例简介

假设你想创建一个展示你家里所有藏书的wiki。你希望你的wiki中有两种类型的页面:书,以及作者。 每个书的页面都应包含书名,作者,类型,出版年份,以及总页数。 每个作者页面应该包含作者的名字,国籍,以及你所收藏的他的作品列表。

模板示例

Template:Book

<noinclude>
这是“书”的模板。
{{#cargo_declare:_table=Books
|Authors=List (,) of Page
|Genres=List (,) of String
|Year_of_publication=Date
|Number_of_pages=Integer}}
</noinclude>
<includeonly>
{{#cargo_store:_table=Books
|Authors={{{Authors|}}}
|Genres={{{Genres|}}}
|Year_of_publication={{{Year of publication|}}}
|Number_of_pages={{{Number of pages|}}} }}
{|
! 作者
| {{#arraymap:{{{Authors|}}}|,|x|{{#formredlink:form=Author|target=x}} }}
|-
! 类别
| {{{Genres|}}}
|-
! 出版年份
| {{{Year of publication|}}}
|-
! 总页数
| {{{Number of pages|}}}
|}
</includeonly>

Template:Author

<noinclude>
这是“作者”的模板

{{#cargo_declare:_table=Authors
|Country=String}}
</noinclude>
<includeonly>
{{#cargo_store:_table=Authors
|Country={{{Country|}}} }}
{|
! Country of origin
| {{{Country|}}}
|-
! Books
| {{#cargo_query:tables=Books|where=Authors HOLDS "{{PAGENAME}}"}}
|}
</includeonly>

简要说明

这就是具有信息框样式的模板,这些模板既定义数据结构又以易于查询的方式存储其数据。
你可以在此模板中看到调用Cargo的三个主要解析器函数的示例:#cargo_declare#cargo_store#cargo_query; 并希望三者如何相互作用。请特别注意#cargo_declare#cargo_store的位置:前者在标记中,后者在标记中。
这些模板的其他一些注意事项:

  • 对于这两个模板,实体名称(即书籍或作者的名称)均未存储在模板中。这是因为页面本身的名称将保留该书或作者的名称-并且还将存储在这两个表的“_pageName”字段中。
  • 最后,“Author”模板包含所谓的聚合查询,该查询查询“Books”数据表(由另一个模板定义),并获取在“Authors”字段的值中包含当前页面的所有页面的名称。(在默认情况下,它将以逗号分隔的列表显示该信息。)这是一种非常常见的查询类型。这意味着你无需将相同的数据存储在两个不同的页面中,因为一个页面可以简单地查询数据。
  • 此示例代码中有一行需要另一扩展才能工作:Template:Book中的这一行包括对#arraymap和#formredlink的调用,这两者均是由Page Forms扩展定义的。该行执行了相当复杂的操作:它将“ {{{Authors|}}}”的值分成一个或多个子值(用逗号分隔),然后,对于每个值,要么直接链接到该页面(如果该页面已经存在);或使用“Form:Author”中的表单定义链接到用于创建该页面的表单。因此,此模板不仅以已安装Page Forms扩展为前提,而且以创建“作者”表单为前提。如果你没有安装页面表单,则可以用更简单的内容替换该行,例如:

    | {{{Authors |}}}

但是,强烈建议任何使用Cargo的Wiki使用Page Forms扩展功能。

写在最后

如果你只是学会了Cargo的基本用法并且还有那么一点点模板的知识,那么差不多也可以动手玩一下了。如果你希望有Cargo有更出色以及更加便捷的操作,那么还请期待Page Forms的教程。

补充

1、分组(group by)后怎样合并数据

参见文章《Cargo查询中,如果使用group by之后怎样合并数据》。

最后修改:2023 年 12 月 17 日
如果觉得我的文章对你有用,请随意赞赏