% XML2STRUCT Convert XML file or string (text) into a MATLAB structure.
% Also see FEX #28518: http://www.mathworks.com/matlabcentral/fileexchange/28518-xml2struct
function [theStruct,filename] = xml2struct(xmlStrOrFilename)
    % If this is an XML text string (not filename)
    if ~exist(xmlStrOrFilename,'file')
        % Store the XML string in a temporary file
        filename = [tempname '.xml'];
        fid = fopen(filename,'Wt');
        fwrite(fid,xmlStrOrFilename);
        fclose(fid);
    else
        filename = xmlStrOrFilename;
    end

    % Parse the XML file
    try
        tree = xmlread(filename);
    catch
        error('Failed to read XML file %s.',filename);
    end
    
    % Recurse over child nodes. This could run into problems with very deeply nested trees
    try
        theStruct = parseChildNodes(tree);
    catch err
        error('Unable to parse XML file %s:\n%s',filename,err.message);
    end
    try theStruct = theStruct{1}; catch, end  % de-cellize
end  % xml2struct

% -- XML2STRUCT subfunction parseChildNodes -----
function children = parseChildNodes(theNode)
    % Recurse over node children.
    children = {};
    if theNode.hasChildNodes
        childNodes = theNode.getChildNodes;
        numChildNodes = childNodes.getLength;        
        idx = 1;
        for count = 1:numChildNodes
            try
                theChild = childNodes.item(count-1);
                thisChild = makeStructFromNode(theChild);
                if ~isempty(thisChild) && (~strcmp(thisChild.Name,'#text') || ~isempty(thisChild.Data))
                    children{idx} = thisChild; %#ok<AGROW>
                    idx = idx + 1;
                end
            catch err
                unused = err; %#ok
            end
        end
    end
end  % parseChildNodes

% -- XML2STRUCT subfunction makeStructFromNode -----
function nodeStruct = makeStructFromNode(theNode)
    % Create structure of node info.
    nodeStruct = struct(                         ...
        'Name', char(theNode.getNodeName),       ...
        'Attributes', parseAttributes(theNode),  ...
        'Data', '');
    nodeStruct.Children = parseChildNodes(theNode);  % not in struct() above since might be {}!

    if any(strcmp(methods(theNode), 'getData'))
        nodeStruct.Data = strtrim(char(theNode.getData));
    else
        nodeStruct.Data = '';
    end

    % Correct leaf nodes
    if length(nodeStruct.Children)==1 && ...
       strcmp(nodeStruct.Children{1}.Name,'#text') && ...
       isempty(nodeStruct.Data)
          nodeStruct.Data = nodeStruct.Children{1}.Data;
          nodeStruct.Children = [];
    end
    
    % Correct Children tags
    for child = 1 : length(nodeStruct.Children)
        thisChild = nodeStruct.Children{child};
        childName = genvarname(thisChild.Name);
        if any(strcmp(childName,{'Name';'Attributes';'Data'})),  childName(end+1) = '_';  end %#ok<AGROW>
        if isequal(fieldnames(thisChild),{'Name';'Attributes';'Data'}) && isempty(thisChild.Attributes)
            thisChild = thisChild.Data;  % leaf node
        end
        if ~isfield(nodeStruct,childName)
            nodeStruct.(childName) = thisChild;
        else
            try
                if ischar(thisChild)
                    if ~iscell(nodeStruct.(childName))
                        nodeStruct.(childName) = {nodeStruct.(childName)};
                    end
                    nodeStruct.(childName)(end+1) = {thisChild};
                else
                    nodeStruct.(childName)(end+1) = thisChild;
                end
            catch
                % Probably an incompatible struct
                try
                    %nodeStruct.(childName) = thisChild;
                    childNum = length(nodeStruct.(childName));
                    %nodeStruct.(childName)(end+1).Name = nodeStruct.(childName)(end).Name;
                    fieldNames = fieldnames(thisChild);
                    for fieldIdx = 1 : length(fieldNames)
                        thisFieldName = fieldNames{fieldIdx};
                        nodeStruct.(childName)(childNum+1).(thisFieldName) = thisChild.(thisFieldName);
                    end
                catch
                    a = 1;  %#ok<NASGU> ignore: better to skip a field than to croak everything...
                end
            end
        end

        % Remove empty Attributes, Data fields
        try
            if isempty(nodeStruct.Attributes),  nodeStruct = rmfield(nodeStruct,'Attributes'); end
            if isempty(nodeStruct.Data),        nodeStruct = rmfield(nodeStruct,'Data'); end
        catch
            % never mind...
        end
    end
    nodeStruct = rmfield(nodeStruct,'Children');
end  % makeStructFromNode

% -- XML2STRUCT subfunction parseAttributes -----
function attributes = parseAttributes(theNode)
    % Create attributes structure.
    attributes = [];
    if theNode.hasAttributes
        theAttributes = theNode.getAttributes;
        numAttributes = theAttributes.getLength;
        %allocCell = cell(1, numAttributes);
        %attributes = struct('Name',allocCell, 'Value',allocCell);
        for count = 1 : numAttributes
            attrib = theAttributes.item(count-1);
            %attributes(count).Name = char(attrib.getName);
            %attributes(count).Value = char(attrib.getValue);
            attributes.(genvarname(char(attrib.getName))) = char(attrib.getValue);
        end
    end
end  % parseAttributes
