Created
June 11, 2022 01:22
-
-
Save mbarnes/bdf8d4efc6cb9e210cd30af385995a11 to your computer and use it in GitHub Desktop.
Convert "mame -listxml" output to JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# | |
# Convert "mame -listxml" output to JSON. | |
# | |
# MAME's XML output is huge, so this dumps each complete | |
# <machine> element to keep memory usage bounded. | |
# | |
# Output can be piped to 'jq' to query machines. | |
# | |
# e.g. Select coin-op machines with trackball controls: | |
# | |
# mame -listxml | mamexmltojson | \ | |
# jq '.machine[] | select( | |
# (.runnable != "no") and \ | |
# (.cloneof == null) and \ | |
# (.input.coins != null) and \ | |
# (.input.control | \ | |
# (type == "array" and \ | |
# any(.type | contains("trackball")))))' | |
# | |
import argparse | |
import json | |
import sys | |
from html.parser import HTMLParser | |
class MAMEParser(HTMLParser): | |
LIST_ELEMENTS = set([ | |
# <machine> children | |
'biosset', | |
'rom', | |
'disk', | |
'device_ref', | |
'sample', | |
'chip', | |
'display', | |
'dipswitch', | |
'configuration', | |
'port', | |
'adjuster', | |
'feature', | |
'device', | |
'slot', | |
'softwarelist', | |
'ramoption', | |
# <input> children | |
'control', | |
# <dipswitch> children | |
'diplocation', | |
'dipvalue', | |
# <configuration> children | |
'conflocation', | |
'confsetting', | |
# <port> children | |
'analog', | |
# <device> children | |
'extension', | |
# <slot> | |
'slotoption', | |
]) | |
def reset(self): | |
super().reset() | |
self.tags = [] | |
self.stack = [] | |
self.machine = None | |
self.indent = ' ' | |
self.indentlvl = 0 | |
def print(self, message): | |
print((self.indent * self.indentlvl) + message) | |
def dump_machine(self, sep=','): | |
machine_dump = json.dumps(self.machine, indent=self.indent) + sep | |
for line in machine_dump.split('\n'): | |
self.print(line) | |
def handle_starttag(self, tag, attrs): | |
if tag == 'mame': | |
self.print('{') | |
self.indentlvl += 1 | |
for name,value in attrs: | |
self.print('"{}": "{}",'.format(name, value)) | |
self.print('"machine": [') | |
self.indentlvl += 1 | |
else: | |
attrs = {name: value for name, value in attrs} | |
if tag == 'machine': | |
if self.machine: | |
self.dump_machine() | |
self.machine = attrs | |
else: | |
parent = self.stack[-1] | |
if tag in self.LIST_ELEMENTS: | |
# Special case: if attrs has only a 'name' key, append the value | |
if tag != 'ramoption' and len(attrs) == 1 and 'name' in attrs: | |
parent.setdefault(tag, []).append(attrs['name']) | |
else: | |
parent.setdefault(tag, []).append(attrs) | |
elif attrs: | |
assert tag not in parent, 'unexpected duplicate <{}>'.format(tag) | |
parent[tag] = attrs | |
self.tags.append(tag) | |
self.stack.append(attrs) | |
def handle_endtag(self, tag): | |
if tag == 'mame': | |
if self.machine: | |
self.dump_machine(sep='') | |
self.print(']') | |
self.indentlvl -= 1 | |
self.print('}') | |
self.indentlvl -= 1 | |
else: | |
self.tags.pop() | |
self.stack.pop() | |
def handle_data(self, data): | |
data = data.strip() | |
# Ignore embedded DTD | |
if self.tags and data: | |
tag = self.tags[-1] | |
# <ramoption> has both attributes and data | |
if tag == 'ramoption': | |
parent = self.stack[-1] | |
parent['value'] = data | |
else: | |
parent = self.stack[-2] | |
assert tag not in parent, 'unexpected duplicate <{}>'.format(tag) | |
parent[tag] = data | |
if __name__ == '__main__': | |
arg_parser = argparse.ArgumentParser(description='Convert MAME XML to JSON') | |
arg_parser.add_argument( | |
'file', nargs='?', | |
type=argparse.FileType(), | |
default=sys.stdin, | |
help='File containing "mame -listxml" output') | |
args = arg_parser.parse_args() | |
parser = MAMEParser() | |
for line in args.file: | |
parser.feed(line) | |
parser.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment