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 (xml mainly) 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 the TextWrangler script.

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 (~/Library/Application Support/Sublime Text 2/Packages/User/), and changed the keymap file to reflect the new command name.

Here's the code:

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 -"

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

  • 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>