0

I can't seem to find exactly how to do this. I have this XML file

<session>
  <translations>
   <translation>
      <inside>198.18.133.1</inside>
      <name>adfs.domain1.com</name>
    </translation>
    <translation>
      <inside>198.18.135.60</inside>
      <name>hds.domain2.com</name>
    </translation>
   </translations>
 </session>

and I want to extract the domain from a particular name node based on the string found in the inside node. As you can see I have multiple name and inside nodes. With the following Bash file I can extract the first instance of name

#!/bin/bash
domain="$(echo "cat /session/translations/translation/name/text()" | xmllint --nocdata --shell session.xml | sed '1d;$d')"
domain="${domain:5}"
printf '%s\n' "Domain is: $domain"

This will give me domain1.com.

Sometimes I could have more translations or less and they aren't always in the same order. So I need a way to pull the name IF the inside node matches 198.18.133.1 or pull the name IF the inside node matches 198.18.135.60, etc.

halfer
  • 19,824
  • 17
  • 99
  • 186
Jason Murray
  • 147
  • 2
  • 4
  • 13
  • Have you considered XSLT? You can do it with Java SE and Saxonica HE. – Jeff Holt Sep 10 '18 at 20:31
  • @jeff6times7, there's no need for a JVM -- every modern Linux distro (and MacOS) ships with `xsltproc`. Maybe if you needed something newer than 1.0, but this query doesn't need a modern version of the standard. – Charles Duffy Sep 10 '18 at 20:31
  • @CharlesDuffy Are you sure Jason's using linux? – Jeff Holt Sep 10 '18 at 20:32
  • Even if it's not Linux, it's something that ships with bash, and that narrows it down enough. xsltproc is pretty much ubiquitous these days -- it's widely used for compiling documentation to HTML at build time. If you have a recent (last-15-years) UNIX-family system with a compiler/development toolchain, it's more more common to not have a JVM than to not have xsltproc. – Charles Duffy Sep 10 '18 at 20:33
  • @CharlesDuffy Then it's hopefully an easy decision based upon the long-term requirements. If the user's *not* headed towards a complicated transformation, then XSLT is overkill. – Jeff Holt Sep 10 '18 at 20:38
  • 2
    Incidentally, XMLStarlet compiles command-line queries down to XSLT -- one can (in the relevant usage modes) ask it to output an XSLT template which will perform the same operation given on the command line. – Charles Duffy Sep 10 '18 at 20:39
  • BTW, I'd suggest `domain=${domain#*.}` to strip off everything up to and including the first period -- that way it'll work the same way if that prefix's length isn't exactly four characters. – Charles Duffy Sep 10 '18 at 20:51

1 Answers1

1

Your current XPath expression, of:

/session/translations/translation/name/text()

...can easily be changed to:

/session/translations/translation[inside="198.18.133.1"]/name/text()

...to perform the desired filtering.


Doing this with XMLStarlet, rather than XMLLint, might look more like:

xmlstarlet sel -t -m '/session/translations/translation[inside="198.18.133.1"]/name' -v . -n

If adding the -C argument to the sel subcommand, it will emit the XSLT template which it's evaluating under-the-hood, which you could run anywhere with xsltproc installed, including systems without XMLStarlet:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0" extension-element-prefixes="exslt">
  <xsl:output omit-xml-declaration="yes" indent="no"/>
  <xsl:template match="/">
    <xsl:for-each select="/session/translation/translation[inside=&quot;198.18.133.1&quot;]/name">
      <xsl:call-template name="value-of-template">
        <xsl:with-param name="select" select="."/>
      </xsl:call-template>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each>
  </xsl:template>
  <xsl:template name="value-of-template">
    <xsl:param name="select"/>
    <xsl:value-of select="$select"/>
    <xsl:for-each select="exslt:node-set($select)[position()&gt;1]">
      <xsl:value-of select="'&#10;'"/>
      <xsl:value-of select="."/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thanks, I tried to change the XPath expression and it gave me this error. "XPath error : Invalid predicate /session/translations/translation[inside=198.18.133.1]/name/text() ^ xmlXPathEval: evaluation failed /session/translations/translation[inside=198.18.133.1]/name/text(): no such node – Jason Murray Sep 10 '18 at 20:44
  • the ^ was under the period after 18 if that helps – Jason Murray Sep 10 '18 at 20:45
  • 1
    See how it's just `inside=198.18.133.1`, not `inside="198.18.133.1"`, in the error? You don't want to let the shell eat your quotes. – Charles Duffy Sep 10 '18 at 20:45
  • ...there's a reason I used single quotes on the outside in this answer -- so the double-quotes inside them would be preserved. – Charles Duffy Sep 10 '18 at 20:47
  • Sweet yes that was it! Thanks so much for the quick response! Works like a champ! – Jason Murray Sep 10 '18 at 20:52