function [ tomlini ] = tomlini(pFilename, pReservedCharacters) %TOMLINI Reads a file that consist of a specific combination of a ini and toml file. % Creates a struct variable that has properties name as the sections in % the file. The values of these properties either have the type struct, % cell or double, depending on how the data can be parsed. % % Parameters: % pFilename (string) The filename that should be read. % pReservedCharacters (struct) Optional. It is possible to customize % the characters that are used for parsing. % Default values: % section [ % sectionTitleEnd ] % comment % % variableIndicator - % % % Example file: % % [SectionName] % -var value % -var2 value % % [SecondSectionName] % 10 20 30 40 % 50 60 70 80 % % [ThirdSection] % sometext 10 20 % someOtherText 30 40 %% Initialize return variables tomlini = false; %% Initialize variables required for processing tomlini = {}; % File specific characters required for processing reservedCharacters = struct( ... 'section', '[', ... % character indicating new section 'sectionTitleEnd', ']', ... % immediately follows the section title 'comment', '%', ... % character to indicate comments 'variableIndicator', '-' ... % indicates a variable in the current line ); %% Parameter check parameterError = false; if (~ischar(pFilename)) warning([mfilename ': Given parameter is not a string!']); parameterError = true; end if (exist('pReservedCharacters', 'var') && isstruct(pReservedCharacters)) % merge the two structs names = fieldnames(pReservedCharacters); for i = 1:length(names) reservedCharacters.(names{i}) = pReservedCharacters.(names{i}); end end if (parameterError) return; else clear parameterError; end %% Read the file fileContent = fileread(pFilename); %% Parse the file allSections = splitIntoSections(fileContent); for i = 1:length(allSections) if (isempty(allSections{i}) || isComment(allSections{i})) % ignore the line continue; end [title, content] = extractTitleAndContent(allSections(i)); switch determineParseType(content) case 'matrix' tomlini.(title) = parseAsMatrix(content, title); case 'struct' tomlini.(title) = parseAsStruct(content); case 'cell' tomlini.(title) = parseAsCell(content); otherwise warning([mfilename ': Something went wrong during type estimation for parsing!']); end end function [ type ] = determineParseType(pContent) lines = splitlines(pContent); % check if everything can be parsed as number tmpRow = strsplit(lines{1}, {'\t', ' '}, 'CollapseDelimiter', true); tmpRow = tmpRow(~cellfun('isempty', tmpRow)); nanInRow = contains(tmpRow, 'NaN'); rowAsNumber = str2double(tmpRow); if (nanInRow == isnan(rowAsNumber)) type = 'matrix'; return; end % it cannot be parsed as matrix, so check if it can be parsed as % struct hasVariableIndicator = ... (lines{1}(1) == reservedCharacters.variableIndicator); alphabeticLetters = isletter(lines{1}); if hasVariableIndicator && alphabeticLetters(2) type = 'struct'; else type = 'cell'; end end function [ sections ] = splitIntoSections(pFileContent) sections = split(pFileContent, reservedCharacters.section); % remove all empty cells sections = sections(~cellfun('isempty', sections)); end function [ title, content ] = extractTitleAndContent(pSection) parts = split(pSection, reservedCharacters.sectionTitleEnd); title = strrep(strtrim(parts{1}), ' ', '_'); % replace space title = strrep(title, '\t', '_'); % replace tabs content = strtrim(parts{2}); end function [ isComment ] = isComment(pLine) trimmedLine = strtrim(pLine); if (isempty(trimmedLine)) isComment = true; return; end if (trimmedLine(1) == reservedCharacters.comment) isComment = true; else isComment = false; end end function [ returnContent ] = parseAsStruct(pContent) lines = splitlines(pContent); returnContent = struct(); for i = 1:length(lines) if (isempty(lines{i}) || isComment(lines{i})) continue end [line, comment] = extractValueAndComment(lines{i}); [key, value] = strtok(line(2:end)); valueAsNumber = str2num(value); if isempty(valueAsNumber) parsedValue = strtrim(value); else parsedValue = valueAsNumber; end % check if key already exists if (isfield(returnContent, key)) % add it to the existing key as new line parsedValue = [ returnContent.(key); parsedValue]; end returnContent.(key) = parsedValue; end end function [ matrix ] = parseAsMatrix(pContent, pTitle) lines = splitlines(pContent); %matrix = []; columnCount = 0; for i = 1:length(lines) if (isempty(lines{i}) || isComment(lines{i})) continue end % cut rest of line if a comment character is available [line, comment] = extractValueAndComment(lines{i}); lines{i} = line; clear line; % split by whitespaces lineAsCell = strsplit(lines{i}, {'\t', ' '}, 'CollapseDelimiters', true); % remove empty cells lineAsCell = lineAsCell(~cellfun('isempty', lineAsCell)); lineAsDouble = str2double(lineAsCell); if i == 1 matrix = lineAsDouble; columnCount = length(lineAsDouble); else if (length(lineAsDouble) ~= columnCount) warning( ... "Expecting " + num2str(columnCount) + " columns in line " + ... num2str(i) + " of section " + pTitle + ", but found " + ... num2str(length(lineAsDouble)) + " columns. Line is being skipped!" ... ); continue; end matrix = [matrix; lineAsDouble]; end end end function [ cell ] = parseAsCell(pContent) lines = splitlines(pContent); cell = {}; for i = 1:length(lines) if (isempty(lines{i}) || isComment(lines{i})) continue end [line, comment] = extractValueAndComment(lines{i}); cell{i} = strsplit( ... strtrim(line), {' ', '\t'}, 'CollapseDelimiters', true ... ); end cell = vertcat(cell{:}); % combine all cells to one end function [ value, comment ] = extractValueAndComment(pString) tmp = strsplit(pString, reservedCharacters.comment); value = tmp{1}; if length(tmp) == 2 comment = tmp{2}; else comment = ''; end end end