Skip to content

Instantly share code, notes, and snippets.

@cmitu
Created March 17, 2018 09:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmitu/2f1a0dab4e5086b8fca99b081677b6ec to your computer and use it in GitHub Desktop.
Save cmitu/2f1a0dab4e5086b8fca99b081677b6ec to your computer and use it in GitHub Desktop.
Exports one gamelist xml file to Excel.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Script to export one gamelist.xml file to an Excel spreadsheet.
Without arguments
* it searches for a `gamelist.xml` file in the running dir
* outputs a `gamelist.xlsx` file in the running dir
'''
import xml.etree.ElementTree as et
import logging as log
import os.path
import fnmatch
import argparse
import xlsxwriter
from datetime import datetime as dt
# Set up logging using the logging module.
log.basicConfig(level=log.INFO, format=u"%(asctime)s %(levelname)-6s %(message)s")
logger = log.getLogger(__name__)
# Date time format
DATE_TIME_FORMAT = "%Y%m%dT%H%M%S"
def get_xml_element_text(xml, node_name):
if xml.find(node_name) is None or xml.find(node_name).text is None:
return None
else:
return xml.find(node_name).text.strip()
def is_number(s):
try:
int(s)
return True
except:
return False
def is_float(s):
try:
float(s)
return True
except:
return False
def get_xml_element_bool(xml, node_name):
"""
Returns either yes or None, depending on the value of the @parm node_name.
"""
if xml.find(node_name) is None:
return None
elif xml.find(node_name).text.lower() == "false" or xml.find(node_name).text.lower() == "no":
return None
else:
return "yes"
def get_xml_element_date(xml, node_name):
"""
Returns a DateTime or a String, depending on the value of the @parm node_name.
"""
global DATE_TIME_FORMAT
if not xml.find(node_name) is None and not xml.find(node_name).text is None:
date_text = xml.find(node_name).text
# Release date can appear as both ISO date or just as an year.
# If it's an ISO date, then try to convert it, otherwise just return the text
if len(date_text) < 6:
return date_text
else:
try:
date = dt.strptime(xml.find(node_name).text, DATE_TIME_FORMAT)
return date
except ValueError:
return date_text
else:
return None
def get_xml_element_int(xml, node_name):
"""
Returns None or a Number, depending on the value of the @parm.
"""
if xml.find(node_name) is None:
return None
else:
try:
return int(xml.find(node_name).text)
except ValueError:
return xml.find(node_name).text
except TypeError:
return None
class System(object):
"""
Class that models an ES System, storing the attributes of the System and its list of Games
"""
info_keys = ("name")
def __init__(self, name):
self.info = { 'name': name }
self.games = [] # List of games
def __str__(self):
return self.info['name'] + ", games: " + str(len(self.games))
class Game:
info_keys = ['name', 'gametype', 'genre', 'version', 'originaltitle', 'alternatetitle', 'desc', 'publisher', 'developer', 'hackedby', 'translatedby']
info_keys += ['path', 'playerstext', 'releasetext']
info_keys += ['video', 'marquee', 'thumbnail']
info_keys += ['region', 'platform', 'media', 'controller']
info_keys += ['boxfront', 'cart', 'title', 'action', 'threedbox']
info_keys += ['gamefaq', 'manual', 'vgmap', 'license', 'programmer', 'musician']
info_date = ['releasedate', 'hackreleasedate', 'transreleasedate']
info_int = ['players']
@staticmethod
def get_headers():
return (Game.info_keys + Game.info_date + Game.info_int)
def __init__():
self.info = dict.fromkeys(Game.get_headers())
def __init__(self, obj):
self.info = dict.fromkeys(Game.info_keys)
# Get the text metadata
for attr in self.info.keys():
self.info[attr] = get_xml_element_text(obj, attr)
# Get the date metadata
for attr in Game.info_date:
self.info[attr] = get_xml_element_date(obj, attr)
# Get the integer metadata
for attr in Game.info_int:
self.info[attr] = get_xml_element_int(obj, attr)
def __str__(self):
return str("{0}\t{1}".format(self.info["name"]), str(self.info["path"]))
def check_rom(rom_folder, rom_path):
"""
Method to check if a ROM is present in the filesystem.
Returns true if the ROM is present, false otherwise.
"""
# The Rom path in the gamelist might be absolute or relative.
# Check if the path begins with an '/' to decide if it's an absolute path.
path_to_check = rom_path
if not rom_path.startswith('/'):
path_to_check = rom_folder + "/" + rom_path
return os.path.isfile(path_to_check)
def get_rom_path(rom_folder, rom_path):
if not rom_path.startswith('/'):
path_to_check = rom_folder + "/" + rom_path
return os.path.realpath(path_to_check)
def parse_gamelist(gamelist_path = "gamelist.xml"):
s = System("games")
systems = []
try:
gamelist = et.parse(gamelist_path)
except IOError:
logger.warn("Could not open the gamelist file %s !", gamelist_path)
exit(1)
except et.ParseError as v:
logger.error("Incorrect XML file: %s", format(v))
exit(1)
# Ok, we have the gamelist, get each game and parse it.
for game in gamelist.findall('game'):
rom = Game(game)
logger.debug('Found - %s', rom.info['name'])
s.games.append(rom)
# If we have more than 1 ROM in the system, add it to the exported list
if len(s.games) > 0:
systems.append(s)
else:
logger.debug(
"System %s has no games/roms, it's excluded from the export", s.info['name'])
return systems
# Export the system list to excel
def xlsx_export_workbook(systems, output='export.xlsx'):
if not len(systems):
raise "Exported system list is empty"
return
# Create the Workbook
wb = xlsxwriter.Workbook(output,
{'default_date_format': 'dd-mm-yyyy',
'in_memory': True,
})
# Add some metadata to it
wb.set_properties({
'title': 'Game List Export',
'subject': 'Game List Export',
'category': 'Gaming',
'author': "XlsxWriter (github.com/jmcnamara/XlsxWriter), version " + xlsxwriter.__version__,
})
wb.set_custom_property('Date Exported', dt.now())
fmt_bold = wb.add_format({'bold': True})
fmt_bold_2 = wb.add_format({'bold': True, 'bg_color': 'red', 'color': 'white'})
fmt_sys_header = wb.add_format({'bold': True, 'bg_color': 'green', 'color': 'white'})
fmt_fav_row = wb.add_format({'bg_color': '#FFCC7C'})
table_headers = list(map(lambda x: {'header': str(x).capitalize()}, Game.get_headers()))
for i, s in enumerate(systems):
# Add a worksheet for each system.
b = wb.add_worksheet(s.info['name'])
# Create a table with each system and the # of games detected in each system.
# Print the table header
b.set_column(0, 0, 50)
t = b.add_table(0, 0, len(s.games), len(Game.get_headers()) - 1,
{
'style': 'Table Style Medium 7',
'columns': table_headers,
# The name of the Table should only containt letters + numbers.
# 'name'c: s.info["name"].replace('[^[a-zA-Z0-9]', ''),
'autofilter': False,
'banded_rows': False,
})
# Print the table rows
for j, g in enumerate(s.games):
xlsx_export_system_row(wb, b, j+1, g)
# Close the workbook
wb.close()
def xlsx_export_system_row(workbook, sheet, row_number, game, system_name=None):
fmt_fav = workbook.add_format({'align': 'center'})
# On special collections, 1st column is the name of the system where the game belongs
# Only shown when set.
if system_name is not None:
sheet.write(row_number, 0, system_name)
offset = 1
else:
offset = 0
for column, header in enumerate(Game.get_headers()):
if header in Game.info_date and type(game.info[header]).__name__ == "datetime":
sheet.write_datetime(row_number, column + offset, game.info[header])
elif header in ('playcount', 'players') and is_number(game.info[header]):
sheet.write_number(row_number, column + offset, int(game.info[header]))
elif header in ('rating',) and is_float(game.info[header]):
sheet.write_number(row_number, column + offset, float(game.info[header]))
elif header.lower() in ('favorite', 'kidgame', 'hidden'):
sheet.write(row_number, column + offset, game.info[header], fmt_fav)
else:
sheet.write(row_number, column + offset, game.info[header])
# If we're on the 'All' sheet, add the description of the game in the cell comments
if sheet.get_name().lower() == "all" and header.lower() == "name" and not game.info['desc'] is None:
sheet.write_comment(row_number, column + offset,
game.info['desc'], {'x_scale': 4, 'y_scale': 4})
def parse_arguments():
parser = argparse.ArgumentParser(
description='Export an XML formatted file to an Excel spreadsheet')
parser.add_argument('input', nargs='?',
default="gamelist.xml",
help="Gamelist file to parse (default is 'gamelist.xml'")
parser.add_argument('output', nargs='?',
default="gamelist.xlsx",
help="Export file (default is 'gamelist.xlsx')")
parser.add_argument('-d', '--debug', action='store_true',
help="run script with with debug info", default=False)
args = parser.parse_args()
return (args.input, args.output, args.debug)
if __name__ == "__main__":
# Parse arguments
(input, output, debug) = parse_arguments()
# Set logging level; default is INFO, add debugging if requested via parameter
if debug:
logger.setLevel(log.DEBUG)
logger.debug("Starting")
systems = parse_gamelist(input)
# See how many games we have
total_games = sum(map(lambda system: len(system.games), systems))
logger.info("Total games after parsing gamelist files - " + str(total_games))
if total_games < 1:
logger.warn("No games to export, exiting..")
exit(1)
logger.info("Exporting to file %s",output)
xlsx_export_workbook(systems, output)
logger.debug("Finished")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment