function [ s ] = xml2struct( file )
%Convert xml file into a MATLAB structure
% [ s ] = xml2struct( file )
%
% A file containing:
% <XMLname attrib1="Some value">
%   <Element>Some text</Element>
%   <DifferentElement attrib2="2">Some more text</Element>
%   <DifferentElement attrib3="2" attrib4="1">Even more text</DifferentElement>
% </XMLname>
%
% Will produce:
% s.XMLname.Attributes.attrib1 = "Some value";
% s.XMLname.Element.Text = "Some text";
% s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2";
% s.XMLname.DifferentElement{1}.Text = "Some more text";
% s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2";
% s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1";
% s.XMLname.DifferentElement{2}.Text = "Even more text";
%
% Please note that the following characters are substituted
% '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_'
%
% Written by W. Falkena, ASTI, TUDelft, 21-08-2010
% Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011
% Added CDATA support by I. Smirnov, 20-3-2012
%
% Modified by X. Mo, University of Wisconsin, 12-5-2012
% Modified by Sirius, 24-11-2012

    if (nargin < 1)
        clc;
        help xml2struct
        return
    end
    
    if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl')
        % input is a java xml object
        xDoc = file;
    else
        %check for existance
        if (exist(file,'file') == 0)
            %Perhaps the xml extension was omitted from the file name. Add the
            %extension and try again.
            if (isempty(strfind(file,'.xml')))
                file = [file '.xml'];
            end
            
            if (exist(file,'file') == 0)
                error(['The file ' file ' could not be found']);
            end
        end
        %read the xml file
        xDoc = xmlread(file);
    end
    
    %parse xDoc into a MATLAB structure
    s = parseChildNodes(xDoc);
    
end

% ----- Subfunction parseChildNodes -----
function children = parseChildNodes(theNode)
    % Recurse over node children.
    children = struct;
    attr = parseAttributes(theNode);
    if ~isempty(attr)
        children.Attributes = attr;
    end

    theChild = getFirstChild(theNode);
    while ~isempty(theChild)
        type = getNodeType(theChild);

        if type==theChild.TEXT_NODE||type==theChild.CDATA_SECTION_NODE
            text = toCharArray(getTextContent(theChild))';
            if ~isempty(regexprep(text,'[\s]*',''))
                if ~isfield(children,'Text')
                    children.Text = text;
                else
                    %just append the text
                    children.Text = [children.Text text];
                end
            end
        else
            if type==theChild.COMMENT_NODE
                name = 'Comment';
                childs = toCharArray(getTextContent(theChild))';
            else
                name = xmlName2Chars(getNodeName(theChild));

                %parse child nodes
                childs = parseChildNodes(theChild);
            end
		
            %XML allows the same elements to be defined multiple times,
            %put each in a different cell
            if (isfield(children,name))
                if (~iscell(children.(name)))
                    %put existsing element into cell format
                    children.(name) = {children.(name)};
                end
                %add new element
                children.(name){end+1} = childs;
            else
                %add previously unknown (new) element to the structure
                children.(name) = childs;
            end
        end
        theChild = getNextSibling(theChild);
    end
end

% ----- Subfunction parseAttributes -----
function attributes = parseAttributes(theNode)
    % Create attributes structure.

    if hasAttributes(theNode)
       attributes = struct;
       theAttributes = getAttributes(theNode);
       numAttributes = getLength(theAttributes);

       for count = 1:numAttributes
            attrib = item(theAttributes,count-1);
            attr_name = xmlName2Chars(getName(attrib));
            attributes.(attr_name) = toCharArray(getValue(attrib))';
       end
    else
       attributes = [];
    end
end

% ----- Subfunction xmlName2Chars -----
function chars = xmlName2Chars(name)
    %make sure name is allowed as structure name
    chars = toCharArray(name)';
    chars = strrep(chars, '-', '_dash_');
    chars = strrep(chars, ':', '_colon_');
    chars = strrep(chars, '.', '_dot_');
end
