Hack 13 Edit XML with Vim

figs/moderate.gif figs/hack13.gif

With some special configuration, Vim can become a powerful XML editor.

So you want to edit XML, but Vim is your favorite editor? The good news is that you don't need an XML-specific editor! If you're mortal, you'll soon discover that editing raw XML can become tedious even in Vim (with its default configuration). But Vim is highly customizable and extensible. After a little tailoring, Vim performs excellently as an XML editor, with syntax highlighting, automatic indentation, navigational aids, and automation.

2.4.1 Basic Configuration

I will assume you have Vim set up the way you like it already on a Unix system, so we won't fiddle much with your .vimrc file. Example 2-1 shows the bare minimum of what you need to make the rest of the hack work properly.

Example 2-1. Minimum .vimrc file
" $HOME/.vimrc

" Don't pretend to be vi

set nocompatible

" Turn on syntax highlighting

syntax on

" Indicate that we want to detect filetypes and want to run filetype

" plugins.

filetype plugin on

Everything else will go in a filetype plug-in. Vim will source this file when it detects that you are editing an XML file (i.e., when the file ends with the .xml suffix or if it has a proper XML declaration). Example 2-2 is a good starter ftplugin. Save it to your home directory as .vim/after/ftplugin/xml.vim. (The file xml.vim is in the book's file archive.) The after segment of the path means that it will be sourced after all the normal scripts, plug-ins, and so on are sourced, which allows you to override defaults and other plug-ins without changing the original scripts. That makes upgrading those scripts easier.

Example 2-2. The ftplugin xml.vim
" $VIMRUNTIME/after/ftplugin/xml.vim

" Turn on auto-indentation

   

set autoindent

   

" Let's use a 2-character indent

   

set shiftwidth=2

   

" With smarttab set, we can press tab at the beginning

" of a line and get shiftwidth indent even though

" tabstop is something else (e.g. the default 8)

   

set smarttab

   

" A lot of XML looks really bad and gets really confusing if

" screen-wrapped. I prefer to turn off wrapping.

   

set nowrap

On Windows, your vimrc file is $VIM\_vimrc (default C:\vim\_vimrc). Consider $VIMRUNTIME by default to be C:\vim\vimfiles. So, for example, you should install the ftplugin in C:\vim\vimfiles\after\ftplugin. See :help dos-locations for more details.

2.4.2 Syntax Highlighting

All XML files can benefit from the generic XML syntax highlighting included with Vim. But not all XML is created equal. Imagine you came up with a nifty XML format for your data and now you want certain items to be emphasized to make editing and reading easier. That probably wasn't too hard to imagine, was it?

There are two steps to customized syntax highlighting. The first step is telling Vim how to differentiate your special XML from regular XML. The second step is to define how your special syntax highlighting differs.

We'll work with a simple RSS 2.0 file [Hack #83] as our example, called frodo.rss (Example 2-3). Figure 2-5 shows frodo.rss in gVim, a GUI version of Vim (ftp://ftp.vim.org/pub/vim/pc/gvim63.zip), on Debian (GTK2 in Grand Canyon theme).

Example 2-3. frodo.rss
<?xml version="1.0"?>

<rss version="2.0">

  <channel>

    <title>The Gondor Times</title>

    <link>http://www.gondortimes.com/</link>

    <description>News for the race of men.</description>

    <item>

      <title>Sauron defeated!</title>

      <category>Middle Earth</category>

      <description>Now comes the story of Nine-fingered Frodo and the

          Ring of Doom.</description>

      <guid>http://www.gondortimes.com/middleearth/3/25</guid>

    </item>

  </channel>

</rss>

Figure 2-5. frodo.rss in gVim
figs/xmlh_0205.gif


In order for Vim to treat an XML vocabulary specially, it needs to differentiate it from normal XML. The easiest way to do this is by using a different file extension. Setting up a new filetype with a unique extension is straightforward (see :help new-filetype). Let's define a filetype for RSS 2.0 files with .vim/filetype.vim, as shown in Example 2-4.

Example 2-4. filetype.vim
" my filetype file

if exists("did_load_filetypes")

    finish

endif

augroup filetypedetect

    au! BufRead,BufNewFile *.rss         set filetype=rss

augroup END

Now when you restart Vim and edit frodo.rss, you should notice that it is not being recognized as XML any longer. That means no syntax highlighting, no ftplugin, nada. Wait, don't despair! You don't need to rewrite syntax highlighting for XML.

Let's set a modest goal for ourselves: highlight the channel and item tag names in bold, and make category tag names a different color. The way to achieve this is to utilize the xmlTagHook cluster provided by the syntax/xml.vim author. A syntax cluster is an open-ended bag of syntax groups that can occur in certain places. That means we can define a syntax group and say that it belongs to the xmlTagHook cluster, and it will be matched only when the match would otherwise be a regular XML tag. Let's look at Example 2-5.

Example 2-5. rss.vim
" Vim syntax file

" Language:     RSS 2.0

" Maintainer:   Hans Fugal <hans@fugal.net>

" Last Change:  Thu, 11 Mar 2004 15:44:28 -0700

   

" REFERENCES:

"   1. http://blogs.law.harvard.edu/tech/rss

   

" Quit when a syntax file was already loaded

if exists("b:current_syntax")

    finish

endif

   

" Base our syntax highlighting on xml

runtime syntax/xml.vim

   

syn match rssElement /\<channel\>/

syn match rssElement /\<item\>/

syn match rssCategory /\<category\>/

   

syn cluster xmlTagHook add=rssElement,rssCategory

   

highlight rssElement cterm=bold gui=bold

highlight link rssCategory Statement

Those syn match lines define the patterns that will match the rssElement group. The \< and \> match the beginning and end of a word (tag), respectively. So we are matching the full word channel or the full word item. The syn cluster line adds our new rssElement and rssCategory groups to the xmlTagHook cluster. The highlight lines define how we want things to look. You'll want to look up the help on highlight because that's where your creativity can really flow.

syntax/xml.vim also provides these hooks:

  • xmlAttribHook

  • xmlNamespaceHook

  • xmlTagHook

  • xmlStartTagHook

  • xmlRegionHook

  • xmlCdataHook

See the comments in syntax/xml.vim from the Vim distribution for more details.

OK, so now you have syntax highlighting, but you've lost the behaviors we defined in after/ftplugin/xml.vim, and many more behaviors that we'll talk about shortly that you certainly won't want to miss out on. The easiest solution to this problem is to create a symbolic link from after/ftplugin/xml.vim to after/ftplugin/rss.vim (or whatever new filetype you created).

2.4.3 Indentation

Vim's auto-indent is OK, but smart indentation is better. Enabling smart indent for XML in Vim is as simple as putting filetype indent on in your .vimrc or in your ftplugin. If you don't like the XML smart indentation but do like filetype indent on in your .vimrc, then you can put let b:did_indent = 1 in your XML ftplugin.

Sometimes you want to clean up the formatting of the whole XML file. To do this you can either type gg=G with filetype indent on or use an external filter. The internal method is prone to messing with your whitespace in verbatim elements, while external formatters such as Paul DuBois's xmlformat (http://www.kitebird.com/software/xmlformat/) or xmllint with --format (see [Hack #9]) can do a much better job.

To use xmlformat to format your XML in Vim, try the following command (assuming xmlformat is installed and in the path):

:!xmlformat %

To use xmllint, try:

:!xmllint --format %

Both of these commands are good candidates for mappings (see "Automation").

2.4.4 Folding

Folding is a new feature in Vim Version 6 and later that allows you to collapse arbitrary portions of your document down to one line, similar to collapsing and expanding trees in menus or dialog boxes. XML lends itself well to folding because of its hierarchical nature. There are several ways to do folding (see :help folding). The easiest way to fold XML is to use the syntax method. To enable folding in XML documents, add the following lines to your XML ftplugin:

set foldenable

set foldmethod=syntax

set foldlevel=1

The set foldlevel=1 line tells Vim to leave the top element unfolded. You can tune this number to your liking. You can also tune it to specific files by using a modeline at the bottom of the file; for example:

<!-- vim:foldlevel=5 -->

All fold commands start with z. zo opens a fold, zc closes one, zm increases the number of folds (decreases foldlevel), and zr reduces the number of folds. zM and zR fold everything and nothing, respectively. Figure 2-6 shows an example of folding with frodo.rss.

Figure 2-6. frodo.rss in gVim with folding
figs/xmlh_0206.gif


2.4.5 Automation

The biggest problem with editing XML by hand is all that typing. Vim can help you avoid a lot of that extra typing thanks to some wonderful contributed scripts that can be found at http://www.vim.org.


matchit.vim

Extends the behavior of % and includes support for XML. Pressing % will take you from opening tag to closing tag, and vice versa. It was written by Benji Fisher. See http://www.vim.org/scripts/script.php?script_id=39.


closetag.vim

Closes the next open tag when you press Ctrl-_. This is a real boon when you get tired of typing things like </xsl:apply-templates>. It was written by Steven Mueller. See http://www.vim.org/scripts/script.php?script_id=13.


EnhCommentify.vim

A handy script for commenting and uncommenting segments of your file, and supports XML very well. It was written by Meikel Brandmeyer. See http://www.vim.org/scripts/script.php?script_id=23.


xmledit.vim

The mother of all XML Vim scripts is xmledit.vim. It further enhances %, provides other navigation facilities, auto-creates closing tags, can %delete a surrounding tag, and has a dialog-driven tag creator in visual mode that will wrap a tag around whatever is selected. xmledit.vim makes writing XML much less tedious, and it makes well-formedness easier than ever to achieve. It was written by Devin Weaver. See http://www.vim.org/scripts/script.php?script_id=301.

There are many other scripts and tips available. In addition to general scripts and tips, you may find yourself with specific desires for automation. Do you create similar XML documents frequently by hand? Use a template (see :help :r). Maybe you want to map xmlformat to a simple keystroke (see :help key-mapping). Perhaps you type <xsl:apply-templates> far too often and would prefer to type <a-t> and watch it magically expand to <xsl:apply-templates> (see :help abbrev). Maybe an abbreviation isn't enough and you want <a-t> to expand to <xsl:apply-templates select=""> with the cursor right between the quotation marks, in which case you're in the realm of scripting (see :help vim-script-info). If you don't like Vim's scripting language, you can extend Vim with Perl, Python, Ruby, or Tcl. With Vim, the sky's the limit.

2.4.6 See Also

  • The Vim help files: :help or online at http://vimdoc.sourceforge.net/

  • Tobias Rief's excellent HOWTO on Vim as an XML editor: http://www.pinkjuice.com/howto/vimxml/

  • Steve Oualline's Vi Improved?Vim (New Riders Press) is a must-have reference for any serious Vimmer (also available to read online at http://vimdoc.sourceforge.net/)

  • Vim tips: http://www.vim.org/tips/tip_search_results.php?keywords=xml&order_by=rating&direction=descending&search=search

  • Vim scripts: http://www.vim.org/scripts/script_search_results.php?keywords=xml&script_type=&orderby=rating&direction=descending&search=search

?Hans Fugal