Extract values from XML using xmlstarlet - xmlstarlet

How can I extract using xmlstarlet the local port from this xml example:
<?xml version="1.0"?>
<opmn xmlns="http://www.oracle.com/ias-instance">
<notification-server>
<port local="6101" remote="6200" request="6003"/>
<log-file path="$ORACLE_HOME\opmn\logs\ons.log" level="4" rotation-size="1500000"/>
<ssl enabled="true" wallet-file="$ORACLE_HOME\opmn\conf\ssl.wlt\default"/>
</notification-server>
</opmn>

xml sel -N ias=http://www.oracle.com/ias-instance -t -v //ias:port/#local example.xml
Or more precise
xml sel -N ias=http://www.oracle.com/ias-instance -t -v /ias:opmn/ias:notification-server/ias:port/#local example.xml

Related

Extracting and modifying XML with deep structure from Linux command line

I would like to select and change a value in an XML file. I'm trying to use xmlstarlet for this.
I have this file
<?xml version='1.0' encoding='UTF-8'?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd">
<House>
<Id>
<Number>1</Number>
</Id>
</House>
<Car>
<Id>
<Number>2</Number>
</Id>
</Car>
</DeviceDescription>
My problem is the xmlns= field which xmlstarlet is picky about. Without this field I can use
xmlstarlet sel -t -v '/Description/House/Id/Number' /tmp/x.xml
I found that I can use a default namespace like this, but that returns both Id's
xmlstarlet sel -t -m "//_:Id" -v '_:Number' /tmp/x.xml
How do I specify a full path?
To only match the House id, add it to the -m argument:
xml sel -t -m '//_:House/_:Id' -v '_:Number'
If you want to use the namespace, specify it with -N, e.g.:
xml sel -N ns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" \
-t -v 'ns:DeviceDescription/ns:House/ns:Id/ns:Number'
So to update the value:
xml ed -N ns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" \
-u 'ns:DeviceDescription/ns:House/ns:Id/ns:Number' -v 3
Output:
<?xml version="1.0" encoding="UTF-8"?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd">
<House>
<Id>
<Number>3</Number>
</Id>
</House>
<Car>
<Id>
<Number>2</Number>
</Id>
</Car>
</DeviceDescription>

xmlstarlet "does not work" for XMLs with namespaces

I'm using media info, to get some xml information about movie:
mediainfo --Output=XML Krtek\ a\ buldozer-jdvwqZUEbhc.mkv | xmlstarlet format
which output is:
<?xml version="1.0" encoding="UTF-8"?>
<MediaInfo xmlns="https://mediaarea.net/mediainfo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://mediaarea.net/mediainfo https://mediaarea.net/mediainfo/mediainfo_2_0.xsd" version="2.0">
<creatingLibrary version="18.03" url="https://mediaarea.net/MediaInfo">MediaInfoLib</creatingLibrary>
<media ref="Krtek a buldozer-jdvwqZUEbhc.mkv">
<track type="General">
<UniqueID>101120522676894244607292274887483611459</UniqueID>
<VideoCount>1</VideoCount>
<AudioCount>1</AudioCount>
<FileExtension>mkv</FileExtension>
<Format>Matroska</Format>
<Format_Version>4</Format_Version>
<FileSize>60132643</FileSize>
<Duration>374.101</Duration>
<OverallBitRate>1285912</OverallBitRate>
<FrameRate>25.000</FrameRate>
<FrameCount>9352</FrameCount>
<IsStreamable>Yes</IsStreamable>
<File_Modified_Date>UTC 2018-10-15 07:09:29</File_Modified_Date>
<File_Modified_Date_Local>2018-10-15 09:09:29</File_Modified_Date_Local>
<Encoded_Application>Lavf57.71.100</Encoded_Application>
<Encoded_Library>Lavf57.71.100</Encoded_Library>
<extra>
<ErrorDetectionType>Per level 1</ErrorDetectionType>
</extra>
</track>
<track type="Video">
<StreamOrder>0</StreamOrder>
<ID>1</ID>
<UniqueID>1</UniqueID>
<Format>AVC</Format>
<Format_Profile>High</Format_Profile>
<Format_Level>4</Format_Level>
<Format_Settings_CABAC>Yes</Format_Settings_CABAC>
<Format_Settings_RefFrames>3</Format_Settings_RefFrames>
<CodecID>V_MPEG4/ISO/AVC</CodecID>
<Duration>374.080000000</Duration>
<Width>1920</Width>
<Height>1080</Height>
<Stored_Height>1088</Stored_Height>
<Sampled_Width>1920</Sampled_Width>
<Sampled_Height>1080</Sampled_Height>
<PixelAspectRatio>1.000</PixelAspectRatio>
<DisplayAspectRatio>1.778</DisplayAspectRatio>
<FrameRate_Mode>CFR</FrameRate_Mode>
<FrameRate_Mode_Original>VFR</FrameRate_Mode_Original>
<FrameRate>25.000</FrameRate>
<FrameCount>9352</FrameCount>
<ColorSpace>YUV</ColorSpace>
<ChromaSubsampling>4:2:0</ChromaSubsampling>
<BitDepth>8</BitDepth>
<ScanType>Progressive</ScanType>
<Delay>0.000</Delay>
<Default>Yes</Default>
<Forced>No</Forced>
<colour_range>Limited</colour_range>
<colour_description_present>Yes</colour_description_present>
<colour_primaries>BT.709</colour_primaries>
<transfer_characteristics>BT.709</transfer_characteristics>
<matrix_coefficients>BT.709</matrix_coefficients>
</track>
<track type="Audio">
<StreamOrder>1</StreamOrder>
<ID>2</ID>
<UniqueID>2</UniqueID>
<Format>Opus</Format>
<CodecID>A_OPUS</CodecID>
<Duration>374.101000000</Duration>
<Channels>2</Channels>
<ChannelPositions>Front: L R</ChannelPositions>
<SamplingRate>48000</SamplingRate>
<SamplingCount>17956848</SamplingCount>
<BitDepth>32</BitDepth>
<Compression_Mode>Lossy</Compression_Mode>
<Delay>0.000</Delay>
<Delay_Source>Container</Delay_Source>
<Language>en</Language>
<Default>Yes</Default>
<Forced>No</Forced>
</track>
</media>
</MediaInfo>
now say that I want to get all IDs:
... | xmlstarlet sel -t -v "//ID"
and nothing is printed. What? Why? Well it turned out, that if i remove all parameters from tag on second line, the same selection command will work. Now I undestand, that xmlstarlet (probably) works just fine, I'm just missing some magic flag or syntax, so that it can process xmls with defined namespaces. Can someone advice?
You need to use the namespace with -N option, and use it in the query like <namespace>:<xpath>:
... | xmlstarlet sel -N n="https://mediaarea.net/mediainfo" -t -v "//n:ID"
From the help page:
-N <name>=<value>
- predefine namespaces (name without 'xmlns:')
ex: xsql=urn:oracle-xsql
Multiple -N options are allowed.

xmlstarlet replace xml node value

I'm new using xmlstarlet. I'd like to know how to change the value of xml node using xmlstarlet.
I tried something.
xmlstarlet ed --inplace -u '/file_input/uri' 'string("s3://my_source")' template.xml > output.xml
doesn't work.
My expected output as s3://my_source and s3://mydestination. I'd like to change source_path and destination_path node.
<?xml version="1.0" encoding="UTF-8"?>
<job version="2.10.8">
<input>
<deblock_enable>Auto</deblock_enable>
<deblock_strength>0</deblock_strength>
<no_psi>false</no_psi>
<order>1</order>
<timecode_source>zerobased</timecode_source>
<file_input>
<certificate_file nil="true"/>
<password>upass</password>
<uri>source_path</uri>
<username>uname</username>
</file_input>
<file_group_settings>
<rollover_interval nil="true"/>
<destination>
<password>upass</password>
<username>uname</username>
<uri>destination_path</uri>
</destination>
</file_group_settings>
</input>
</job>
Thank you very much
With xmlstarlet:
xmlstarlet edit \
--update "//job/input/file_input/uri" \
--value 's3://my_source' \
--update "//job/input/file_group_settings/destination/uri" \
--value 's3://mydestination' file.xml
If you want to edit file.xml inplace, add option -L.
See: xmlstarlet edit --help

How do I insert an element directly after another element with XMLStarlet?

With this example XML:
<rootnode>
<element-a />
<element-b />
<element-d />
<element-e />
</rootnode>
How do I insert element <element-c/> directly after the element <element-b/> using XMLStarlet?
xml ed -i (or --insert) will put the it before the node, xml ed -a (or --append) will put it after, so you can use either one of:
xml ed -i /rootnode/element-d -t elem -n element-c -v "" file.xml
xml ed -a /rootnode/element-b -t elem -n element-c -v "" file.xml

Selecting elements from xml file with xmlstarlet, can't quite get the syntax right

Here is the xml snippet:
$ cat short.xml
<hostnames>
<hostname name="yahoo.com" type="user"/>
<hostname name="ir1.fp.vip.sp2.yahoo.com" type="PTR"/>
</hostnames>
<hostnames>
<hostname name="Inc.com" type="user"/>
<hostname name="www.inc.com" type="PTR"/>
</hostnames>
The desired output is:
yahoo.com | ir1.fp.vip.sp2.yahoo.com
Inc.com | www.inc.com
What I have so far that is only partially working:
$ xml sel -t -m "//hostname" -v "#name" -n short.xml
I cannot seem to trap the Type= condition properly. TIA.
Two more solutions using xmlstarlet only once (no need to iterate):
xmlstr='
<root>
<hostnames>
<hostname name="yahoo.com" type="user"/>
<hostname name="ir1.fp.vip.sp2.yahoo.com" type="PTR"/>
</hostnames>
<hostnames>
<hostname name="Inc.com" type="user"/>
<hostname name="www.inc.com" type="PTR"/>
</hostnames>
</root>
'
echo "$xmlstr" | xmlstarlet sel -T -t -m "//hostnames" -m "hostname[#type='user']" -v '#name' -o " | " -b -m "hostname[#type='PTR']" -v '#name' -n
echo "$xmlstr" | xmlstarlet sel -T -t -m "//hostname" -i "#type='user'" -v '#name' -o " | " -b -i "#type='PTR'" -v '#name' -n
You need to count the hostnames with xmlstarlet el or something and then iterate with something like:
xmlstarlet sel -t -c "//hostnames[1]" short.xml | xmlstarlet sel -t -m "//hostname/#name" -v . -o ' | '
This would be a lot easier if the XML was better designed. :)
The example given in question is invalid xml.
xmlstarlet --version
1.3.1
compiled against libxml2 2.8.0, linked with 20800
compiled against libxslt 1.1.26, linked with 10126
xmlstarlet val -e short.xml
short.xml:5.1: Extra content at the end of the document
<hostnames>
^
short.xml - invalid
The idea in the answer by mitm is quite nice cure.
$ xml sel -t -m //hostnames -v "concat(hostname[1]/#name,'|',hostname[2]/#name)" -n file.xml
$ xml sel -t -m //hostnames -v hostname[1]/#name -o "|" -v hostname[2]/#name -n file.xml
$ xml sel -t -m //hostname[#type='user'] -v #name -o "|" -v following-sibling::hostname/#name -n file.xml

Resources