Also available at

Also available at my website http://tosh.me/ and on Twitter @toshafanasiev

Thursday, 17 March 2011

XML Format Function for Vim in Python

Visual Studio has a nice feature that lets you format an XML (or other) document to make it more readable (Edit > Advanced > Format Document). I really like this feature and felt the need to add it (for XML at least) to my text editor of choice, Vim.
Vim lets you define your own functions as follows (note that user functions must start with a capital letter):

function! YourFunction()
" definition of function goes here
endfunction

Which is great, but it does involve learning Vim's scripting language - an activity which is not that high on my list right now. What I discovered is that the default build of Vim includes a Python interpreter which can be invoked on Vim's command line like this:

:py[thon] # python command goes here

With multi-line sessions being initiated with a termination sequence of your choosing:

:py[thon] << ENDPY

Where ENDPY is an example of a termination sequence - it's just a symbol that you enter to tell Vim that you're finished writing your Python script/command so that it can go ahead and execute it - you could use any symbol you like; many examples use EOF.
This multi-line bit is what you need to implement your Vim functions in Python - you create a Vim function wrapper around a block of Python; access to Vim's buffers etc. is provided in the form of a built-in Python module called vim (see here for more info).
Continuing the dummy function definition above:
function! YourFunction()
python << ENDPY
import vim

# definition of function goes here (in Python!)

ENDPY
endfunction
You'd make this declaration in your .vimrc file; to invoke it you'd issue the following to Vim while in normal mode:
:call YourFunction()
That's a bit more typing than you'd expect in Vim; to cut this down you'd map this function call to a command as follows (note that, as for functions, user commands must start with a capital letter):
command! -nargs=0 Yfunc call YourFunction()
So now you need only type the following to call your function:
:Yfunc
This is all well and good, but very abstract; plus it doesn't show you the vim object in action. What prompted all of this in the first place was the need to format XML, so here is my solution, complete with command mapping:
function! PrettyXml()

python << ENDPY

import vim
from xml.dom import minidom

try:
  b = vim.current.buffer
  x = minidom.parseString( '\n'.join( b ) )
  b[:] = None
  for l in x.toprettyxml().split('\n'):
    b.append( str( l ) )
  del b[0] # remove empty first line
  
except Exception as ex:
  print ex

ENDPY

endfunction

command! -nargs=0 Pxml call PrettyXml()

This shows me getting access to the contents of the current buffer - the list-like object vim.current.buffer containing the lines of the buffer; using the Python Standard Library to achieve my goal and finally; updating the contents of the buffer.
This solution is a quick hack and as such has (at least) the following limitations:
  • It only deals with the whole buffer; selections are supported by the vim object, but I haven't made use of them
  • It only deals with whole XML documents; I haven't tried to support fragments
  • It is a bit picky about XML declarations - if you specify UTF-16 but encode with UTF-8 or ASCII, it will barf (just 'cos the Python minidom does) - arguably though, that's a feature.
This is not a very complicated example (and arguably quite a lazy solution) but what it does show is the potential - you can use the whole of the Python Standard Library to manipulate the contents of your buffer - there's nothing to stop you writing a function to tweet the current selection, for instance.

No comments:

Post a comment