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:
parent
1793cad07b
commit
2dbade4adc
6 changed files with 160 additions and 4 deletions
|
@ -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,16 +476,35 @@ 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)
|
||||||
for node in tree.xpath(xpath, namespaces=namespaces):
|
if insertbefore or insertafter:
|
||||||
node.extend(new_kids)
|
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)
|
finish(module, tree, xpath, namespaces, changed=True)
|
||||||
else:
|
else:
|
||||||
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…
Reference in a new issue