Increase all numbers except one or more (separated by commas) conditional on preceding string(s) (for a book index)

Viewed 57

I'm trying to increase by a fixed amount every page number in a file with the following content (it is an index for a book):

Adam und Eva 42–44 (Abb. 14, 15)

Biblioteca Apostolica Vaticana Cod. gr. 1613 31 31 (Abb. 8)

Hamburg, Staatsbibliothek Ms. 151 in scrin. 35 (Abb. 11)

Transverberation der Hl. Theresa von Ávila 10, 18 (Abb. 2, Detail S. 8)

The file contains numbers for years, figures etc. However, page numbers

  • are never preceded by "Abb. " or "Ms. "
  • have 3 digits or less

I'd like to add the number 4 to the page numbers, ideally leading to

Adam und Eva 46–48 (Abb. 14, 15)

Biblioteca Apostolica Vaticana Cod. gr. 1613 35 35 (Abb. 8)

Hamburg, Staatsbibliothek Ms. 151 in scrin. 39 (Abb. 11)

Transverberation der Hl. Theresa von Ávila 14, 22 (Abb. 2, Detail S. 12)

A verbal rule could be: Add 4 to every number if it has 3 digits or less and if it is not preceded by "Abb. |Ms. " or another number which is separated by ", " and, in turn, preceded by "Abb. |Ms. "

The following line

perl -pe 's/(?<!Abb. )(\b\d{1,3}\b)/$1+4/eg' original.md

produces

Adam und Eva 46--48 (Abb. 14, 19)
Biblioteca Apostolica Vaticana Cod. gr. 1613 35 35 (Abb. 8)
Hamburg, Staatsbibliothek Ms. 155 in scrin. 39 (Abb. 11)
Transverberation der Hl. Theresa von Ávila 14, 22 (Abb. 2, Detail S. 12)

Two problems remain, the first one of which is most pressing:

  1. The second figure number on line 1 has of course increased by 4. But I don't know how to fix this. I'm aware that I could expand the middle part to something like (\b\d{1,3}\b),?\s?(\b\d{1,3}\b)? and reference the second number with $2, but I don't know how to deal with the separating comma (if it exists) in the replacement.
  2. The number after "Ms. " has increased by 4. But if I change the negative lookbehind to (?<!(Abb. |Ms. )), I receive the error Variable length lookbehind not implemented in regex m/(?<!(Abb. |Ms. ))(\b\d{1,3}\b)/ at -e line 1. I don't know an alternative to such an implementation.

Any help on these two problems would be much appreciated!

1 Answers

You can use the following regex:

See regex in use here

(?:Abb|Ms)\.\s+\d{1,3}(?:,\s+\d{1,3}\b)*(*SKIP)(*FAIL)|\b\d{1,3}\b

This regex works in the following way:

  • (?:Abb|Ms) Match either Abb or Ms literally
  • \.\s+ Match the literal . character, followed by one or more whitespace characters.
  • \d{1,3} Match between 1 and 3 digits
  • (?:,\s+\d{1,3}\b)* Match the following non-capture group any number of times:
    • ,\s+\d{1,3}\b Match ,, followed by a whitespace character one or more times, then by 1 to 3 digits and assert the end of the digit using a word boundary
  • (*SKIP) control verb that causes the regex to give up on the current match if it tries to backtrack past its position (which means that it did match this string and will prevent the second option from matching)
  • (*FAIL) control verb that forces this match to fail causing the current match to be excluded from the results

The second option is what actually matches: \b\d{1,3}\b - match between 1 and 3 digits asserting each side as a word boundary.


If \b doesn't properly match every location, you may want to replace \b with (?:(?<=\D)|^) for preceding and (?=\D|$) for proceeding word boundaries respectively:

See regex in use here

(?:Abb|Ms)\.\s+\d{1,3}(?:,\s+\d{1,3}(?=\D|$))*(*SKIP)(*FAIL)|(?:(?<=\D)|^)\d{1,3}(?=\D|$)

These lookbehind/lookaheads work by asserting either a non-digit character or anchor to start/end of string exists in the previous/next position.

Related