Formatting XML in Sublime Text 2 (xmllint)


I'm always looking for the best tools to make my life easier. In this constant search for the best text editor for coding and general text work (mainly XML) I decided to try the amazing Sublime Text 2.

I was using TextWrangler for a while and had finally manage to get XML pretty formatting to work on it again but I was struggling to get the same experience out of Sublime Text 2. My ideal workflow is to paste some unformatted XML, press some hotkey combination and end up something pretty. Because there's always someone with with the same problem, a quick google search took me to this page where there are a couple of scripts to do just this. Read that first to get a grip on how to implement these simple scripts.

While this seemed to work pretty well I was still having some trouble with some files that weren't being formatted exactly as I expected. These scripts use the open source tidy command, which is pretty cool. However, for some reason my XMLs (which included some CDATA elements) were not being formatted properly. So I decided to use xmllint instead, just like I had done before in TextWrangler.

Turns out it's pretty easy to change the script to use xmllint instead of tidy. I created a new file called tidy_xml_lint.py, saved it to the same place where user scripts are supposed to be (OS X: ~/Library/Application Support/Sublime Text 2/Packages/User/ and WINDOWS: %APPDATA%\Sublime Text 2\Packages\User), and changed the keymap file to reflect the new command name.

Here's the code for OS X (or get the gist here https://gist.github.com/bergonzzi/7977004):

import sublime, sublime_plugin, subprocess

class TidyXmlLintCommand(sublime_plugin.TextCommand):
  def run(self, edit):
    command = "XMLLINT_INDENT=$'\t' xmllint --format --encode utf-8 -"

    # help from http://www.sublimetext.com/forum/viewtopic.php?f=2&p=12451
    xmlRegion = sublime.Region(0, self.view.size())
    p = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
    result, err = p.communicate(self.view.substr(self.view.sel()[0]).encode('utf-8'))

    if err != "":
      self.view.set_status('xmllint', "xmllint: "+err)
      sublime.set_timeout(self.clear,10000)
    else:
      self.view.replace(edit, self.view.sel()[0], result.decode('utf-8'))
      sublime.set_timeout(self.clear,0)

  def clear(self):
    self.view.erase_status('xmllint')

Don't forget to change your key bindings file as well (Preferences > Key Bindings - User)

[ { "keys": ["ctrl+shift+x"], "command": "tidy_xml_lint" }, { "keys": ["ctrl+shift+j"], "command": "prettify_json" } ]

Update:

This article became quite popular, it's great to see it's helping so many people! There are some good comments below so I'm going add the best ones here for your convenience.

Michael Argentini offers a nice tip to add this command to the Format menu:

You can add this command to the Selection>Format submenu as well! Create a file named “Main.sublime-menu” in the Packages>User folder where you created the Python file above. Then put the following in it:

[
    {
        "caption": "Selection",
        "children": [
            {
                "caption": "Format",
                "children": [
                    {
                        "caption": "Tidy with XML Lint",
                        "command": "tidy_xml_lint"
                    }
                ],
                "id": "format"
            }
        ],
        "id": "selection"
    }
]

Dimitris Baltas offers a solution for the dollar sign problem:

(...)
I also had the issue with the $ sign that Jon G mentioned. (on sublime-text-2, ubuntu 12.04)
I resolved it by removing the $ in line 5
from
command = "XMLLINT_INDENT=$'\t' xmllint --format --encode utf-8 -"
to
command = "XMLLINT_INDENT='\t' xmllint --format --encode utf-8 -"

Protke was kind enough to summarize the necessary changes for Windows users, here's the full script (or get the gist here https://gist.github.com/protke/7193842):

import sublime, sublime_plugin, subprocess

class TidyXmlLintCommand(sublime_plugin.TextCommand):
  def run(self, edit):
    command = "xmllint -format -encode utf-8 -"

    # help from http://www.sublimetext.com/forum/viewtopic.php?f=2&p=12451
    # discussion on http://www.bergspot.com/blog/2012/05/formatting-xml-in-sublime-text-2-xmllint/
    if self.view.sel()[0].empty():
      xmlRegion = sublime.Region(0, self.view.size())
    else:
      xmlRegion = self.view.sel()[0]

    p = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
    result, err = p.communicate(self.view.substr(xmlRegion).encode('utf-8'))

    if err != b"":
      self.view.set_status('xmllint', "xmllint: " + err.decode("utf-8"))
      sublime.set_timeout(self.clear,10000)
    else:
      self.view.replace(edit, self.view.sel()[0], result.decode('utf-8').replace("\r",""))
      sublime.set_timeout(self.clear,0)

  def clear(self):
    self.view.erase_status('xmllint')

As an alternative to installing your own plugin there's also a package available that you can easily install from within Sublime Text 2. See here https://github.com/alek-sys/sublimetext_indentxml for more details.

37 comments to Formatting XML in Sublime Text 2 (xmllint)

  • I have created a complete instructions gist for Sublime Text 2 here:

    https://gist.github.com/cbmeeks/8317048

  • Justin

    I modified Jacob’s Win7 Sublime 3 code so that the CR tag doesn’t appear after reformatting, here’s the altered line of code:

    self.view.replace(edit, self.view.sel()[0], result.decode(‘utf-8′).replace(“\r”, “”))

    • Protke!

      So, integrating everything said so far, here it is, for W7 users:
      import sublime, sublime_plugin, subprocess

      class TidyXmlLintCommand(sublime_plugin.TextCommand):
      def run(self, edit):
      command = "xmllint -format -encode utf-8 -"

      # help from http://www.sublimetext.com/forum/viewtopic.php?f=2&p=12451
      # discussion on http://www.bergspot.com/blog/2012/05/formatting-xml-in-sublime-text-2-xmllint/
      if self.view.sel()[0].empty():
      xmlRegion = sublime.Region(0, self.view.size())
      else:
      xmlRegion = self.view.sel()[0]

      p = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
      result, err = p.communicate(self.view.substr(xmlRegion).encode('utf-8'))

      if err != b"":
      self.view.set_status('xmllint', "xmllint: " + err.decode("utf-8"))
      sublime.set_timeout(self.clear,10000)
      else:
      self.view.replace(edit, self.view.sel()[0], result.decode('utf-8').replace("\r",""))
      sublime.set_timeout(self.clear,0)

      def clear(self):
      self.view.erase_status('xmllint')

      or just

      https://gist.github.com/protke/7193842

  • Vladox

    Here an improved version that detects if no selection has been and applies the formatting to all the text as you would expect…

    https://gist.github.com/vladox/6601324


    import sublime, sublime_plugin, subprocess

    class TidyXmlLintCommand(sublime_plugin.TextCommand):
    def run(self, edit):
    command = "XMLLINT_INDENT='\t' xmllint --format --encode utf-8 -"

    # help from http://www.sublimetext.com/forum/viewtopic.php?f=2&p=12451
    if self.view.sel()[0].empty():
    xmlRegion = sublime.Region(0, self.view.size())
    else:
    xmlRegion = self.view.sel()[0]

    p = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
    result, err = p.communicate(self.view.substr(xmlRegion).encode('utf-8'))

    if err != b"":
    self.view.set_status('xmllint', "xmllint: " + err.decode("utf-8"))
    sublime.set_timeout(self.clear,10000)
    else:
    self.view.replace(edit, xmlRegion, result.decode('utf-8'))
    sublime.set_timeout(self.clear,0)

    def clear(self):
    self.view.erase_status('xmllint')

  • [...] When I attended the BuildWindows conference this summer, I saw many of the presenters using Sublime as a text editor. It runs on both PCs with Windows and Mac. It also works on Linux. It seemed to be the consensus editor of choice. http://www.sublimetext.com/. I have enabled my Sublime plugin to prettify the XML files I work with on a regular basis. I used this tip here: http://www.bergspot.com/blog/2012/05/formatting-xml-in-sublime-text-2-xmllint/ [...]

  • On Windows, I needed to use the version of xmllint from Google that allows the console redirection support. The 2009 version I had would not work.

  • Devid

    I just can’t manage to do this under Windows 7. I installed xmllint via package manager, but when i select xmllint format nothing happens. I also installed the xmllint.exe file on my Desktop and tried to Format via command prompt and it works. I have put the xmllint in my variable path but from sublime text 2 it is not working as it should, i get errors. I have also tried to give it my absolute path but that is not working either

  • Looks like there’s a package for xmllint now which offers indentation, formatting, validation, etc.

    Open command palette: Cmd-Shift-P
    select “Package Control: Install Package”
    start typing “xmllint” and hit enter

    After it’s installed (progress in the footer bar), you can open command palatte and type xmllint to see the options. Pretty nice.

  • Dimitris Baltas

    Jacob’s trick with b”
    if err != b”:
    made the plugin working on sublime text 3 on my ubuntu 12.04 installation.
    https://gist.github.com/dbaltas/6054139

  • Jacob Dingus

    On Windows 7, Sublime Text 3, I had to modify the command line as Vivek mentioned above, as well as the error handling, checking for b” and it will only print(err) it has trouble converting strings it seems like.

    import sublime, sublime_plugin, subprocess

    class TidyXmlLintCommand(sublime_plugin.TextCommand):
    def run(self, edit):
    command = "xmllint --format --encode utf-8 -"

    # help from http://www.sublimetext.com/forum/viewtopic.php?f=2&p=12451
    xmlRegion = sublime.Region(0, self.view.size())
    p = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
    result, err = p.communicate(self.view.substr(self.view.sel()[0]).encode('utf-8'))

    if err != b'':
    print(err)
    sublime.set_timeout(self.clear,10000)
    else:
    self.view.replace(edit, self.view.sel()[0], result.decode('utf-8'))
    sublime.set_timeout(self.clear,0)

    def clear(self):
    self.view.erase_status('xmllint')

  • kourouma

    Thank you for this amazing work. It work like I was expecting tool to do. You save my time.

  • Bala

    Thank you very much it works fine. In your reply sb:authors starting element duplicated, please remove it for others to refer with out any confusion.

  • Bala

    sorry this is the code

    <sb:authors><sb:author><INS><ce:given-name>J.</ce:given-name></INS><INS><ce:surname>Bachman</ce:surname></INS></sb:author></sb:authors>

    • Hi,

      You can format xml with namespaces as well, in your example you’d have to add the namespace declaration otherwise it’s not valid xml (because you’re using name prefixes, see more info here http://www.w3schools.com/xml/xml_namespaces.asp).

      Try formatting this and you’ll see it works fine:


      <sb:authors xmlns:sb="http://www.w3.org/TR/html4/" xmlns:ce="http://www.w3.org/TR/html4/"><sb:author><INS><ce:given-name>J.</ce:given-name></INS><INS><ce:surname>Bachman</ce:surname></INS></sb:author></sb:authors>

  • Bala

    Hi,

    Very userfuly and nice post. Thank you very much for your help. Is there any chance to format the xml having name space elements as mentioned below:

    G.Bala

  • Damir

    Hello ! What theme for Sublime Text do you use? Nice colors :)

  • [...] formatting XML using xmllint- follow the instructions to get your own XML tidy this one preserves spaces as some XML Tidier will remove spaces after certain tags for example house winterfell will become housewinterfell after you tidy it which could lead to errors in copy. [...]

  • Beezer

    Can anyone tell me how to use XMLlint to validate an XML file against a schema? I have the Sublime LInter plugin installed, but I’m unsure of how to specify a schema location. Thanks.

  • Vivek

    To make this work on Windows, change the line with the command to:

    command = “xmllint –format –encode utf-8 -”

    Also, make sure that xmllint.exe is in your default environment PATH. You should restart Sublime if you changed PATH externally.

  • Dimitris Baltas

    Thanks for the great plugin.
    I also had the issue with the $ sign that Jon G mentioned. (on sublime-text-2, ubuntu 12.04)
    I resolved it by removing the $ in line 5
    from
    command = “XMLLINT_INDENT=$’\t’ xmllint –format –encode utf-8 -”
    to
    command = “XMLLINT_INDENT=’\t’ xmllint –format –encode utf-8 -”

    A nice addition would be the ability to format the whole document if there is no selected area.

  • Thanks for this!! I’m slowly getting rid of all the little things I go back to TextMate for, and this was a biggie. So awesome!

  • You can add this command to the Selection>Format submenu as well! Create a file named “Main.sublime-menu” in the Packages>User folder where you created the Python file above. Then put the following in it:


    [
    {
    "id": "selection",
    "caption": "Selection",
    "children":
    [
    {
    "id": "format",
    "caption": "Format",
    "children":
    [
    {
    "caption": "Tidy with XML Lint",
    "command": "tidy_xml_lint"
    }
    ]
    }
    ]
    }
    ]

  • Jon G

    First off, thanks. This is great.
    However, I have one problem and I’m not sure how to solve it. When I use xmllint, instead of getting tabs I get a dollar sign and then the tab. Any idea how to pull the dollar sign out?

  • Excellent, Nice and easy, works like a charm. Now I only have to get it in the command window (shift-command-p) because I always forget key shortcuts after not working with them for a while :)
    Thanks

  • Eddie Ridwan

    Works for me. Much appreciated.

  • [...] that it's missing. This is where the plugin system and the community proves useful. I found this post which is pretty straight forward but for my setup didn't work (Windows 7). I made it work very [...]

  • Thanks, works great. I came from TextMate and looked for the exact same thing!

  • Eli Shalnev

    Hi, do you know how could i make it work on Windows?
    I downloaded and installed the HTML Tidy as suggested in the github article..but..
    I’m getting this error:

    ————–
    Traceback (most recent call last):
    File “.\sublime_plugin.py”, line 350, in run_
    File “.\tidy_xml_lint.py”, line 10, in run
    result, err = p.communicate(self.view.substr(self.view.sel()[0]).encode(‘utf-8′))
    File “.\subprocess.py”, line 701, in communicate
    File “.\subprocess.py”, line 911, in _communicate
    IOError: [Errno 32] Broken pipe
    ————–

    Thanks in advance!

  • Bob Greene

    Thanks for the post. I’m just getting started with Sublime and still trying to match my old toolbox. This will help lots.

Leave a Reply

  

  

  

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>