0

I am trying to add attribute under an existing attribute in an XML file. I have been using xmlstarted for edit but never used it for update. Would be helpful is someone tell how to add a new attr and an element under that new attrb -

Here is the snippet of my xml file -

Here is what i usually does to edit the sub elements with the hel;p of xmlstarlet-

xmlstarlet edit  --inplace -u "/allocations/queue[@name='root']/queue[@name='paas_api_q1']/queue[@name='child1_sq1']/minResources" --value "$value" myfile.xml

Before -

<?xml version="1.0"?>
<allocations>
  <queue name="root">
    <aclSubmitApps> bddbagrp,mapr</aclSubmitApps>
    <aclAdministerApps> bddbagrp,root,mapr,trmte_id</aclAdministerApps>
    <schedulingPolicy>drf</schedulingPolicy>
    <defaultMinSharePreemptionTimeout>60</defaultMinSharePreemptionTimeout>
    <fairSharePreemptionTimeout>60</fairSharePreemptionTimeout>
    <queue name="paas_api_q1">
      <minResources>90000 mb,15 vcores,2 disks</minResources>
      <maxResources>135000 mb,22 vcores,3 disks</maxResources>
      <queue name="child1_sq1">
        <minResources>66000 mb,11 vcores,2 disks</minResources>
        <maxResources>264000 mb,44 vcores,8 disks</maxResources>
        <aclSubmitApps> paas_opsauto_admin_unix</aclSubmitApps>
        <aclAdministerApps> paas_opsauto_admin_unix</aclAdministerApps>
        <label>allnodes||balanced</label>
      </queue>
    </queue>
  </queue>
  <queuePlacementPolicy>
    <rule create="false" name="specified"/>
    <rule name="reject"/>
  </queuePlacementPolicy>
</allocations>

After it should look like this.. Here i am adding a new attribute under an existing attr - pass_api_q1 .

<!-- language: lang-xml -->
<?xml version="1.0"?>
<allocations>
  <queue name="root">
    <aclSubmitApps> bddbagrp,mapr</aclSubmitApps>
    <aclAdministerApps> bddbagrp,root,mapr,trmte_id</aclAdministerApps>
    <schedulingPolicy>drf</schedulingPolicy>
    <defaultMinSharePreemptionTimeout>60</defaultMinSharePreemptionTimeout>
    <fairSharePreemptionTimeout>60</fairSharePreemptionTimeout>
    <queue name="paas_api_q1">
      <minResources>90000 mb,15 vcores,2 disks</minResources>
      <maxResources>135000 mb,22 vcores,3 disks</maxResources>
      <queue name="child1_sq1">
        <minResources>66000 mb,11 vcores,2 disks</minResources>
        <maxResources>264000 mb,44 vcores,8 disks</maxResources>
        <aclSubmitApps> paas_opsauto_admin_unix</aclSubmitApps>
        <aclAdministerApps> paas_opsauto_admin_unix</aclAdministerApps>
        <label>allnodes||balanced</label>
      </queue>
      <queue name="child2_sq1">
        <minResources>66000 mb,11 vcores,2 disks</minResources>
        <maxResources>132000 mb,22 vcores,4 disks</maxResources>
        <aclSubmitApps> paas_opsauto_admin_unix</aclSubmitApps>
        <aclAdministerApps> paas_opsauto_admin_unix</aclAdministerApps>
        <label>allnodes||balanced</label>
      </queue>
    </queue>
  </queue>
  <queuePlacementPolicy>
    <rule create="false" name="specified"/>
    <rule name="reject"/>
  </queuePlacementPolicy>
</allocations>

Any help/direction would be appreciated.

Daniel Haley
  • 51,389
  • 6
  • 69
  • 95

2 Answers2

2

You could use a bunch of -i/-a/-s to add the new elements (see here), but I think it would be easier to use XSLT with tr. You could put your elements in another file and pass it in as a parameter.

Example...

XML fragment to add (fragment.xml)

<?xml version="1.0"?>
<queue name="child2_sq1">
    <minResources>66000 mb,11 vcores,2 disks</minResources>
    <maxResources>132000 mb,22 vcores,4 disks</maxResources>
    <aclSubmitApps> paas_opsauto_admin_unix</aclSubmitApps>
    <aclAdministerApps> paas_opsauto_admin_unix</aclAdministerApps>
    <label>allnodes||balanced</label>
</queue>

XSLT (test.xsl)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:param name="frag"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!--If you wanted to make this stylesheet reusable, you could pass a new
  value in for the "match" attribute by selecting this template by its
  "name" value (@name='target').-->
  <xsl:template match="queue[@name='paas_api_q1']" name="target">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
      <xsl:copy-of select="$frag"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

xmlstarlet command line

xmlstarlet tr test.xsl -p frag='document("fragment.xml")/*' input.xml

Output

<?xml version="1.0"?>
<allocations>
  <queue name="root">
    <aclSubmitApps> bddbagrp,mapr</aclSubmitApps>
    <aclAdministerApps> bddbagrp,root,mapr,trmte_id</aclAdministerApps>
    <schedulingPolicy>drf</schedulingPolicy>
    <defaultMinSharePreemptionTimeout>60</defaultMinSharePreemptionTimeout>
    <fairSharePreemptionTimeout>60</fairSharePreemptionTimeout>
    <queue name="paas_api_q1">
      <minResources>90000 mb,15 vcores,2 disks</minResources>
      <maxResources>135000 mb,22 vcores,3 disks</maxResources>
      <queue name="child1_sq1">
        <minResources>66000 mb,11 vcores,2 disks</minResources>
        <maxResources>264000 mb,44 vcores,8 disks</maxResources>
        <aclSubmitApps> paas_opsauto_admin_unix</aclSubmitApps>
        <aclAdministerApps> paas_opsauto_admin_unix</aclAdministerApps>
        <label>allnodes||balanced</label>
      </queue>
      <queue name="child2_sq1">
        <minResources>66000 mb,11 vcores,2 disks</minResources>
        <maxResources>132000 mb,22 vcores,4 disks</maxResources>
        <aclSubmitApps> paas_opsauto_admin_unix</aclSubmitApps>
        <aclAdministerApps> paas_opsauto_admin_unix</aclAdministerApps>
        <label>allnodes||balanced</label>
      </queue>
    </queue>
  </queue>
  <queuePlacementPolicy>
    <rule create="false" name="specified"/>
    <rule name="reject"/>
  </queuePlacementPolicy>
</allocations>
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
0

As Daniel Haley points out, using xmlstarlet exclusively can be tedious because it doesn't have a copy/paste functionality which would have made the task much simpler. As an exercise, I tried the code below which seems to work:

xmlstarlet ed  
     -a "//queue/queue[@name='paas_api_q1']/queue"
     -t elem -n queue -v "$(xmlstarlet sel  -t -m //queue/queue[@name]/*/* -c . myfile.xml)"
     -i "//queue[@name='paas_api_q1']//queue[not(@name)]" --type attr --name "name" -v "child2_sq1" \
   myfile.xml | xmlstarlet unesc

Basically, it creates a new node in the appropriate place, creates a copy of the original node (using a variable), inserts the copy into the value of new node, adding an attribute and a value to the new node and, finally, unescaping the tags from the copy of the original node.

As I said, just an exercise...

While I haven't tried it, I would guess that this task could also be performed using, instead of xmlstarlet, xidel and its support for xquery.

Jack Fleeting
  • 24,385
  • 6
  • 23
  • 45