I have a large xhtml document that is broken into some 245 long paragraphs, each with a unique id in the form of "ud###"

<p class="windowbg" id="ud000">
   <!--- lots of content --->
<p class="windowbg" id="ud244">
   <!--- and a bit more --->

Since each 'paragraph' is several times larger than the viewport, i wanted to add links at the top of each to enable jumping around.

Here's what i came up with:

%s/\"ud\(\d\{3}\)\">/\=submatch(0)."<a href =\"#ud".printf("%03d", submatch(1) - 1)."\">prev<\/a> <a href=\"#ud".printf("%03d", submatch(1) + 1)."\">next<\/a>"/

Now, the problem is that it didn't work.

The first 9 replacements were perfect, with the relevant line coming out like this:

<p class="windowbg" id="ud001"><a href="#ud000">prev</a> <a href="#ud002">next</a>

But from 010 to 017, they came out weird, the links off by two

<p class="windowbg" id="ud010"><a href="#ud007">prev</a> <a href="#ud009">next</a>

Then 018 and 019 were perfect, 020 through 027 came out off by four, 028 and 029 were correct, 030 through 037 were off by 6...

I don't know how long the pattern holds, and I can't figure out why it shows up in the first place.

Any ideas? Is this some well known bug?


Octal numbers! Numbers starting with 0 are taken to be in octal. So, 00108 = 000810, and subtracting one, we get 000710 = 00078.

See :h octal:

Conversion from a String to a Number is done by converting the first digits to
a number.  Hexadecimal "0xf9", Octal "017", and Binary "0b10" numbers are
recognized.  If the String doesn't start with digits, the result is zero.
To avoid a leading zero to cause octal conversion, or for using a different
base, use str2nr().

So, your substitution replacement will look like:

\=printf("%s <a href='#ud%03d'>prev<\/a> <a href='#ud%03d'>next<\/a>", submatch(0), str2nr(submatch(1)) - 1, str2nr(submatch(1)) + 1)/

Note that you can use a single printf for the whole thing.

str2nr can take a base, but:

When {base} is omitted base 10 is used.  This also means that
a leading zero doesn't cause octal conversion to be used, as
with the default String to Number conversion.
  • In hindsight, the source of the problem should have been obvious, what with the pattern I was seeing. I suppose it just never occurred to me that vim might bother to deal with any base other than decimal. That printf statement is amazing too. I have much to learn. Feb 25 '16 at 8:19
  • Oh. Just a small quibble, in that the printf statement seems to treat the backslashes as literals, which left all the <a> tags unclosed. Easy enough to fix Feb 25 '16 at 8:47
  • @FitzmorrisPR Oh, yeah, I used single-quoted strings, so I needn't have escaped the /.
    – muru
    Feb 25 '16 at 8:48
  • @FitzmorrisPR Guess I was mistaken. I had to use double quotes for the format string, and escape the / before it worked correctly (or use a character other than / to separate :s's patterns).
    – muru
    Feb 25 '16 at 8:51

It's because Vim is interpreting the numbers as octal. See :h octal. The suggestion there is to use str2nr() so wrap your submatch calls

%s/\"ud\(\d\{3}\)\">/\=submatch(0)."<a href =\"#ud".printf("%03d", str2nr(submatch(1)) - 1)."\">prev<\/a> <a href=\"#ud".printf("%03d", str2nr(submatch(1)) + 1)."\">next<\/a>"/
                                                                   ^^^^^^^           ^                                                  ^^^^^^^           ^

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.