The Problem

I’m dealing with a huge XML response body from a restaurant API. I wanted to check if the data I requested was indeed in there. Below is a snippet.

<?xml version="1.0" encoding="UTF-8"?>
<searchResponse>
  <totalResults>2345</totalResults>
  <totalPages>1</totalPages>
  <searchSeed>-2120627666</searchSeed>
  <businesses>
    <business>
      <businessId>24604</businessId>
      <businessTypeId>1</businessTypeId>
      <businessName>VERRE Y TABLE</businessName>
      <street>Chaussée de Roodebeek</street>
      <boxNumber>249</boxNumber>
      <zip>1200</zip>
      <city>Woluwe-Saint-Lambert</city>
      <region>Brussels</region>
      <phone>02.779.54.69</phone>
      <hasOldReservationForm>false</hasOldReservationForm>
      <cuisines>
        <cuisine>Seasonal</cuisine>
        <cuisine>Grill</cuisine>
        <cuisine>French</cuisine>
      </cuisines>
      <url>http://en.resto.be/restaurant/brussels/1200-woluwe-saint-lambert/24604-verre-y-table/</url>
      <pictures>
        <picture>https://images.resto.com/view?iid=resto.be:080b2d97-04bf-400d-8552-9d19bd4810d6&amp;context=default&amp;width=740&amp;height=500&amp;hash=4f019870b9acc21d78ab578f4dd731f8</picture>
        <picture>https://images.resto.com/view?iid=resto.be:576a793b-cfc6-43e9-be3d-aaa576e8e098&amp;context=default&amp;width=740&amp;height=500&amp;hash=4f019870b9acc21d78ab578f4dd731f8</picture>
        <picture>https://images.resto.com/view?iid=resto.be:d078a2cf-6acb-4768-9076-e7066cefb841&amp;context=default&amp;width=740&amp;height=500&amp;hash=4f019870b9acc21d78ab578f4dd731f8</picture>
        <picture>https://images.resto.com/view?iid=resto.be:2fbb9cdc-fee5-4425-9d67-937da7fba26f&amp;context=default&amp;width=740&amp;height=500&amp;hash=4f019870b9acc21d78ab578f4dd731f8</picture>
        <picture>https://images.resto.com/view?iid=resto.be:4f3ab9f2-0b3b-4850-a214-a8eb95857d36&amp;context=default&amp;width=740&amp;height=500&amp;hash=4f019870b9acc21d78ab578f4dd731f8</picture>
        <picture>https://images.resto.com/view?iid=resto.be:1d414d7b-55c0-4d00-bc2e-aa5bfbe29769&amp;context=default&amp;width=740&amp;height=500&amp;hash=4f019870b9acc21d78ab578f4dd731f8</picture>
      </pictures>
      <lon>4.42617</lon>
      <lat>50.848111</lat>
      <avgRating1>9.3</avgRating1>
      <avgRating2>8.8</avgRating2>
      <avgRating3>9.6</avgRating3>
      <avgRatingCombined>9.233334</avgRatingCombined>
      <nrOfRatings>186</nrOfRatings>
      <nrOfMenus>0</nrOfMenus>
      <nrOfPromotions>0</nrOfPromotions>
      <isPremium>false</isPremium>
      <isTopResult>true</isTopResult>
      <isClient>true</isClient>
      <timeslots />
      <hasTablebooker>true</hasTablebooker>
      <tablebookerId>50467330</tablebookerId>
      <paidTablebookerProfile>true</paidTablebookerProfile>
      <bibGourmand>false</bibGourmand>
      <description>Dans un cadre cosy et chaleureux, la nouvelle direction du Verre y Table accueille tous les adeptes de gastronomie fine et unique.&lt;br&gt;&lt;br&gt;Le Verre y Table est donc idéal pour une agréable soirée en famille, entre amis, collègues ou amoureux. A cet endroit spacieux mais cosy, s'ajoute un service attentionné et efficace.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;a href="https://www.facebook.com/Le.Verre.Y.Table/?fref=ts" target="_blank"&gt;&lt;img src="http://sitebe.resto.com/facebook/facebook_en.jpg" alt="facebook" border="0"&gt;&lt;/a&gt;</description>
      <budgetId>3</budgetId>
      <accommodationIds>
        <accommodationId>76</accommodationId>
        <accommodationId>78</accommodationId>
        <accommodationId>470</accommodationId>
        <accommodationId>450</accommodationId>
      </accommodationIds>
      <budgetPrice>30</budgetPrice>
      <cityUrl>restaurant/bruxelles/1200-woluwe-saint-lambert</cityUrl>
    </business>
  </businesses>
</searchResponse>

Querying with xmllint

You have jq for JSON, and XPath for XML. I wanted to make sure the business with id 24604 was in there. The XPath query is quite easy to construct, and you can try it out on a small snippet with Free Formatter XPATH tester.

I came up with /searchResponse/businesses/business[businessId='$EXTERNAL_ID'] . To drill it down:

  1. Look into the searchResponse element, then in the businesses child element, and then its business elements;
  2. Take the business child that has as businessId element with value 24604 (the value of the EXTERNAL_ID environment variable)

xmllint support XPath querying, and chances are that you already have it if you have a linux like operating system. It’s quite simple, just type in your terminal:

xmllint --xpath "/searchResponse/businesses/business[businessId='3627']" en.xml

and you get:

<business><businessId>3627</businessId><businessTypeId>1</businessTypeId><businessName>BRASSERIE MEEUS</businessName><street>Rue du Luxembourg</street><boxNumber>17</boxNumber><zip>1000</zip><city>Brussels (center)</city><region>Brussels</region><phone>02.502.31.93</phone><hasOldReservationForm>false</hasOldReservationForm><cuisines><cuisine>Brasserie</cuisine></cuisines><url>http://en.resto.be/restaurant/brussels/1000-brussels-center/3627-brasserie-meeus/</url><neighbourhoods><neighbourhood>186|Quartier européen - Shuman</neighbourhood></neighbourhoods><lon>4.367756</lon><lat>50.840007</lat><avgRatingCombined>0.0</avgRatingCombined><nrOfRatings>0</nrOfRatings><nrOfMenus>0</nrOfMenus><nrOfPromotions>0</nrOfPromotions><isPremium>false</isPremium><isTopResult>false</isTopResult><isClient>false</isClient><timeslots/><hasTablebooker>false</hasTablebooker><paidTablebookerProfile>false</paidTablebookerProfile><bibGourmand>false</bibGourmand><budgetId>5</budgetId><accommodationIds><accommodationId>77</accommodationId><accommodationId>78</accommodationId><accommodationId>81</accommodationId><accommodationId>450</accommodationId></accommodationIds><neighbourhoodIds><neighbourhoodId>186</neighbourhoodId></neighbourhoodIds><budgetPrice>50</budgetPrice><cityUrl>restaurant/bruxelles/1000-brussels-center</cityUrl></business>

So we get what we want, but it doesn’t look that great.

Formatting with xmllint

Luckily xmllint supports formatting as well. So piping in the output of the previous command should work. And it does, but you need to provide an additional dash at the end to get it accepted. The complete command is now:

xmllint --xpath "/searchResponse/businesses/business[businessId='3627']" en.xml|xmllint --format -

and the output is:

<?xml version="1.0"?>
<business>
  <businessId>3627</businessId>
  <businessTypeId>1</businessTypeId>
  <businessName>BRASSERIE MEEUS</businessName>
  <street>Rue du Luxembourg</street>
  <boxNumber>17</boxNumber>
  <zip>1000</zip>
  <city>Brussels (center)</city>
  <region>Brussels</region>
  <phone>02.502.31.93</phone>
  <hasOldReservationForm>false</hasOldReservationForm>
  <cuisines>
    <cuisine>Brasserie</cuisine>
  </cuisines>
  <url>http://en.resto.be/restaurant/brussels/1000-brussels-center/3627-brasserie-meeus/</url>
  <neighbourhoods>
    <neighbourhood>186|Quartier europ&#xE9;en - Shuman</neighbourhood>
  </neighbourhoods>
  <lon>4.367756</lon>
  <lat>50.840007</lat>
  <avgRatingCombined>0.0</avgRatingCombined>
  <nrOfRatings>0</nrOfRatings>
  <nrOfMenus>0</nrOfMenus>
  <nrOfPromotions>0</nrOfPromotions>
  <isPremium>false</isPremium>
  <isTopResult>false</isTopResult>
  <isClient>false</isClient>
  <timeslots/>
  <hasTablebooker>false</hasTablebooker>
  <paidTablebookerProfile>false</paidTablebookerProfile>
  <bibGourmand>false</bibGourmand>
  <budgetId>5</budgetId>
  <accommodationIds>
    <accommodationId>77</accommodationId>
    <accommodationId>78</accommodationId>
    <accommodationId>81</accommodationId>
    <accommodationId>450</accommodationId>
  </accommodationIds>
  <neighbourhoodIds>
    <neighbourhoodId>186</neighbourhoodId>
  </neighbourhoodIds>
  <budgetPrice>50</budgetPrice>
  <cityUrl>restaurant/bruxelles/1000-brussels-center</cityUrl>
</business>

It is nitpicking, but I love the syntax highlighting feature of HTTPie, so what about XML syntax highlighting?

Highlighting with highlight

Highlight supports a lot of languages and you can easily install it with brew:

brew install highlight

I just want to print the syntax highlighting in the terminal, the I set formatting to ansi , so the complete command (XPATH querying, formatting and highlighting combined) then turns into:

xmllint --xpath "/searchResponse/businesses/business[businessId='$EXTERNAL_ID']" en.xml|xmllint --format - | highlight --out-format=ansi --syntax=xml

and the output is:

<?xml version="1.0"?>
<business>
  <businessId>3627</businessId>
  <businessTypeId>1</businessTypeId>
  <businessName>BRASSERIE MEEUS</businessName>
  <street>Rue du Luxembourg</street>
  <boxNumber>17</boxNumber>
  <zip>1000</zip>
  <city>Brussels (center)</city>
  <region>Brussels</region>
  <phone>02.502.31.93</phone>
  <hasOldReservationForm>false</hasOldReservationForm>
  <cuisines>
    <cuisine>Brasserie</cuisine>
  </cuisines>
  <url>http://en.resto.be/restaurant/brussels/1000-brussels-center/3627-brasserie-meeus/</url>
  <neighbourhoods>
    <neighbourhood>186|Quartier europ&#xE9;en - Shuman</neighbourhood>
  </neighbourhoods>
  <lon>4.367756</lon>
  <lat>50.840007</lat>
  <avgRatingCombined>0.0</avgRatingCombined>
  <nrOfRatings>0</nrOfRatings>
  <nrOfMenus>0</nrOfMenus>
  <nrOfPromotions>0</nrOfPromotions>
  <isPremium>false</isPremium>
  <isTopResult>false</isTopResult>
  <isClient>false</isClient>
  <timeslots/>
  <hasTablebooker>false</hasTablebooker>
  <paidTablebookerProfile>false</paidTablebookerProfile>
  <bibGourmand>false</bibGourmand>
  <budgetId>5</budgetId>
  <accommodationIds>
    <accommodationId>77</accommodationId>
    <accommodationId>78</accommodationId>
    <accommodationId>81</accommodationId>
    <accommodationId>450</accommodationId>
  </accommodationIds>
  <neighbourhoodIds>
    <neighbourhoodId>186</neighbourhoodId>
  </neighbourhoodIds>
  <budgetPrice>50</budgetPrice>
  <cityUrl>restaurant/bruxelles/1000-brussels-center</cityUrl>
</business>

As additional proof - a screenshot of my terminal:

image-20230508183513864