【问题标题】:Regex to match MediaWiki template and its parameters正则表达式匹配 MediaWiki 模板及其参数
【发布时间】:2011-06-30 08:50:05
【问题描述】:

我正在编写一个简单的 Javascript 来为当前正在编辑的文章中的特定模板添加特定参数。

维基百科模板的结构如下:

 {{Template name|unnamed parameter|named parameter=some value|another parameter=[[target article|article name]]|parameter={{another template|another tamplate's parameter}}}}

一个模板也可以多行,例如:

{{Template 
|name=John
|surname=Smith
|pob=[[London|London, UK]]
}}

如需进一步参考,请查看http://en.wikipedia.org/wiki/Help:Template

首先我想匹配整个模板。我来了部分解决方案,即:

document.editform.wpTextbox1.value.match(/\{\{template name((.|\n)*?)\}\}$/gmis)

但是问题是它只匹配从初始括号到第一个嵌套模板的右括号的文本(第一个示例)。

此外,我想以数组形式获取它的参数。所以对于结果,我想得到一个带有特定顺序参数的数组。 数组(参数pob的值,参数名称的值,参数surname的值,参数pod的值(本例为空,因为未设置))

我会用它来清理一些文章中的非标准化格式并添加一些新参数。

谢谢!

【问题讨论】:

  • 维基百科模板似乎不是一种正则语言,因此,正则表达式并不是解析它们的正确工具。您最好寻找另一种语言的解析器并将其移植到 JavaScript 代码中。
  • 希望大家不要介意我加了regex标签,让擅长javascript正则表达式的人注意到这个问题。另外,我认为标题有点难以理解:我建议使用“正则表达式以匹配 MediaWiki 模板包含语法”之类的内容(因为 Wikipedia 使用 MediaWiki 引擎)。
  • 我很确定可以使用正则表达式解析参数。我还发现了另一个类似的问题(link),用正则表达式部分解决了。但这不是我所需要的。
  • 没错,大多数正则表达式实现可以解析(或匹配)远远超过常规语言,但这通常不是一个好主意,因为它会导致大多数人无法理解的可怕正则表达式,因此是一场噩梦维护。
  • 那么你有什么建议?但是,JavaScript 仍然存在限制,因为 Wikipedia 安装的 MediaWiki 不支持其他用户脚本语言。

标签: javascript regex wikipedia


【解决方案1】:

编写简单的解析器。

用正则表达式解决这类问题是不对的。它与匹配括号相同 - 使用正则表达式很难。正则表达式一般不适用于嵌套表达式。

试试这样的:

var parts = src.split(/(\{\{|\}\})/);
for (var i in parts) {
  if (parts[i] == '{{') // starting new (sub) template
  else if (parts[i] == '}}') // ending (sub) template
  else // content (or outside)
}

这只是伪代码,因为我现在很着急,将更新此代码以使其正常工作...

更新(2011 年 8 月 9 日)

var NO_TPL = 0, // outside any tpl - ignoring...
    IN_TPL = 1, // inside tpl
    IN_LIST = 3; // inside list of arguments

function parseWiki(src) {
  var tokens = src.split(/(\{\{|\}\}|\||=|\[\[|\]\])/),
      i = -1, end = tokens.length - 1,
      token, next, state = NO_TPL,
      work = [], workChain = [], stateChain = [];

  function trim(value) {
    return value.replace(/^\s*/, '').replace(/\s*$/, '');
  }

  // get next non empty token
  function getNext(next) {
    while (!next && i < end) next = trim(tokens[++i]);
    return next;
  }

  // go into tpl / list of arguments
  function goDown(newState, newWork, newWorkKey) {
    stateChain.push(state);
    workChain.push(work);

    if (newWorkKey) {
      work[newWorkKey] = newWork;
    } else {
      work.push(newWork);
    }

    work = newWork;
    state = newState;
  }

  // jump up from tpl / list of arguments
  function goUp() {
    work = workChain.pop();
    state = stateChain.pop();
  }

  // state machine
  while ((token = getNext())) {
    switch(state) {

      case IN_TPL:
        switch(token) {
          case '}}': goUp(); break;
          case '|': break;
          default:
            next = getNext();
            if (next != '=') throw "invalid";
            next = getNext();
            if (next == '[[') {
              goDown(IN_LIST, [], token);
            } else if (next == '{{') {
              goDown(IN_TPL, {id: getNext()}, token);
            } else {
              work[token] = next;
            }
        }
        break;

      case IN_LIST:
        switch(token) {
          case ']]': goUp(); break;
          case '|': break;
          default: work.push(token);
        }
        break;

      case NO_TPL:
        if (token == '{{') {
          next = getNext();
          goDown(IN_TPL, {id: next});
        }
        break;
    }
  }

  return work;
}

单元测试

describe('wikiTpl', function() {
  it('should do empty tpl', function() {
    expect(parseWiki('{{name}}'))
      .toEqual([{id: 'name'}]);
  });

  it('should ignore text outside from tpl', function() {
    expect(parseWiki(' abc {{name}} x y'))
    .toEqual([{id: 'name'}]);
  });

  it('should do simple param', function() {
    expect(parseWiki('{{tpl | p1= 2}}'))
      .toEqual([{id: 'tpl', p1: '2'}]);
  });

  it('should do list of arguments', function() {
    expect(parseWiki('{{name | a= [[1|two]]}}'))
      .toEqual([{id: 'name', a: ['1', 'two']}]);
  });

  it('should do param after list', function() {
    expect(parseWiki('{{name | a= [[1|two|3]] | p2= true}}'))
      .toEqual([{id: 'name', a: ['1', 'two', '3'], p2: 'true'}]);
  });

  it('should do more tpls', function() {
    expect(parseWiki('{{first | a= [[1|two|3]] }} odd test {{second | b= 2}}'))
      .toEqual([{id: 'first', a: ['1', 'two', '3']}, {id: 'second', b: '2'}]);
  });

  it('should allow nested tpl', function() {
    expect(parseWiki('{{name | a= {{nested | p1= 1}} }}'))
      .toEqual([{id: 'name', a: {id: 'nested', p1: '1'}}]);
  });
});

注意:我在这些单元测试中使用 Jasmine 的语法。您可以使用包含整个测试环境的 AngularJS 轻松运行它 - 在 http://angularjs.org 上查看它

【讨论】:

  • 嗨 ;) 感谢您的努力和抱歉迟到的回复,但我的时间很紧迫正如您所提到的,它只是一个伪代码,它不应该是功能性的。困扰我的是如何使用您建议的方法匹配整个模板。我更多地考虑编写一个简单的函数来计算左大括号出现的次数,并通过将此计数与结束的计数进行比较,您可以轻松确定是否到达模板结尾...
  • 通过这个伪代码,您可以将整个 src 解析为对象表示,因此您可以轻松更改任何内容,例如将属性添加到模板中,删除等...我刚刚再次阅读了您的问题,也许这对您来说太多了...因此,您可以迭代此数组(parts),而不是构建对象表示并做任何你想做的事 - 找到你想要的模板 - 改变它(通过删除/添加字符串到数组中)然后构建更新的字符串,parts.join('') 这有意义吗?
  • 发一些你需要用它做的模板字符串和操作的例子,我会尽力帮助...
  • 一个例子是:{{naslov | prejsnji1= [[3|tri]] |纳斯洛夫= 4 |波格拉夫耶= |许可证=公共| vir={{dlib|6cc05ae24d30}} }} 结果将是 Array( [prejsnji1]=>"[[3|tri]]", [naslov]=>"4",[poglavje]=>"", [licenca]=>"public", [vir]=>"{{dlib|U6cc05ae24d30}}" )
  • 添加了带有单元测试的完整(工作)源代码。简单的解析器,但应该满足您的要求...如果您愿意,您可以轻松添加更多单元测试和更多功能...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多