3

I have an XML document with many (dozens) of the following tags:

<item prop1="val1" prop2="val2" />

I would like to change this to:

<item prop1="val1" prop2="val2">
   <sometihng>text</something>
</item>

However, I cannot even figure out how to change the self-cosing "item" tag into an open + close tag. Further complicating this is the fact that each "item" tag may have a different number of properties.

2

You could use the following substitution command:

:%s%<item[^>]\{-}\zs */>\ze%></item>%g

In this case I use % as separator instead of /. That way I need less escaping.

To explain the regex:

  • <item[^>]\{-} matches <item followed by any number of characters, that are not >. The \{-} matches 0 or more, but as few as possible.
  • \zs */>\ze: This matches the end of the tag with possible whitespaces. The interesting part it \zs and \ze. This defines the part of the match that should be replaced. Everything matched before \zs is left unchanged.

In words: Replace the /> of an "item" tag with ></item>.

If you want a line break between opening and closing tag, use:

:%s%\(\s*\)<item[^>]\{-}\zs */>\ze%>\r\1</item>%g

This time the leading \(\s*\) matches the leading whitespaces and \r\1 inserts the leading whitespaces after the line break. So indent is kept.

BTW: This only works, when self-closing tag is on one line.

To replace any self closing tag, you could use this:

:%s%\(\s*\)<\([-A-Za-z0-9_\.]*\)[^>]\{-}\zs */>\ze%>\r\1</\2>%g
1

So after doing some more research I came up with something that is able to do this:

:%s/<item \(.*\)\/>/<item \1 >\r\t<something>text<\/something>\r<\/item>
0

I would do something like (untested)

qq0f<lyiwf>F/i><Enter><<Esc>pq

Where <Enter> and <Esc> should be the keys Enter and Esc on your keyboard.

This should record a macro that does the transformation. It assumes the whole tag is on one line.

Theoretically, you could then follow with

g/<.*\\>/normal! @q

To transform the whole file.

0

Plugin Emmet

The plugin emmet provides the mapping <C-y>j to split or join tags. The help section explains what emmet does when using this mapping:

12. Split/join tag                             *emmet-split-join-tag* *<C-y>j*

  To join block, type '<C-y>j'.
>
  <div class="foo">
      cursor is here
  </div>
<
  Type '<C-y>j' in insert mode. Then,
>
  <div class="foo" />
<
  And type '<C-y>j' in there again.
>
  <div class="foo">
  </div>

Sometimes you have to modify many tags individually because you have to insert different content between the tags which makes a global substitution not perfect. However, a global substitution could still be the first step.

Following steps achieve the change described in the question:

  1. Modify single tag

    1. /<item to move to tag <item ... />
    2. <C-y>j (:h emmet-split-join)
    3. cit (:h it)
    4. something<C-y>; (:h emmet-expand-word)
    5. text specific for this line<Esc>
    6. n jump to next <item which should be changed
  2. Modify all lines and insert the same text everywhere

    In this case I would use the :substitute command similar to you. However, I want to show that it is also be possible to use the plugin in combination with :global and :normal (or with a macro not shown here). I assume that all <item .../> are placed on separate lines

    :g/item/exec "norm f<\<C-y>jcitsomething\<C-y>;text"
    

Formatting

To format your xml source code, you can consider using tidy for formatprg by placing following into ~/.vim/after/ftplugin/xml.vim:

setlocal formatprg=tidy\ -xml\ -quiet\ --indent\ auto\ --tidy-mark\ no\ --indent-spaces\ 2\ --vertical-space\ yes

Now you can format the whole xml file afterwards with gggqG.

Furthermore, if you like to combine formatprg=tidy ... with indentexpr=XmlIndentGet(v:lnum,1) from $VIMRUNTIME/indent/xml.vim, have a look at following question Automatically reindent lines filtered through formatprg.

0

Plugins Switch & SplitJoin by Andrew Radev

Install the two plugins and add following to ~/.vim/after/ftplugin/xml.vim for switch.vim:

let b:switch_custom_definitions =
      \ [
      \   {
      \     '<\(\S*\) \(.*\)\s*/>': '<\1 \2></\1>',
      \     '<\(\S*\) \(.*\S\)\s*>.*</\1>': '<\1 \2 />',
      \   },
      \ ]

Additionally, you could install vim-surround or vim-sandwich to add more easily the tag something with ysiwt+something.

Again, this answer targets the situation where it is more suitable to change individual tags:

  1. /<item to move to first <item
  2. gs from switch.vim to toggle tag between <item .../> and <item ...></item>
    (check toggle by pressing gsgsgs)
  3. gS from splitjoin.vim to change from a single line to multi-line format
  4. o to insert a new line below with the correct indentation
  5. your text<Esc>
  6. ysiwt+something<CR> surround your text with a tag with the help of vim-surround
  7. n jump to next <item which should be changed

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.