Source code for pvlib.iotools.panond
"""
Get .PAN or .OND file data into a nested dictionary.
"""
def _num_type(value):
"""
Determine if a value is float, int or a string
"""
if '.' in value:
try: # Detect float
value_out = float(value)
return value_out
except ValueError: # Otherwise leave as string
value_out = value
return value_out
else:
try: # Detect int
value_out = int(value)
return value_out
except ValueError: # Otherwise leave as string
value_out = value
return value_out
def _element_type(element):
"""
Determine if an element is a list then pass to _num_type()
"""
if ',' in element: # Detect a list.
# .pan/.ond don't use ',' to indicate 1000. If that changes,
# a new method of list detection needs to be found.
values = element.split(',')
element_out = []
for val in values: # Determine datatype of each value
element_out.append(_num_type(val))
return element_out
else:
return _num_type(element)
def _parse_panond(fbuf):
"""
Parse a .pan or .ond text file into a nested dictionary.
Parameters
----------
fbuf : File-like object
Buffer of a .pan or .ond file
Returns
-------
component_info : dict
Contents of the .pan or .ond file following the indentation of the
file. The value of datatypes are assumed during reading. The value
units are the default used by PVsyst.
"""
component_info = {} # Component
dict_levels = [component_info]
lines = fbuf.read().splitlines()
for i in range(0, len(lines) - 1):
if lines[i] == '': # Skipping blank lines
continue
# Reading blank lines. Stopping one short to avoid index error.
# Last line never contains important data.
# Creating variables to assist new level in dictionary creation logic
indent_lvl_1 = (len(lines[i]) - len(lines[i].lstrip(' '))) // 2
indent_lvl_2 = (len(lines[i + 1]) - len(lines[i + 1].lstrip(' '))) // 2
# Split the line into key/value pair
line_data = lines[i].split('=')
key = line_data[0].strip()
# Logical to make sure there is a value to extract
if len(line_data) > 1:
value = _element_type(line_data[1].strip())
else:
value = None
# add a level to the dict. If a key/value pair triggers the new level,
# the key/value will be repeated in the new dict level.
# Not vital to file function.
if indent_lvl_2 > indent_lvl_1:
current_level = dict_levels[indent_lvl_1]
new_level = {}
current_level[key] = new_level
dict_levels = dict_levels[: indent_lvl_1 + 1] + [new_level]
current_level = dict_levels[indent_lvl_1 + 1]
current_level[key] = value
elif indent_lvl_2 <= indent_lvl_1: # add key/value to dict
current_level = dict_levels[indent_lvl_1]
current_level[key] = value
return component_info
[docs]
def read_panond(filename, encoding=None):
"""
Retrieve Module or Inverter data from a .pan or .ond text file,
respectively.
Parameters
----------
filename : str or path object
Name or path of a .pan/.ond file
encoding : str, optional
Encoding of the file. Some files may require specifying
``encoding='utf-8-sig'`` to import correctly.
Returns
-------
content : dict
Contents of the .pan or .ond file following the indentation of the
file. The value of datatypes are assumed during reading. The value
units are the default used by PVsyst.
Notes
-----
The parser is intended for use with .pan and .ond files that were created
for use by PVsyst. At time of publication, no documentation for these
files was available. So, this parser is based on inferred logic, rather
than anything specified by PVsyst. At time of creation, tested
.pan/.ond files used UTF-8 encoding.
The parser assumes that the file being parsed uses indentation of two
spaces (' ') to create a new level in a nested dictionary, and that
key/values pairs of interest are separated using '='. This further means
that lines not containing '=' are omitted from the final returned
dictionary.
Additionally, the indented lines often contain values themselves. This
leads to a conflict with the .pan/.ond file and the ability of nested a
dictionary to capture that information. The solution implemented here is
to repeat that key to the new nested dictionary within that new level.
The parser takes an additional step to infer the datatype present in
each value. The .pan/.ond files appear to have intentially left datatype
indicators (e.g. floats have '.' decimals). However, there is still the
possibility that the datatype applied from this parser is incorrect. In
that event the user would need to convert to the desired datatype.
"""
with open(filename, "r", encoding=encoding) as fbuf:
content = _parse_panond(fbuf)
return content