Home > Software design >  Working with XML elements that have both attributes and content
Working with XML elements that have both attributes and content

Time:06-09

I have a file confluence.cfg.xml with the following structure

<?xml version='1.0' encoding='UTF-8'?>
<confluence-configuration>
  <setupStep>setupstart</setupStep>
  <setupType>custom</setupType>
  <buildNumber>0</buildNumber>
  <properties>
    <property name="confluence.database.choice">postgresql</property>
    <property name="confluence.database.connection.type">database-type-standard</property>
    <property name="webwork.multipart.saveDir">${localHome}/temp</property>
    <property name="attachments.dir">${confluenceHome}/attachments</property>
  </properties>
</confluence-configuration>

I want to add and remove multiple <property> elements. However I am not getting the syntax right. Here I am trying to remove two of the elements (the code runs but removes every <property> elements rather than just the two I want):

- name: "Remove elements"
  xml:
    path: "{{ atl_product_home }}/confluence.cfg.xml"
    xpath: /confluence-configuration/properties/property
    attribute: name
    value: 
      - confluence.database.choice
      - confluence.database.connection.type
    state: absent

And here I am trying to add a single, say <property name="foo">bar</property> element.

- name: "Add elements"
  xml:
    path: "{{ atl_product_home }}/confluence.cfg.xml"
    xpath: /confluence-configuration/properties
    add_children:
      - property: bar
      - property:
          name: foo

The above gives me two elements (<property>bar</property> and <property name="foo"/>), but I can't seem to specify both at the same time. And once I can specify an element properly, I'd like to be able to add a list of them.

CodePudding user response:

In order to remove nodes, you need to to have the right XPath, the value won't be taken into consideration, this parameter is meant for the state: present.

This said, XPath supports selecting a nodes via attributes and does also support or expressions, so, your task to remove nodes can be written as:

- name: Remove elements
  xml:
    path: "{{ atl_product_home }}/confluence.cfg.xml"
    xpath: >-
      /confluence-configuration
      /properties
      /property[
        @name='confluence.database.choice'
        or @name='confluence.database.connection.type'
      ]
    state: absent

Regarding the addition of the node, once again, your attribute can be part of your XPath, if this XPath does not exists, then, Ansible will create it and assign the needed value:

- name: Add <property name="foo">bar</property>
  xml:
    path: "{{ atl_product_home }}/confluence.cfg.xml"
    xpath: /confluence-configuration/properties/property[@name='foo']
    value: bar

Given the playbook

- hosts: localhost
  gather_facts: no
  vars:
    atl_product_home: .
    xml: |-
      <?xml version='1.0' encoding='UTF-8'?>
      <confluence-configuration>
        <setupStep>setupstart</setupStep>
        <setupType>custom</setupType>
        <buildNumber>0</buildNumber>
        <properties>
          <property name="confluence.database.choice">
            postgresql
          </property>
          <property name="confluence.database.connection.type">
            database-type-standard
          </property>
          <property name="webwork.multipart.saveDir">
            ${localHome}/temp
          </property>
          <property name="attachments.dir">
            ${confluenceHome}/attachments
          </property>
        </properties>
      </confluence-configuration>

  tasks:
    - name: Create configuration file
      copy:
        content: "{{ xml }}"
        dest: "{{ atl_product_home }}/confluence.cfg.xml"

    - name: Remove elements
      xml:
        path: "{{ atl_product_home }}/confluence.cfg.xml"
        xpath: >-
          /confluence-configuration
          /properties
          /property[
            @name='confluence.database.choice'
            or @name='confluence.database.connection.type'
          ]
        state: absent

    - name: Add <property name="foo">bar</property>
      xml:
        path: "{{ atl_product_home }}/confluence.cfg.xml"
        xpath: /confluence-configuration/properties/property[@name='foo']
        value: bar

    - debug:
        var: lookup('file', atl_product_home ~ '/confluence.cfg.xml')

This yields:

PLAY [localhost] **************************************************************

TASK [Create configuration file] **********************************************
changed: [localhost]

TASK [Remove elements] ********************************************************
changed: [localhost]

TASK [Add <property name="foo">bar</property>] ********************************
changed: [localhost]

TASK [debug] ******************************************************************
ok: [localhost] => 
  lookup('file', atl_product_home ~ '/confluence.cfg.xml'): |-
    <?xml version='1.0' encoding='UTF-8'?>
    <confluence-configuration>
      <setupStep>setupstart</setupStep>
      <setupType>custom</setupType>
      <buildNumber>0</buildNumber>
      <properties>
        <property name="webwork.multipart.saveDir">
          ${localHome}/temp
        </property>
        <property name="attachments.dir">
          ${confluenceHome}/attachments
        </property>
      <property name="foo">bar</property></properties>
    </confluence-configuration>

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=3  

CodePudding user response:

Thank you, β.εηοιτ.βε!

The only thing missing was the ability to add multiple elements at once. Taking your example, the solution for me is:

- name: "Remove elements from {{ atl_product_home }}/confluence.cfg.xml"
  xml:
    path: "{{ atl_product_home }}/confluence.cfg.xml"
    xpath: >-
      /confluence-configuration
      /properties
      /property[
        @name='confluence.database.choice'
        or @name='confluence.database.connection.type'
      ]
    state: absent
- name: "Add elements to {{ atl_product_home }}/confluence.cfg.xml"
  xml:
    path: "{{ atl_product_home }}/confluence.cfg.xml"
    xpath: "/confluence-configuration/properties/property[@name='{{ item.attribute }}']"
    value: "{{ item.value }}"
    pretty_print: true
  loop:
    - { attribute: 'access.mode', value: 'READ_WRITE' }
    - { attribute: 'admin.ui.allow.daily.backup.custom.location', value: 'false' }
  • Related