#!/usr/bin/python # Copyright (c) 2014, Red Hat, Inc. # Copyright (c) 2014, Tim Bielawa # Copyright (c) 2014, Magnus Hedemark # Copyright (c) 2017, Dag Wieers # Copyright (c) 2026, Shreyash Bhosale # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: xml_info short_description: Query XML files or strings description: - A read-only interface to query XML files or strings using XPath expressions. - Supports counting matches, listing matched XPath paths, and retrieving element text or attributes. version_added: "13.1.0" extends_documentation_fragment: - community.general._attributes - community.general._attributes.info_module - community.general._xml options: xpath: required: true seealso: - module: community.general.xml description: Manage bits and pieces of XML files or strings. - name: Introduction to XPath description: A brief tutorial on XPath (w3schools.com). link: https://www.w3schools.com/xml/xpath_intro.asp - name: XPath Reference document description: The reference documentation on XSLT/XPath (developer.mozilla.org). link: https://developer.mozilla.org/en-US/docs/Web/XPath author: - Tim Bielawa (@tbielawa) - Magnus Hedemark (@magnus919) - Dag Wieers (@dagwieers) - Shreyash Bhosale (@Shreyashxredhat) """ EXAMPLES = r""" # Consider the following XML file: # # # Tasty Beverage Co. # # Rochefort 10 # St. Bernardus Abbot 12 # Schlitz # # 10 # # #
https://tastybeverageco.com
#
#
# Retrieve and display the number of nodes - name: Get count of 'beers' nodes community.general.xml_info: path: /foo/bar.xml xpath: /business/beers/beer count: true register: hits - ansible.builtin.debug: var: hits.count # Retrieve and display the matching XPath paths - name: Get matching paths for 'beer' nodes community.general.xml_info: path: /foo/bar.xml xpath: /business/beers/beer print_match: true register: hits - ansible.builtin.debug: var: hits.matches # How to read an attribute value and access it in Ansible - name: Read an element's attribute values community.general.xml_info: path: /foo/bar.xml xpath: /business/rating content: attribute register: xmlresp - name: Show an attribute value ansible.builtin.debug: var: xmlresp.matches[0].rating.subjective # How to read text content - name: Read an element's text content community.general.xml_info: path: /foo/bar.xml xpath: /business/rating content: text register: xmlresp - name: Show text content ansible.builtin.debug: var: xmlresp.matches[0].rating # Using an XML string instead of a file - name: Count nodes in an XML string community.general.xml_info: xmlstring: "12" xpath: /config/item count: true register: hits # Using namespaces - name: Count nodes in a namespaced XML file community.general.xml_info: path: /foo/bar.xml xpath: /x:foo/x:bar/y:baz namespaces: x: http://x.test y: http://y.test count: true register: hits """ RETURN = r""" count: description: The count of xpath matches. type: int returned: when parameter O(count) is set sample: 2 matches: description: The xpath matches found. type: list returned: when parameter O(print_match) or O(content) is set """ import copy from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils._xml import ( COMMON_ARGUMENT_SPEC, check_lxml, collect_element_attr, collect_element_text, count_matches, get_matches, parse_xml_doc, validate_xpath, ) def main(): argument_spec = copy.deepcopy(COMMON_ARGUMENT_SPEC) argument_spec["xpath"]["required"] = True module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=[ ["path", "xmlstring"], ["count", "print_match", "content"], ], mutually_exclusive=[ ["count", "print_match", "content"], ["path", "xmlstring"], ], ) xml_file = module.params["path"] xml_string = module.params["xmlstring"] xpath = module.params["xpath"] namespaces = module.params["namespaces"] content = module.params["content"] print_match = module.params["print_match"] count = module.params["count"] strip_cdata_tags = module.params["strip_cdata_tags"] huge_tree = module.params["huge_tree"] check_lxml(module) validate_xpath(module, xpath) doc = parse_xml_doc( module, xml_file=xml_file, xml_string=xml_string, strip_cdata_tags=strip_cdata_tags, huge_tree=huge_tree, resolve_entities=False, ) if print_match: result = get_matches(doc, xpath, namespaces) module.exit_json(**result) if count: result = count_matches(doc, xpath, namespaces) module.exit_json(**result) if content == "attribute": elements = collect_element_attr(doc, xpath, namespaces) if elements is None: module.fail_json(msg=f"Xpath {xpath} does not reference a node!") module.exit_json(count=len(elements), matches=elements) elif content == "text": elements = collect_element_text(doc, xpath, namespaces) if elements is None: module.fail_json(msg=f"Xpath {xpath} does not reference a node!") module.exit_json(count=len(elements), matches=elements) if __name__ == "__main__": main()