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&context=default&width=740&height=500&hash=4f019870b9acc21d78ab578f4dd731f8</picture>
<picture>https://images.resto.com/view?iid=resto.be:576a793b-cfc6-43e9-be3d-aaa576e8e098&context=default&width=740&height=500&hash=4f019870b9acc21d78ab578f4dd731f8</picture>
<picture>https://images.resto.com/view?iid=resto.be:d078a2cf-6acb-4768-9076-e7066cefb841&context=default&width=740&height=500&hash=4f019870b9acc21d78ab578f4dd731f8</picture>
<picture>https://images.resto.com/view?iid=resto.be:2fbb9cdc-fee5-4425-9d67-937da7fba26f&context=default&width=740&height=500&hash=4f019870b9acc21d78ab578f4dd731f8</picture>
<picture>https://images.resto.com/view?iid=resto.be:4f3ab9f2-0b3b-4850-a214-a8eb95857d36&context=default&width=740&height=500&hash=4f019870b9acc21d78ab578f4dd731f8</picture>
<picture>https://images.resto.com/view?iid=resto.be:1d414d7b-55c0-4d00-bc2e-aa5bfbe29769&context=default&width=740&height=500&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.<br><br>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.<br><br><br><a href="https://www.facebook.com/Le.Verre.Y.Table/?fref=ts" target="_blank"><img src="http://sitebe.resto.com/facebook/facebook_en.jpg" alt="facebook" border="0"></a></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:
- Look into the
searchResponse
element, then in thebusinesses
child element, and then itsbusiness
elements; - Take the
business
child that has asbusinessId
element with value 24604 (the value of theEXTERNAL_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é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é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: