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