szydan
szydan

Reputation: 2596

Substitution pattern to escape quotes in vim

I have text with some lines contains properly escaped double quotes in strings and some not like below:

bla1 "aaa"bbb"ccc" bla1
bla2 "aaa\"bbb\"ccc" bla2

The results after substitution should be

bla1 "aaa\"bbb\"ccc" bla1
bla2 "aaa\"bbb\"ccc" bla2

but not:

bla1 "aaa\"bbb\"ccc" bla1
bla2 "aaa\\"bbb\\"ccc" bla2

In other words it should escape the double quotes in lines where they are not escaped and do not touch lines which are already properly escaped

So far I got the second result with this

%s:\(\s".\+\)\(".\+\)\(".\+"\s\):\1\\\2\\\3:g

Then I've tried a negative lookbehind to tell the engine to not match if there is a backslash before the quotes

(?<!\) which in vim should be something like @<!\

%s:\(\s".\+\)\@<!\\\(".\+\)@<!\\\(".\+"\s\):\1\\\2\\\3:g

But I think I've got a little lost.

Note:
There is only one such string per line The string is enclosed in double quotes and can contain double quotes inside - only this inside one should be escaped

Upvotes: 3

Views: 4887

Answers (4)

FDinoff
FDinoff

Reputation: 31419

Since you said there is only 1 string on each line, you can chain substitute commands to get the result that you want. (This also leads to easier regexes in all parts of the command)

:%s/"\zs.*\ze"/\=substitute(submatch(0), '\\\@<!"', '\\"', 'g')

Explanation:

  1. :%s/"\zs.*\ze" matches everything on the line between the first and the last quote. We use the greedy .* to do this. \zs marks the start of the match and \ze marks the end of the match.
  2. After that we can pass the match to a second substitute command by adding \= to the start of the replacement. This means that the result of the expression after it will be the replacement string.

    substitute(submatch(0), '\\\@<!"', '\\"', 'g')
    

    submatch(0) is everything between the quotes. We then replace all quotes that don't have a slash before it (\\\@<!") with a \".

Take a loot at :h sub-replace-expression, :h /\zs and :h /\ze


Example Input:

bla1     "aaa"bbb"ccc"      bla1
bla2     "aaa\"bbb\"ccc"    bla2
bla\bla3 "aaa"bbb"ccc"      bla3 
blabla4  "aaa"bbb" "BBB"ccc" bla4       
bla\bla5 "aaa"bbb" "BBB"ccc" bla5
bla\bla5 "aaa"bbb""BBB"ccc" bla5

Example Output:

bla1     "aaa\"bbb\"ccc"      bla1
bla2     "aaa\"bbb\"ccc"    bla2
bla\bla3 "aaa\"bbb\"ccc"      bla3 
blabla4  "aaa\"bbb\" \"BBB\"ccc" bla4       
bla\bla5 "aaa\"bbb\" \"BBB\"ccc" bla5
bla\bla5 "aaa\"bbb\"\"BBB\"ccc" bla5

Upvotes: 3

Jeff
Jeff

Reputation: 1807

You could just stick an inverse global onto one of the command you wrote already. Now it only will apply to lines that don't contain already escaped quotes:

:v/\\"/s:\(\s".\+\)\(".\+\)\(".\+"\s\):\1\\\2\\\3:g

Upvotes: 1

Cole Tierney
Cole Tierney

Reputation: 10304

This might work:

%s/\([^\\]\)\("\)/\1\\\2/g

Upvotes: 0

Kevin
Kevin

Reputation: 56049

:%s/\([^ \\]\)"\([^ ]\)/\1\\"\2/g

This finds quotation marks not preceded by a slash or space and not followed by a space.

Upvotes: 0

Related Questions