function [S,St] = hhcsv2cell( fn, cSep, tSep, fSep )
% [S,St] = hhcsv2cell( fn, cSep, tSep, fSep ) : read csv file fn and return it as cell matrix, use cSep, tSep, fSep as column, text and fractional part separators
%
%       fn  : path of csv file
%       cSep: e.g. ';', column separator character
%       tSep: e.g. '"', text separator character
%       fSep: e.g. ',', fractional part separator character used in numerical numbers (US: '.' as in 123,456.789 or 123456.789, EU: ',' as in 123.456,789 or 123456,789 for a fractional part 789/1000)
%       S   : spread sheet cell matrix, each cell is either empty, a string or a number
%       St  : spread sheet cell matrix, each cell is either empty or a string (no number conversion, numbers as text string)
%
% Generated on 01.02.2017 by ralf <tmp002@gmx.net>, Version 1.00
%
% tested successfully on csv-file fn generated bei Libreoffice/SaveAs/csv/... using options
%     character set: Unicode (UTF-8)
%   field delimiter: ;                % == cSep
%    text delimiter: "                % == tSep
%               yes: save cell content as shown
%                no: save cell formulas instead of calculated values
%               yes: quote all text cells
%                no: fixed column width
%
% Example: [S,St] = hhcsv2cell( 'abc.csv', ';', '"', ',' );  % where 'abc.csv' is your csv-file, that uses column separators ';', text quotations '"' and ',' to separate the fractional part in numbers
%
% In csv-files,
% * numbers are either all quoted or all unquoted
% * text strings are either all quoted or all unquoted
% * number formats may equal the format of IP addresses, which should not be read-in as numbers, but as text strings that preserve the meaningful position of the IP adresses' dots '.'
% * spreadsheet row separators and linefeeds within spreadsheet cells share the same separator "\n" 
% * column separators may equal the fractional part separators of numbers
%
% It becomes clear, that the original spreadsheet can only then be recovered properly, if the csv-file format was chosen carefully.
%
% One of the best ways to preserve original spreadsheet content is to format the csv-file
% (A) to embrace text in quotes '"'
% (B) to separate spreadsheet columns by ';'
%
% (A) allows to distinguish between QUOTED text cells and UNQUOTED number cells in situations where it is not clear, whether cell content is numerical or not (e.g. IP addresses vs. numbers)
% (A) allows to distinguish between spreadsheet row ends (newline character NOT embedded by quotation) and text-cell-internal linefeeds (newline character IS embedded by quotation) both encoded by the same newline character "\n"
% (B) forces number cells to remain UNQUOTED in the csv file (as an option in LibreOffice) making sure that the QUOTED text strings can be distinguished from UNQUOTED number strings
%
% csv-files that fail to comply with (A) or (B) cannot fully be recovered by any software read-in, including Octave's csv2cell(), because important information is lost during the encoding to csv.
%
% In other words, the chosen csv-format determines, whether there is a way to read-in text-cell-internal linefeeds unambiguously / correctly or not.
%
% Chose quotes to surround your text cells in the csv-output and then correct Octave-read-in of text-cell-internal linefeeds becomes feasible!
%
% For this case of quoted, potentially linefeed-containing spreadsheet text cells in the csv-file, hhcsv2cell() was written.
%
% hhcsv2cell() adapts to fractional part separators in numbers, that may vary according to the chosen number format, usually either ',' or '.' as in "123,456" or "123.456".
%
% Note, that csv-files may embrace numbers in quotes for the case that the column separator, e.g. ',', was chosen to equal the fractional part separator in numbers. hhcsv2cell() does not convert quote-embraced number strings, e.g. "123,456" to numerical values, but does so for unquoted 123,456. Chose ';' as column separator in LibreOffice's csv output for numerical spreadsheet cells to be formatted as unquoted numerical text strings in the csv-file. 
%
% To summarize, in LibreOffice's csv output:
% - good choice: You chose ';' as column separator cSep to have numerical spreadsheet cells be output as UNQUOTED numbers in the csv-file, which ARE converted to numbers by hhcsv2cell(). 
% - bad choice:  You chose ',' as column separator cSep to have LibreOffice QUOTE all numbers in the csv-file, which then NONE ARE converted to numbers by [S,St]=hhcsv2cell() making its outputs S and St identical.
% - good choice: You chose '"' as text separator tSep so hhcsv2cell() is able to identify numerical spreadsheet cells and convert them to numbers in S (with St encoding all cells as character strings including numerical cells)
%
% If your choice of fSep fails to match the fractional part separators of numbers in the csv-file, then number conversion may fail placing nan in S whereever it applies.
% 

  if isempty(fn) | length(cSep) * length(tSep) * length(fSep) ~= 1, 
    error( 'invalid arguments in function call to hhcsv2cell(). abort.' );  % separators must be single characters and a csv file name must be specified
  endif  

  pnu         = '([+-]?(?:[,\.]\d+|\d+(?:(?:\.\d\d\d)*(?:,\d+)?|(?:,\d\d\d)*(?:\.\d+)?)))';     % NOT QUOTED admissible number patterns
  pnt         = ['(?:' tSep '(?:' tSep tSep '|[^' tSep '])+' tSep ')'];                         %     QUOTED text patterns, e.g. '(?:"(?:""|[^"])+")';

  S           = [];
  fid         = fopen( fn, 'rt' );
 %T           = fread( fid, [1,inf], 'char' );                            % fails to deliver umlauts, etc
  T           = textscan(fid,'%s', 'whitespace', "", "delimiter", "\n");  % one cell containing one cell array of lines
                fclose( fid );
              
  T           = ["\n" strjoin( T{1}, "\n" )];                             % undo textscan's removal of \n, which either is a spreadsheet row separator or a text cell internal linefeed; enforce T to be a simple string
      

  [~,~,~,~,t] = regexp( T, ['([' cSep '\n])(' pnt '|' pnu ')?' ] );

  tt          = cellfun(@(x) [x,{''}](1:2)', t, 'un', 0);  % array of two-element cell arrays with first element being either "\n" or ";" and the second being the (potentially empty) string found in one spreadsheet cell
  bnl         = cellfun(@(x) x{1}=="\n", tt );             % true, where tt's cell array starts with newline "\n" indicating the start of a new spreadsheet row
  inl         = find(bnl);
  nx          = max(diff(inl));                            % # of spreadsheet columns
  ny          = length(inl);                               % # of spreadsheet rows
  S           = cell(nx,ny);
  ix=iy       = 0;
  ii          = [cellfun(@(x) [iy=iy+(x{1}=="\n"), ix=1+ix*(x{1}~="\n")]', tt, 'un', 0){:}]';   %      y,x-locations in cell matrix S for each element in tt
  ii          = (ii(:,1)-1)*nx+ii(:,2);                                                         % absolute locations in cell matrix S for each element in tt
  S(ii)       = [tt{:}](2,:);
  S           = S';
  % now, we have the spreadsheet in S as strings, with text strings framed by tSep and number strings not framed by tSep
  bne         = ~cellfun(@isempty, S );
  bt          = bne;                                                                    % true, where S not empty
  bt(bne)     = cellfun(@(x) (x(1)==tSep&x(end)==tSep), S(bne));                        % true, where S contains text strings
  bn          = bne & ~bt;                                                              % true, where S contains number strings
  S(bt)       = cellfun(@(x) x(2:end-1), S(bt), 'un', 0);                               % strip embracing tSeps
  St          = S;
  mSep        = [',.'](1+(fSep==','));                                                  % mille separator character 
  S(bn)       = cellfun(@(x) { x=x(x~=mSep), x(x==fSep)='.', x }{3}, S(bn), 'un', 0 );  % remove mille separators, enforce '.' as fractional separator

  S(bn)       = cellfun(@str2double, S(bn), 'un', 0 );                                  % convert to number 

endfunction