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 7 years ago committed by Dag Wieers
parent 1793cad07b
commit 2dbade4adc

@ -122,6 +122,26 @@ options:
type: bool type: bool
default: no default: no
version_added: '2.7' 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: requirements:
- lxml >= 2.3.0 - lxml >= 2.3.0
notes: notes:
@ -202,6 +222,16 @@ EXAMPLES = r'''
- beer: Old Motor Oil - beer: Old Motor Oil
- beer: Old Curmudgeon - 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 # NOTE: The 'state' defaults to 'present' and 'value' defaults to 'null' for elements
- name: Add a 'validxhtml' element to the 'website' element - name: Add a 'validxhtml' element to the 'website' element
xml: xml:
@ -446,9 +476,12 @@ def set_target_children(module, tree, xpath, namespaces, children, in_type):
finish(module, tree, xpath, namespaces, changed=changed) 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): if is_node(tree, xpath, namespaces):
new_kids = children_to_nodes(module, children, in_type) new_kids = children_to_nodes(module, children, in_type)
if insertbefore or insertafter:
insert_target_children(tree, xpath, namespaces, new_kids, insertbefore, insertafter)
else:
for node in tree.xpath(xpath, namespaces=namespaces): for node in tree.xpath(xpath, namespaces=namespaces):
node.extend(new_kids) node.extend(new_kids)
finish(module, tree, xpath, namespaces, changed=True) finish(module, tree, xpath, namespaces, changed=True)
@ -456,6 +489,22 @@ def add_target_children(module, tree, xpath, namespaces, children, in_type):
finish(module, tree, xpath, namespaces) 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): def _extract_xpstr(g):
return g[1:-1] return g[1:-1]
@ -776,6 +825,8 @@ def main():
input_type=dict(type='str', default='yaml', choices=['xml', 'yaml']), input_type=dict(type='str', default='yaml', choices=['xml', 'yaml']),
backup=dict(type='bool', default=False), backup=dict(type='bool', default=False),
strip_cdata_tags=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, supports_check_mode=True,
# TODO: Implement this as soon as #28662 (required_by functionality) is merged # TODO: Implement this as soon as #28662 (required_by functionality) is merged
@ -790,6 +841,8 @@ def main():
['content', 'text', ['xpath']], ['content', 'text', ['xpath']],
['count', True, ['xpath']], ['count', True, ['xpath']],
['print_match', True, ['xpath']], ['print_match', True, ['xpath']],
['insertbefore', True, ['xpath']],
['insertafter', True, ['xpath']],
], ],
required_one_of=[ required_one_of=[
['path', 'xmlstring'], ['path', 'xmlstring'],
@ -798,6 +851,7 @@ def main():
mutually_exclusive=[ mutually_exclusive=[
['add_children', 'content', 'count', 'print_match', 'set_children', 'value'], ['add_children', 'content', 'count', 'print_match', 'set_children', 'value'],
['path', 'xmlstring'], ['path', 'xmlstring'],
['insertbefore', 'insertafter'],
], ],
) )
@ -817,6 +871,8 @@ def main():
count = module.params['count'] count = module.params['count']
backup = module.params['backup'] backup = module.params['backup']
strip_cdata_tags = module.params['strip_cdata_tags'] 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 # Check if we have lxml 2.3.0 or newer installed
if not HAS_LXML: if not HAS_LXML:
@ -881,7 +937,7 @@ def main():
# add_children set? # add_children set?
if add_children: 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 # 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-elements.yml
- include_tasks: test-add-children-from-groupvars.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-children-with-attributes.yml
- include_tasks: test-add-element-implicitly.yml - include_tasks: test-add-element-implicitly.yml
- include_tasks: test-count.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