Introduce 'insertbefore' and 'insertafter' to specify the position (#44811)

* Introduce 'insertbefore' and 'insertafter' to specify the position children have to be inserted.

* Added version_added to new options

* Xpath in example needs single quotes

* Added integration tests for insertafter and insertbefore

* Changed version_added to 2.8
pull/50353/head
rwinand 6 years ago committed by Dag Wieers
parent 1793cad07b
commit 2dbade4adc

@ -122,6 +122,26 @@ options:
type: bool
default: no
version_added: '2.7'
insertbefore:
description:
- Add additional child-element(s) before the first selected element for a given C(xpath).
- Child elements must be given in a list and each item may be either a string
(eg. C(children=ansible) to add an empty C(<ansible/>) child element),
or a hash where the key is an element name and the value is the element value.
- This parameter requires C(xpath) to be set.
type: bool
default: no
version_added: '2.8'
insertafter:
description:
- Add additional child-element(s) after the last selected element for a given C(xpath).
- Child elements must be given in a list and each item may be either a string
(eg. C(children=ansible) to add an empty C(<ansible/>) child element),
or a hash where the key is an element name and the value is the element value.
- This parameter requires C(xpath) to be set.
type: bool
default: no
version_added: '2.8'
requirements:
- lxml >= 2.3.0
notes:
@ -202,6 +222,16 @@ EXAMPLES = r'''
- beer: Old Motor Oil
- beer: Old Curmudgeon
- name: Add several more beers to the 'beers' element and add them before the 'Rochefort 10' element
xml:
path: /foo/bar.xml
xpath: '/business/beers/beer[text()=\"Rochefort 10\"]'
insertbefore: yes
add_children:
- beer: Old Rasputin
- beer: Old Motor Oil
- beer: Old Curmudgeon
# NOTE: The 'state' defaults to 'present' and 'value' defaults to 'null' for elements
- name: Add a 'validxhtml' element to the 'website' element
xml:
@ -446,16 +476,35 @@ def set_target_children(module, tree, xpath, namespaces, children, in_type):
finish(module, tree, xpath, namespaces, changed=changed)
def add_target_children(module, tree, xpath, namespaces, children, in_type):
def add_target_children(module, tree, xpath, namespaces, children, in_type, insertbefore, insertafter):
if is_node(tree, xpath, namespaces):
new_kids = children_to_nodes(module, children, in_type)
for node in tree.xpath(xpath, namespaces=namespaces):
node.extend(new_kids)
if insertbefore or insertafter:
insert_target_children(tree, xpath, namespaces, new_kids, insertbefore, insertafter)
else:
for node in tree.xpath(xpath, namespaces=namespaces):
node.extend(new_kids)
finish(module, tree, xpath, namespaces, changed=True)
else:
finish(module, tree, xpath, namespaces)
def insert_target_children(tree, xpath, namespaces, children, insertbefore, insertafter):
"""
Insert the given children before or after the given xpath. If insertbefore is True, it is inserted before the
first xpath hit, with insertafter, it is inserted after the last xpath hit.
"""
insert_target = tree.xpath(xpath, namespaces=namespaces)
loc_index = 0 if insertbefore else -1
index_in_parent = insert_target[loc_index].getparent().index(insert_target[loc_index])
parent = insert_target[0].getparent()
if insertafter:
index_in_parent += 1
for child in children:
parent.insert(index_in_parent, child)
index_in_parent += 1
def _extract_xpstr(g):
return g[1:-1]
@ -776,6 +825,8 @@ def main():
input_type=dict(type='str', default='yaml', choices=['xml', 'yaml']),
backup=dict(type='bool', default=False),
strip_cdata_tags=dict(type='bool', default=False),
insertbefore=dict(type='bool', default=False),
insertafter=dict(type='bool', default=False),
),
supports_check_mode=True,
# TODO: Implement this as soon as #28662 (required_by functionality) is merged
@ -790,6 +841,8 @@ def main():
['content', 'text', ['xpath']],
['count', True, ['xpath']],
['print_match', True, ['xpath']],
['insertbefore', True, ['xpath']],
['insertafter', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
@ -798,6 +851,7 @@ def main():
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match', 'set_children', 'value'],
['path', 'xmlstring'],
['insertbefore', 'insertafter'],
],
)
@ -817,6 +871,8 @@ def main():
count = module.params['count']
backup = module.params['backup']
strip_cdata_tags = module.params['strip_cdata_tags']
insertbefore = module.params['insertbefore']
insertafter = module.params['insertafter']
# Check if we have lxml 2.3.0 or newer installed
if not HAS_LXML:
@ -881,7 +937,7 @@ def main():
# add_children set?
if add_children:
add_target_children(module, doc, xpath, namespaces, add_children, input_type)
add_target_children(module, doc, xpath, namespaces, add_children, input_type, insertbefore, insertafter)
# No?: Carry on

@ -0,0 +1,17 @@
<?xml version='1.0' encoding='UTF-8'?>
<business type="bar">
<name>Tasty Beverage Co.</name>
<beers>
<beer>Rochefort 10</beer>
<beer>St. Bernardus Abbot 12</beer>
<beer>Old Rasputin</beer>
<beer>Old Motor Oil</beer>
<beer>Old Curmudgeon</beer>
<beer>Schlitz</beer>
</beers>
<rating subjective="true">10</rating>
<website>
<mobilefriendly/>
<address>http://tastybeverageco.com</address>
</website>
</business>

@ -0,0 +1,17 @@
<?xml version='1.0' encoding='UTF-8'?>
<business type="bar">
<name>Tasty Beverage Co.</name>
<beers>
<beer>Rochefort 10</beer>
<beer>Old Rasputin</beer>
<beer>Old Motor Oil</beer>
<beer>Old Curmudgeon</beer>
<beer>St. Bernardus Abbot 12</beer>
<beer>Schlitz</beer>
</beers>
<rating subjective="true">10</rating>
<website>
<mobilefriendly/>
<address>http://tastybeverageco.com</address>
</website>
</business>

@ -37,6 +37,8 @@
- include_tasks: test-add-children-elements.yml
- include_tasks: test-add-children-from-groupvars.yml
- include_tasks: test-add-children-insertafter.yml
- include_tasks: test-add-children-insertbefore.yml
- include_tasks: test-add-children-with-attributes.yml
- include_tasks: test-add-element-implicitly.yml
- include_tasks: test-count.yml

@ -0,0 +1,32 @@
---
- name: Setup test fixture
copy:
src: fixtures/ansible-xml-beers.xml
dest: /tmp/ansible-xml-beers.xml
- name: Add child element
xml:
path: /tmp/ansible-xml-beers.xml
xpath: '/business/beers/beer[text()="St. Bernardus Abbot 12"]'
insertafter: yes
add_children:
- beer: Old Rasputin
- beer: Old Motor Oil
- beer: Old Curmudgeon
pretty_print: yes
register: add_children_insertafter
- name: Compare to expected result
copy:
src: results/test-add-children-insertafter.xml
dest: /tmp/ansible-xml-beers.xml
check_mode: yes
diff: yes
register: comparison
- name: Test expected result
assert:
that:
- add_children_insertafter.changed == true
- comparison.changed == false # identical

@ -0,0 +1,32 @@
---
- name: Setup test fixture
copy:
src: fixtures/ansible-xml-beers.xml
dest: /tmp/ansible-xml-beers.xml
- name: Add child element
xml:
path: /tmp/ansible-xml-beers.xml
xpath: '/business/beers/beer[text()="St. Bernardus Abbot 12"]'
insertbefore: yes
add_children:
- beer: Old Rasputin
- beer: Old Motor Oil
- beer: Old Curmudgeon
pretty_print: yes
register: add_children_insertbefore
- name: Compare to expected result
copy:
src: results/test-add-children-insertbefore.xml
dest: /tmp/ansible-xml-beers.xml
check_mode: yes
diff: yes
register: comparison
- name: Test expected result
assert:
that:
- add_children_insertbefore.changed == true
- comparison.changed == false # identical
Loading…
Cancel
Save