"""
ANDES input parsers and output formatters.
"""
import importlib
import io
import logging
import os
import chardet
from typing import Union
from andes.utils.misc import elapsed
from andes.io import xlsx, psse, json, matpower # NOQA
logger = logging.getLogger(__name__)
# Input formats is a dictionary of supported format names and the accepted file extensions
# The first file will be parsed by read() function and the addfile will be parsed by read_add()
# Typically, column based formats, such as IEEE CDF and PSS/E RAW, are faster to parse
input_formats = {
'xlsx': ('xlsx',),
'json': ('json',),
'matpower': ('m', ),
'psse': ('raw', 'dyr'),
}
# Output formats is a dictionary of supported output formats and their extensions
# The static data will be written by write() function and the addfile by writeadd()
output_formats = {
'xlsx': ('xlsx',),
'json': ('json',),
}
[docs]def get_output_ext(out_format):
"""
Helper function to get the output extension for the given output format.
Parameters
----------
out_format : str
Output format name.
Returns
-------
str : file extension without dot or empty if not supported
"""
if (out_format is None) or (out_format is True):
logger.warning('Dump to XLSX format by default')
return 'xlsx'
if out_format in output_formats:
return output_formats[out_format][0]
else:
logger.error(f"Dump format <{out_format}> not supported.")
return ''
[docs]def guess(system):
"""
Guess the input format based on extension and content.
Also stores the format name to `system.files.input_format`.
Parameters
----------
system : System
System instance with the file name set to `system.files`
Returns
-------
str
format name
"""
files = system.files
maybe = []
if files.input_format:
maybe.append(files.input_format)
# first, guess by extension
for key, val in input_formats.items():
if files.ext.strip('.').lower() in val:
maybe.append(key)
# second, guess by lines
true_format = ''
for item in maybe:
parser = importlib.import_module('.' + item, __name__)
testlines = getattr(parser, 'testlines')
if testlines(files.case):
true_format = item
files.input_format = true_format
logger.debug('Input format guessed as %s.', true_format)
break
if not true_format:
logger.error('Unable to determine case format.')
# guess addfile format
if files.addfile:
_, add_ext = os.path.splitext(files.addfile)
for key, val in input_formats.items():
if add_ext[1:] in val:
files.add_format = key
logger.debug('Addfile format guessed as %s.', key)
break
return true_format
[docs]def parse(system):
"""
Parse input file with the given format in `system.files.input_format`.
Returns
-------
bool
True if successful; False otherwise.
"""
t, _ = elapsed()
# exit when no input format is given
if not system.files.input_format:
if not guess(system):
logger.error('Input format unknown for file "%s".', system.files.case)
return False
# try parsing the base case file
logger.info('Parsing input file "%s"...', system.files.case)
input_format = system.files.input_format
parser = importlib.import_module('.' + input_format, __name__)
if not parser.read(system, system.files.case):
logger.error('Error parsing file "%s" with <%s> parser.', system.files.fullname, input_format)
return False
_, s = elapsed(t)
logger.info('Input file parsed in %s.', s)
# Try parsing the addfile
t, _ = elapsed()
if system.files.addfile:
logger.info('Parsing additional file "%s"...', system.files.addfile)
add_format = system.files.add_format
add_parser = importlib.import_module('.' + add_format, __name__)
if not add_parser.read_add(system, system.files.addfile):
logger.error('Error parsing addfile "%s" with %s parser.', system.files.addfile, input_format)
return False
_, s = elapsed(t)
logger.info('Addfile parsed in %s.', s)
return True
[docs]def dump(system, output_format, full_path=None, overwrite=False, **kwargs):
"""
Dump the System data into the requested output format.
Parameters
----------
system
System object
output_format : str
Output format name. 'xlsx' will be used if is not an instance of `str`.
Returns
-------
bool
True if successful; False otherwise.
"""
if (output_format is None) or (output_format is True):
output_format = 'xlsx'
output_ext = get_output_ext(output_format)
if output_ext == '':
return False
if full_path is not None:
system.files.dump = full_path
else:
system.files.dump = os.path.join(system.files.output_path,
system.files.name + '.' + output_ext)
writer = importlib.import_module('.' + output_format, __name__)
t, _ = elapsed()
ret = writer.write(system, system.files.dump, overwrite=overwrite, **kwargs)
_, s = elapsed(t)
if ret:
logger.info('Format conversion completed in %s.', s)
return True
else:
logger.error('Format conversion failed.')
return False
[docs]def read_file_like(infile: Union[str, io.IOBase]):
"""
Read a file-like object and return a list of splitted lines.
"""
if isinstance(infile, str):
with open(infile, 'rb') as fb:
charset = chardet.detect(fb.read())
logger.debug("Detected raw file encoding: %s", charset)
f = open(infile, 'r', encoding=charset['encoding'])
else:
f = infile
in_data = f.read()
lines_list = in_data.splitlines()
if f is not infile:
f.close()
return lines_list