Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I jump to the next sibling of an XML element with Vim?

Tags:

vim

xml

I'm editing a number of XML files now that have some kind of structure like:

<Parent>
  <Child name="Fred">
    Some content
  </Child>
  <Child name="George" value="other content" />
  ...
</Parent>

In other words, the content of some parent element will be a sequence of child elements (with the same name in this case). As examples, think of a list of items for sale in a catalogue, or even a sequence of <xsl:template> in an <xsl:stylesheet>.

What I would like to do is map some key sequence in vim to go to the next (or previous) child element when I am within the parent element, and ideally to be able to prefix this with a count (so that I can "go to the fifth next child, for example). Ideally, I will need to be outside the content of the Child element for this to make sense, so that inside the Child element I can jump between Grandchildren with the same map. I looked online for plugins/solutions to this and haven't found anything.

What I can currently do is vat<Esc>j in the case where the next Child is on the immediate next line after the close of this Child (similarly vato<Esc>k for preceding Child). However, I have a few problems with this:

  1. I can't prefix this with a count; doing so before the mapping causes weird behaviour (see :help v, prefixing v with count doesn't do what I'd like here); doing so inside the mapping (e.g. v2at) selects successively 'higher' enclosing elements.
  2. This won't work if the sequence of Child elements don't follow directly after each other, each opening tag following on the immediate next line from the preceding closing tag.
  3. If I do this on the last Child of a Parent, I'll go to the closing tag of the parent (or some of its text content if it exists, or whatever)
  4. Clobbers the previously selected region

Number 2 is not hugely critical - I can probably enforce correct formatting on the files I'm editing, and can use xmllint to do this easily. I still would prefer a more 'semantic' approach for elegance and robustness, if possible. Number 3 is really not a big deal, it would just be a nice bonus if I could either stay where I am, or go to the first Child, when on the last Child. Number 4 has yet to be an issue at all for me. Maybe it will interfere with a plugin, or my work in the future, but whatever, certainly not critical.

Number 1 is the critical one though, as I frequently find myself wanting to go so many Children down. Currently, I'm looking for the Child I want to go to, glancing at my relativenumber for that line, then going that many lines down (or up). This is

  • Annoying, and not obvious to the way I think about the content of the XML
  • Not easily scriptable/macroable, at least not robustly

Another possible solution would be to do a search for Child and then go [count]n or [count]N. This has a few issues as well:

  1. Either I have to think of and type the name Child myself, which is entirely not programmable; or I do something like vato<Esc>l* which can't be prefixed by a count in the way I'd want
  2. Breaks when nesting Child elements inside each other (has use cases), splitting Child elements across separate Parent elements, when the string "Child" appears anywhere other than as a Child element etc.
  3. Clobbers the previous search; this is a nuisance when I'm using the search to highlight something currently relevant, or still want to be able to jump to the next search item after jumping to the next element.

Ideally I would create some kind of function+command+mapping that would handle all of this in a robust, intelligent manner. Then I could just mindlessly use my command while editing, and have no headaches trying to record a macro that works by jumping between elements, etc. However, I am still quite new to vimscripting and am not quite sure how to begin.

like image 702
Danwizard208 Avatar asked Oct 16 '13 17:10

Danwizard208


1 Answers

They obviously don't satisfy all your requirements but these crude commands may help…

/^\s\{<C-r>=indent(".")<CR>}<\w\+\s<CR>
?^\s\{<C-r>=indent(".")<CR>}<\w\+\s<CR>

Jump forward/backward to the next XML tag at the same indentation level.

like image 111
romainl Avatar answered Sep 27 '22 02:09

romainl