Code covered by the BSD License  

Highlights from
writeexcel

from writeexcel by Peder Axensten
Writes data to an Excel xml file on any system (NO Excel or ActiveX required!).

writeexcel(file, varargin)
function writeexcel(file, varargin)
	
% Usage: writeexcel(file, varlist)
% 
% Writes data to an Excel xml file on any system (NO Excel or ActiveX required!).
%
% Scalars will end up on a sheet called "Scalars" as name (in the first column) and
%   value (in the second column) pairs. Arrays and cell arrays are saved on individual 
%   sheets, named by the variable name.
% 
% If you find any errors, please let me know at peder at axensten dot se. 
%   This is still pretty much work in progress, see 'to do', below. 
% 
% ARGUMENTS:
% file        File name path. If prefixed with a '>' (see example, below), the data will 
%             be added to an existing file (that was generated by this macro!).
% varlist     List of variables, expressions, and/or strings. Variables and expressions 
%             will be written to the file, expressions must be preceded by a '\name', below.
%             A special case is if a variable of type structure is called DocumentProperties,
%             its field names and values will set the document properties (see the example).
%             Strings can be of the following:
%             - '\name' sets the name of the next variable or expression, names must be unique.
% 
% EXAMPLE:
% DocumentProperties= struct('Title','My Title','Author','Me','Subject','very interesting');
% astring= 'Hi ya!';
% ascalar= pi;
% anarray= [1 2 3; 4 5 6];
% acellarr= {'abc', 'def'; pi, NaN; -Inf Inf};
% writeexcel('Workbook.xml', anarray, ascalar, astring, acellarr, DocumentProperties);
% writeexcel('>Workbook.xml', '\another scalar', 2*pi/5);
% 
% HISTORY:
% Version 1.0, first working version, 2007-09-04.
% Version 1.1, released 2007-09-06:
% - Fixed a bug when saving vectors.
% - Fixed a bug when adding scalars when there previously were none.
% - Now UTF-8 works.
% - Added check for possible fopen problems.
% - Added better input argument check.
% - Can now add document metadata through structure named DocumentProperties.
% - Writing structures too, columns named by fieldnames (the struct class).
% - Writing symbolic expressions too (the sym class).
% Version 1.2, released 2007-09-18:
% - Fixed mlint warnings.
% - Fixed problem with certain characters in meta data.
% - Removed all regexprep calls for better compatibility.
% - Removed automatic .xls suffix.
%
% TO DO:
% - '%format' or {'%form1', '%form2', etc.} to control how columns will be shown (numbers will be 
%   stored with 'all' decimals, but the column formats will be set)
% 
% COPYRIGHT:  (c) 2007 Peder Axensten. Use at own risk.
%
% KEYWORDS:   save, export, write, Microsoft, Excel, xml
% 
% REQS:       Does NOT require Excel, Active X, or a Windows system!
%
% REFERENCE:  http://msdn2.microsoft.com/en-us/library/aa140066(office.10).aspx

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	% Check the arguments.
	if(nargin < 1),				error('At least one arguments!');			end
	
	% Get the name of the calling function, if there is one.
	thesource=		dbstack;
	if(numel(thesource) < 2)
		thesource=		sprintf('%s, on %s', 'Command Window', datestr(now));
	else
		thesource=		sprintf('%s, line %u, on %s', thesource(2).file, thesource(2).line, datestr(now));
	end
	
	% Initialise.
	scalarsend=		sprintf('\t\t<!-- END OF SCALARS HERE! -->\n');
	worksheetsstart=sprintf('\t<!-- START OF WORKSHEETS HERE! -->\n');
	worksheetsend=	sprintf('\t<!-- END OF WORKSHEETS HERE! -->\n');
	docpropsend=	sprintf('\t\t<!-- END OF DOCUMENTPROPERTIES HERE! -->\n');
	tags.docprop=	sprintf('%%s\t\t<%%s>%%s</%%s>\n');
	tags.doc=		sprintf('%s', ...
						'<?xml version="1.0" encoding="UTF-8"?>\n', ...
						'<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n', ...
						'\txmlns:html="http://www.w3.org/TR/REC-html40"\n', ...
						'\txmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">\n', ...
						'\t<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">\n', ...
						sprintf(tags.docprop, '', 'AppName', 'MATLAB', 'AppName'), ...
						sprintf(tags.docprop, '', 'Category', 'MATLAB generated data', 'Category'), ...
						docpropsend, ...
						'\t</DocumentProperties>\n', ...
						'\t<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">\n', ...
						'\t\t<WindowHeight>15000</WindowHeight>\n', ...
						'\t\t<WindowWidth>13000</WindowWidth>\n', ...
						'\t\t<WindowTopX>10</WindowTopX>\n', ...
						'\t\t<WindowTopY>40</WindowTopY>\n', ...
						'\t</ExcelWorkbook>\n', ...
						'\t<Styles>\n', ...
						['\t\t<Style ss:ID="ColHead"><Font ss:Bold="1"/>', ...
											'<Alignment ss:Horizontal="Center"/></Style>\n'], ...
						'\t\t<Style ss:ID="RowHead"><Font ss:Bold="1"/></Style>\n', ...
						'\t</Styles>\n', ...
						worksheetsstart, ...
						'%s', ...
						worksheetsend, ...
						'</Workbook>\n');
	tags.sheet=		['%s\t<Worksheet ss:Name="%s"><Table>\n\t\t<!-- Added by %s -->\n', ...
											'%s\t</Table></Worksheet>\n'];
	tags.row=		'%s\t\t<Row>\n%s\t\t</Row>\n';
	tags.col=		'%s\t\t\t<Cell><Data ss:Type="%s">%s</Data></Cell>\n';
	tags.scalar=	sprintf(tags.col, '', 'String', '%s');
	tags.scalar=	sprintf(tags.col, tags.scalar, '%s', '%s');
	tags.scalar=	sprintf(tags.row, '%s', [sprintf('\t\t\t<!-- Added by %%s. -->\n') tags.scalar]);
	tags.funcol=	'%s\t\t\t<Cell ss:Formula="=%s"><Data ss:Type="Number"/></Cell>\n';
	
	% Create the sheets.
	argname0=	'';
	thesheets=	'';
	thescalars=	'';
	docprops=	'';
	for arglist= 1:length(varargin)	% Loop thru the arguments.
		therows=	'';
		argval=		varargin{arglist};
		argname=	inputname(1+arglist);
		if(~isempty(argname0))
			argname=		argname0;
		end
		if(isempty(argname))		% This argument is not a variable
			if(argval(1) == '\')	% It's a string defining the name of next expression.
				argname0=		argval(2:end);
			elseif(ischar(argval))	% It's some other kind of string.
				warning('pax:writeexcel:argcheck', 'Unrecognized string in argument %d: "%s"', arglist+1, argval);
			else					% It's some other expression.
				warning('pax:writeexcel:argcheck', 'Unnamed expression in argument %d (class %s).', ...
																				arglist+1, class(argval));
			end
		elseif(strcmp('DocumentProperties', argname) && isstruct(argval) && (numel(argval) == 1))	
			% This argument is a variable called DocumentProperties.
			fields=			fieldnames(argval);
			for n= 1:length(fields)
				if(~ischar(argval.(fields{n})))
					error('All field values must be strings in DocumentProperties!');
				end
				docprops=		sprintf(tags.docprop, docprops,fields{n}, cleanup(argval.(fields{n})), fields{n});
			end
		else
			% This argument is a variable to be saved.
			argval=			do2cell(argval, arglist+1);
			if(numel(argval) == 1)
				% This is a scalarm, add it to the scalars' sheet.
				[kind, val]=	oneitem(argval{1, 1}, arglist+1, 1, 1);
				thescalars=		sprintf(tags.scalar, thescalars, thesource, argname, kind, val);
			else
				% Not a scalar: give it a sheet of its own.
				for row= 1:size(argval, 1)
					thecols=		'';
					for col= 1:size(argval, 2)
						[kind, val]=	oneitem(argval{row, col}, arglist+1, row, col);
						thecols=		sprintf(tags.col, thecols, kind, val);
					end
					therows=		sprintf(tags.row, therows, thecols);
				end
				thesheets=		sprintf(tags.sheet, thesheets, argname, thesource, therows);
			end
			argname0=		'';
		end
	end
	
	% Save the file.
	if(file(1) == '>')
		% We are adding data to a previous file.
		file=		file(2:end);
		[fh, err]=	fopen(file, 'r', 'native', 'UTF-8');
		if(~isempty(err)),	error([err ': ' file(2:end)]);		end
		xls=	fread(fh, '*char')';
		fclose(fh);
		
		% Add the new scalars to xls!
		if(~isempty(thescalars) && isempty(findstr(xls, scalarsend)))
			% We have scalars now, but there were none stored in the file previously.
			thescalars=	sprintf(tags.sheet, '', 'Scalars', '(check each row for source)', ...
						[thescalars scalarsend]);
			xls=		strrep(xls, worksheetsstart, thescalars);
		else
			% Add present scalars to previous.
			xls=		strrep(xls, scalarsend, [thescalars scalarsend]);
		end
		% Add the new sheets to xls!
		xls=		strrep(xls, worksheetsend, [thesheets worksheetsend]);
	else
		% There is no previous file.
		if(length(thescalars)+length(thesheets) == 0)
			% There is nothing to save at the moment, create an empty xml file.
			thescalars=	sprintf(tags.scalar, '', thesource, '', 'String', '');
		end
		if(~isempty(thescalars))
			% There are scalars, at their sheet.
			thescalars=	sprintf(tags.sheet, '', 'Scalars', '(check each row for source)', ...
						[thescalars scalarsend]);
		end
		% Complete the xml document.
		xls=		sprintf(tags.doc, [thescalars thesheets]);
	end
	
	% Add the new document properties to xls!
	xls=		strrep(xls, docpropsend, [docprops docpropsend]);
	
	% Save the results.
	[fh, err]=	fopen(file, 'w', 'native', 'UTF-8');
	if(~isempty(err)),	error([err ': ' file]);		end
	fprintf(fh, xls);
	fclose(fh);
	
	% Check that all worksheet names are unique!
	names=		regexpi(xls, '<Worksheet ss:Name="[^""]*">', 'match');
	if(size(names, 2) ~= size(unique(names), 2))
		warning('pax:writeexcel:argcheck', ...
				[	'There are multiple work-sheets with the same name. ' ...
					'Excel will probably not open this file. ' ...
					'Check that the same variable name is not used more than once.']);
	end
end


function val= do2cell(val, argnum)
	err=	['Problem in argument ' num2str(argnum) ': Number of dimensions must not exceed %d for %s'];
	switch class(val)
		case 'cell'
			if(ndims(val) > 2),				error(err, 2, 'cell arrays');		end
		case 'struct'
			if(numel(val) > length(val)),	error(err, 1, 'structures');		end
			val= cat(2, fieldnames(val), squeeze(struct2cell(val)))';
		case 'char'
			if(ndims(val) > 3), 			error(err, 3, 'char arrays');		end
			val= cellstr(val);
		case 'function_handle'
			if(ndims(val) > 2), 			error(err, 2, 'function handles');	end
			val= cellstr(func2str(val));
		case {'double', 'single'}
			if(ndims(val) > 2), 			error(err, 2, 'numeric arrays');	end
			val= num2cell(val);
		case 'sym'
			if(ndims(val) > 2), 			error(err, 2, 'symbolic expressions');	end
			val= squeeze(cell(val));
		case {'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64'}
			if(ndims(val) > 2), 			error(err, 2, 'numeric arrays');	end
			val= num2cell(val);
		otherwise
			error(['Problem in argument ' num2str(argnum) ': Unrecognised class ' class(val)]);
	end
end


function [kind, val]= oneitem(val, argnum, row, col)
	switch class(val)
		case 'char'
			kind=		'String';
		case 'function_handle'
			kind=		'String';
			val=		func2str(val);
		case 'logical'
			kind=		'Boolean';
			val=		num2str(val);
		case {'double','single','int8','uint8','int16','uint16','int32','uint32','int64','uint64'}
			if(numel(val) > 1)
				error('Problem in argument %d row %d col %d: Cell array may not contain an array', ...
				 	argnum, row, col);
			end
			if(isnan(val))
				kind=		'String';
				val=		'&ndash;';
			elseif(isinf(val))
				kind=		'String';
				if(val < 0),	val=	'-&infin;';
				else			val=	'&infin;';
				end
			else
				kind=		'Number';
				val=		num2str(val);
			end
		case {'cell','struct'}
			error('Problem in argument %d row %d col %d: Cell array may not contain %s', ...
			 	argnum, row, col, class(val));
		otherwise
			error('Problem in argument %d row %d col %d: Unrecognised class %s', ...
			 	argnum, row, col, class(val));
	end
	val=		cleanup(val);
end


function s= cleanup(s)
	s=		strrep(s, '<', '&lt;');
	s=		strrep(s, '>', '&gt;');
end

Contact us at files@mathworks.com