Регулярное выражение для сопоставления труб, не входящих в скобки или фигурные скобки, с вложенными блоками - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь разобрать какую-то вики-разметку. Например, следующее:

{{Some infobox royalty|testing
| name = Louis
| title = Prince Napoléon 
| elevation_imperial_note= <ref name="usgs">{{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref>
| a = [[AA|aa]] | b =  {{cite
|title=TITLE
|author=AUTHOR}}
}}

может быть текстом для начала. Сначала я удаляю начальный {{ и окончательный }}, чтобы я мог предположить, что они пропали.

Я хочу сделать .split(<regex>) для строки, чтобы разделить строку на все | символов, которые не находятся в скобках или скобках. Регулярное выражение должно игнорировать символы | в [[AA|aa]], <ref name="usgs">{{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref> и {{cite|title=TITLE|author=AUTHOR}}. Ожидаемый результат:

[
 'testing'
 'name = Louis', 
 'title = Prince Napoléon', 
 'elevation_imperial_note= <ref name="usgs">{{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref>',
 'a = [[AA|aa]]',
 'b =  {{cite\n|title=TITLE\n|author=AUTHOR}}'
]

В любой точке могут быть разрывы строк, поэтому я не могу просто искать \n|. Если в нем есть лишние пробелы, это нормально. Я могу легко лишить \s* или \n*.

https://regex101.com/r/dEDcAS/2

1 Ответ

0 голосов
/ 01 ноября 2018

Ниже приведено чистое решение Ruby. Я предполагаю, что скобки и скобки в строке сбалансированы.

str =<<BITTER_END
Some infobox royalty|testing
| name = Louis
| title = Prince Napoléon 
| elevation_imperial_note= <ref name="usgs">{{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref>
| a = [[AA|aa]] | b =  {{cite
|title=TITLE
|author=AUTHOR}}
BITTER_END

stack = []
last = 0
str.each_char.with_index.with_object([]) do |(c,i),locs|
  puts "c=#{c}, i=#{i}, locs=#{locs}, stack=#{stack}" 
  case c
  when ']', '}'
    puts "  pop #{c} from stack"
    stack.pop
  when '[', '{'
    puts "  push #{c} onto stack"
    stack << c
  when '|'
    puts stack.empty? ? "  record location of #{c}" : "  skip | as stack is non-empty" 
    locs << i if stack.empty?
  end
    puts "  after: locs=#{locs}, stack=#{stack}" 
end.map do |i|
  old_last = last
  last = i+1
  str[old_last..i-1].strip if i > 0
end.tap { |a| a << str[last..-1].strip if last < str.size }
  #=> ["Some infobox royalty",
  #    "testing",
  #    "name = Louis", 
  #    "title = Prince Napoléon",
  #    "elevation_imperial_note= <ref name=\"usgs\">
  #      {{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref>",
  #    "a = [[AA|aa]]",
  #    "b =  {{cite\n|title=TITLE\n|author=AUTHOR}}"]

Обратите внимание, что для улучшения читабельности я разбил строку, которая является предпоследним элементом возвращаемого массива 1 .

Объяснение

Для объяснения того, как определяются местоположения символов канала, по которым определяется разделение, запустите Heredoc выше, чтобы определить str (Heredoc должен быть сначала без отступа), а затем выполните следующий код. Все будет раскрыто. (Вывод длинный, поэтому обратите внимание на изменения в массивах locs и stack.)

stack = []
str.each_char.with_index.with_object([]) do |(c,i),locs|
  puts "c=#{c}, i=#{i}, locs=#{locs}, stack=#{stack}" 
  case c
  when ']', '}'
    puts "  pop #{c} from stack"
    stack.pop
  when '[', '{'
    puts "  push #{c} onto stack"
    stack << c
  when '|'
    puts stack.empty? ? "  record location of #{c}" : "  skip | as stack is non-empty" 
    locs << i if stack.empty?
  end
    puts "  after: locs=#{locs}, stack=#{stack}" 
end
  #=> [20, 29, 44, 71, 167, 183]

При желании можно подтвердить, что скобки и скобки сбалансированы следующим образом.

def balanced?(str)
  h = { '}'=>'{', ']'=>'[' }
  stack = []
  str.each_char do |c|
    case c
    when '[', '{'
      stack << c
    when ']', '}'
      stack.last == h[c] ? (stack.pop) : (return false)
    end
  end   
  stack.empty?
end

balanced?(str)
  #=> true

balanced?("[[{]}]")
  #=> false

1 ... и, в интересах прозрачности, иметь возможность использовать определенное слово .

...