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
This commit is contained in:
rwinand 2019-01-08 15:38:51 +01:00 committed by Dag Wieers
parent 1793cad07b
commit 2dbade4adc
6 changed files with 160 additions and 4 deletions

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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