From 186ad7365187ea00f5462cdc08df2356e24176ad Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 1 Nov 2022 18:09:05 -0700 Subject: [PATCH] add raw option to xml parser for _ attribute prefix --- docs/parsers/xml.md | 7 ++++- jc/parsers/xml.py | 35 +++++++++++++++-------- tests/fixtures/generic/xml-nmap-raw.json | 1 + tests/fixtures/generic/xml-nmap.json | 1 + tests/fixtures/generic/xml-nmap.xml | 36 ++++++++++++++++++++++++ tests/test_xml.py | 27 ++++++++++++++++++ 6 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 tests/fixtures/generic/xml-nmap-raw.json create mode 100644 tests/fixtures/generic/xml-nmap.json create mode 100644 tests/fixtures/generic/xml-nmap.xml diff --git a/docs/parsers/xml.md b/docs/parsers/xml.md index 88a69ab3..eb838faf 100644 --- a/docs/parsers/xml.md +++ b/docs/parsers/xml.md @@ -5,6 +5,11 @@ jc - JSON Convert `XML` file parser +This parser adds a `@` prefix to attributes by default. This can be changed +to a `_` prefix by using the `-r` (cli) or `raw=True` (module) option. + +Text values for nodes will have the key-name of `#text`. + Usage (cli): $ cat foo.xml | jc --xml @@ -93,4 +98,4 @@ Returns: ### Parser Information Compatibility: linux, darwin, cygwin, win32, aix, freebsd -Version 1.6 by Kelly Brazil (kellyjonbrazil@gmail.com) +Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com) diff --git a/jc/parsers/xml.py b/jc/parsers/xml.py index 92f39d8d..57b4b24e 100644 --- a/jc/parsers/xml.py +++ b/jc/parsers/xml.py @@ -1,5 +1,10 @@ """jc - JSON Convert `XML` file parser +This parser adds a `@` prefix to attributes by default. This can be changed +to a `_` prefix by using the `-r` (cli) or `raw=True` (module) option. + +Text values for nodes will have the key-name of `#text`. + Usage (cli): $ cat foo.xml | jc --xml @@ -68,10 +73,15 @@ Examples: import jc.utils from jc.exceptions import LibraryNotInstalled +try: + import xmltodict +except Exception: + raise LibraryNotInstalled('The xmltodict library is not installed.') + class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.6' + version = '1.7' description = 'XML file parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' @@ -82,7 +92,7 @@ class info(): __version__ = info.version -def _process(proc_data): +def _process(proc_data, has_data=False): """ Final processing to conform to the schema. @@ -94,9 +104,13 @@ def _process(proc_data): Dictionary representing an XML document. """ + raw_output = [] - # No further processing - return proc_data + if has_data: + # standard output with @ prefix for attributes + raw_output = xmltodict.parse(proc_data) + + return raw_output def parse(data, raw=False, quiet=False): @@ -113,22 +127,19 @@ def parse(data, raw=False, quiet=False): Dictionary. Raw or processed structured data. """ - # check if xml library is installed and fail gracefully if it is not - try: - import xmltodict - except Exception: - raise LibraryNotInstalled('The xmltodict library is not installed.') - jc.utils.compatibility(__name__, info.compatible, quiet) jc.utils.input_type_check(data) raw_output = [] + has_data = False if jc.utils.has_data(data): + has_data = True - raw_output = xmltodict.parse(data) + # modified output with _ prefix for attributes + raw_output = xmltodict.parse(data, attr_prefix='_') if raw: return raw_output else: - return _process(raw_output) + return _process(data, has_data) diff --git a/tests/fixtures/generic/xml-nmap-raw.json b/tests/fixtures/generic/xml-nmap-raw.json new file mode 100644 index 00000000..accbe22f --- /dev/null +++ b/tests/fixtures/generic/xml-nmap-raw.json @@ -0,0 +1 @@ +{"nmaprun":{"_scanner":"nmap","_args":"nmap -oX - -p 443 galaxy.ansible.com","_start":"1666781498","_startstr":"Wed Oct 26 11:51:38 2022","_version":"7.92","_xmloutputversion":"1.05","scaninfo":{"_type":"connect","_protocol":"tcp","_numservices":"1","_services":"443"},"verbose":{"_level":"0"},"debugging":{"_level":"0"},"hosthint":{"status":{"_state":"up","_reason":"unknown-response","_reason_ttl":"0"},"address":{"_addr":"172.67.68.251","_addrtype":"ipv4"},"hostnames":{"hostname":{"_name":"galaxy.ansible.com","_type":"user"}}},"host":{"_starttime":"1666781498","_endtime":"1666781498","status":{"_state":"up","_reason":"syn-ack","_reason_ttl":"0"},"address":{"_addr":"172.67.68.251","_addrtype":"ipv4"},"hostnames":{"hostname":[{"_name":"galaxy.ansible.com","_type":"user"},{"_name":"galaxy.ansible.com","_type":"PTR"}]},"ports":{"port":{"_protocol":"tcp","_portid":"443","state":{"_state":"open","_reason":"syn-ack","_reason_ttl":"0"},"service":{"_name":"https","_method":"table","_conf":"3"}}},"times":{"_srtt":"12260","_rttvar":"9678","_to":"100000"}},"runstats":{"finished":{"_time":"1666781498","_timestr":"Wed Oct 26 11:51:38 2022","_summary":"Nmap done at Wed Oct 26 11:51:38 2022; 1 IP address (1 host up) scanned in 0.10 seconds","_elapsed":"0.10","_exit":"success"},"hosts":{"_up":"1","_down":"0","_total":"1"}}}} diff --git a/tests/fixtures/generic/xml-nmap.json b/tests/fixtures/generic/xml-nmap.json new file mode 100644 index 00000000..849ea04a --- /dev/null +++ b/tests/fixtures/generic/xml-nmap.json @@ -0,0 +1 @@ +{"nmaprun":{"@scanner":"nmap","@args":"nmap -oX - -p 443 galaxy.ansible.com","@start":"1666781498","@startstr":"Wed Oct 26 11:51:38 2022","@version":"7.92","@xmloutputversion":"1.05","scaninfo":{"@type":"connect","@protocol":"tcp","@numservices":"1","@services":"443"},"verbose":{"@level":"0"},"debugging":{"@level":"0"},"hosthint":{"status":{"@state":"up","@reason":"unknown-response","@reason_ttl":"0"},"address":{"@addr":"172.67.68.251","@addrtype":"ipv4"},"hostnames":{"hostname":{"@name":"galaxy.ansible.com","@type":"user"}}},"host":{"@starttime":"1666781498","@endtime":"1666781498","status":{"@state":"up","@reason":"syn-ack","@reason_ttl":"0"},"address":{"@addr":"172.67.68.251","@addrtype":"ipv4"},"hostnames":{"hostname":[{"@name":"galaxy.ansible.com","@type":"user"},{"@name":"galaxy.ansible.com","@type":"PTR"}]},"ports":{"port":{"@protocol":"tcp","@portid":"443","state":{"@state":"open","@reason":"syn-ack","@reason_ttl":"0"},"service":{"@name":"https","@method":"table","@conf":"3"}}},"times":{"@srtt":"12260","@rttvar":"9678","@to":"100000"}},"runstats":{"finished":{"@time":"1666781498","@timestr":"Wed Oct 26 11:51:38 2022","@summary":"Nmap done at Wed Oct 26 11:51:38 2022; 1 IP address (1 host up) scanned in 0.10 seconds","@elapsed":"0.10","@exit":"success"},"hosts":{"@up":"1","@down":"0","@total":"1"}}}} diff --git a/tests/fixtures/generic/xml-nmap.xml b/tests/fixtures/generic/xml-nmap.xml new file mode 100644 index 00000000..7823cf21 --- /dev/null +++ b/tests/fixtures/generic/xml-nmap.xml @@ -0,0 +1,36 @@ + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + diff --git a/tests/test_xml.py b/tests/test_xml.py index cf4eda9f..5b0b4bc4 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -15,6 +15,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/xml-foodmenu.xml'), 'r', encoding='utf-8') as f: generic_xml_foodmenu = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/xml-nmap.xml'), 'r', encoding='utf-8') as f: + generic_xml_nmap = f.read() + # output with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/xml-cd_catalog.json'), 'r', encoding='utf-8') as f: generic_xml_cd_catalog_json = json.loads(f.read()) @@ -22,6 +25,12 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/xml-foodmenu.json'), 'r', encoding='utf-8') as f: generic_xml_foodmenu_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/xml-nmap.json'), 'r', encoding='utf-8') as f: + generic_xml_nmap_json = json.loads(f.read()) + + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/xml-nmap-raw.json'), 'r', encoding='utf-8') as f: + generic_xml_nmap_r_json = json.loads(f.read()) + def test_xml_nodata(self): """ @@ -29,6 +38,12 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.xml.parse('', quiet=True), []) + def test_xml_nodata_r(self): + """ + Test xml parser with no data and raw output + """ + self.assertEqual(jc.parsers.xml.parse('', raw=True, quiet=True), []) + def test_xml_cd_catalog(self): """ Test the cd catalog xml file @@ -41,6 +56,18 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.xml.parse(self.generic_xml_foodmenu, quiet=True), self.generic_xml_foodmenu_json) + def test_xml_nmap(self): + """ + Test nmap xml output + """ + self.assertEqual(jc.parsers.xml.parse(self.generic_xml_nmap, quiet=True), self.generic_xml_nmap_json) + + def test_xml_nmap_r(self): + """ + Test nmap xml raw output + """ + self.assertEqual(jc.parsers.xml.parse(self.generic_xml_nmap, raw=True, quiet=True), self.generic_xml_nmap_r_json) + if __name__ == '__main__': unittest.main()