Extracting and modifying XML with deep structure from Linux command line - xmlstarlet

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>

Related

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

xmlstarlet delete element if string found tag

I need to delete entire cellNote element below if epm_default_cloud_admin is found in any tag, tried different things with xmlstarlet and it won;t happen, can you help?
<cell>
<cellNote>
<DIM1>Actual</DIM1>
<author>epm_default_cloud_admin</author>
<modified>2016-11-16 08:28:38.0</modified>
<title></title>
</cellNote>
<cellNote>
<DIM1>Actual</DIM1>
<contents>Variance in meals is due to Annual Sales Conference</contents>
<author>Frank</author>
<modified>2016-12-23 20:10:13.0</modified>
<title></title>
</cellNote>
<cell>
few things I tried below:
xmlstarlet ed -a "/cell/cellNote" --type elem -n string -v "epm_default_cloud_admin"
xmlstarlet ed -d '/cell/cellNote/author[. = 'epm_default_cloud_admin']'
At first, to deal with a valid xml - ensure that cell tag has both opening and closing tag (your input contain both as opening <cell>).
xmlstarlet solution:
xmlstarlet ed -d "//cellNote[*[contains(text(),'epm_default_cloud_admin')]]" input.xml
The output:
<?xml version="1.0"?>
<cell>
<cellNote>
<DIM1>Actual</DIM1>
<contents>Variance in meals is due to Annual Sales Conference</contents>
<author>Frank</author>
<modified>2016-12-23 20:10:13.0</modified>
<title/>
</cellNote>
</cell>

Extract values from XML using 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

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

Resources