diff --git a/private/addtrirep.m b/private/addtrirep.m new file mode 100644 index 0000000..6e4045c --- /dev/null +++ b/private/addtrirep.m @@ -0,0 +1,52 @@ +function tNew = addtrirep(varargin) +%ADDTRIREP creates a new TriRep that contains tA and tB. +% Usage: +% tNew = addtrirep(tA, tB, tC ....) +% tNew = addtrirep({tA, tB, tC ...}) +% Where: +% tA,tB etc are TriRep +% +% ADDTRIREP creates a new TriRep that contains tA and tB. +% +% Author: Nick Linton (2011) +% Modifications - + + if nargin == 1 + tCell = varargin{1}; + else + tCell = varargin; + end + + nV = 0; + nT = 0; + for i = 1:length(tCell) + if ~isa(tCell{i},'TriRep') + error('ADDTRIREP: the input must be TriRep objects or a cell array of TriRep objects.') + end + nV = nV + size(tCell{i}.X,1); + nT = nT + size(tCell{i}.Triangulation,1); + end + + newT = zeros(nT,3); + newX = zeros(nV,3); + + currentV = 1; + currentT = 1; + for i = 1:length(tCell) + if ~isa(tCell{i},'TriRep') + error('ADDTRIREP: the input must be TriRep objects or a cell array of TriRep objects.') + end + nV = size(tCell{i}.X,1); + nT = size(tCell{i}.Triangulation,1); + newX(currentV:(currentV+nV-1),:) = tCell{i}.X; + newT(currentT:(currentT+nT-1),:) = tCell{i}.Triangulation + currentV-1; + currentV = currentV+nV; + currentT = currentT+nT; + end + + tNew = TriRep(newT, newX); + +end + + + diff --git a/private/xml_io_tools/base64decode.m b/private/base64decode.m similarity index 100% rename from private/xml_io_tools/base64decode.m rename to private/base64decode.m diff --git a/private/xml_io_tools/base64encode.m b/private/base64encode.m similarity index 100% rename from private/xml_io_tools/base64encode.m rename to private/base64encode.m diff --git a/private/colorBrewer.m b/private/colorBrewer.m new file mode 100644 index 0000000..87f5f97 --- /dev/null +++ b/private/colorBrewer.m @@ -0,0 +1,65 @@ +function rgb = colorBrewer(name) +% COLORBREWER Returns RGB data for the nice colors +% +% Usage: +% rgb = colorBrewer(name) +% Where: +% name - is 'r'|'g'|'b'|'p'|'o'|'y' +% rgb - is the RGB colorspec +% +% COLORBREWER See http://colorbrewer2.org/ +% +% Author: Steven Williams (2014) +% Modifications - +% +% Info on Code Testing: +% --------------------------------------------------------------- +% test code +% --------------------------------------------------------------- +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +if isnumeric(name) + if name==1 + rgb = [228 26 28]/256; + elseif name==2 + rgb = [55 126 184]/256; + elseif name==3 + rgb = [77 175 74]/256; + elseif name==4 + rgb = [152 78 163]/256; + elseif name==5 + rgb = [255 127 0]/256; + elseif name==6 + rgb = [255 255 51]/256; + elseif name==7 + rgb = [166 86 40]/256; + elseif name==8 + rgb = [247 129 191]/256; + elseif name==9 + rgb = [153 153 153]/256; + end +else + switch name + case 'r' + rgb = [228 26 28]/256; + case 'g' + rgb = [77 175 74]/256; + case 'b' + rgb = [55 126 184]/256; + case 'p' + rgb = [152 78 163]/256; + case 'o' + rgb = [255 127 0]/256; + case 'y' + rgb = [255 255 51]/256; + case 'g1' + rgb = [27 158 119]/256; + case 'o1' + rgb = [217 95 2]/256; + case 'p1' + rgb = [117 112 179]/256; + end +end diff --git a/private/deleteFolder.m b/private/deleteFolder.m new file mode 100644 index 0000000..7fd6638 --- /dev/null +++ b/private/deleteFolder.m @@ -0,0 +1,59 @@ +function deleteFolder( folderPath, varargin ) +% DELETEFOLDER Deletes the folder folderPath +% +% Usage: +% deleteFolder( folderPath ) +% Where: +% folderPath - the full path to the folder +% +% DELETEFOLDER Detailed description goes here +% +% DELETEFOLDER accepts the following parameter-value pairs +% 'verbose' {false}|true +% +% Author: Steven Williams (2020) +% Modifications - +% +% Info on Code Testing: +% --------------------------------------------------------------- +% test code +% --------------------------------------------------------------- +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +% Parse input +nStandardArgs = 1; % UPDATE VALUE +verbose = false; +if nargin > nStandardArgs + for i = nStandardArgs+1:2:nargin + switch varargin{i} + case 'verbose' + verbose = varargin{i+1}; + end + end +end + +% Check to make sure that folder actually exists. Warn user if it doesn't. +if ~isdir(folderPath) + error('DELETEFOLDER: Specified path does not exist') + return; +end + +% Get a list of all files in the folder with the desired file name pattern. +theFiles = nameFiles(folderPath); + +% Delete each file one by one +for k = 1 : length(theFiles) + fullFileName = fullfile(folderPath, theFiles{k}); + if verbose + fprintf(1, 'Now deleting %s\n', fullFileName); + end + delete(fullFileName); +end + +% Remove the directory +rmdir(folderPath) + +end \ No newline at end of file diff --git a/private/du.m b/private/du.m new file mode 100644 index 0000000..0386bcd --- /dev/null +++ b/private/du.m @@ -0,0 +1,33 @@ +function cmdout = du( argument ) +% DU is a wrapper function for the Unix file system tool du +% +% Usage: +% cmdout = du(argument) +% Where: +% argument - is the input provided to do +% cmdout - is the information returned by du in a string +% +% DU Detailed description goes here +% +% Author: Steven Williams (2020) +% Modifications - +% +% Info on Code Testing: +% --------------------------------------------------------------- +% test code +% --------------------------------------------------------------- +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +%create the system call +arg = ['du ' argument]; + +%system call +[status, cmdout] = system(arg); + +%remove the carriage return from the end of cmdout +cmdout(end) = []; + +end \ No newline at end of file diff --git a/private/editTriangulation.m b/private/editTriangulation.m new file mode 100644 index 0000000..3cf51ac --- /dev/null +++ b/private/editTriangulation.m @@ -0,0 +1,70 @@ +function [tr2, isVertUsed] = editTriangulation(tr) +% EDITTRIANGULATION Graphically remove triangles from a TriRep object +% +% Usage: +% tr2 = editTriangulation(tr) +% Where: +% tr - is the original triangulation +% tr2 - is the new triangulation with elements removed +% isVertUsed - indexes into tr.X for vertices that are used in the new +% triangulation, tr2 +% +% EDITTRIANGULATION uses GET3DFACES to remove triangles from a TriRep +% object. Controls: +% Left click - select triangles to remove +% Shift-Left click - select triangles to keep +% Ctrl-Left click - select area up to the boundary +% d - done +% +% Author: Steven Williams (2013) +% Modifications - +% 2016 - refactored by moving userdata manipulation into a separate fcn +% +% Info on Code Testing: +% --------------------- +% +% --------------------- +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +% Edit to remove the valve orifice +disp('E D I T T R I A N G U L A T I O N'); +disp('-----------------------------------'); +disp('Left click - select triangles to remove (shift to undo)'); +disp('Cltr-left - select area'); +disp('Press d when done'); +disp(''); + +editFig = figure; + +% Construct the trirep +no = tr.X; %node +el = tr.Triangulation; %element + +hP = trisurf(tr); +set(hP, 'FaceVertexCData', zeros(length(tr.Triangulation),3)+0.7 ... + ,'FaceColor', 'flat' ... + ); +axis equal vis3d +light + +get3dfaces(hP,'on'); + +%Wait until a key is pressed, then only progressif it was 'd' +key = get(editFig, 'CurrentCharacter'); +while ~strcmpi(key, 'd') + pause; + key = get(editFig, 'CurrentCharacter'); +end + +% Now, continue by creating the new TriRep +iRemove = get3dfaces(hP); +close(editFig); + +el(iRemove, :) = []; +tr2 = repack(TriRep(el, no)); + +[tr2, isVertUsed] = repack(tr2); +end \ No newline at end of file diff --git a/private/extract_coordinates_from_path.m b/private/extract_coordinates_from_path.m new file mode 100644 index 0000000..9ad0e4a --- /dev/null +++ b/private/extract_coordinates_from_path.m @@ -0,0 +1,17 @@ +function [x,y,z] = extract_coordinates_from_path(path) + +%this elegant way is incompatible with the older versions of matlab +% x = cellfun(@(p) p.x, path); %the simplest way to extract coordinates from the path +% y = cellfun(@(p) p.y, path); %if it looks complicated, you can use "for" similar to example1.m +% z = cellfun(@(p) p.z, path); + +x = zeros(length(path),1); +y = x; +z = y; + +for i=1:length(path) + x(i) = path{i}.x; + y(i) = path{i}.y; + z(i) = path{i}.z; +end; + diff --git a/private/findjobj.m b/private/findjobj.m new file mode 100644 index 0000000..608c38c --- /dev/null +++ b/private/findjobj.m @@ -0,0 +1,3210 @@ +function [handles,levels,parentIdx,listing] = findjobj(container,varargin) +%findjobj Find java objects contained within a specified java container or Matlab GUI handle +% +% Syntax: +% [handles, levels, parentIds, listing] = findjobj(container, 'PropName',PropValue(s), ...) +% +% Input parameters: +% container - optional handle to java container uipanel or figure. If unsupplied then current figure will be used +% 'PropName',PropValue - optional list of property pairs (case insensitive). PropName may also be named -PropName +% 'position' - filter results based on those elements that contain the specified X,Y position or a java element +% Note: specify a Matlab position (X,Y = pixels from bottom left corner), not a java one +% 'size' - filter results based on those elements that have the specified W,H (in pixels) +% 'class' - filter results based on those elements that contain the substring (or java class) PropValue +% Note1: filtering is case insensitive and relies on regexp, so you can pass wildcards etc. +% Note2: '-class' is an undocumented findobj PropName, but only works on Matlab (not java) classes +% 'property' - filter results based on those elements that possess the specified case-insensitive property string +% Note1: passing a property value is possible if the argument following 'property' is a cell in the +% format of {'propName','propValue'}. Example: FINDJOBJ(...,'property',{'Text','click me'}) +% Note2: partial property names (e.g. 'Tex') are accepted, as long as they're not ambiguous +% 'depth' - filter results based on specified depth. 0=top-level, Inf=all levels (default=Inf) +% 'flat' - same as specifying: 'depth',0 +% 'not' - negates the following filter: 'not','class','c' returns all elements EXCEPT those with class 'c' +% 'persist' - persist figure components information, allowing much faster results for subsequent invocations +% 'nomenu' - skip menu processing, for "lean" list of handles & much faster processing; +% This option is the default for HG containers but not for figure, Java or no container +% 'print' - display all java elements in a hierarchical list, indented appropriately +% Note1: optional PropValue of element index or handle to java container +% Note2: normally this option would be placed last, after all filtering is complete. Placing this +% option before some filters enables debug print-outs of interim filtering results. +% Note3: output is to the Matlab command window unless the 'listing' (4th) output arg is requested +% 'list' - same as 'print' +% 'debug' - list found component positions in the Command Window +% +% Output parameters: +% handles - list of handles to java elements +% levels - list of corresponding hierarchy level of the java elements (top=0) +% parentIds - list of indexes (in unfiltered handles) of the parent container of the corresponding java element +% listing - results of 'print'/'list' options (empty if these options were not specified) +% +% Note: If no output parameter is specified, then an interactive window will be displayed with a +% ^^^^ tree view of all container components, their properties and callbacks. +% +% Examples: +% findjobj; % display list of all javaelements of currrent figure in an interactive GUI +% handles = findjobj; % get list of all java elements of current figure (inc. menus, toolbars etc.) +% findjobj('print'); % list all java elements in current figure +% findjobj('print',6); % list all java elements in current figure, contained within its 6th element +% handles = findjobj(hButton); % hButton is a matlab button +% handles = findjobj(gcf,'position',getpixelposition(hButton,1)); % same as above but also return hButton's panel +% handles = findjobj(hButton,'persist'); % same as above, persist info for future reuse +% handles = findjobj('class','pushbutton'); % get all pushbuttons in current figure +% handles = findjobj('class','pushbutton','position',123,456); % get all pushbuttons at the specified position +% handles = findjobj(gcf,'class','pushbutton','size',23,15); % get all pushbuttons with the specified size +% handles = findjobj('property','Text','not','class','button'); % get all non-button elements with 'text' property +% handles = findjobj('-property',{'Text','click me'}); % get all elements with 'text' property = 'click me' +% +% Sample usage: +% hButton = uicontrol('string','click me'); +% jButton = findjobj(hButton,'nomenu'); +% % or: jButton = findjobj('property',{'Text','click me'}); +% jButton.setFlyOverAppearance(1); +% jButton.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); +% set(jButton,'FocusGainedCallback',@myMatlabFunction); % some 30 callback points available... +% jButton.get; % list all changeable properties... +% +% hEditbox = uicontrol('style','edit'); +% jEditbox = findjobj(hEditbox,'nomenu'); +% jEditbox.setCaretColor(java.awt.Color.red); +% jEditbox.KeyTypedCallback = @myCallbackFunc; % many more callbacks where this came from... +% jEdit.requestFocus; +% +% Technical explanation & details: +% http://undocumentedmatlab.com/blog/findjobj/ +% http://undocumentedmatlab.com/blog/findjobj-gui-display-container-hierarchy/ +% +% Known issues/limitations: +% - Cannot currently process multiple container objects - just one at a time +% - Initial processing is a bit slow when the figure is laden with many UI components (so better use 'persist') +% - Passing a simple container Matlab handle is currently filtered by its position+size: should find a better way to do this +% - Matlab uipanels are not implemented as simple java panels, and so they can't be found using this utility +% - Labels have a write-only text property in java, so they can't be found using the 'property',{'Text','string'} notation +% +% Warning: +% This code heavily relies on undocumented and unsupported Matlab functionality. +% It works on Matlab 7+, but use at your own risk! +% +% Bugs and suggestions: +% Please send to Yair Altman (altmany at gmail dot com) +% +% Change log: +% 2013-06-30: Additional fixes for the upcoming HG2 +% 2013-05-15: Fix for the upcoming HG2 +% 2013-02-21: Fixed HG-Java warnings +% 2013-01-23: Fixed callbacks table grouping & editing bugs; added hidden properties to the properties tooltip; updated help section +% 2013-01-13: Improved callbacks table; fixed tree refresh failure; fixed: tree node-selection didn't update the props pane nor flash the selected component +% 2012-07-25: Fixes for R2012a as well as some older Matlab releases +% 2011-12-07: Fixed 'File is empty' messages in compiled apps +% 2011-11-22: Fix suggested by Ward +% 2011-02-01: Fixes for R2011a +% 2010-06-13: Fixes for R2010b; fixed download (m-file => zip-file) +% 2010-04-21: Minor fix to support combo-boxes (aka drop-down, popup-menu) on Windows +% 2010-03-17: Important release: Fixes for R2010a, debug listing, objects not found, component containers that should be ignored etc. +% 2010-02-04: Forced an EDT redraw before processing; warned if requested handle is invisible +% 2010-01-18: Found a way to display label text next to the relevant node name +% 2009-10-28: Fixed uitreenode warning +% 2009-10-27: Fixed auto-collapse of invisible container nodes; added dynamic tree tooltips & context-menu; minor fix to version-check display +% 2009-09-30: Fix for Matlab 7.0 as suggested by Oliver W; minor GUI fix (classname font) +% 2009-08-07: Fixed edge-case of missing JIDE tables +% 2009-05-24: Added support for future Matlab versions that will not support JavaFrame +% 2009-05-15: Added sanity checks for axes items +% 2009-04-28: Added 'debug' input arg; increased size tolerance 1px => 2px +% 2009-04-23: Fixed location of popupmenus (always 20px high despite what's reported by Matlab...); fixed uiinspect processing issues; added blog link; narrower action buttons +% 2009-04-09: Automatic 'nomenu' for uicontrol inputs; significant performance improvement +% 2009-03-31: Fixed position of some Java components; fixed properties tooltip; fixed node visibility indication +% 2009-02-26: Indicated components visibility (& auto-collapse non-visible containers); auto-highlight selected component; fixes in node icons, figure title & tree refresh; improved error handling; display FindJObj version update description if available +% 2009-02-24: Fixed update check; added dedicated labels icon +% 2009-02-18: Fixed compatibility with old Matlab versions +% 2009-02-08: Callbacks table fixes; use uiinspect if available; fix update check according to new FEX website +% 2008-12-17: R2008b compatibility +% 2008-09-10: Fixed minor bug as per Johnny Smith +% 2007-11-14: Fixed edge case problem with class properties tooltip; used existing object icon if available; added checkbox option to hide standard callbacks +% 2007-08-15: Fixed object naming relative property priorities; added sanity check for illegal container arg; enabled desktop (0) container; cleaned up warnings about special class objects +% 2007-08-03: Fixed minor tagging problems with a few Java sub-classes; displayed UIClassID if text/name/tag is unavailable +% 2007-06-15: Fixed problems finding HG components found by J. Wagberg +% 2007-05-22: Added 'nomenu' option for improved performance; fixed 'export handles' bug; fixed handle-finding/display bugs; "cleaner" error handling +% 2007-04-23: HTMLized classname tooltip; returned top-level figure Frame handle for figure container; fixed callbacks table; auto-checked newer version; fixed Matlab 7.2 compatibility issue; added HG objects tree +% 2007-04-19: Fixed edge case of missing figure; displayed tree hierarchy in interactive GUI if no output args; workaround for figure sub-menus invisible unless clicked +% 2007-04-04: Improved performance; returned full listing results in 4th output arg; enabled partial property names & property values; automatically filtered out container panels if children also returned; fixed finding sub-menu items +% 2007-03-20: First version posted on the MathWorks file exchange: http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317 +% +% See also: +% java, handle, findobj, findall, javaGetHandles, uiinspect (on the File Exchange) + +% License to use and modify this code is granted freely to all interested, as long as the original author is +% referenced and attributed as such. The original author maintains the right to be solely associated with this work. + +% Programmed and Copyright by Yair M. Altman: altmany(at)gmail.com +% $Revision: 1.39 $ $Date: 2013/06/30 22:34:52 $ + + % Ensure Java AWT is enabled + error(javachk('awt')); + + persistent pContainer pHandles pLevels pParentIdx pPositions + + try + % Initialize + handles = handle([]); + levels = []; + parentIdx = []; + positions = []; % Java positions start at the top-left corner + %sizes = []; + listing = ''; + hg_levels = []; + hg_handles = handle([]); % HG handles are double + hg_parentIdx = []; + nomenu = false; + menuBarFoundFlag = false; + + % Force an EDT redraw before processing, to ensure all uicontrols etc. are rendered + drawnow; pause(0.02); + + % Default container is the current figure's root panel + if nargin + if isempty(container) % empty container - bail out + return; + elseif ischar(container) % container skipped - this is part of the args list... + varargin = {container, varargin{:}}; + origContainer = getCurrentFigure; + [container,contentSize] = getRootPanel(origContainer); + elseif isequal(container,0) % root + origContainer = handle(container); + container = com.mathworks.mde.desk.MLDesktop.getInstance.getMainFrame; + contentSize = [container.getWidth, container.getHeight]; + elseif ishghandle(container) % && ~isa(container,'java.awt.Container') + container = container(1); % another current limitation... + hFig = ancestor(container,'figure'); + origContainer = handle(container); + if isa(origContainer,'uimenu') + % getpixelposition doesn't work for menus... - damn! + varargin = {'class','MenuPeer', 'property',{'Label',strrep(get(container,'Label'),'&','')}, varargin{:}}; + elseif ~isa(origContainer, 'figure') && ~isempty(hFig) + % See limitations section above: should find a better way to directly refer to the element's java container + try + % Note: 'PixelBounds' is undocumented and unsupported, but much faster than getpixelposition! + % ^^^^ unfortunately, its Y position is inaccurate in some cases - damn! + %size = get(container,'PixelBounds'); + pos = fix(getpixelposition(container,1)); + %varargin = {'position',pos(1:2), 'size',pos(3:4), 'not','class','java.awt.Panel', varargin{:}}; + catch + try + figName = get(hFig,'name'); + if strcmpi(get(hFig,'number'),'on') + figName = regexprep(['Figure ' num2str(hFig) ': ' figName],': $',''); + end + mde = com.mathworks.mde.desk.MLDesktop.getInstance; + jFig = mde.getClient(figName); + if isempty(jFig), error('dummy'); end + catch + warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility + jFig = get(get(hFig,'JavaFrame'),'FigurePanelContainer'); + end + pos = []; + try + pxsize = get(container,'PixelBounds'); + pos = [pxsize(1)+5, jFig.getHeight - (pxsize(4)-5)]; + catch + % never mind... + end + end + if size(pos,2) == 2 + pos(:,3:4) = 0; + end + if ~isempty(pos) + if isa(handle(container),'uicontrol') && strcmp(get(container,'style'),'popupmenu') + % popupmenus (combo-box dropdowns) are always 20px high + pos(2) = pos(2) + pos(4) - 20; + pos(4) = 20; + end + %varargin = {'position',pos(1:2), 'size',size(3:4)-size(1:2)-10, 'not','class','java.awt.Panel', varargin{:}}; + varargin = {'position',pos(1:2)+[0,pos(4)], 'size',pos(3:4), 'not','class','java.awt.Panel', 'nomenu', varargin{:}}; + end + elseif isempty(hFig) + hFig = handle(container); + end + [container,contentSize] = getRootPanel(hFig); + elseif isjava(container) + % Maybe a java container obj (will crash otherwise) + origContainer = container; + contentSize = [container.getWidth, container.getHeight]; + else + error('YMA:findjobj:IllegalContainer','Input arg does not appear to be a valid GUI object'); + end + else + % Default container = current figure + origContainer = getCurrentFigure; + [container,contentSize] = getRootPanel(origContainer); + end + + % Check persistency + if isequal(pContainer,container) + % persistency requested and the same container is reused, so reuse the hierarchy information + [handles,levels,parentIdx,positions] = deal(pHandles, pLevels, pParentIdx, pPositions); + else + % Pre-allocate space of complex data containers for improved performance + handles = repmat(handles,1,1000); + positions = zeros(1000,2); + + % Check whether to skip menu processing + nomenu = paramSupplied(varargin,'nomenu'); + + % Traverse the container hierarchy and extract the elements within + traverseContainer(container,0,1); + + % Remove unnecessary pre-allocated elements + dataLen = length(levels); + handles (dataLen+1:end) = []; + positions(dataLen+1:end,:) = []; + end + + % Process persistency check before any filtering is done + if paramSupplied(varargin,'persist') + [pContainer, pHandles, pLevels, pParentIdx, pPositions] = deal(container,handles,levels,parentIdx,positions); + end + + % Save data for possible future use in presentObjectTree() below + allHandles = handles; + allLevels = levels; + allParents = parentIdx; + selectedIdx = 1:length(handles); + %[positions(:,1)-container.getX, container.getHeight - positions(:,2)] + + % Debug-list all found compponents and their positions + if paramSupplied(varargin,'debug') + for handleIdx = 1 : length(allHandles) + thisObj = handles(handleIdx); + pos = sprintf('%d,%d %dx%d',[positions(handleIdx,:) getWidth(thisObj) getHeight(thisObj)]); + disp([repmat(' ',1,levels(handleIdx)) '[' pos '] ' char(toString(thisObj))]); + end + end + + % Process optional args + % Note: positions is NOT returned since it's based on java coord system (origin = top-left): will confuse Matlab users + processArgs(varargin{:}); + + % De-cell and trim listing, if only one element was found (no use for indented listing in this case) + if iscell(listing) && length(listing)==1 + listing = strtrim(listing{1}); + end + + % If no output args and no listing requested, present the FINDJOBJ interactive GUI + if nargout == 0 && isempty(listing) + + % Get all label positions + hg_labels = []; + labelPositions = getLabelsJavaPos(container); + + % Present the GUI (object tree) + if ~isempty(container) + presentObjectTree(); + else + warnInvisible; + end + + % Display the listing, if this was specifically requested yet no relevant output arg was specified + elseif nargout < 4 && ~isempty(listing) + if ~iscell(listing) + disp(listing); + else + for listingIdx = 1 : length(listing) + disp(listing{listingIdx}); + end + end + end + + % Display a warning if the requested handle was not found because it's invisible + if nargout && isempty(handles) + warnInvisible; + end + + return; %debug point + + catch + % 'Cleaner' error handling - strip the stack info etc. + err = lasterror; %#ok + err.message = regexprep(err.message,'Error using ==> [^\n]+\n',''); + if isempty(findstr(mfilename,err.message)) + % Indicate error origin, if not already stated within the error message + err.message = [mfilename ': ' err.message]; + end + rethrow(err); + end + + %% Display a warning if the requested handle was not found because it's invisible + function warnInvisible + try + stk = dbstack; + stkNames = {stk.name}; + OutputFcnIdx = find(~cellfun(@isempty,regexp(stkNames,'_OutputFcn'))); + if ~isempty(OutputFcnIdx) + OutputFcnName = stkNames{OutputFcnIdx}; + warning('YMA:FindJObj:OutputFcn',['No Java reference was found for the requested handle, because the figure is still invisible in ' OutputFcnName '()']); + elseif ishandle(origContainer) && isprop(origContainer,'Visible') && strcmpi(get(origContainer,'Visible'),'off') + warning('YMA:FindJObj:invisibleHandle','No Java reference was found for the requested handle, probably because it is still invisible'); + end + catch + % Never mind... + end + end + + %% Check existence of a (case-insensitive) optional parameter in the params list + function [flag,idx] = paramSupplied(paramsList,paramName) + %idx = find(~cellfun('isempty',regexpi(paramsList(cellfun(@ischar,paramsList)),['^-?' paramName]))); + idx = find(~cellfun('isempty',regexpi(paramsList(cellfun('isclass',paramsList,'char')),['^-?' paramName]))); % 30/9/2009 fix for ML 7.0 suggested by Oliver W + flag = any(idx); + end + + %% Get current figure (even if its 'HandleVisibility' property is 'off') + function curFig = getCurrentFigure + oldShowHidden = get(0,'ShowHiddenHandles'); + set(0,'ShowHiddenHandles','on'); % minor fix per Johnny Smith + curFig = gcf; + set(0,'ShowHiddenHandles',oldShowHidden); + end + + %% Get Java reference to top-level (root) panel - actually, a reference to the java figure + function [jRootPane,contentSize] = getRootPanel(hFig) + try + contentSize = [0,0]; % initialize + jRootPane = hFig; + figName = get(hFig,'name'); + if strcmpi(get(hFig,'number'),'on') + figName = regexprep(['Figure ' num2str(hFig) ': ' figName],': $',''); + end + mde = com.mathworks.mde.desk.MLDesktop.getInstance; + jFigPanel = mde.getClient(figName); + jRootPane = jFigPanel; + jRootPane = jFigPanel.getRootPane; + catch + try + warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility + jFrame = get(hFig,'JavaFrame'); + jFigPanel = get(jFrame,'FigurePanelContainer'); + jRootPane = jFigPanel; + jRootPane = jFigPanel.getComponent(0).getRootPane; + catch + % Never mind + end + end + try + % If invalid RootPane - try another method... + warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility + jFrame = get(hFig,'JavaFrame'); + jAxisComponent = get(jFrame,'AxisComponent'); + jRootPane = jAxisComponent.getParent.getParent.getRootPane; + catch + % Never mind + end + try + % If invalid RootPane, retry up to N times + tries = 10; + while isempty(jRootPane) && tries>0 % might happen if figure is still undergoing rendering... + drawnow; pause(0.001); + tries = tries - 1; + jRootPane = jFigPanel.getComponent(0).getRootPane; + end + + % If still invalid, use FigurePanelContainer which is good enough in 99% of cases... (menu/tool bars won't be accessible, though) + if isempty(jRootPane) + jRootPane = jFigPanel; + end + contentSize = [jRootPane.getWidth, jRootPane.getHeight]; + + % Try to get the ancestor FigureFrame + jRootPane = jRootPane.getTopLevelAncestor; + catch + % Never mind - FigurePanelContainer is good enough in 99% of cases... (menu/tool bars won't be accessible, though) + end + end + + %% Traverse the container hierarchy and extract the elements within + function traverseContainer(jcontainer,level,parent) + persistent figureComponentFound menuRootFound + + % Record the data for this node + %disp([repmat(' ',1,level) '<= ' char(jcontainer.toString)]) + thisIdx = length(levels) + 1; + levels(thisIdx) = level; + parentIdx(thisIdx) = parent; + handles(thisIdx) = handle(jcontainer,'callbackproperties'); + try + positions(thisIdx,:) = getXY(jcontainer); + %sizes(thisIdx,:) = [jcontainer.getWidth, jcontainer.getHeight]; + catch + positions(thisIdx,:) = [0,0]; + %sizes(thisIdx,:) = [0,0]; + end + if level>0 + positions(thisIdx,:) = positions(thisIdx,:) + positions(parent,:); + if ~figureComponentFound && ... + strcmp(jcontainer.getName,'fComponentContainer') && ... + isa(jcontainer,'com.mathworks.hg.peer.FigureComponentContainer') % there are 2 FigureComponentContainers - only process one... + + % restart coordinate system, to exclude menu & toolbar areas + positions(thisIdx,:) = positions(thisIdx,:) - [jcontainer.getRootPane.getX, jcontainer.getRootPane.getY]; + figureComponentFound = true; + end + elseif level==1 + positions(thisIdx,:) = positions(thisIdx,:) + positions(parent,:); + else + % level 0 - initialize flags used later + figureComponentFound = false; + menuRootFound = false; + end + parentId = length(parentIdx); + + % Traverse Menu items, unless the 'nomenu' option was requested + if ~nomenu + try + for child = 1 : getNumMenuComponents(jcontainer) + traverseContainer(jcontainer.getMenuComponent(child-1),level+1,parentId); + end + catch + % Probably not a Menu container, but maybe a top-level JMenu, so discard duplicates + %if isa(handles(end).java,'javax.swing.JMenuBar') + if ~menuRootFound && strcmp(class(java(handles(end))),'javax.swing.JMenuBar') %faster... + if removeDuplicateNode(thisIdx) + menuRootFound = true; + return; + end + end + end + end + + % Now recursively process all this node's children (if any), except menu items if so requested + %if isa(jcontainer,'java.awt.Container') + try % try-catch is faster than checking isa(jcontainer,'java.awt.Container')... + %if jcontainer.getComponentCount, jcontainer.getComponents, end + if ~nomenu || menuBarFoundFlag || isempty(strfind(class(jcontainer),'FigureMenuBar')) + lastChildComponent = java.lang.Object; + child = 0; + while (child < jcontainer.getComponentCount) + childComponent = jcontainer.getComponent(child); + % Looping over menus sometimes causes jcontainer to get mixed up (probably a JITC bug), so identify & fix + if isequal(childComponent,lastChildComponent) + child = child + 1; + childComponent = jcontainer.getComponent(child); + end + lastChildComponent = childComponent; + %disp([repmat(' ',1,level) '=> ' num2str(child) ': ' char(class(childComponent))]) + traverseContainer(childComponent,level+1,parentId); + child = child + 1; + end + else + menuBarFoundFlag = true; % use this flag to skip further testing for FigureMenuBar + end + catch + % do nothing - probably not a container + %dispError + end + + % ...and yet another type of child traversal... + try + if ~isdeployed % prevent 'File is empty' messages in compiled apps + jc = jcontainer.java; + else + jc = jcontainer; + end + for child = 1 : jc.getChildCount + traverseContainer(jc.getChildAt(child-1),level+1,parentId); + end + catch + % do nothing - probably not a container + %dispError + end + + % TODO: Add axis (plot) component handles + end + + %% Get the XY location of a Java component + function xy = getXY(jcontainer) + % Note: getX/getY are better than get(..,'X') (mem leaks), + % ^^^^ but sometimes they fail and revert to getx.m ... + % Note2: try awtinvoke() catch is faster than checking ismethod()... + % Note3: using AWTUtilities.invokeAndWait() directly is even faster than awtinvoke()... + try %if ismethod(jcontainer,'getX') + %positions(thisIdx,:) = [jcontainer.getX, jcontainer.getY]; + cls = getClass(jcontainer); + location = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getLocation',[]),[]); + x = location.getX; + y = location.getY; + catch %else + try + x = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getX',[]),[]); + y = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getY',[]),[]); + catch + try + x = awtinvoke(jcontainer,'getX()'); + y = awtinvoke(jcontainer,'getY()'); + catch + x = get(jcontainer,'X'); + y = get(jcontainer,'Y'); + end + end + end + %positions(thisIdx,:) = [x, y]; + xy = [x,y]; + end + + %% Get the number of menu sub-elements + function numMenuComponents = getNumMenuComponents(jcontainer) + + % The following line will raise an Exception for anything except menus + numMenuComponents = jcontainer.getMenuComponentCount; + + % No error so far, so this must be a menu container... + % Note: Menu subitems are not visible until the top-level (root) menu gets initial focus... + % Try several alternatives, until we get a non-empty menu (or not...) + % TODO: Improve performance - this takes WAY too long... + if jcontainer.isTopLevelMenu && (numMenuComponents==0) + jcontainer.requestFocus; + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + drawnow; pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + jcontainer.setSelected(true); + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + drawnow; pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + jcontainer.doClick; % needed in order to populate the sub-menu components + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + drawnow; %pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + jcontainer.doClick; % close menu by re-clicking... + if (numMenuComponents == 0) + drawnow; %pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + end + else + % ok - found sub-items + % Note: no need to close menu since this will be done when focus moves to the FindJObj window + %jcontainer.doClick; % close menu by re-clicking... + end + end + end + jcontainer.setSelected(false); % de-select the menu + end + end + end + end + + %% Remove a specific tree node's data + function nodeRemovedFlag = removeDuplicateNode(thisIdx) + nodeRemovedFlag = false; + for idx = 1 : thisIdx-1 + if isequal(handles(idx),handles(thisIdx)) + levels(thisIdx) = []; + parentIdx(thisIdx) = []; + handles(thisIdx) = []; + positions(thisIdx,:) = []; + %sizes(thisIdx,:) = []; + nodeRemovedFlag = true; + return; + end + end + end + + %% Process optional args + function processArgs(varargin) + + % Initialize + invertFlag = false; + listing = ''; + + % Loop over all optional args + while ~isempty(varargin) && ~isempty(handles) + + % Process the arg (and all its params) + foundIdx = 1 : length(handles); + if iscell(varargin{1}), varargin{1} = varargin{1}{1}; end + if ~isempty(varargin{1}) && varargin{1}(1)=='-' + varargin{1}(1) = []; + end + switch lower(varargin{1}) + case 'not' + invertFlag = true; + case 'position' + [varargin,foundIdx] = processPositionArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'size' + [varargin,foundIdx] = processSizeArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'class' + [varargin,foundIdx] = processClassArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'property' + [varargin,foundIdx] = processPropertyArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'depth' + [varargin,foundIdx] = processDepthArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'flat' + varargin = {'depth',0, varargin{min(2:end):end}}; + [varargin,foundIdx] = processDepthArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case {'print','list'} + [varargin,listing] = processPrintArgs(varargin{:}); + case {'persist','nomenu','debug'} + % ignore - already handled in main function above + otherwise + error('YMA:findjobj:IllegalOption',['Option ' num2str(varargin{1}) ' is not a valid option. Type ''help ' mfilename ''' for the full options list.']); + end + + % If only parent-child pairs found + foundIdx = find(foundIdx); + if ~isempty(foundIdx) && isequal(parentIdx(foundIdx(2:2:end)),foundIdx(1:2:end)) + % Return just the children (the parent panels are uninteresting) + foundIdx(1:2:end) = []; + end + + % If several possible handles were found and the first is the container of the second + if length(foundIdx) > 1 && isequal(handles(foundIdx(1)).java, handles(foundIdx(2)).getParent) + % Discard the uninteresting component container + foundIdx(1) = []; + end + + % Filter the results + selectedIdx = selectedIdx(foundIdx); + handles = handles(foundIdx); + levels = levels(foundIdx); + parentIdx = parentIdx(foundIdx); + positions = positions(foundIdx,:); + + % Remove this arg and proceed to the next one + varargin(1) = []; + + end % Loop over all args + end + + %% Process 'print' option + function [varargin,listing] = processPrintArgs(varargin) + if length(varargin)<2 || ischar(varargin{2}) + % No second arg given, so use the first available element + listingContainer = handles(1); %#ok - used in evalc below + else + % Get the element to print from the specified second arg + if isnumeric(varargin{2}) && (varargin{2} == fix(varargin{2})) % isinteger doesn't work on doubles... + if (varargin{2} > 0) && (varargin{2} <= length(handles)) + listingContainer = handles(varargin{2}); %#ok - used in evalc below + elseif varargin{2} > 0 + error('YMA:findjobj:IllegalPrintFilter','Print filter index %g > number of available elements (%d)',varargin{2},length(handles)); + else + error('YMA:findjobj:IllegalPrintFilter','Print filter must be a java handle or positive numeric index into handles'); + end + elseif ismethod(varargin{2},'list') + listingContainer = varargin{2}; %#ok - used in evalc below + else + error('YMA:findjobj:IllegalPrintFilter','Print filter must be a java handle or numeric index into handles'); + end + varargin(2) = []; + end + + % use evalc() to capture output into a Matlab variable + %listing = evalc('listingContainer.list'); + + % Better solution: loop over all handles and process them one by one + listing = cell(length(handles),1); + for componentIdx = 1 : length(handles) + listing{componentIdx} = [repmat(' ',1,levels(componentIdx)) char(handles(componentIdx).toString)]; + end + end + + %% Process 'position' option + function [varargin,foundIdx] = processPositionArgs(varargin) + if length(varargin)>1 + positionFilter = varargin{2}; + %if (isjava(positionFilter) || iscom(positionFilter)) && ismethod(positionFilter,'getLocation') + try % try-catch is faster... + % Java/COM object passed - get its position + positionFilter = positionFilter.getLocation; + filterXY = [positionFilter.getX, positionFilter.getY]; + catch + if ~isscalar(positionFilter) + % position vector passed + if (length(positionFilter)>=2) && isnumeric(positionFilter) + % Remember that java coordinates start at top-left corner, Matlab coords start at bottom left... + %positionFilter = java.awt.Point(positionFilter(1), container.getHeight - positionFilter(2)); + filterXY = [container.getX + positionFilter(1), container.getY + contentSize(2) - positionFilter(2)]; + + % Check for full Matlab position vector (x,y,w,h) + %if (length(positionFilter)==4) + % varargin{end+1} = 'size'; + % varargin{end+1} = fix(positionFilter(3:4)); + %end + else + error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); + end + elseif length(varargin)>2 + % x,y passed as separate arg values + if isnumeric(positionFilter) && isnumeric(varargin{3}) + % Remember that java coordinates start at top-left corner, Matlab coords start at bottom left... + %positionFilter = java.awt.Point(positionFilter, container.getHeight - varargin{3}); + filterXY = [container.getX + positionFilter, container.getY + contentSize(2) - varargin{3}]; + varargin(3) = []; + else + error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); + end + else + error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); + end + end + + % Compute the required element positions in order to be eligible for a more detailed examination + % Note: based on the following constraints: 0 <= abs(elementX-filterX) + abs(elementY+elementH-filterY) < 7 + baseDeltas = [positions(:,1)-filterXY(1), positions(:,2)-filterXY(2)]; % faster than repmat()... + %baseHeight = - baseDeltas(:,2);% -abs(baseDeltas(:,1)); + %minHeight = baseHeight - 7; + %maxHeight = baseHeight + 7; + %foundIdx = ~arrayfun(@(b)(invoke(b,'contains',positionFilter)),handles); % ARGH! - disallowed by Matlab! + %foundIdx = repmat(false,1,length(handles)); + %foundIdx(length(handles)) = false; % faster than repmat()... + foundIdx = (abs(baseDeltas(:,1)) < 7) & (abs(baseDeltas(:,2)) < 7); % & (minHeight >= 0); + %fi = find(foundIdx); + %for componentIdx = 1 : length(fi) + %foundIdx(componentIdx) = handles(componentIdx).getBounds.contains(positionFilter); + + % Search for a point no farther than 7 pixels away (prevents rounding errors) + %foundIdx(componentIdx) = handles(componentIdx).getLocationOnScreen.distanceSq(positionFilter) < 50; % fails for invisible components... + + %p = java.awt.Point(positions(componentIdx,1), positions(componentIdx,2) + handles(componentIdx).getHeight); + %foundIdx(componentIdx) = p.distanceSq(positionFilter) < 50; + + %foundIdx(componentIdx) = sum(([baseDeltas(componentIdx,1),baseDeltas(componentIdx,2)+handles(componentIdx).getHeight]).^2) < 50; + + % Following is the fastest method found to date: only eligible elements are checked in detailed + % elementHeight = handles(fi(componentIdx)).getHeight; + % foundIdx(fi(componentIdx)) = elementHeight > minHeight(fi(componentIdx)) && ... + % elementHeight < maxHeight(fi(componentIdx)); + %disp([componentIdx,elementHeight,minHeight(fi(componentIdx)),maxHeight(fi(componentIdx)),foundIdx(fi(componentIdx))]) + %end + + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'size' option + function [varargin,foundIdx] = processSizeArgs(varargin) + if length(varargin)>1 + sizeFilter = lower(varargin{2}); + %if (isjava(sizeFilter) || iscom(sizeFilter)) && ismethod(sizeFilter,'getSize') + try % try-catch is faster... + % Java/COM object passed - get its size + sizeFilter = sizeFilter.getSize; + filterWidth = sizeFilter.getWidth; + filterHeight = sizeFilter.getHeight; + catch + if ~isscalar(sizeFilter) + % size vector passed + if (length(sizeFilter)>=2) && isnumeric(sizeFilter) + %sizeFilter = java.awt.Dimension(sizeFilter(1),sizeFilter(2)); + filterWidth = sizeFilter(1); + filterHeight = sizeFilter(2); + else + error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); + end + elseif length(varargin)>2 + % w,h passed as separate arg values + if isnumeric(sizeFilter) && isnumeric(varargin{3}) + %sizeFilter = java.awt.Dimension(sizeFilter,varargin{3}); + filterWidth = sizeFilter; + filterHeight = varargin{3}; + varargin(3) = []; + else + error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); + end + else + error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); + end + end + %foundIdx = ~arrayfun(@(b)(invoke(b,'contains',sizeFilter)),handles); % ARGH! - disallowed by Matlab! + foundIdx(length(handles)) = false; % faster than repmat()... + for componentIdx = 1 : length(handles) + %foundIdx(componentIdx) = handles(componentIdx).getSize.equals(sizeFilter); + % Allow a 2-pixel tollerance to account for non-integer pixel sizes + foundIdx(componentIdx) = abs(handles(componentIdx).getWidth - filterWidth) <= 3 && ... % faster than getSize.equals() + abs(handles(componentIdx).getHeight - filterHeight) <= 3; + end + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'class' option + function [varargin,foundIdx] = processClassArgs(varargin) + if length(varargin)>1 + classFilter = varargin{2}; + %if ismethod(classFilter,'getClass') + try % try-catch is faster... + classFilter = char(classFilter.getClass); + catch + if ~ischar(classFilter) + error('YMA:findjobj:IllegalClassFilter','Class filter must be a java object, class or string'); + end + end + + % Now convert all java classes to java.lang.Strings and compare to the requested filter string + try + foundIdx(length(handles)) = false; % faster than repmat()... + jClassFilter = java.lang.String(classFilter).toLowerCase; + for componentIdx = 1 : length(handles) + % Note: JVM 1.5's String.contains() appears slightly slower and is available only since Matlab 7.2 + foundIdx(componentIdx) = handles(componentIdx).getClass.toString.toLowerCase.indexOf(jClassFilter) >= 0; + end + catch + % Simple processing: slower since it does extra processing within opaque.char() + for componentIdx = 1 : length(handles) + % Note: using @toChar is faster but returns java String, not a Matlab char + foundIdx(componentIdx) = ~isempty(regexpi(char(handles(componentIdx).getClass),classFilter)); + end + end + + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'property' option + function [varargin,foundIdx] = processPropertyArgs(varargin) + if length(varargin)>1 + propertyName = varargin{2}; + if iscell(propertyName) + if length(propertyName) == 2 + propertyVal = propertyName{2}; + propertyName = propertyName{1}; + elseif length(propertyName) == 1 + propertyName = propertyName{1}; + else + error('YMA:findjobj:IllegalPropertyFilter','Property filter must be a string (case insensitive name of property) or cell array {propName,propValue}'); + end + end + if ~ischar(propertyName) + error('YMA:findjobj:IllegalPropertyFilter','Property filter must be a string (case insensitive name of property) or cell array {propName,propValue}'); + end + propertyName = lower(propertyName); + %foundIdx = arrayfun(@(h)isprop(h,propertyName),handles); % ARGH! - disallowed by Matlab! + foundIdx(length(handles)) = false; % faster than repmat()... + + % Split processing depending on whether a specific property value was requested (ugly but faster...) + if exist('propertyVal','var') + for componentIdx = 1 : length(handles) + try + % Find out whether this element has the specified property + % Note: findprop() and its return value schema.prop are undocumented and unsupported! + prop = findprop(handles(componentIdx),propertyName); % faster than isprop() & enables partial property names + + % If found, compare it to the actual element's property value + foundIdx(componentIdx) = ~isempty(prop) && isequal(get(handles(componentIdx),prop.Name),propertyVal); + catch + % Some Java classes have a write-only property (like LabelPeer with 'Text'), so we end up here + % In these cases, simply assume that the property value doesn't match and continue + foundIdx(componentIdx) = false; + end + end + else + for componentIdx = 1 : length(handles) + try + % Find out whether this element has the specified property + % Note: findprop() and its return value schema.prop are undocumented and unsupported! + foundIdx(componentIdx) = ~isempty(findprop(handles(componentIdx),propertyName)); + catch + foundIdx(componentIdx) = false; + end + end + end + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'depth' option + function [varargin,foundIdx] = processDepthArgs(varargin) + if length(varargin)>1 + level = varargin{2}; + if ~isnumeric(level) + error('YMA:findjobj:IllegalDepthFilter','Depth filter must be a number (=maximal element depth)'); + end + foundIdx = (levels <= level); + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Convert property data into a string + function data = charizeData(data) + if isa(data,'com.mathworks.hg.types.HGCallback') + data = get(data,'Callback'); + end + if ~ischar(data) && ~isa(data,'java.lang.String') + newData = strtrim(evalc('disp(data)')); + try + newData = regexprep(newData,' +',' '); + newData = regexprep(newData,'Columns \d+ through \d+\s',''); + newData = regexprep(newData,'Column \d+\s',''); + catch + %never mind... + end + if iscell(data) + newData = ['{ ' newData ' }']; + elseif isempty(data) + newData = ''; + elseif isnumeric(data) || islogical(data) || any(ishandle(data)) || numel(data) > 1 %&& ~isscalar(data) + newData = ['[' newData ']']; + end + data = newData; + elseif ~isempty(data) + data = ['''' data '''']; + end + end % charizeData + + %% Prepare a hierarchical callbacks table data + function setProp(list,name,value,category) + prop = eval('com.jidesoft.grid.DefaultProperty();'); % prevent JIDE alert by run-time (not load-time) evaluation + prop.setName(name); + prop.setType(java.lang.String('').getClass); + prop.setValue(value); + prop.setEditable(true); + prop.setExpert(true); + %prop.setCategory(['' category ' callbacks']); + prop.setCategory([category ' callbacks']); + list.add(prop); + end % getTreeData + + %% Prepare a hierarchical callbacks table data + function list = getTreeData(data) + list = java.util.ArrayList(); + names = regexprep(data,'([A-Z][a-z]+).*','$1'); + %hash = java.util.Hashtable; + others = {}; + for propIdx = 1 : length(data) + if (propIdx < length(data) && strcmp(names{propIdx},names{propIdx+1})) || ... + (propIdx > 1 && strcmp(names{propIdx},names{propIdx-1})) + % Child callback property + setProp(list,data{propIdx,1},data{propIdx,2},names{propIdx}); + else + % Singular callback property => Add to 'Other' category at bottom of the list + others(end+1,:) = data(propIdx,:); %#ok + end + end + for propIdx = 1 : size(others,1) + setProp(list,others{propIdx,1},others{propIdx,2},'Other'); + end + end % getTreeData + + %% Get callbacks table data + function [cbData, cbHeaders, cbTableEnabled] = getCbsData(obj, stripStdCbsFlag) + % Initialize + cbData = {'(no callbacks)'}; + cbHeaders = {'Callback name'}; + cbTableEnabled = false; + + try + classHdl = classhandle(handle(obj)); + cbNames = get(classHdl.Events,'Name'); + if ~isempty(cbNames) && ~iscom(obj) %only java-based please... + cbNames = strcat(cbNames,'Callback'); + end + propNames = get(classHdl.Properties,'Name'); + propCbIdx = []; + if ischar(propNames), propNames={propNames}; end % handle case of a single callback + if ~isempty(propNames) + propCbIdx = find(~cellfun(@isempty,regexp(propNames,'(Fcn|Callback)$'))); + cbNames = unique([cbNames; propNames(propCbIdx)]); %#ok logical is faster but less debuggable... + end + if ~isempty(cbNames) + if stripStdCbsFlag + cbNames = stripStdCbs(cbNames); + end + if iscell(cbNames) + cbNames = sort(cbNames); + if size(cbNames,1) < size(cbNames,2) + cbNames = cbNames'; + end + end + hgHandleFlag = 0; try hgHandleFlag = ishghandle(obj); catch, end %#ok + try + obj = handle(obj,'CallbackProperties'); + catch + hgHandleFlag = 1; + end + if hgHandleFlag + % HG handles don't allow CallbackProperties - search only for *Fcn + cbNames = propNames(propCbIdx); + end + if iscom(obj) + cbs = obj.eventlisteners; + if ~isempty(cbs) + cbNamesRegistered = cbs(:,1); + cbData = setdiff(cbNames,cbNamesRegistered); + %cbData = charizeData(cbData); + if size(cbData,2) > size(cbData(1)) + cbData = cbData'; + end + cbData = [cbData, cellstr(repmat(' ',length(cbData),1))]; + cbData = [cbData; cbs]; + [sortedNames, sortedIdx] = sort(cbData(:,1)); + sortedCbs = cellfun(@charizeData,cbData(sortedIdx,2),'un',0); + cbData = [sortedNames, sortedCbs]; + else + cbData = [cbNames, cellstr(repmat(' ',length(cbNames),1))]; + end + elseif iscell(cbNames) + cbNames = sort(cbNames); + %cbData = [cbNames, get(obj,cbNames)']; + cbData = cbNames; + oldWarn = warning('off','MATLAB:hg:JavaSetHGProperty'); + warning('off','MATLAB:hg:PossibleDeprecatedJavaSetHGProperty'); + for idx = 1 : length(cbNames) + try + cbData{idx,2} = charizeData(get(obj,cbNames{idx})); + catch + cbData{idx,2} = '(callback value inaccessible)'; + end + end + warning(oldWarn); + else % only one event callback + %cbData = {cbNames, get(obj,cbNames)'}; + %cbData{1,2} = charizeData(cbData{1,2}); + try + cbData = {cbNames, charizeData(get(obj,cbNames))}; + catch + cbData = {cbNames, '(callback value inaccessible)'}; + end + end + cbHeaders = {'Callback name','Callback value'}; + cbTableEnabled = true; + end + catch + % never mind - use default (empty) data + end + end % getCbsData + + %% Get relative (0.0-1.0) divider location + function divLocation = getRalativeDivlocation(jDiv) + divLocation = jDiv.getDividerLocation; + if divLocation > 1 % i.e. [pixels] + visibleRect = jDiv.getVisibleRect; + if jDiv.getOrientation == 0 % vertical + start = visibleRect.getY; + extent = visibleRect.getHeight - start; + else + start = visibleRect.getX; + extent = visibleRect.getWidth - start; + end + divLocation = (divLocation - start) / extent; + end + end % getRalativeDivlocation + + %% Try to set a treenode icon based on a container's icon + function setTreeNodeIcon(treenode,container) + try + iconImage = []; + iconImage = container.getIcon; + if ~isempty(findprop(handle(iconImage),'Image')) % get(iconImage,'Image') is easier but leaks memory... + iconImage = iconImage.getImage; + else + a=b; %#ok cause an error + end + catch + try + iconImage = container.getIconImage; + catch + try + if ~isempty(iconImage) + ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment; + gd = ge.getDefaultScreenDevice; + gc = gd.getDefaultConfiguration; + image = gc.createCompatibleImage(iconImage.getIconWidth, iconImage.getIconHeight); % a BufferedImage object + g = image.createGraphics; + iconImage.paintIcon([], g, 0, 0); + g.dispose; + iconImage = image; + end + catch + % never mind... + end + end + end + if ~isempty(iconImage) + iconImage = setIconSize(iconImage); + treenode.setIcon(iconImage); + end + end % setTreeNodeIcon + + %% Present the object hierarchy tree + function presentObjectTree() + import java.awt.* + import javax.swing.* + hTreeFig = findall(0,'tag','findjobjFig'); + iconpath = [matlabroot, '/toolbox/matlab/icons/']; + cbHideStd = 0; % Initial state of the cbHideStdCbs checkbox + if isempty(hTreeFig) + % Prepare the figure + hTreeFig = figure('tag','findjobjFig','menuBar','none','toolBar','none','Name','FindJObj','NumberTitle','off','handleVisibility','off','IntegerHandle','off'); + figIcon = ImageIcon([iconpath 'tool_legend.gif']); + drawnow; + try + mde = com.mathworks.mde.desk.MLDesktop.getInstance; + jTreeFig = mde.getClient('FindJObj').getTopLevelAncestor; + jTreeFig.setIcon(figIcon); + catch + warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility + jTreeFig = get(hTreeFig,'JavaFrame'); + jTreeFig.setFigureIcon(figIcon); + end + vsplitPaneLocation = 0.8; + hsplitPaneLocation = 0.5; + else + % Remember cbHideStdCbs checkbox & dividers state for later + userdata = get(hTreeFig, 'userdata'); + try cbHideStd = userdata.cbHideStdCbs.isSelected; catch, end %#ok + try + vsplitPaneLocation = getRalativeDivlocation(userdata.vsplitPane); + hsplitPaneLocation = getRalativeDivlocation(userdata.hsplitPane); + catch + vsplitPaneLocation = 0.8; + hsplitPaneLocation = 0.5; + end + + % Clear the figure and redraw + clf(hTreeFig); + figure(hTreeFig); % bring to front + end + + % Traverse all HG children, if root container was a HG handle + if ishghandle(origContainer) %&& ~isequal(origContainer,container) + traverseHGContainer(origContainer,0,0); + end + + % Prepare the tree pane + warning('off','MATLAB:uitreenode:MigratingFunction'); % R2008b compatibility + warning('off','MATLAB:uitreenode:DeprecatedFunction'); % R2008a compatibility + tree_h = com.mathworks.hg.peer.UITreePeer; + hasChildren = sum(allParents==1) > 1; + icon = [iconpath 'upfolder.gif']; + [rootName, rootTitle] = getNodeName(container); + try + root = uitreenode('v0', handle(container), rootName, icon, ~hasChildren); + catch % old matlab version don't have the 'v0' option + root = uitreenode(handle(container), rootName, icon, ~hasChildren); + end + setTreeNodeIcon(root,container); % constructor must accept a char icon unfortunately, so need to do this afterwards... + if ~isempty(rootTitle) + set(hTreeFig, 'Name',['FindJObj - ' char(rootTitle)]); + end + nodedata.idx = 1; + nodedata.obj = container; + set(root,'userdata',nodedata); + root.setUserObject(container); + setappdata(root,'childHandle',container); + tree_h.setRoot(root); + treePane = tree_h.getScrollPane; + treePane.setMinimumSize(Dimension(50,50)); + jTreeObj = treePane.getViewport.getComponent(0); + jTreeObj.setShowsRootHandles(true) + jTreeObj.getSelectionModel.setSelectionMode(javax.swing.tree.TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + %jTreeObj.setVisible(0); + %jTreeObj.getCellRenderer.setLeafIcon([]); + %jTreeObj.getCellRenderer.setOpenIcon(figIcon); + %jTreeObj.getCellRenderer.setClosedIcon([]); + treePanel = JPanel(BorderLayout); + treePanel.add(treePane, BorderLayout.CENTER); + progressBar = JProgressBar(0); + progressBar.setMaximum(length(allHandles) + length(hg_handles)); % = # of all nodes + treePanel.add(progressBar, BorderLayout.SOUTH); + + % Prepare the image pane +%disable for now, until we get it working... +%{ + try + hFig = ancestor(origContainer,'figure'); + [cdata, cm] = getframe(hFig); %#ok cm unused + tempfname = [tempname '.png']; + imwrite(cdata,tempfname); % don't know how to pass directly to BufferedImage, so use disk... + jImg = javax.imageio.ImageIO.read(java.io.File(tempfname)); + try delete(tempfname); catch end + imgPanel = JPanel(); + leftPanel = JSplitPane(JSplitPane.VERTICAL_SPLIT, treePanel, imgPanel); + leftPanel.setOneTouchExpandable(true); + leftPanel.setContinuousLayout(true); + leftPanel.setResizeWeight(0.8); + catch + leftPanel = treePanel; + end +%} + leftPanel = treePanel; + + % Prepare the inspector pane + classNameLabel = JLabel([' ' char(class(container))]); + classNameLabel.setForeground(Color.blue); + updateNodeTooltip(container, classNameLabel); + inspectorPanel = JPanel(BorderLayout); + inspectorPanel.add(classNameLabel, BorderLayout.NORTH); + % TODO: Maybe uncomment the following when we add the HG tree - in the meantime it's unused (java properties are un-groupable) + %objReg = com.mathworks.services.ObjectRegistry.getLayoutRegistry; + %toolBar = awtinvoke('com.mathworks.mlwidgets.inspector.PropertyView$ToolBarStyle','valueOf(Ljava.lang.String;)','GROUPTOOLBAR'); + %inspectorPane = com.mathworks.mlwidgets.inspector.PropertyView(objReg, toolBar); + inspectorPane = com.mathworks.mlwidgets.inspector.PropertyView; + identifiers = disableDbstopError; %#ok "dbstop if error" causes inspect.m to croak due to a bug - so workaround + inspectorPane.setObject(container); + inspectorPane.setAutoUpdate(true); + % TODO: Add property listeners + % TODO: Display additional props + inspectorTable = inspectorPane; + try + while ~isa(inspectorTable,'javax.swing.JTable') + inspectorTable = inspectorTable.getComponent(0); + end + catch + % R2010a + inspectorTable = inspectorPane.getComponent(0).getScrollPane.getViewport.getComponent(0); + end + toolTipText = 'hover mouse over the red classname above to see the full list of properties'; + inspectorTable.setToolTipText(toolTipText); + jideTableUtils = []; + try + % Try JIDE features - see http://www.jidesoft.com/products/JIDE_Grids_Developer_Guide.pdf + com.mathworks.mwswing.MJUtilities.initJIDE; + jideTableUtils = eval('com.jidesoft.grid.TableUtils;'); % prevent JIDE alert by run-time (not load-time) evaluation + jideTableUtils.autoResizeAllColumns(inspectorTable); + inspectorTable.setRowAutoResizes(true); + inspectorTable.getModel.setShowExpert(1); + catch + % JIDE is probably unavailable - never mind... + end + inspectorPanel.add(inspectorPane, BorderLayout.CENTER); + % TODO: Add data update listeners + + % Prepare the callbacks pane + callbacksPanel = JPanel(BorderLayout); + stripStdCbsFlag = true; % initial value + [cbData, cbHeaders, cbTableEnabled] = getCbsData(container, stripStdCbsFlag); + %{ + %classHdl = classhandle(handle(container)); + %eventNames = get(classHdl.Events,'Name'); + %if ~isempty(eventNames) + % cbNames = sort(strcat(eventNames,'Callback')); + % try + % cbData = [cbNames, get(container,cbNames)']; + % catch + % % R2010a + % cbData = cbNames; + % if isempty(cbData) + % cbData = {}; + % elseif ~iscell(cbData) + % cbData = {cbData}; + % end + % for idx = 1 : length(cbNames) + % cbData{idx,2} = get(container,cbNames{idx}); + % end + % end + % cbTableEnabled = true; + %else + % cbData = {'(no callbacks)',''}; + % cbTableEnabled = false; + %end + %cbHeaders = {'Callback name','Callback value'}; + %} + try + % Use JideTable if available on this system + %callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); %#ok + %callbacksTable = eval('com.jidesoft.grid.PropertyTable(callbacksTableModel);'); % prevent JIDE alert by run-time (not load-time) evaluation + try + list = getTreeData(cbData); %#ok + model = eval('com.jidesoft.grid.PropertyTableModel(list);'); %#ok prevent JIDE alert by run-time (not load-time) evaluation + + % Auto-expand if only one category + if model.getRowCount==1 % length(model.getCategories)==1 fails for some unknown reason... + model.expandFirstLevel; + end + + %callbacksTable = eval('com.jidesoft.grid.TreeTable(model);'); %#ok prevent JIDE alert by run-time (not load-time) evaluation + callbacksTable = eval('com.jidesoft.grid.PropertyTable(model);'); %#ok prevent JIDE alert by run-time (not load-time) evaluation + + %callbacksTable.expandFirstLevel; + callbacksTable.setShowsRootHandles(true); + callbacksTable.setShowTreeLines(false); + %callbacksTable.setShowNonEditable(0); %=SHOW_NONEDITABLE_NEITHER + callbacksPane = eval('com.jidesoft.grid.PropertyPane(callbacksTable);'); % prevent JIDE alert by run-time (not load-time) evaluation + callbacksPane.setShowDescription(false); + catch + callbacksTable = eval('com.jidesoft.grid.TreeTable(cbData,cbHeaders);'); % prevent JIDE alert by run-time (not load-time) evaluation + end + callbacksTable.setRowAutoResizes(true); + callbacksTable.setColumnAutoResizable(true); + callbacksTable.setColumnResizable(true); + jideTableUtils.autoResizeAllColumns(callbacksTable); + callbacksTable.setTableHeader([]); % hide the column headers since now we can resize columns with the gridline + callbacksLabel = JLabel(' Callbacks:'); % The column headers are replaced with a header label + callbacksLabel.setForeground(Color.blue); + %callbacksPanel.add(callbacksLabel, BorderLayout.NORTH); + + % Add checkbox to show/hide standard callbacks + callbacksTopPanel = JPanel; + callbacksTopPanel.setLayout(BoxLayout(callbacksTopPanel, BoxLayout.LINE_AXIS)); + callbacksTopPanel.add(callbacksLabel); + callbacksTopPanel.add(Box.createHorizontalGlue); + jcb = JCheckBox('Hide standard callbacks', cbHideStd); + set(handle(jcb,'CallbackProperties'), 'ActionPerformedCallback',{@cbHideStdCbs_Callback,callbacksTable}); + try + set(jcb, 'userdata',callbacksTable, 'tooltip','Hide standard Swing callbacks - only component-specific callbacks will be displayed'); + catch + jcb.setToolTipText('Hide standard Swing callbacks - only component-specific callbacks will be displayed'); + %setappdata(jcb,'userdata',callbacksTable); + end + callbacksTopPanel.add(jcb); + callbacksPanel.add(callbacksTopPanel, BorderLayout.NORTH); + catch + % Otherwise, use a standard Swing JTable (keep the headers to enable resizing) + callbacksTable = JTable(cbData,cbHeaders); + end + cbToolTipText = 'Callbacks may be ''strings'', @funcHandle or {@funcHandle,arg1,...}'; + callbacksTable.setToolTipText(cbToolTipText); + callbacksTable.setGridColor(inspectorTable.getGridColor); + cbNameTextField = JTextField; + cbNameTextField.setEditable(false); % ensure that the callback names are not modified... + cbNameCellEditor = DefaultCellEditor(cbNameTextField); + cbNameCellEditor.setClickCountToStart(intmax); % i.e, never enter edit mode... + callbacksTable.getColumnModel.getColumn(0).setCellEditor(cbNameCellEditor); + if ~cbTableEnabled + callbacksTable.getColumnModel.getColumn(1).setCellEditor(cbNameCellEditor); + end + hModel = callbacksTable.getModel; + set(handle(hModel,'CallbackProperties'), 'TableChangedCallback',{@tbCallbacksChanged,container,callbacksTable}); + %set(hModel, 'UserData',container); + try + cbScrollPane = callbacksPane; %JScrollPane(callbacksPane); + %cbScrollPane.setHorizontalScrollBarPolicy(cbScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + catch + cbScrollPane = JScrollPane(callbacksTable); + cbScrollPane.setVerticalScrollBarPolicy(cbScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + end + callbacksPanel.add(cbScrollPane, BorderLayout.CENTER); + callbacksPanel.setToolTipText(cbToolTipText); + + % Prepare the top-bottom JSplitPanes + vsplitPane = JSplitPane(JSplitPane.VERTICAL_SPLIT, inspectorPanel, callbacksPanel); + vsplitPane.setOneTouchExpandable(true); + vsplitPane.setContinuousLayout(true); + vsplitPane.setResizeWeight(0.8); + + % Prepare the left-right JSplitPane + hsplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, vsplitPane); + hsplitPane.setOneTouchExpandable(true); + hsplitPane.setContinuousLayout(true); + hsplitPane.setResizeWeight(0.6); + pos = getpixelposition(hTreeFig); + + % Prepare the bottom pane with all buttons + lowerPanel = JPanel(FlowLayout); + blogUrlLabel = 'Undocumented
Matlab.com
'; + jWebsite = createJButton(blogUrlLabel, @btWebsite_Callback, 'Visit the UndocumentedMatlab.com blog'); + jWebsite.setContentAreaFilled(0); + lowerPanel.add(jWebsite); + lowerPanel.add(createJButton('Refresh
tree', {@btRefresh_Callback, origContainer, hTreeFig}, 'Rescan the component tree, from the root down')); + lowerPanel.add(createJButton('Export to
workspace', {@btExport_Callback, jTreeObj, classNameLabel}, 'Export the selected component handles to workspace variable findjobj_hdls')); + lowerPanel.add(createJButton('Request
focus', {@btFocus_Callback, jTreeObj, root}, 'Set the focus on the first selected component')); + lowerPanel.add(createJButton('Inspect
object', {@btInspect_Callback, jTreeObj, root}, 'View the signature of all methods supported by the first selected component')); + lowerPanel.add(createJButton('Check for
updates', {@btCheckFex_Callback}, 'Check the MathWorks FileExchange for the latest version of FindJObj')); + + % Display everything on-screen + globalPanel = JPanel(BorderLayout); + globalPanel.add(hsplitPane, BorderLayout.CENTER); + globalPanel.add(lowerPanel, BorderLayout.SOUTH); + [obj, hcontainer] = javacomponent(globalPanel, [0,0,pos(3:4)], hTreeFig); + set(hcontainer,'units','normalized'); + drawnow; + hsplitPane.setDividerLocation(hsplitPaneLocation); % this only works after the JSplitPane is displayed... + vsplitPane.setDividerLocation(vsplitPaneLocation); % this only works after the JSplitPane is displayed... + %restoreDbstopError(identifiers); + + % Refresh & resize the screenshot thumbnail +%disable for now, until we get it working... +%{ + try + hAx = axes('Parent',hTreeFig, 'units','pixels', 'position',[10,10,250,150], 'visible','off'); + axis(hAx,'image'); + image(cdata,'Parent',hAx); + axis(hAx,'off'); + set(hAx,'UserData',cdata); + set(imgPanel, 'ComponentResizedCallback',{@resizeImg, hAx}, 'UserData',lowerPanel); + imgPanel.getGraphics.drawImage(jImg, 0, 0, []); + catch + % Never mind... + end +%} + % If all handles were selected (i.e., none were filtered) then only select the first + if (length(selectedIdx) == length(allHandles)) && ~isempty(selectedIdx) + selectedIdx = 1; + end + + % Store handles for callback use + userdata.handles = allHandles; + userdata.levels = allLevels; + userdata.parents = allParents; + userdata.hg_handles = hg_handles; + userdata.hg_levels = hg_levels; + userdata.hg_parents = hg_parentIdx; + userdata.initialIdx = selectedIdx; + userdata.userSelected = false; % Indicates the user has modified the initial selections + userdata.inInit = true; + userdata.jTree = jTreeObj; + userdata.jTreePeer = tree_h; + userdata.vsplitPane = vsplitPane; + userdata.hsplitPane = hsplitPane; + userdata.classNameLabel = classNameLabel; + userdata.inspectorPane = inspectorPane; + userdata.callbacksTable = callbacksTable; + userdata.jideTableUtils = jideTableUtils; + try + userdata.cbHideStdCbs = jcb; + catch + userdata.cbHideStdCbs = []; + end + + % Update userdata for use in callbacks + try + set(tree_h,'userdata',userdata); + catch + setappdata(tree_h,'userdata',userdata); + end + try + set(callbacksTable,'userdata',userdata); + catch + setappdata(callbacksTable,'userdata',userdata); + end + set(hTreeFig,'userdata',userdata); + + % Select the root node if requested + % Note: we must do so here since all other nodes except the root are processed by expandNode + if any(selectedIdx==1) + tree_h.setSelectedNode(root); + end + + % Set the initial cbHideStdCbs state + try + if jcb.isSelected + drawnow; + evd.getSource.isSelected = jcb.isSelected; + cbHideStdCbs_Callback(jcb,evd,callbacksTable); + end + catch + % never mind... + end + + % Set the callback functions + tree_hh = handle(tree_h,'CallbackProperties'); + set(tree_hh, 'NodeExpandedCallback', {@nodeExpanded, tree_h}); + set(tree_hh, 'NodeSelectedCallback', {@nodeSelected, tree_h}); + + % Set the tree mouse-click callback + % Note: default actions (expand/collapse) will still be performed? + % Note: MousePressedCallback is better than MouseClickedCallback + % since it fires immediately when mouse button is pressed, + % without waiting for its release, as MouseClickedCallback does + handleTree = tree_h.getScrollPane; + jTreeObj = handleTree.getViewport.getComponent(0); + jTreeObjh = handle(jTreeObj,'CallbackProperties'); + set(jTreeObjh, 'MousePressedCallback', {@treeMousePressedCallback,tree_h}); % context (right-click) menu + set(jTreeObjh, 'MouseMovedCallback', @treeMouseMovedCallback); % mouse hover tooltips + + % Update userdata + userdata.inInit = false; + try + set(tree_h,'userdata',userdata); + catch + setappdata(tree_h,'userdata',userdata); + end + set(hTreeFig,'userdata',userdata); + + % Pre-expand all rows + expandNode(progressBar, jTreeObj, tree_h, root, 0); + %jTreeObj.setVisible(1); + + % Hide the progressbar now that we've finished expanding all rows + try + hsplitPane.getLeftComponent.setTopComponent(treePane); + catch + % Probably not a vSplitPane on the left... + hsplitPane.setLeftComponent(treePane); + end + hsplitPane.setDividerLocation(hsplitPaneLocation); % need to do it again... + + % Set keyboard focus on the tree + jTreeObj.requestFocus; + drawnow; + + % Check for a newer version + checkVersion(); + + % Reset the last error + lasterr(''); %#ok + end + + %% Rresize image pane + function resizeImg(varargin) %#ok - unused (TODO: waiting for img placement fix...) + try + hPanel = varargin{1}; + hAx = varargin{3}; + lowerPanel = get(hPanel,'UserData'); + newJPos = cell2mat(get(hPanel,{'X','Y','Width','Height'})); + newMPos = [1,get(lowerPanel,'Height'),newJPos(3:4)]; + set(hAx, 'units','pixels', 'position',newMPos, 'Visible','on'); + uistack(hAx,'top'); % no good... + set(hPanel,'Opaque','off'); % also no good... + catch + % Never mind... + dispError + end + return; + end + + %% "dbstop if error" causes inspect.m to croak due to a bug - so workaround by temporarily disabling this dbstop + function identifiers = disableDbstopError + dbStat = dbstatus; + idx = find(strcmp({dbStat.cond},'error')); + identifiers = [dbStat(idx).identifier]; + if ~isempty(idx) + dbclear if error; + msgbox('''dbstop if error'' had to be disabled due to a Matlab bug that would have caused Matlab to crash.', 'FindJObj', 'warn'); + end + end + + %% Restore any previous "dbstop if error" + function restoreDbstopError(identifiers) %#ok + for itemIdx = 1 : length(identifiers) + eval(['dbstop if error ' identifiers{itemIdx}]); + end + end + + %% Recursively expand all nodes (except toolbar/menubar) in startup + function expandNode(progressBar, tree, tree_h, parentNode, parentRow) + try + if nargin < 5 + parentPath = javax.swing.tree.TreePath(parentNode.getPath); + parentRow = tree.getRowForPath(parentPath); + end + tree.expandRow(parentRow); + progressBar.setValue(progressBar.getValue+1); + numChildren = parentNode.getChildCount; + if (numChildren == 0) + pause(0.0002); % as short as possible... + drawnow; + end + nodesToUnExpand = {'FigureMenuBar','MLMenuBar','MJToolBar','Box','uimenu','uitoolbar','ScrollBar'}; + numChildren = parentNode.getChildCount; + for childIdx = 0 : numChildren-1 + childNode = parentNode.getChildAt(childIdx); + + % Pre-select the node based upon the user's FINDJOBJ filters + try + nodedata = get(childNode, 'userdata'); + try + userdata = get(tree_h, 'userdata'); + catch + userdata = getappdata(tree_h, 'userdata'); + end + %fprintf('%d - %s\n',nodedata.idx,char(nodedata.obj)) + if ~ishghandle(nodedata.obj) && ~userdata.userSelected && any(userdata.initialIdx == nodedata.idx) + pause(0.0002); % as short as possible... + drawnow; + if isempty(tree_h.getSelectedNodes) + tree_h.setSelectedNode(childNode); + else + newSelectedNodes = [tree_h.getSelectedNodes, childNode]; + tree_h.setSelectedNodes(newSelectedNodes); + end + end + catch + % never mind... + dispError + end + + % Expand child node if not leaf & not toolbar/menubar + if childNode.isLeafNode + + % This is a leaf node, so simply update the progress-bar + progressBar.setValue(progressBar.getValue+1); + + else + % Expand all non-leaves + expandNode(progressBar, tree, tree_h, childNode); + + % Re-collapse toolbar/menubar etc., and also invisible containers + % Note: if we simply did nothing, progressbar would not have been updated... + try + childHandle = getappdata(childNode,'childHandle'); %=childNode.getUserObject + visible = childHandle.isVisible; + catch + visible = 1; + end + visible = visible && isempty(findstr(get(childNode,'Name'),'color="gray"')); + %if any(strcmp(childNode.getName,nodesToUnExpand)) + %name = char(childNode.getName); + if any(cellfun(@(s)~isempty(strmatch(s,char(childNode.getName))),nodesToUnExpand)) || ~visible + childPath = javax.swing.tree.TreePath(childNode.getPath); + childRow = tree.getRowForPath(childPath); + tree.collapseRow(childRow); + end + end + end + catch + % never mind... + dispError + end + end + + %% Create utility buttons + function hButton = createJButton(nameStr, handler, toolTipText) + try + jButton = javax.swing.JButton(['
' nameStr]); + jButton.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + jButton.setToolTipText(toolTipText); + try + minSize = jButton.getMinimumSize; + catch + minSize = jButton.getMinimumSize; % for HG2 - strange indeed that this is needed! + end + jButton.setMinimumSize(java.awt.Dimension(minSize.getWidth,35)); + hButton = handle(jButton,'CallbackProperties'); + set(hButton,'ActionPerformedCallback',handler); + catch + % Never mind... + a= 1; + end + end + + %% Flash a component off/on for the specified duration + % note: starts with 'on'; if numTimes is odd then ends with 'on', otherwise with 'off' + function flashComponent(jComps,delaySecs,numTimes) + persistent redBorder redBorderPanels + try + % Handle callback data from right-click (context-menu) + if iscell(numTimes) + [jComps,delaySecs,numTimes] = deal(numTimes{:}); + end + + if isempty(redBorder) % reuse if possible + redBorder = javax.swing.border.LineBorder(java.awt.Color.red,2,0); + end + for compIdx = 1 : length(jComps) + try + oldBorder{compIdx} = jComps(compIdx).getBorder; %#ok grow + catch + oldBorder{compIdx} = []; %#ok grow + end + isSettable(compIdx) = ismethod(jComps(compIdx),'setBorder'); %#ok grow + if isSettable(compIdx) + try + % some components prevent border modification: + oldBorderFlag = jComps(compIdx).isBorderPainted; + if ~oldBorderFlag + jComps(compIdx).setBorderPainted(1); + isSettable(compIdx) = jComps(compIdx).isBorderPainted; %#ok grow + jComps(compIdx).setBorderPainted(oldBorderFlag); + end + catch + % do nothing... + end + end + if compIdx > length(redBorderPanels) + redBorderPanels{compIdx} = javax.swing.JPanel; + redBorderPanels{compIdx}.setBorder(redBorder); + redBorderPanels{compIdx}.setOpaque(0); % transparent interior, red border + end + try + redBorderPanels{compIdx}.setBounds(jComps(compIdx).getBounds); + catch + % never mind - might be an HG handle + end + end + for idx = 1 : 2*numTimes + if idx>1, pause(delaySecs); end % don't pause at start + visible = mod(idx,2); + for compIdx = 1 : length(jComps) + try + jComp = jComps(compIdx); + + % Prevent Matlab crash (java buffer overflow...) + if jComp.isa('com.mathworks.mwswing.desk.DTSplitPane') || ... + jComp.isa('com.mathworks.mwswing.MJSplitPane') + continue; + end + + % HG handles are highlighted by setting their 'Selected' property + if isa(jComp,'uimenu') + if visible + oldColor = get(jComp,'ForegroundColor'); + setappdata(jComp,'findjobj_oldColor',oldColor); + set(jComp,'ForegroundColor','red'); + else + oldColor = getappdata(jComp,'findjobj_oldColor'); + set(jComp,'ForegroundColor',oldColor); + rmappdata(jComp,'ForegroundColor'); + end + + elseif ishghandle(jComp) + if visible + set(jComp,'Selected','on'); + else + set(jComp,'Selected','off'); + end + + else %if isjava(jComp) + + jParent = jComps(compIdx).getParent; + + % Most Java components allow modifying their borders + if isSettable(compIdx) + if visible + jComp.setBorder(redBorder); + try jComp.setBorderPainted(1); catch, end %#ok + else %if ~isempty(oldBorder{compIdx}) + jComp.setBorder(oldBorder{compIdx}); + end + jComp.repaint; + + % The other Java components are highlighted by a transparent red-border + % panel that is placed on top of them in their parent's space + elseif ~isempty(jParent) + if visible + jParent.add(redBorderPanels{compIdx}); + jParent.setComponentZOrder(redBorderPanels{compIdx},0); + else + jParent.remove(redBorderPanels{compIdx}); + end + jParent.repaint + end + end + catch + % never mind - try the next component (if any) + end + end + drawnow; + end + catch + % never mind... + dispError; + end + return; % debug point + end % flashComponent + + %% Select tree node + function nodeSelected(src, evd, tree) %#ok + try + if iscell(tree) + [src,node] = deal(tree{:}); + else + node = evd.getCurrentNode; + end + %nodeHandle = node.getUserObject; + nodedata = get(node,'userdata'); + nodeHandle = nodedata.obj; + try + userdata = get(src,'userdata'); + catch + try + userdata = getappdata(java(src),'userdata'); + catch + userdata = getappdata(src,'userdata'); + end + if isempty(userdata) + try userdata = get(java(src),'userdata'); catch, end + end + end + if ~isempty(nodeHandle) && ~isempty(userdata) + numSelections = userdata.jTree.getSelectionCount; + selectionPaths = userdata.jTree.getSelectionPaths; + if (numSelections == 1) + % Indicate that the user has modified the initial selection (except if this was an initial auto-selected node) + if ~userdata.inInit + userdata.userSelected = true; + try + set(src,'userdata',userdata); + catch + try + setappdata(java(src),'userdata',userdata); + catch + setappdata(src,'userdata',userdata); + end + end + end + + % Update the fully-qualified class name label + numInitialIdx = length(userdata.initialIdx); + thisHandle = nodeHandle; + try + if ~ishghandle(thisHandle) + thisHandle = java(nodeHandle); + end + catch + % never mind... + end + if ~userdata.inInit || (numInitialIdx == 1) + userdata.classNameLabel.setText([' ' char(class(thisHandle))]); + else + userdata.classNameLabel.setText([' ' num2str(numInitialIdx) 'x handles (some handles hidden by unexpanded tree nodes)']); + end + if ishghandle(thisHandle) + userdata.classNameLabel.setText(userdata.classNameLabel.getText.concat(' (HG handle)')); + end + userdata.inspectorPane.dispose; % remove props listeners - doesn't work... + updateNodeTooltip(nodeHandle, userdata.classNameLabel); + + % Update the data properties inspector pane + % Note: we can't simply use the evd nodeHandle, because this node might have been DE-selected with only one other node left selected... + %nodeHandle = selectionPaths(1).getLastPathComponent.getUserObject; + nodedata = get(selectionPaths(1).getLastPathComponent,'userdata'); + nodeHandle = nodedata.obj; + %identifiers = disableDbstopError; % "dbstop if error" causes inspect.m to croak due to a bug - so workaround + userdata.inspectorPane.setObject(thisHandle); + + % Update the callbacks table + try + stripStdCbsFlag = getappdata(userdata.callbacksTable,'hideStdCbs'); + [cbData, cbHeaders, cbTableEnabled] = getCbsData(nodeHandle, stripStdCbsFlag); %#ok cbTableEnabled unused + try + % Use JideTable if available on this system + list = getTreeData(cbData); %#ok + callbacksTableModel = eval('com.jidesoft.grid.PropertyTableModel(list);'); %#ok prevent JIDE alert by run-time (not load-time) evaluation + + % Expand if only one category + if length(callbacksTableModel.getCategories)==1 + callbacksTableModel.expandFirstLevel; + end + catch + callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); + end + set(handle(callbacksTableModel,'CallbackProperties'), 'TableChangedCallback',{@tbCallbacksChanged,nodeHandle,userdata.callbacksTable}); + %set(callbacksTableModel, 'UserData',nodeHandle); + userdata.callbacksTable.setModel(callbacksTableModel) + userdata.callbacksTable.setRowAutoResizes(true); + userdata.jideTableUtils.autoResizeAllColumns(userdata.callbacksTable); + catch + % never mind... + %dispError + end + pause(0.005); + drawnow; + %restoreDbstopError(identifiers); + + % Highlight the selected object (if visible) + flashComponent(nodeHandle,0.2,3); + + elseif (numSelections > 1) % Multiple selections + + % Get the list of all selected nodes + jArray = javaArray('java.lang.Object', numSelections); + toolTipStr = ''; + sameClassFlag = true; + for idx = 1 : numSelections + %jArray(idx) = selectionPaths(idx).getLastPathComponent.getUserObject; + nodedata = get(selectionPaths(idx).getLastPathComponent,'userdata'); + try + jArray(idx) = java(nodedata.obj); + catch + jArray(idx) = nodedata.obj; + end + toolTipStr = [toolTipStr ' ' class(jArray(idx)) ' ']; %#ok grow + if (idx < numSelections), toolTipStr = [toolTipStr '
']; end %#ok grow + if (idx > 1) && sameClassFlag && ~isequal(jArray(idx).getClass,jArray(1).getClass) + sameClassFlag = false; + end + end + toolTipStr = [toolTipStr '']; + + % Update the fully-qualified class name label + if sameClassFlag + classNameStr = class(jArray(1)); + else + classNameStr = 'handle'; + end + if all(ishghandle(jArray)) + if strcmp(classNameStr,'handle') + classNameStr = 'HG handles'; + else + classNameStr = [classNameStr ' (HG handles)']; + end + end + classNameStr = [' ' num2str(numSelections) 'x ' classNameStr]; + userdata.classNameLabel.setText(classNameStr); + userdata.classNameLabel.setToolTipText(toolTipStr); + + % Update the data properties inspector pane + %identifiers = disableDbstopError; % "dbstop if error" causes inspect.m to croak due to a bug - so workaround + userdata.inspectorPane.getRegistry.setSelected(jArray, true); + + % Update the callbacks table + try + % Get intersecting callback names & values + stripStdCbsFlag = getappdata(userdata.callbacksTable,'hideStdCbs'); + [cbData, cbHeaders, cbTableEnabled] = getCbsData(jArray(1), stripStdCbsFlag); %#ok cbHeaders & cbTableEnabled unused + if ~isempty(cbData) + cbNames = cbData(:,1); + for idx = 2 : length(jArray) + [cbData2, cbHeaders2] = getCbsData(jArray(idx), stripStdCbsFlag); %#ok cbHeaders2 unused + if ~isempty(cbData2) + newCbNames = cbData2(:,1); + [cbNames, cbIdx, cb2Idx] = intersect(cbNames,newCbNames); %#ok cb2Idx unused + cbData = cbData(cbIdx,:); + for cbIdx = 1 : length(cbNames) + newIdx = find(strcmp(cbNames{cbIdx},newCbNames)); + if ~isequal(cbData2{newIdx,2}, cbData{cbIdx,2}) + cbData{cbIdx,2} = ''; + end + end + else + cbData = cbData([],:); %=empty cell array + end + if isempty(cbData) + break; + end + end + end + cbHeaders = {'Callback name','Callback value'}; + try + % Use JideTable if available on this system + list = getTreeData(cbData); %#ok + callbacksTableModel = eval('com.jidesoft.grid.PropertyTableModel(list);'); %#ok prevent JIDE alert by run-time (not load-time) evaluation + + % Expand if only one category + if length(callbacksTableModel.getCategories)==1 + callbacksTableModel.expandFirstLevel; + end + catch + callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); + end + set(handle(callbacksTableModel,'CallbackProperties'), 'TableChangedCallback',{@tbCallbacksChanged,jArray,userdata.callbacksTable}); + %set(callbacksTableModel, 'UserData',jArray); + userdata.callbacksTable.setModel(callbacksTableModel) + userdata.callbacksTable.setRowAutoResizes(true); + userdata.jideTableUtils.autoResizeAllColumns(userdata.callbacksTable); + catch + % never mind... + dispError + end + + pause(0.005); + drawnow; + %restoreDbstopError(identifiers); + + % Highlight the selected objects (if visible) + flashComponent(jArray,0.2,3); + end + + % TODO: Auto-highlight selected object (?) + %nodeHandle.requestFocus; + end + catch + dispError + end + end + + %% IFF utility function for annonymous cellfun funcs + function result = iff(test,trueVal,falseVal) %#ok + try + if test + result = trueVal; + else + result = falseVal; + end + catch + result = false; + end + end + + %% Get an HTML representation of the object's properties + function dataFieldsStr = getPropsHtml(nodeHandle, dataFields) + try + % Get a text representation of the fieldnames & values + undefinedStr = ''; + hiddenStr = ''; + dataFieldsStr = ''; % just in case the following croaks... + if isempty(dataFields) + return; + end + dataFieldsStr = evalc('disp(dataFields)'); + if dataFieldsStr(end)==char(10), dataFieldsStr=dataFieldsStr(1:end-1); end + + % Strip out callbacks + dataFieldsStr = regexprep(dataFieldsStr,'^\s*\w*Callback(Data)?:[^\n]*$','','lineanchors'); + dataFieldsStr = regexprep(dataFieldsStr,'\n\n','\n'); + + % Convert into a Matlab handle() + %nodeHandle = handle(nodeHandle); + try + % ensure this is a Matlab handle, not a java object + nodeHandle = handle(nodeHandle, 'CallbackProperties'); + catch + try + % HG handles don't allow CallbackProperties... + nodeHandle = handle(nodeHandle); + catch + % Some Matlab class objects simply cannot be converted into a handle() + end + end + + % HTMLize tooltip data + % First, set the fields' font based on its read-write status + fieldNames = fieldnames(dataFields); + for fieldIdx = 1 : length(fieldNames) + thisFieldName = fieldNames{fieldIdx}; + %accessFlags = get(findprop(nodeHandle,thisFieldName),'AccessFlags'); + try + hProp = findprop(nodeHandle,thisFieldName); + accessFlags = get(hProp,'AccessFlags'); + visible = get(hProp,'Visible'); + catch + accessFlags = []; + visible = 'on'; + end + if isfield(accessFlags,'PublicSet') && strcmp(accessFlags.PublicSet,'on') + % Bolden read/write fields + thisFieldFormat = ['' thisFieldName ':$2']; + elseif ~isfield(accessFlags,'PublicSet') + % Undefined - probably a Matlab-defined field of com.mathworks.hg.peer.FigureFrameProxy... + thisFieldFormat = ['' thisFieldName ':$2']; + undefinedStr = ', undefined'; + else % PublicSet=='off' + % Gray-out & italicize any read-only fields + thisFieldFormat = ['' thisFieldName ':$2']; + end + if strcmpi(visible,'off') + %thisFieldFormat = ['' thisFieldFormat '']; %#ok + thisFieldFormat = regexprep(thisFieldFormat, '(.*):(.*)', '$1:$2'); + hiddenStr = ', hidden'; + end + dataFieldsStr = regexprep(dataFieldsStr, ['([\s\n])' thisFieldName ':([^\n]*)'], ['$1' thisFieldFormat]); + end + catch + % never mind... - probably an ambiguous property name + %dispError + end + + % Method 1: simple
list + %dataFieldsStr = strrep(dataFieldsStr,char(10),' 
  '); + + % Method 2: 2-column + dataFieldsStr = regexprep(dataFieldsStr, '^\s*([^:]+:)([^\n]*)\n^\s*([^:]+:)([^\n]*)$', '', 'lineanchors'); + dataFieldsStr = regexprep(dataFieldsStr, '^[^<]\s*([^:]+:)([^\n]*)$', '', 'lineanchors'); + dataFieldsStr = ['(modifiable' undefinedStr hiddenStr ' & read-only fields)

  

 $1 $2    $3 $4 
 $1 $2  
' dataFieldsStr '
']; + end + + %% Update tooltip string with a node's data + function updateNodeTooltip(nodeHandle, uiObject) + try + toolTipStr = class(nodeHandle); + dataFieldsStr = ''; + + % Add HG annotation if relevant + if ishghandle(nodeHandle) + hgStr = ' HG Handle'; + else + hgStr = ''; + end + + % Prevent HG-Java warnings + oldWarn = warning('off','MATLAB:hg:JavaSetHGProperty'); + warning('off','MATLAB:hg:PossibleDeprecatedJavaSetHGProperty'); + + % Note: don't bulk-get because (1) not all properties are returned & (2) some properties cause a Java exception + % Note2: the classhandle approach does not enable access to user-defined schema.props + ch = classhandle(handle(nodeHandle)); + dataFields = []; + [sortedNames, sortedIdx] = sort(get(ch.Properties,'Name')); + for idx = 1 : length(sortedIdx) + sp = ch.Properties(sortedIdx(idx)); + % TODO: some fields (see EOL comment below) generate a Java Exception from: com.mathworks.mlwidgets.inspector.PropertyRootNode$PropertyListener$1$1.run + if strcmp(sp.AccessFlags.PublicGet,'on') % && ~any(strcmp(sp.Name,{'FixedColors','ListboxTop','Extent'})) + try + dataFields.(sp.Name) = get(nodeHandle, sp.Name); + catch + dataFields.(sp.Name) = 'Error!'; + end + else + dataFields.(sp.Name) = '(no public getter method)'; + end + end + dataFieldsStr = getPropsHtml(nodeHandle, dataFields); + catch + % Probably a non-HG java object + try + % Note: the bulk-get approach enables access to user-defined schema-props, but not to some original classhandle Properties... + dataFields = get(nodeHandle); + dataFieldsStr = getPropsHtml(nodeHandle, dataFields); + catch + % Probably a missing property getter implementation + try + % Inform the user - bail out on error + err = lasterror; %#ok + dataFieldsStr = ['

' strrep(err.message, char(10), '
')]; + catch + % forget it... + end + end + end + + % Restore warnings + try warning(oldWarn); catch, end + + % Set the object tooltip + if ~isempty(dataFieldsStr) + toolTipStr = [' ' char(toolTipStr) '' hgStr ': ' dataFieldsStr '']; + end + uiObject.setToolTipText(toolTipStr); + end + + %% Expand tree node + function nodeExpanded(src, evd, tree) %#ok + % tree = handle(src); + % evdsrc = evd.getSource; + evdnode = evd.getCurrentNode; + + if ~tree.isLoaded(evdnode) + + % Get the list of children TreeNodes + childnodes = getChildrenNodes(tree, evdnode); + + % Add the HG sub-tree (unless already included in the first tree) + childHandle = getappdata(evdnode.handle,'childHandle'); %=evdnode.getUserObject + if evdnode.isRoot && ~isempty(hg_handles) && ~isequal(hg_handles(1).java, childHandle) + childnodes = [childnodes, getChildrenNodes(tree, evdnode, true)]; + end + + % If we have a single child handle, wrap it within a javaArray for tree.add() to "swallow" + if (length(childnodes) == 1) + chnodes = childnodes; + childnodes = javaArray('com.mathworks.hg.peer.UITreeNode', 1); + childnodes(1) = java(chnodes); + end + + % Add child nodes to the current node + tree.add(evdnode, childnodes); + tree.setLoaded(evdnode, true); + end + end + + %% Get an icon image no larger than 16x16 pixels + function iconImage = setIconSize(iconImage) + try + iconWidth = iconImage.getWidth; + iconHeight = iconImage.getHeight; + if iconWidth > 16 + newHeight = fix(iconHeight * 16 / iconWidth); + iconImage = iconImage.getScaledInstance(16,newHeight,iconImage.SCALE_SMOOTH); + elseif iconHeight > 16 + newWidth = fix(iconWidth * 16 / iconHeight); + iconImage = iconImage.getScaledInstance(newWidth,16,iconImage.SCALE_SMOOTH); + end + catch + % never mind... - return original icon + end + end % setIconSize + + %% Get list of children nodes + function nodes = getChildrenNodes(tree, parentNode, isRootHGNode) + try + iconpath = [matlabroot, '/toolbox/matlab/icons/']; + nodes = handle([]); + try + userdata = get(tree,'userdata'); + catch + userdata = getappdata(tree,'userdata'); + end + hdls = userdata.handles; + nodedata = get(parentNode,'userdata'); + if nargin < 3 + %isJavaNode = ~ishghandle(parentNode.getUserObject); + isJavaNode = ~ishghandle(nodedata.obj); + isRootHGNode = false; + else + isJavaNode = ~isRootHGNode; + end + + % Search for this parent node in the list of all nodes + parents = userdata.parents; + nodeIdx = nodedata.idx; + + if isJavaNode && isempty(nodeIdx) % Failback, in case userdata doesn't work for some reason... + for hIdx = 1 : length(hdls) + %if isequal(handle(parentNode.getUserObject), hdls(hIdx)) + if isequal(handle(nodedata.obj), hdls(hIdx)) + nodeIdx = hIdx; + break; + end + end + end + if ~isJavaNode + if isRootHGNode % =root HG node + thisChildHandle = userdata.hg_handles(1); + childName = getNodeName(thisChildHandle); + hasGrandChildren = any(parents==1); + icon = []; + if hasGrandChildren && length(hg_handles)>1 + childName = childName.concat(' - HG root container'); + icon = [iconpath 'figureicon.gif']; + end + try + nodes = uitreenode('v0', thisChildHandle, childName, icon, ~hasGrandChildren); + catch % old matlab version don't have the 'v0' option + try + nodes = uitreenode(thisChildHandle, childName, icon, ~hasGrandChildren); + catch + % probably an invalid handle - ignore... + end + end + + % Add the handler to the node's internal data + % Note: could also use 'userdata', but setUserObject() is recommended for TreeNodes + % Note2: however, setUserObject() sets a java *BeenAdapter object for HG handles instead of the required original class, so use setappdata + %nodes.setUserObject(thisChildHandle); + setappdata(nodes,'childHandle',thisChildHandle); + nodedata.idx = 1; + nodedata.obj = thisChildHandle; + set(nodes,'userdata',nodedata); + return; + else % non-root HG node + parents = userdata.hg_parents; + hdls = userdata.hg_handles; + end % if isRootHGNode + end % if ~isJavaNode + + % If this node was found, get the list of its children + if ~isempty(nodeIdx) + %childIdx = setdiff(find(parents==nodeIdx),nodeIdx); + childIdx = find(parents==nodeIdx); + childIdx(childIdx==nodeIdx) = []; % faster... + numChildren = length(childIdx); + for cIdx = 1 : numChildren + thisChildIdx = childIdx(cIdx); + thisChildHandle = hdls(thisChildIdx); + childName = getNodeName(thisChildHandle); + try + visible = thisChildHandle.Visible; + if visible + try visible = thisChildHandle.Width > 0; catch, end %#ok + end + if ~visible + childName = ['' char(childName) '']; %#ok grow + end + catch + % never mind... + end + hasGrandChildren = any(parents==thisChildIdx); + try + isaLabel = isa(thisChildHandle.java,'javax.swing.JLabel'); + catch + isaLabel = 0; + end + if hasGrandChildren && ~any(strcmp(class(thisChildHandle),{'axes'})) + icon = [iconpath 'foldericon.gif']; + elseif isaLabel + icon = [iconpath 'tool_text.gif']; + else + icon = []; + end + try + nodes(cIdx) = uitreenode('v0', thisChildHandle, childName, icon, ~hasGrandChildren); + catch % old matlab version don't have the 'v0' option + try + nodes(cIdx) = uitreenode(thisChildHandle, childName, icon, ~hasGrandChildren); + catch + % probably an invalid handle - ignore... + end + end + + % Use existing object icon, if available + try + setTreeNodeIcon(nodes(cIdx),thisChildHandle); + catch + % probably an invalid handle - ignore... + end + + % Pre-select the node based upon the user's FINDJOBJ filters + try + if isJavaNode && ~userdata.userSelected && any(userdata.initialIdx == thisChildIdx) + pause(0.0002); % as short as possible... + drawnow; + if isempty(tree.getSelectedNodes) + tree.setSelectedNode(nodes(cIdx)); + else + newSelectedNodes = [tree.getSelectedNodes, nodes(cIdx).java]; + tree.setSelectedNodes(newSelectedNodes); + end + end + catch + % never mind... + end + + % Add the handler to the node's internal data + % Note: could also use 'userdata', but setUserObject() is recommended for TreeNodes + % Note2: however, setUserObject() sets a java *BeenAdapter object for HG handles instead of the required original class, so use setappdata + % Note3: the following will error if invalid handle - ignore + try + if isJavaNode + thisChildHandle = thisChildHandle.java; + end + %nodes(cIdx).setUserObject(thisChildHandle); + setappdata(nodes(cIdx),'childHandle',thisChildHandle); + nodedata.idx = thisChildIdx; + nodedata.obj = thisChildHandle; + set(nodes(cIdx),'userdata',nodedata); + catch + % never mind (probably an invalid handle) - leave unchanged (like a leaf) + end + end + end + catch + % Never mind - leave unchanged (like a leaf) + %error('YMA:findjobj:UnknownNodeType', 'Error expanding component tree node'); + dispError + end + end + + %% Get a node's name + function [nodeName, nodeTitle] = getNodeName(hndl,charsLimit) + try + % Initialize (just in case one of the succeding lines croaks) + nodeName = ''; + nodeTitle = ''; + if ~ismethod(hndl,'getClass') + try + nodeName = class(hndl); + catch + nodeName = hndl.type; % last-ditch try... + end + else + nodeName = hndl.getClass.getSimpleName; + end + + % Strip away the package name, leaving only the regular classname + if ~isempty(nodeName) && ischar(nodeName) + nodeName = java.lang.String(nodeName); + nodeName = nodeName.substring(nodeName.lastIndexOf('.')+1); + end + if (nodeName.length == 0) + % fix case of anonymous internal classes, that do not have SimpleNames + try + nodeName = hndl.getClass.getName; + nodeName = nodeName.substring(nodeName.lastIndexOf('.')+1); + catch + % never mind - leave unchanged... + end + end + + % Get any unique identifying string (if available in one of several fields) + labelsToCheck = {'label','title','text','string','displayname','toolTipText','TooltipString','actionCommand','name','Tag','style'}; %,'UIClassID'}; + nodeTitle = ''; + strField = ''; %#ok - used for debugging + while ((~isa(nodeTitle,'java.lang.String') && ~ischar(nodeTitle)) || isempty(nodeTitle)) && ~isempty(labelsToCheck) + try + nodeTitle = get(hndl,labelsToCheck{1}); + strField = labelsToCheck{1}; %#ok - used for debugging + catch + % never mind - probably missing prop, so skip to next one + end + labelsToCheck(1) = []; + end + if length(nodeTitle) ~= numel(nodeTitle) + % Multi-line - convert to a long single line + nodeTitle = nodeTitle'; + nodeTitle = nodeTitle(:)'; + end + if isempty(char(nodeTitle)) + % No title - check whether this is an HG label whose text is gettable + try + location = hndl.getLocationOnScreen; + pos = [location.getX, location.getY, hndl.getWidth, hndl.getHeight]; + dist = sum((labelPositions-repmat(pos,size(labelPositions,1),[1,1,1,1])).^2, 2); + [minVal,minIdx] = min(dist); + % Allow max distance of 8 = 2^2+2^2 (i.e. X&Y off by up to 2 pixels, W&H exact) + if minVal <= 8 % 8=2^2+2^2 + nodeTitle = get(hg_labels(minIdx),'string'); + % Preserve the label handles & position for the tooltip & context-menu + %hg_labels(minIdx) = []; + %labelPositions(minIdx,:) = []; + end + catch + % never mind... + end + end + if nargin<2, charsLimit = 25; end + extraStr = regexprep(nodeTitle,{sprintf('(.{%d,%d}).*',charsLimit,min(charsLimit,length(nodeTitle)-1)),' +'},{'$1...',' '},'once'); + if ~isempty(extraStr) + if ischar(extraStr) + nodeName = nodeName.concat(' (''').concat(extraStr).concat(''')'); + else + nodeName = nodeName.concat(' (').concat(num2str(extraStr)).concat(')'); + end + %nodeName = nodeName.concat(strField); + end + catch + % Never mind - use whatever we have so far + %dispError + end + end + + %% Strip standard Swing callbacks from a list of events + function evNames = stripStdCbs(evNames) + try + stdEvents = {'AncestorAdded', 'AncestorMoved', 'AncestorRemoved', 'AncestorResized', ... + 'CaretPositionChanged', 'ComponentAdded', 'ComponentRemoved', ... + 'ComponentHidden', 'ComponentMoved', 'ComponentResized', 'ComponentShown', ... + 'PropertyChange', 'FocusGained', 'FocusLost', ... + 'HierarchyChanged', 'InputMethodTextChanged', ... + 'KeyPressed', 'KeyReleased', 'KeyTyped', ... + 'MouseClicked', 'MouseDragged', 'MouseEntered', 'MouseExited', ... + 'MouseMoved', 'MousePressed', 'MouseReleased', 'MouseWheelMoved', ... + 'VetoableChange'}; + stdEvents = [stdEvents, strcat(stdEvents,'Callback'), strcat(stdEvents,'Fcn')]; + evNames = setdiff(evNames,stdEvents)'; + catch + % Never mind... + dispError + end + end + + %% Callback function for checkbox + function cbHideStdCbs_Callback(src, evd, callbacksTable, varargin) %#ok + try + % Store the current checkbox value for later use + if nargin < 3 + try + callbacksTable = get(src,'userdata'); + catch + callbacksTable = getappdata(src,'userdata'); + end + end + if evd.getSource.isSelected + setappdata(callbacksTable,'hideStdCbs',1); + else + setappdata(callbacksTable,'hideStdCbs',[]); + end + + % Rescan the current node + try + userdata = get(callbacksTable,'userdata'); + catch + userdata = getappdata(callbacksTable,'userdata'); + end + nodepath = userdata.jTree.getSelectionModel.getSelectionPath; + try + ed.getCurrentNode = nodepath.getLastPathComponent; + nodeSelected(handle(userdata.jTreePeer),ed,[]); + catch + % ignore - probably no node selected + end + catch + % Never mind... + dispError + end + end + + %% Callback function for button + function btWebsite_Callback(src, evd, varargin) %#ok + try + web('http://UndocumentedMatlab.com/'); + catch + % Never mind... + dispError + end + end + + %% Callback function for button + function btRefresh_Callback(src, evd, varargin) %#ok + try + % Set cursor shape to hourglass until we're done + hTreeFig = varargin{2}; + set(hTreeFig,'Pointer','watch'); + drawnow; + object = varargin{1}; + + % Re-invoke this utility to re-scan the container for all children + findjobj(object); + catch + % Never mind... + end + + % Restore default cursor shape + set(hTreeFig,'Pointer','arrow'); + end + + %% Callback function for button + function btExport_Callback(src, evd, varargin) %#ok + try + % Get the list of all selected nodes + if length(varargin) > 1 + jTree = varargin{1}; + numSelections = jTree.getSelectionCount; + selectionPaths = jTree.getSelectionPaths; + hdls = handle([]); + for idx = 1 : numSelections + %hdls(idx) = handle(selectionPaths(idx).getLastPathComponent.getUserObject); + nodedata = get(selectionPaths(idx).getLastPathComponent,'userdata'); + hdls(idx) = handle(nodedata.obj,'CallbackProperties'); + end + + % Assign the handles in the base workspace & inform user + assignin('base','findjobj_hdls',hdls); + classNameLabel = varargin{2}; + msg = ['Exported ' char(classNameLabel.getText.trim) ' to base workspace variable findjobj_hdls']; + else + % Right-click (context-menu) callback + data = varargin{1}; + obj = data{1}; + varName = data{2}; + if isempty(varName) + varName = inputdlg('Enter workspace variable name','FindJObj'); + if isempty(varName), return; end % bail out on + varName = varName{1}; + if isempty(varName) || ~ischar(varName), return; end % bail out on empty/null + varName = genvarname(varName); + end + assignin('base',varName,handle(obj,'CallbackProperties')); + msg = ['Exported object to base workspace variable ' varName]; + end + msgbox(msg,'FindJObj','help'); + catch + % Never mind... + dispError + end + end + + %% Callback function for button + function btFocus_Callback(src, evd, varargin) %#ok + try + % Request focus for the specified object + object = getTopSelectedObject(varargin{:}); + object.requestFocus; + catch + try + object = object.java.getPeer.requestFocus; + object.requestFocus; + catch + % Never mind... + %dispError + end + end + end + + %% Callback function for button + function btInspect_Callback(src, evd, varargin) %#ok + try + % Inspect the specified object + if length(varargin) == 1 + object = varargin{1}; + else + object = getTopSelectedObject(varargin{:}); + end + if isempty(which('uiinspect')) + + % If the user has not indicated NOT to be informed about UIInspect + if ~ispref('FindJObj','dontCheckUIInspect') + + % Ask the user whether to download UIINSPECT (YES, no, no & don't ask again) + answer = questdlg({'The object inspector requires UIINSPECT from the MathWorks File Exchange. UIINSPECT was created by Yair Altman, like this FindJObj utility.','','Download & install UIINSPECT?'},'UIInspect','Yes','No','No & never ask again','Yes'); + switch answer + case 'Yes' % => Yes: download & install + try + % Download UIINSPECT + baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/17935'; + fileUrl = [baseUrl '?controller=file_infos&download=true']; + %file = urlread(fileUrl); + %file = regexprep(file,[char(13),char(10)],'\n'); %convert to OS-dependent EOL + + % Install... + %newPath = fullfile(fileparts(which(mfilename)),'uiinspect.m'); + %fid = fopen(newPath,'wt'); + %fprintf(fid,'%s',file); + %fclose(fid); + [fpath,fname,fext] = fileparts(which(mfilename)); + zipFileName = fullfile(fpath,'uiinspect.zip'); + urlwrite(fileUrl,zipFileName); + unzip(zipFileName,fpath); + rehash; + catch + % Error downloading: inform the user + msgbox(['Error in downloading: ' lasterr], 'UIInspect', 'warn'); %#ok + web(baseUrl); + end + + % ...and now run it... + %pause(0.1); + drawnow; + dummy = which('uiinspect'); %#ok used only to load into memory + uiinspect(object); + return; + + case 'No & never ask again' % => No & don't ask again + setpref('FindJObj','dontCheckUIInspect',1); + + otherwise + % forget it... + end + end + drawnow; + + % No UIINSPECT available - run the good-ol' METHODSVIEW()... + methodsview(object); + else + uiinspect(object); + end + catch + try + if isjava(object) + methodsview(object) + else + methodsview(object.java); + end + catch + % Never mind... + dispError + end + end + end + + %% Callback function for button + function btCheckFex_Callback(src, evd, varargin) %#ok + try + % Check the FileExchange for the latest version + web('http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317'); + catch + % Never mind... + dispError + end + end + + %% Check for existence of a newer version + function checkVersion() + try + % If the user has not indicated NOT to be informed + if ~ispref('FindJObj','dontCheckNewerVersion') + + % Get the latest version date from the File Exchange webpage + baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/'; + webUrl = [baseUrl '14317']; % 'loadFile.do?objectId=14317']; + webPage = urlread(webUrl); + modIdx = strfind(webPage,'>Updates<'); + if ~isempty(modIdx) + webPage = webPage(modIdx:end); + % Note: regexp hangs if substr not found, so use strfind instead... + %latestWebVersion = regexprep(webPage,'.*?>(20[\d-]+).*','$1'); + dateIdx = strfind(webPage,'class="date">'); + if ~isempty(dateIdx) + latestDate = webPage(dateIdx(end)+13 : dateIdx(end)+23); + try + startIdx = dateIdx(end)+27; + descStartIdx = startIdx + strfind(webPage(startIdx:startIdx+999),''); + descEndIdx = startIdx + strfind(webPage(startIdx:startIdx+999),''); + descStr = webPage(descStartIdx(1)+3 : descEndIdx(1)-2); + descStr = regexprep(descStr,'',''); + catch + descStr = ''; + end + + % Get this file's latest date + thisFileName = which(mfilename); %#ok + try + thisFileData = dir(thisFileName); + try + thisFileDatenum = thisFileData.datenum; + catch % old ML versions... + thisFileDatenum = datenum(thisFileData.date); + end + catch + thisFileText = evalc('type(thisFileName)'); + thisFileLatestDate = regexprep(thisFileText,'.*Change log:[\s%]+([\d-]+).*','$1'); + thisFileDatenum = datenum(thisFileLatestDate,'yyyy-mm-dd'); + end + + % If there's a newer version on the File Exchange webpage (allow 2 days grace period) + if (thisFileDatenum < datenum(latestDate,'dd mmm yyyy')-2) + + % Ask the user whether to download the newer version (YES, no, no & don't ask again) + msg = {['A newer version (' latestDate ') of FindJObj is available on the MathWorks File Exchange:'], '', ... + ['\color{blue}' descStr '\color{black}'], '', ... + 'Download & install the new version?'}; + createStruct.Interpreter = 'tex'; + createStruct.Default = 'Yes'; + answer = questdlg(msg,'FindJObj','Yes','No','No & never ask again',createStruct); + switch answer + case 'Yes' % => Yes: download & install newer file + try + %fileUrl = [baseUrl '/download.do?objectId=14317&fn=findjobj&fe=.m']; + fileUrl = [baseUrl '/14317?controller=file_infos&download=true']; + %file = urlread(fileUrl); + %file = regexprep(file,[char(13),char(10)],'\n'); %convert to OS-dependent EOL + %fid = fopen(thisFileName,'wt'); + %fprintf(fid,'%s',file); + %fclose(fid); + [fpath,fname,fext] = fileparts(thisFileName); + zipFileName = fullfile(fpath,[fname '.zip']); + urlwrite(fileUrl,zipFileName); + unzip(zipFileName,fpath); + rehash; + catch + % Error downloading: inform the user + msgbox(['Error in downloading: ' lasterr], 'FindJObj', 'warn'); %#ok + web(webUrl); + end + case 'No & never ask again' % => No & don't ask again + setpref('FindJObj','dontCheckNewerVersion',1); + otherwise + % forget it... + end + end + end + else + % Maybe webpage not fully loaded or changed format - bail out... + end + end + catch + % Never mind... + end + end + + %% Get the first selected object (might not be the top one - depends on selection order) + function object = getTopSelectedObject(jTree, root) + try + object = []; + numSelections = jTree.getSelectionCount; + if numSelections > 0 + % Get the first object specified + %object = jTree.getSelectionPath.getLastPathComponent.getUserObject; + nodedata = get(jTree.getSelectionPath.getLastPathComponent,'userdata'); + else + % Get the root object (container) + %object = root.getUserObject; + nodedata = get(root,'userdata'); + end + object = nodedata.obj; + catch + % Never mind... + dispError + end + end + + %% Update component callback upon callbacksTable data change + function tbCallbacksChanged(src, evd, object, table) + persistent hash + try + % exit if invalid handle or already in Callback + %if ~ishandle(src) || ~isempty(getappdata(src,'inCallback')) % || length(dbstack)>1 %exit also if not called from user action + if isempty(hash), hash = java.util.Hashtable; end + if ~ishandle(src) || ~isempty(hash.get(src)) % || length(dbstack)>1 %exit also if not called from user action + return; + end + %setappdata(src,'inCallback',1); % used to prevent endless recursion % can't use getappdata(src,...) because it fails on R2010b!!! + hash.put(src,1); + + % Update the object's callback with the modified value + modifiedColIdx = evd.getColumn; + modifiedRowIdx = evd.getFirstRow; + if modifiedRowIdx>=0 %&& modifiedColIdx==1 %sanity check - should always be true + %table = evd.getSource; + %object = get(src,'userdata'); + modifiedRowIdx = table.getSelectedRow; % overcome issues with hierarchical table + cbName = strtrim(table.getValueAt(modifiedRowIdx,0)); + try + cbValue = strtrim(char(table.getValueAt(modifiedRowIdx,1))); + if ~isempty(cbValue) && ismember(cbValue(1),'{[@''') + cbValue = eval(cbValue); + end + if (~ischar(cbValue) && ~isa(cbValue, 'function_handle') && (~iscell(cbValue) || iscom(object(1)))) + revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, ''); + else + for objIdx = 1 : length(object) + obj = object(objIdx); + if ~iscom(obj) + try + try + if isjava(obj) + obj = handle(obj,'CallbackProperties'); + end + catch + % never mind... + end + set(obj, cbName, cbValue); + catch + try + set(handle(obj,'CallbackProperties'), cbName, cbValue); + catch + % never mind - probably a callback-group header + end + end + else + cbs = obj.eventlisteners; + if ~isempty(cbs) + cbs = cbs(strcmpi(cbs(:,1),cbName),:); + obj.unregisterevent(cbs); + end + if ~isempty(cbValue) && ~strcmp(cbName,'-') + obj.registerevent({cbName, cbValue}); + end + end + end + end + catch + revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, lasterr) %#ok + end + end + catch + % never mind... + end + %setappdata(src,'inCallback',[]); % used to prevent endless recursion % can't use setappdata(src,...) because it fails on R2010b!!! + hash.remove(src); + end + + %% Revert Callback table modification + function revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, errMsg) %#ok + try + % Display a notification MsgBox + msg = 'Callbacks must be a ''string'', or a @function handle'; + if ~iscom(object(1)), msg = [msg ' or a {@func,args...} construct']; end + if ~isempty(errMsg), msg = {errMsg, '', msg}; end + msgbox(msg, ['Error setting ' cbName ' value'], 'warn'); + + % Revert to the current value + curValue = ''; + try + if ~iscom(object(1)) + curValue = charizeData(get(object(1),cbName)); + else + cbs = object(1).eventlisteners; + if ~isempty(cbs) + cbs = cbs(strcmpi(cbs(:,1),cbName),:); + curValue = charizeData(cbs(1,2)); + end + end + catch + % never mind... - clear the current value + end + table.setValueAt(curValue, modifiedRowIdx, modifiedColIdx); + catch + % never mind... + end + end % revertCbTableModification + + %% Get the Java positions of all HG text labels + function labelPositions = getLabelsJavaPos(container) + try + labelPositions = []; + + % Ensure we have a figure handle + try + h = hFig; %#ok unused + catch + hFig = getCurrentFigure; + end + + % Get the figure's margins from the Matlab properties + hg_labels = findall(hFig, 'Style','text'); + units = get(hFig,'units'); + set(hFig,'units','pixels'); + outerPos = get(hFig,'OuterPosition'); + innerPos = get(hFig,'Position'); + set(hFig,'units',units); + margins = abs(innerPos-outerPos); + + figX = container.getX; % =outerPos(1) + figY = container.getY; % =outerPos(2) + %figW = container.getWidth; % =outerPos(3) + figH = container.getHeight; % =outerPos(4) + + % Get the relevant screen pixel size + %monitorPositions = get(0,'MonitorPositions'); + %for monitorIdx = size(monitorPositions,1) : -1 : 1 + % screenSize = monitorPositions(monitorIdx,:); + % if (outerPos(1) >= screenSize(1)) % && (outerPos(1)+outerPos(3) <= screenSize(3)) + % break; + % end + %end + %monitorBaseY = screenSize(4) - monitorPositions(1,4); + + % Compute the labels' screen pixel position in Java units ([0,0] = top left) + for idx = 1 : length(hg_labels) + matlabPos = getpixelposition(hg_labels(idx),1); + javaX = figX + matlabPos(1) + margins(1); + javaY = figY + figH - matlabPos(2) - matlabPos(4) - margins(2); + labelPositions(idx,:) = [javaX, javaY, matlabPos(3:4)]; %#ok grow + end + catch + % never mind... + err = lasterror; %#ok debug point + end + end + + %% Traverse an HG container hierarchy and extract the HG elements within + function traverseHGContainer(hcontainer,level,parent) + try + % Record the data for this node + thisIdx = length(hg_levels) + 1; + hg_levels(thisIdx) = level; + hg_parentIdx(thisIdx) = parent; + try + hg_handles(thisIdx) = handle(hcontainer); + catch + hg_handles = handle(hcontainer); + end + parentId = length(hg_parentIdx); + + % Now recursively process all this node's children (if any) + %if ishghandle(hcontainer) + try % try-catch is faster than checking ishghandle(hcontainer)... + allChildren = allchild(handle(hcontainer)); + for childIdx = 1 : length(allChildren) + traverseHGContainer(allChildren(childIdx),level+1,parentId); + end + catch + % do nothing - probably not a container + %dispError + end + + % TODO: Add axis (plot) component handles + catch + % forget it... + end + end + + %% Debuggable "quiet" error-handling + function dispError + err = lasterror; %#ok + msg = err.message; + for idx = 1 : length(err.stack) + filename = err.stack(idx).file; + if ~isempty(regexpi(filename,mfilename)) + funcname = err.stack(idx).name; + line = num2str(err.stack(idx).line); + msg = [msg ' at ' funcname ' line #' line '']; %#ok grow + break; + end + end + disp(msg); + return; % debug point + end + + %% ML 7.0 - compatible ischar() function + function flag = ischar(data) + try + flag = builtin('ischar',data); + catch + flag = isa(data,'char'); + end + end + + %% Set up the uitree context (right-click) menu + function jmenu = setTreeContextMenu(obj,node,tree_h) + % Prepare the context menu (note the use of HTML labels) + import javax.swing.* + titleStr = getNodeTitleStr(obj,node); + titleStr = regexprep(titleStr,'


.*',''); + menuItem0 = JMenuItem(titleStr); + menuItem0.setEnabled(false); + menuItem0.setArmed(false); + %menuItem1 = JMenuItem('Export handle to findjobj_hdls'); + if isjava(obj), prefix = 'j'; else, prefix = 'h'; end %#ok + varname = strrep([prefix strtok(char(node.getName))], '$','_'); + varname = genvarname(varname); + varname(2) = upper(varname(2)); % ensure lowerCamelCase + menuItem1 = JMenuItem(['Export handle to ' varname]); + menuItem2 = JMenuItem('Export handle to...'); + menuItem3 = JMenuItem('Request focus (bring to front)'); + menuItem4 = JMenuItem('Flash component borders'); + menuItem5 = JMenuItem('Display properties & callbacks'); + menuItem6 = JMenuItem('Inspect object'); + + % Set the menu items' callbacks + set(handle(menuItem1,'CallbackProperties'), 'ActionPerformedCallback', {@btExport_Callback,{obj,varname}}); + set(handle(menuItem2,'CallbackProperties'), 'ActionPerformedCallback', {@btExport_Callback,{obj,[]}}); + set(handle(menuItem3,'CallbackProperties'), 'ActionPerformedCallback', {@requestFocus,obj}); + set(handle(menuItem4,'CallbackProperties'), 'ActionPerformedCallback', {@flashComponent,{obj,0.2,3}}); + set(handle(menuItem5,'CallbackProperties'), 'ActionPerformedCallback', {@nodeSelected,{tree_h,node}}); + set(handle(menuItem6,'CallbackProperties'), 'ActionPerformedCallback', {@btInspect_Callback,obj}); + + % Add all menu items to the context menu (with internal separator) + jmenu = JPopupMenu; + jmenu.add(menuItem0); + jmenu.addSeparator; + handleValue=[]; try handleValue = double(obj); catch, end; %#ok + if ~isempty(handleValue) + % For valid HG handles only + menuItem0a = JMenuItem('Copy handle value to clipboard'); + set(handle(menuItem0a,'CallbackProperties'), 'ActionPerformedCallback', sprintf('clipboard(''copy'',%.99g)',handleValue)); + jmenu.add(menuItem0a); + end + jmenu.add(menuItem1); + jmenu.add(menuItem2); + jmenu.addSeparator; + jmenu.add(menuItem3); + jmenu.add(menuItem4); + jmenu.add(menuItem5); + jmenu.add(menuItem6); + end % setTreeContextMenu + + %% Set the mouse-press callback + function treeMousePressedCallback(hTree, eventData, tree_h) %#ok hTree is unused + if eventData.isMetaDown % right-click is like a Meta-button + % Get the clicked node + clickX = eventData.getX; + clickY = eventData.getY; + jtree = eventData.getSource; + treePath = jtree.getPathForLocation(clickX, clickY); + try + % Modify the context menu based on the clicked node + node = treePath.getLastPathComponent; + userdata = get(node,'userdata'); + obj = userdata.obj; + jmenu = setTreeContextMenu(obj,node,tree_h); + + % TODO: remember to call jmenu.remove(item) in item callback + % or use the timer hack shown here to remove the item: + % timerFcn = {@menuRemoveItem,jmenu,item}; + % start(timer('TimerFcn',timerFcn,'StartDelay',0.2)); + + % Display the (possibly-modified) context menu + jmenu.show(jtree, clickX, clickY); + jmenu.repaint; + + % This is for debugging: + userdata.tree = jtree; + setappdata(gcf,'findjobj_hgtree',userdata) + catch + % clicked location is NOT on top of any node + % Note: can also be tested by isempty(treePath) + end + end + end % treeMousePressedCallback + + %% Remove the extra context menu item after display + function menuRemoveItem(hObj,eventData,jmenu,item) %#ok unused + jmenu.remove(item); + end % menuRemoveItem + + %% Get the title for the tooltip and context (right-click) menu + function nodeTitleStr = getNodeTitleStr(obj,node) + try + % Display the full classname and object name in the tooltip + %nodeName = char(node.getName); + %nodeName = strrep(nodeName, '',''); + %nodeName = strrep(nodeName, '',''); + nodeName = char(getNodeName(obj,99)); + [objClass,objName] = strtok(nodeName); + objName = objName(3:end-1); % strip leading ( and trailing ) + if isempty(objName), objName = '(none found)'; end + nodeName = char(node.getName); + objClass = char(obj.getClass.getName); + nodeTitleStr = sprintf('Class name: %s
Text/title: %s',objClass,objName); + + % If the component is invisible, state this in the tooltip + if ~isempty(strfind(nodeName,'color="gray"')) + nodeTitleStr = [nodeTitleStr '
*** Invisible ***']; + end + nodeTitleStr = [nodeTitleStr '
Right-click for context-menu']; + catch + % Possible not a Java object - try treating as an HG handle + try + handleValueStr = sprintf('#: %.99g',double(obj)); + try + type = ''; + type = get(obj,'type'); + type(1) = upper(type(1)); + catch + if ~ishandle(obj) + type = ['Invalid ' char(node.getName) '']; + handleValueStr = '!!!
Perhaps this handle was deleted after this UIInspect tree was
already drawn. Try to refresh by selecting any valid node handle'; + end + end + nodeTitleStr = sprintf('%s handle %s',type,handleValueStr); + try + % If the component is invisible, state this in the tooltip + if strcmp(get(obj,'Visible'),'off') + nodeTitleStr = [nodeTitleStr '
Invisible']; + end + catch + % never mind... + end + catch + % never mind... - ignore + end + end + end % getNodeTitleStr + + %% Handle tree mouse movement callback - used to set the tooltip & context-menu + function treeMouseMovedCallback(hTree, eventData) + try + x = eventData.getX; + y = eventData.getY; + jtree = eventData.getSource; + treePath = jtree.getPathForLocation(x, y); + try + % Set the tooltip string based on the hovered node + node = treePath.getLastPathComponent; + userdata = get(node,'userdata'); + obj = userdata.obj; + tooltipStr = getNodeTitleStr(obj,node); + set(hTree,'ToolTipText',tooltipStr) + catch + % clicked location is NOT on top of any node + % Note: can also be tested by isempty(treePath) + end + catch + dispError; + end + return; % denug breakpoint + end % treeMouseMovedCallback + + %% Request focus for a specific object handle + function requestFocus(hTree, eventData, obj) %#ok hTree & eventData are unused + % Ensure the object handle is valid + if isjava(obj) + obj.requestFocus; + return; + elseif ~ishandle(obj) + msgbox('The selected object does not appear to be a valid handle as defined by the ishandle() function. Perhaps this object was deleted after this hierarchy tree was already drawn. Refresh this tree by selecting a valid node handle and then retry.','FindJObj','warn'); + beep; + return; + end + + try + foundFlag = 0; + while ~foundFlag + if isempty(obj), return; end % sanity check + type = get(obj,'type'); + obj = double(obj); + foundFlag = any(strcmp(type,{'figure','axes','uicontrol'})); + if ~foundFlag + obj = get(obj,'Parent'); + end + end + feval(type,obj); + catch + % never mind... + dispError; + end + end % requestFocus + + +end % FINDJOBJ + + +%% TODO TODO TODO +%{ +- Enh: Improve performance - esp. expandNode() (performance solved in non-nteractive mode) +- Enh: Add property listeners - same problem in MathWork's inspect.m +- Enh: Display additional properties - same problem in MathWork's inspect.m +- Enh: Add axis (plot, Graphics) component handles +- Enh: Group callbacks according to the first word (up to 2nd cap letter) +- Enh: Add figure thumbnail image below the java tree (& indicate corresponding jObject when selected) +- Enh: scroll initially-selected node into view (problem because treenode has no pixel location) +- Fix: java exceptions when getting some fields (com.mathworks.mlwidgets.inspector.PropertyRootNode$PropertyListener$1$1.run) +- Fix: use EDT if available (especially in flashComponent) +%} \ No newline at end of file diff --git a/private/xml_io_tools/gen_object_display.m b/private/gen_object_display.m similarity index 97% rename from private/xml_io_tools/gen_object_display.m rename to private/gen_object_display.m index 8def90e..d2241dc 100644 --- a/private/xml_io_tools/gen_object_display.m +++ b/private/gen_object_display.m @@ -1,143 +1,143 @@ -function gen_object_display( obj_struct,indent ) -% -% gen_object_display - general function to display an object's content -% -% format: gen_object_display( obj_struct,indent ) -% -% input: obj_struct - a copy of the object stored inside a structure -% indent - amount of "indent" when printing to the screen -% -% output: to the screen -% -% example: gen_object_display( struct( my_object_handle) ); -% gen_object_display( ny_structure ); -% -% Correction History: -% 2006-11-01 - Jarek Tuszynski - added support for struct arrays - -%% handle insufficient input -if ( nargin == 0 ) - help gen_object_display; - return; -elseif (nargin == 1) - indent = 1; -end - -%% check input for errors -% if ~isstruct( obj_struct ) -% fprintf( '\n\n\tMake sure that ''obj_struct'' is a struct type\n' ); -% return -% end - -% if (iscell( obj_struct )) -% for i =1:length(obj_struct) -% gen_object_display( obj_struct{i},indent + 2 ); -% end -% return -% end -if ~isstruct( obj_struct ) - space = sprintf( sprintf( '%%%ds',indent ),' ' ); - fprintf( ' %s', space); - disp(obj_struct); - return -end - -% find the longest name -field_list = fieldnames( obj_struct ); -max_strlen = 0; -for idx = 1:length( field_list ) - max_strlen = max( max_strlen,length(field_list{idx}) ); -end - -%% setup the display format (spacing) -space = sprintf( sprintf( '%%%ds',indent ),' ' ); -name_format = sprintf( ' %s%%%ds: ', space, max_strlen ); -name_format2= sprintf( ' %s%%%ds', space, max_strlen ); -max_displen = 110 - max_strlen - indent; - -%% display each field, if it is not too long -for iItem = 1:length( obj_struct ) % loop added by JT - for idx = 1:length( field_list ) - - % prepare field name to be displayed - name = sprintf( name_format,field_list{idx} ); - %temp = getfield( obj_struct,field_list{idx} ); % original by OG - temp = obj_struct(iItem).(field_list{idx}); % modification by JT - - % proceed according the variable's type - switch (1) - case islogical( temp ), % case added by JT - if isscalar(temp) - if (temp) - fprintf( '%strue\n',name ); - else - fprintf( '%sfalse\n',name ); - end - else - fprintf( '%s[%dx%d logical]\n',name,size(temp,1),size(temp,2) ); - end - case ischar( temp ), - if (length(temp) - fprintf( '[No method to display type]' ); - end - fprintf( '\n' ); - end - end - if (length(obj_struct)>1), fprintf('\n'); end % added by JT +function gen_object_display( obj_struct,indent ) +% +% gen_object_display - general function to display an object's content +% +% format: gen_object_display( obj_struct,indent ) +% +% input: obj_struct - a copy of the object stored inside a structure +% indent - amount of "indent" when printing to the screen +% +% output: to the screen +% +% example: gen_object_display( struct( my_object_handle) ); +% gen_object_display( ny_structure ); +% +% Correction History: +% 2006-11-01 - Jarek Tuszynski - added support for struct arrays + +%% handle insufficient input +if ( nargin == 0 ) + help gen_object_display; + return; +elseif (nargin == 1) + indent = 1; +end + +%% check input for errors +% if ~isstruct( obj_struct ) +% fprintf( '\n\n\tMake sure that ''obj_struct'' is a struct type\n' ); +% return +% end + +% if (iscell( obj_struct )) +% for i =1:length(obj_struct) +% gen_object_display( obj_struct{i},indent + 2 ); +% end +% return +% end +if ~isstruct( obj_struct ) + space = sprintf( sprintf( '%%%ds',indent ),' ' ); + fprintf( ' %s', space); + disp(obj_struct); + return +end + +% find the longest name +field_list = fieldnames( obj_struct ); +max_strlen = 0; +for idx = 1:length( field_list ) + max_strlen = max( max_strlen,length(field_list{idx}) ); +end + +%% setup the display format (spacing) +space = sprintf( sprintf( '%%%ds',indent ),' ' ); +name_format = sprintf( ' %s%%%ds: ', space, max_strlen ); +name_format2= sprintf( ' %s%%%ds', space, max_strlen ); +max_displen = 110 - max_strlen - indent; + +%% display each field, if it is not too long +for iItem = 1:length( obj_struct ) % loop added by JT + for idx = 1:length( field_list ) + + % prepare field name to be displayed + name = sprintf( name_format,field_list{idx} ); + %temp = getfield( obj_struct,field_list{idx} ); % original by OG + temp = obj_struct(iItem).(field_list{idx}); % modification by JT + + % proceed according the variable's type + switch (1) + case islogical( temp ), % case added by JT + if isscalar(temp) + if (temp) + fprintf( '%strue\n',name ); + else + fprintf( '%sfalse\n',name ); + end + else + fprintf( '%s[%dx%d logical]\n',name,size(temp,1),size(temp,2) ); + end + case ischar( temp ), + if (length(temp) + fprintf( '[No method to display type]' ); + end + fprintf( '\n' ); + end + end + if (length(obj_struct)>1), fprintf('\n'); end % added by JT end % added by JT \ No newline at end of file diff --git a/private/geodesic_convert_surface_points.m b/private/geodesic_convert_surface_points.m new file mode 100644 index 0000000..f24fdfd --- /dev/null +++ b/private/geodesic_convert_surface_points.m @@ -0,0 +1,46 @@ +%internal geodesic function +%conversion between C++ and matlab surface point representation +% Danil Kirsanov, 09/2007 + +function q = geodesic_convert_surface_points(p) + +if isempty(p) + q = []; + return; +end; + +point_types = {'vertex', 'edge', 'face'}; + +if ~isa(p,'numeric') %convert from matlab to C++ + num_points = length(p); + q = zeros(num_points*5, 1); + for i = 1:num_points + shift = (i-1)*5; + q(shift + 1) = p{i}.x; + q(shift + 2) = p{i}.y; + q(shift + 3) = p{i}.z; + q(shift + 5) = p{i}.id - 1; + + type = find(strcmp(point_types, p{i}.type)); + if isempty(type) + disp('error: point type is incorrect'); + q = []; + return; + else + q(shift + 4) = type - 1; + end + end +else %convert from C++ to matlab + num_points = length(p)/5; + q = cell(num_points, 1); + for i = 1:num_points + shift = (i-1)*5; + point.x = p(shift + 1); + point.y = p(shift + 2); + point.z = p(shift + 3); + point.type = point_types(p(shift + 4) + 1); + point.id = p(shift + 5) + 1; + + q{i} = point; + end; +end; diff --git a/private/geodesic_create_surface_point.m b/private/geodesic_create_surface_point.m new file mode 100644 index 0000000..5bb1927 --- /dev/null +++ b/private/geodesic_create_surface_point.m @@ -0,0 +1,14 @@ +function p = geodesic_create_surface_point(type,id,x,y,z) + +p.type = type; +p.id = id; + +if nargin == 3 + p.x = x(1); + p.y = x(2); + p.z = x(3); +else + p.x = x; + p.y = y; + p.z = z; +end diff --git a/private/geodesic_debug.dll b/private/geodesic_debug.dll new file mode 100644 index 0000000..c9dfac8 Binary files /dev/null and b/private/geodesic_debug.dll differ diff --git a/private/geodesic_delete.m b/private/geodesic_delete.m new file mode 100644 index 0000000..ade80e8 --- /dev/null +++ b/private/geodesic_delete.m @@ -0,0 +1,23 @@ +% delete mesh, algorithm, or everything at once +% Danil Kirsanov, 09/2007 + +function object = geodesic_delete(object) + +global geodesic_library; +if ~libisloaded(geodesic_library) %everything is already cleared + object = []; + return; +end + +if nargin == 0 % the simplest way to delete everything is to unload the library + unloadlibrary(geodesic_library); +else + if libisloaded(geodesic_library) + if strcmp(object.object_type, 'mesh') + calllib(geodesic_library, 'delete_mesh', object.id); % delete mesh and corresponding algorithms + else + calllib(geodesic_library, 'delete_algorithm', object.id); % delete a single algorithm + end + end +end +object = []; diff --git a/private/geodesic_distance_and_source.m b/private/geodesic_distance_and_source.m new file mode 100644 index 0000000..ec541ed --- /dev/null +++ b/private/geodesic_distance_and_source.m @@ -0,0 +1,25 @@ +%finds best source and distance to the best source +% if distance is negative, the best source cannot be found (for example, because the propagation was stopped before it reached this point) +% Danil Kirsanov, 09/2007 + +function [source_id, distance] = geodesic_best_source(algorithm, destination) + +global geodesic_library; + +if nargin == 2 + d = geodesic_convert_surface_points({destination}); + + tmp = 1; + [source_id, tmp1, distance] = calllib(geodesic_library, 'distance_and_source', algorithm.id, d, tmp); + source_id = source_id + 1; +else %return distances and sources for all vertices + tmp = libpointer('doublePtrPtr'); + tmp1 = libpointer('longPtrPtr'); + + [num_vertices, d, s] = calllib(geodesic_library, 'distance_and_source_for_all_vertices', algorithm.id, tmp, tmp1); + + setdatatype(d, 'doublePtr', num_vertices); + distance = d.Value; + setdatatype(s, 'int32Ptr', num_vertices); + source_id = s.Value + 1; +end \ No newline at end of file diff --git a/private/geodesic_matlab_api.h b/private/geodesic_matlab_api.h new file mode 100644 index 0000000..d1e7647 --- /dev/null +++ b/private/geodesic_matlab_api.h @@ -0,0 +1,50 @@ +#ifndef GEODESIC_DLL_HEADER_HPP_234232 +#define GEODESIC_DLL_HEADER_HPP_234232 + +#ifndef GEODESIC_DLL_IMPORT +#define GEODESIC_DLL_IMPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +GEODESIC_DLL_IMPORT long new_mesh(long num_points, //creates new mesh + double* points, + long num_triangles, + long* triangles, + long* num_edges, + double** edges); + +GEODESIC_DLL_IMPORT long new_algorithm(long mesh_id, //creates a geodesic algorithm for a given mesh + long type, + long subdivision); + +GEODESIC_DLL_IMPORT void delete_algorithm(long id); + +GEODESIC_DLL_IMPORT void delete_mesh(long id); //delete mesh and all associated algorithms + +GEODESIC_DLL_IMPORT void propagate(long algorithm_id, //compute distance field for given source points + double* source_points, + long num_sources, + double* stop_points, //limitations on distance field propagation + long num_stop_points, + double max_propagation_distance); + +GEODESIC_DLL_IMPORT long trace_back(long algorithm_id, //using procomputed distance field, compute a shortest path from destination to the closest source + double* destination, + double** path); + +GEODESIC_DLL_IMPORT long distance_and_source(long algorithm_id, //quickly find what source this point belongs to and what is the distance to this source + double* destination, + double* best_source_distance); + +GEODESIC_DLL_IMPORT long distance_and_source_for_all_vertices(long algorithm_id, //same idea as in the previous function + double** distances, //list distance/source info for all vertices of the mesh + long** sources); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/private/geodesic_matlab_api.so b/private/geodesic_matlab_api.so new file mode 100644 index 0000000..d901fdc Binary files /dev/null and b/private/geodesic_matlab_api.so differ diff --git a/private/geodesic_new_algorithm.m b/private/geodesic_new_algorithm.m new file mode 100644 index 0000000..b6ee9b7 --- /dev/null +++ b/private/geodesic_new_algorithm.m @@ -0,0 +1,29 @@ +function algorithm = geodesic_new_algorithm(mesh, type, subdivision) + +global geodesic_library; + +algorithm = []; +if ~libisloaded(geodesic_library) + disp('error: geodesic library is not loaded'); + return; +end + +if nargin == 2 + subdivision = 0; +end + +if strcmp(type, 'subdivision') & subdivision == 0 + type = 'dijkstra'; +end; + +algorithm_types = {'exact', 'subdivision', 'dijkstra'}; +type_id = find(strcmp(type, algorithm_types)); +if isempty(type_id) + disp('error: algorithm type is incorrect'); + return; +end +type_id = type_id - 1; + +algorithm.id = calllib(geodesic_library, 'new_algorithm', mesh.id, type_id, subdivision); +algorithm.type = type; +algorithm.object_type = 'algorithm'; \ No newline at end of file diff --git a/private/geodesic_new_mesh.m b/private/geodesic_new_mesh.m new file mode 100644 index 0000000..e6f7709 --- /dev/null +++ b/private/geodesic_new_mesh.m @@ -0,0 +1,32 @@ +function [mesh, edge_to_vertex, edge_to_face] = geodesic_new_mesh(points, tri) + +global geodesic_library +if ~libisloaded(geodesic_library) + hfile = 'geodesic_matlab_api.h'; + loadlibrary([geodesic_library '.so'], hfile); +end + +dim = find(size(points) == 3); +if dim == 1 + p = points(:); +else + p = points'; + p = p(:); +end; + +dim = find(size(tri) == 3); +if dim == 1 + t = tri(:) - 1; +else + t = tri'; + t = t(:) - 1; +end; + +tmp = libpointer('doublePtrPtr'); +[id, tmp1, tmp2, num_edges, edges] = calllib(geodesic_library, 'new_mesh', length(p)/3, p, length(t)/3, t, 1, tmp); +setdatatype(edges, 'doublePtr', 4, num_edges); + +mesh.id = id; +mesh.object_type = 'mesh'; +edge_to_vertex = (edges.Value(1:2,:) + 1)'; +edge_to_face = (edges.Value(3:4,:) + 1)'; \ No newline at end of file diff --git a/private/geodesic_propagate.m b/private/geodesic_propagate.m new file mode 100644 index 0000000..83cac7b --- /dev/null +++ b/private/geodesic_propagate.m @@ -0,0 +1,22 @@ +function geodesic_propagate(algorithm, source_points, stop_points, max_distance) + +global geodesic_library; + +if nargin < 4 + max_distance = 1e100; +end + +if nargin < 3 + stop_points = []; +end + +if ~libisloaded(geodesic_library) + disp('error: geodesic library is not loaded'); + return; +end + +sources = geodesic_convert_surface_points(source_points); +stops = geodesic_convert_surface_points(stop_points); + +calllib(geodesic_library, 'propagate', algorithm.id, ... + sources, length(sources)/5, stops, length(stops)/5, max_distance); diff --git a/private/geodesic_release.dll b/private/geodesic_release.dll new file mode 100644 index 0000000..c259d6f Binary files /dev/null and b/private/geodesic_release.dll differ diff --git a/private/geodesic_trace_back.m b/private/geodesic_trace_back.m new file mode 100644 index 0000000..9fd859a --- /dev/null +++ b/private/geodesic_trace_back.m @@ -0,0 +1,12 @@ +function path = geodesic_trace_back(algorithm, destination) + +global geodesic_library; + +tmp{1} = destination; +d = geodesic_convert_surface_points(tmp); + +tmp = libpointer('doublePtrPtr'); +[path_length, tmp, path_double] = calllib(geodesic_library, 'trace_back', algorithm.id, d, tmp); + +setdatatype(path_double, 'doublePtr', path_length*5); +path = geodesic_convert_surface_points(path_double.Value); \ No newline at end of file diff --git a/private/xml_io_tools/license.txt b/private/license - xml_io_tools.txt similarity index 100% rename from private/xml_io_tools/license.txt rename to private/license - xml_io_tools.txt diff --git a/private/license geodesic.txt b/private/license geodesic.txt new file mode 100644 index 0000000..d588125 --- /dev/null +++ b/private/license geodesic.txt @@ -0,0 +1,24 @@ +Copyright (c) 2008, Danil Kirsanov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/private/nameFiles.m b/private/nameFiles.m new file mode 100644 index 0000000..90b7a8e --- /dev/null +++ b/private/nameFiles.m @@ -0,0 +1,52 @@ +function listing = nameFiles(pathname, varargin) +% NAMEFFILES lists all files within pathname. +% Usage: +% listing = nameFolds(pathname) +% Where: +% listing - is a cell array of filenames +% pathname - is the directory in question +% +% NAMEFILES lists all files within a directory by using the matlab +% command dir and removing anything that isn't a directory. +% +% Parameter/Value pairs: +% 'showhiddenfiles' {true}|false +% 'extension' string +% +% Author: Steven Williams (2013) +% Modifications - +% +% Info on Code Testing: +% --------------------------------------------------------------- +% test code +% --------------------------------------------------------------- +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +%defaults +showHiddenFiles = true; + +%parse inputs +p = inputParser; +p.addRequired('pathname', @ischar); +p.addParamValue('showhiddenfiles', true, @islogical); +p.addParamValue('extension', [], @ischar); +p.parse(pathname, varargin{:}); +inputs = p.Results; +pathname = inputs.pathname; +extension = inputs.extension; + +d = dir(pathname); +isub = [d(:).isdir]; % returns logical vector +listing = {d(~isub).name}'; +listing(ismember(listing,{'.','..'})) = []; + +if ~showHiddenFiles + listing(strstartcmpi('.', listing)) = []; +end + +if ~isempty(extension) + listing(~strstartcmpi(fliplr(extension), cellfun(@fliplr, listing, 'uniformoutput', false))) = []; +end diff --git a/private/trFaceToVertData.m b/private/trFaceToVertData.m new file mode 100644 index 0000000..39a188b --- /dev/null +++ b/private/trFaceToVertData.m @@ -0,0 +1,37 @@ +function [ vertexData ] = trFaceToVertData( tr, faceData ) +% TRFACETOVERTDATA converts face to vertex data for a TriRep object. +% Usage: +% vertexData = trFaceToVertData(tr, faceData) +% Where: +% tr - is a TriRep object with the following dimensions: +% tr.Triangulation q * 3 +% tr.X p * 1 +% faceData - is an p*1 array of scalar data (by face) +% vertexData - is a q*1 array of scalar data (by vertex) +% +% TRFACETOVERTDATA detailed description. +% +% Author: Steven Williams (2012) +% Modifications - +% +% Info on Code Testing: +% --------------------------------------------------------------- +% test code +% --------------------------------------------------------------- +% +% Use warnings with the following format +% disp('TRFACETOVERTDATA: warning.') +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +[~, locs] = tricentroid(tr); + +F = scatteredInterpolant(locs(:,1), locs(:,2), locs(:,3) ... + , faceData ... + , 'linear' ... % interpolation {nearest|linear|natural} + , 'linear' ... % extrapolation {nearest|linear|none} + ); + +vertexData = F(tr.X); \ No newline at end of file diff --git a/private/trVertToFaceData.m b/private/trVertToFaceData.m new file mode 100644 index 0000000..5621a72 --- /dev/null +++ b/private/trVertToFaceData.m @@ -0,0 +1,38 @@ +function [ faceData ] = trVertToFaceData( tr, vertexData ) +% TRVERTTOFACEDATA converts vertex to face data for a TriRep object. +% Usage: +% faceData = trVertToFaceData(tr, vertexData) +% Where: +% tr - is a TriRep object with the following dimensions: +% tr.Triangulation q * 3 +% tr.X p * 1 +% vertexData - is an p*1 array of scalar data (by vertex) +% faceData - is a q*1 array of scalar data (by face) +% +% TRVERTTOFACEDATA detailed description. +% +% Author: Steven Williams (2012) +% Modifications - +% +% Info on Code Testing: + % --------------------- + % test code + % --------------------- +% +% Use warnings with the following format +% disp('TRVERTTOFACEDATA: warning.') +% +% --------------------------------------------------------------- +% code +% --------------------------------------------------------------- + +%faceData = zeros(length(tr.Triangulation), 1); +% for i = 1:length(tr.Triangulation); +% v1 = tr.Triangulation(i, 1); +% v2 = tr.Triangulation(i, 2); +% v3 = tr.Triangulation(i, 3); +% +% faceData(i) = mean([vertexData(v1) vertexData(v2) vertexData(v3)]); +% end + +faceData = mean(vertexData(tr.Triangulation),2); \ No newline at end of file diff --git a/private/xml_io_tools/html/xml_tutorial_script.html b/private/xml_io_tools/html/xml_tutorial_script.html deleted file mode 100644 index c2bf98c..0000000 --- a/private/xml_io_tools/html/xml_tutorial_script.html +++ /dev/null @@ -1,2538 +0,0 @@ - - - - - Tutorial for xml_io_tools Package

Tutorial for xml_io_tools Package

By Jarek Tuszynski

Package xml_io_tools can read XML files into MATLAB struct and writes MATLAB data types to XML files with help of simple interface to MATLAB's xmlwrite and xmlread functions.

Two function to simplify reading and writing XML files from MATLAB:

  • Function xml_read first calls MATLAB's xmlread function and than converts its output ('Document Object Model' tree of Java objects) to tree of MATLAB struct's. The output is in the format of nested structs and cells. In the output data structure field names are based on XML tags.
  • Function xml_write first convert input tree of MATLAB structs and cells and other types to tree of 'Document Object Model' nodes, and then writes resulting object to XML file using MATLAB's xmlwrite function. .

Contents

This package can:

  • Read most XML files, created inside and outside of MATLAB environment, and convert them to MATLAB data structures.
  • Write any MATLAB's struct tree to XML file
  • Handle XML attributes and special XML nodes like comments, processing instructions and CDATA sections
  • Supports base64 encoding and decoding to allow handling embeded binary data
  • Be studied, modified, customized, rewritten and used in other packages without any limitations. All code is included and documented. Software is distributed under BSD Licence (included).

This package does not:

  • Guarantee to recover the same Matlab objects that were saved. If you need to be able to recover carbon copy of the structure that was saved than you will have to use one of the packages that uses special set of tags saved as xml attributes that help to guide the parsing of XML code. This package does not use those tags.
  • Guarantee to work with older versions of MATLAB. Functions do not work with versions of MATLAB prior to 7.1 (26-Jul-2005).

Change History

  • 2006-11-06 - original version
  • 2006-11-26 - corrected xml_write to handle writing Matlab's column arrays to xml files. Bug discovered and diagnosed by Kalyan Dutta.
  • 2006-11-28 - made changes to handle special node types like: COMMENTS and CDATA sections
  • 2007-03-12 - Writing CDATA sections still did not worked. The problem was diagnosed and fixed by Alberto Amaro. The fix involved rewriting xmlwrite to use Apache Xerces java files directly instead of MATLAB's XMLUtils java class.
  • 2007-06-21 - Fixed problem reported by Anna Kelbert in Reviews about not writing attributes of ROOT node. Also: added support for Processing Instructions, added support for global text nodes: Processing Instructions and comments, allowed writing tag names with special characters
  • 2007-07-20 - Added tutorial script file. Extended support for global text nodes. Added more Preference fields.
  • 2008-01-23 - Fixed problem reported by Anna Krewet of converting dates in format '2007-01-01' to numbers. Improved and added warning messages. Added detection of old Matlab versions incompatible with the library. Expanded documentation.
  • 2008-06-23 - Fixed problem with writing 1D array reported by Mark Neil. Extended xml_read's Pref.Num2Str to 3 settings (never, smart and always) for better control. Added parameter Pref.KeepNS for keeping or ignoring namespace data when reading. Fixed a bug related to writing 2D cell arrays brought up by Andrej's Mosat review.
  • 2008-09-11 - Resubmitting last upload - zip file is still old
  • 2009-02-26 - Small changes. More error handling. More robust in case of large binary objects. Added support for Base64 encoding/decoding of binary objects (using functions by Peter J. Acklam).
  • 2009-06-26 - changes to xml_read: added CellItem parameter to allow better control of reading files with 'item' notation (see comment by Shlomi); changed try-catch statements so xml_read would work for mablab versions prior to 7.5 (see Thomas Pilutti comment)
  • 2009-12-03 - added PreserveSpace parameter for contolling empty string handling as suggested by Sebastiaan. Fix suggested by Michael Murphy. Fixed number recognition code as suggested by Yuan Ren.
  • 2010-05-04 - implemented fixes suggested by Dylan Reynolds from Airbus.
  • 2010-07-28 - implemented support for 2D arrays of cells and structs suggested by Rodney Behn from MIT Lincoln Laboratory. Also attempted large scale cleanup of xml_write function
  • 2010-08-18 - minor extension to allow better handling of logical scalars and arrays and function handles suggested by Andreas Richter and others
  • 2010-09-20 - allow reading and writing of sparse matrices. Improve reading of 1D boolean arrays.
  • 2010-11-05 - Fix problem with empty cells reported by Richard Cotton; fixed issues with reading boolean arrays reported by Zohar Bar-Yehuda; Improved speed of base64 coding and decoding by switching to java based code.

Licence

The package is distributed under BSD License

format compact; % viewing preference
-clear variables;
-type('license.txt')
-
-Copyright (c) 2007, Jaroslaw Tuszynski
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without 
-modification, are permitted provided that the following conditions are 
-met:
-
-    * Redistributions of source code must retain the above copyright 
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright 
-      notice, this list of conditions and the following disclaimer in 
-      the documentation and/or other materials provided with the distribution
-      
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
-POSSIBILITY OF SUCH DAMAGE.
-
-

Write XML file based on a Struct using "xml_write"

Any MATLAB data struct can be saved to XML file.

MyTree=[];
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World';
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Read XML file producing a Struct using "xml_read"

[tree treeName] = xml_read ('test.xml');
-disp([treeName{1} ' ='])
-gen_object_display(tree)
-
MyTree =
-    MyNumber: [13]
-    MyString: 'Hello World'
-

"Pref.XmlEngine" flag in "xml_write"

Occasionaly some operations are performed better by Apache Xerces XML engine than default xmlread function. That is why xml_write provide an option for choosing the underlaying xml engine. Code below performs the same operation as the previous section but using Apache Xerces XML engine. Notice that in this case name of root element was passed as variable and not extracted from the variable name.

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, 'TreeOfMine', Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<TreeOfMine>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</TreeOfMine>
-
-

Writing Struct with different type MATLAB arrays

MyTree=[];
-MyTree.Empty   = [];                   % Empty variable
-MyTree.Num_1x1 = 13;                   % simple scalar
-MyTree.Vec_1x3 = [1 2 3];              % horizontal vector
-MyTree.Vec_4x1 = [1; 2; 3; 4];         % vertical vector
-MyTree.Mat_2x2 = [1, 2; 3, 4];         % 2D matrix
-MyTree.Cube_3D = reshape(1:8,[2 2 2]); % 3D array
-MyTree.String1 = '[2003 10 30]';       % number string with    [] brackets
-MyTree.String2 = ' 2003 10 30 ';       % number string without [] brackets
-MyTree.Logical_1x1 = false;            % single logical
-MyTree.Logical_2x2 = [false, true; true, false]; % 2D matrix of logicals
-MyTree.Logical_Str = 'False False	True True';
-MyTree.Int_2x2 = uint8([1 2;3 4]);     % 2D matrix of uint8 integers
-MyTree.Complex_1x1 = complex(1, 7);    % complex scalar
-MyTree.Complex_2x2 = complex([1 2;3 4],[2 2;7 7]); % 2D matrix of complex numbers
-MyTree.Sparse_9x9 = sparse(1:9,1:9,1); % sparse 9x9 matrix
-MyTree.Function = @sum;                % function handle
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <Empty/>
-   <Num_1x1>13</Num_1x1>
-   <Vec_1x3>[1 2 3]</Vec_1x3>
-   <Vec_4x1>[1;2;3;4]</Vec_4x1>
-   <Mat_2x2>[1 2;3 4]</Mat_2x2>
-   <Cube_3D>[1;2;3;4;5;6;7;8]</Cube_3D>
-   <String1>[2003 10 30]</String1>
-   <String2>2003 10 30</String2>
-   <Logical_1x1>[false]</Logical_1x1>
-   <Logical_2x2>[false true;true false]</Logical_2x2>
-   <Logical_Str>False False True True</Logical_Str>
-   <Int_2x2>[uint8([1 2;3 4])]</Int_2x2>
-   <Complex_1x1>1+i*7</Complex_1x1>
-   <Complex_2x2>[1+i*2 2+i*2;3+i*7 4+i*7]</Complex_2x2>
-   <Sparse_9x9>[sparse([1;2;3;4;5;6;7;8;9], [1;2;3;4;5;6;7;8;9], [1;1;1;1;1;1;1;1;1], 9, 9)]</Sparse_9x9>
-   <Function>[@sum]</Function>
-</MyTree>
-

Read Struct with MATLAB arrays

Notice that 'Cube_3D' did not preserve original dimentions

[tree treeName] = xml_read ('test.xml');
-disp([treeName{1} ' ='])
-gen_object_display(tree)
-
MyTree =
-          Empty: [0x0 double]
-        Num_1x1: [13]
-        Vec_1x3: [1  2  3]
-        Vec_4x1: [4x1 double]
-        Mat_2x2: [2x2 double]
-        Cube_3D: [8x1 double]
-        String1: [2003    10    30]
-        String2: [2003    10    30]
-    Logical_1x1: false
-    Logical_2x2: [2x2 logical]
-    Logical_Str: [1x4 logical]
-        Int_2x2: [2x2 double]
-    Complex_1x1: [1+7i]
-    Complex_2x2: [2x2 double]
-     Sparse_9x9: [9x9 double]
-       Function: [No method to display type]
-

"Pref.StructItem" flag in "xml_write" (controls 1D arrays of structs)

Create a simple structure with 1D array of struct's

MyTree = [];
-MyTree.a(1).b = 'jack';
-MyTree.a(2).b = 'john';
-gen_object_display(MyTree)
-
    a: [1x2 struct]
-       b: 'jack'
-
-       b: 'john'
-
-

Write XML with "StructItem = true" (default). Notice single 'a' section and multiple 'item' sub-sections. Those subsections are used to store array elements

wPref.StructItem = true;
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-gen_object_display(xml_read ('test.xml'))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <item>
-         <b>jack</b>
-      </item>
-      <item>
-         <b>john</b>
-      </item>
-   </a>
-</MyTree>
-
-xml_read output:
-    a: [2x1 struct]
-       b: 'jack'
-
-       b: 'john'
-
-

Write XML with "StructItem = false". Notice multiple 'a' sections

wPref.StructItem = false;
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-gen_object_display(xml_read ('test.xml'))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <b>jack</b>
-   </a>
-   <a>
-      <b>john</b>
-   </a>
-</MyTree>
-
-xml_read output:
-    a: [2x1 struct]
-       b: 'jack'
-
-       b: 'john'
-
-

Notice that xml_read function produced the same struct when reading both files

Potential problems with "StructItem = true":

wPref.StructItem = true;
-MyTree1 = []; MyTree1.a.b    = 'jack';
-MyTree2 = []; MyTree2.a(1).b = 'jack';
-MyTree3 = []; MyTree3.a(2).b = 'jack';
-xml_write('test.xml', MyTree1, [], wPref); type('test.xml');
-xml_write('test.xml', MyTree2, [], wPref); type('test.xml');
-xml_write('test.xml', MyTree3, [], wPref); type('test.xml');
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree1>
-   <a>
-      <b>jack</b>
-   </a>
-</MyTree1>
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree2>
-   <a>
-      <b>jack</b>
-   </a>
-</MyTree2>
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree3>
-   <a>
-      <item>
-         <b/>
-      </item>
-      <item>
-         <b>jack</b>
-      </item>
-   </a>
-</MyTree3>
-

Notice that MyTree1 and MyTree2 produce identical files with no 'items', while MyTree2 and MyTree3 produce very different file structures. It was pointed out to me that files produced from MyTree2 and MyTree3 can not belong to the same schema, which can be a problem. The solution is to use cells.

wPref.CellItem = true;
-wPref.NoCells  = true;
-MyTree2 = []; MyTree2.a{1}.b = 'jack';
-MyTree3 = []; MyTree3.a{2}.b = 'jack';
-xml_write('test.xml', MyTree2, [], wPref); type('test.xml');
-xml_write('test.xml', MyTree3, [], wPref); type('test.xml');
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree2>
-   <a>
-      <item>
-         <b>jack</b>
-      </item>
-   </a>
-</MyTree2>
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree3>
-   <a>
-      <item/>
-      <item>
-         <b>jack</b>
-      </item>
-   </a>
-</MyTree3>
-

"Pref.CellItem" flag in "xml_write" (controls 1D arrays of cells)

Create a simple structure with cell arrays

MyTree = [];
-MyTree.a = {'jack', 'john'};
-disp(MyTree)
-
    a: {'jack'  'john'}
-

Write XML with "CellItem = true" (default). Notice single 'a' section and multiple 'item' sections

Pref=[]; Pref.CellItem = true;
-xml_write('test.xml', MyTree, 'MyTree',Pref);
-type('test.xml')
-fprintf('\nxml_read output:\n');
-disp(xml_read ('test.xml'))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <item>jack</item>
-      <item>john</item>
-   </a>
-</MyTree>
-
-xml_read output:
-    a: {'jack'  'john'}
-

Write XML with "CellItem = false". Notice multiple 'a' sections

Pref=[]; Pref.CellItem = false;
-xml_write('test.xml', MyTree, 'MyTree',Pref);
-type('test.xml')
-fprintf('\nxml_read output:\n');
-disp(xml_read ('test.xml'))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>jack</a>
-   <a>john</a>
-</MyTree>
-
-xml_read output:
-    a: {'jack'  'john'}
-

Notice that xml_read function produced the same struct when reading both files

"Pref.NoCells" flag in "xml_read"

Create a cell/struct mixture object

MyTree = [];
-MyTree.a{1}.b = 'jack';
-MyTree.a{2}.b = [];
-MyTree.a{2}.c = 'john';
-gen_object_display(MyTree);
-
    a: [1x2 cell] = 
-       b: 'jack'
-
-       b: [0x0 double]
-       c: 'john'
-
-

Save it to xml file

Pref=[]; Pref.CellItem = false;
-xml_write('test.xml', MyTree, 'MyTree',Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <b>jack</b>
-   </a>
-   <a>
-      <b/>
-      <c>john</c>
-   </a>
-</MyTree>
-

Read above file with "Pref.NoCells=true" (default) - output is quite different then input By default program is trying to convert everything to struct's and arrays of structs. In case arrays of structs all the structs in array need to have the same fields, and if they are not than MATLAB creates empty fields.

Pref=[]; Pref.NoCells=true;
-gen_object_display(xml_read('test.xml', Pref))
-
    a: [2x1 struct]
-       b: 'jack'
-       c: [0x0 double]
-
-       b: [0x0 double]
-       c: 'john'
-
-

Read above file with "Pref.NoCells=false" - now input and output are the same Cell arrays of structs allow structs in array to have different fields.

Pref=[]; Pref.NoCells=false;
-gen_object_display(xml_read('test.xml', Pref))
-
    a: [1x2 cell] = 
-       b: 'jack'
-
-       b: [0x0 double]
-       c: 'john'
-
-

"Pref.ItemName" flag in "xml_write" (customize 1D arrays of structs and cells)

Create a cell/struct mixture object

MyTree = [];
-MyTree.a{1}.b = 'jack';
-MyTree.a{2}.c = 'john';
-gen_object_display(MyTree);
-
    a: [1x2 cell] = 
-       b: 'jack'
-
-       c: 'john'
-
-

Save it to xml file, using 'item' notation but with different name

Pref=[];
-Pref.CellItem = true;
-Pref.ItemName = 'MyItem';
-xml_write('test.xml', MyTree, 'MyTree',Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <MyItem>
-         <b>jack</b>
-      </MyItem>
-      <MyItem>
-         <c>john</c>
-      </MyItem>
-   </a>
-</MyTree>
-

"Pref.ItemName" flag in "xml_read"

Read above file with default settings ("Pref.ItemName = 'item'") The results do not match the original structure

Pref=[]; Pref.NoCells  = false;
-gen_object_display(xml_read('test.xml', Pref))
-
    a: [1x1 struct]
-       MyItem: [1x2 cell] = 
-               b: 'jack'
-
-               c: 'john'
-
-

Read above file with "Pref.ItemName = 'MyItem'" - now saved and read MATLAB structures are the same

Pref=[];
-Pref.ItemName = 'MyItem';
-Pref.NoCells  = false;
-gen_object_display(xml_read('test.xml', Pref))
-
    a: [1x2 cell] = 
-       b: 'jack'
-
-       c: 'john'
-
-

"Pref.CellItem" flag in "xml_read"

"Pref.ItemName" is used to create xml files with clearly marked arrays "Pref.CellItem" flag in "xml_read" ensures that they are always read as arrays by forcing output to stay in cell format. In cell format s{1} is different than s, while s(1) is indistinguishable from s.

Create a test file

MyTree = [];
-MyTree.a1{1}.b = 'jack'; % a1 - single struct
-MyTree.a2{1}.b = 'jack'; % a2 - cell array of structs with the same fields
-MyTree.a2{2}.b = 'john';
-MyTree.a3{1}.b = 'jack'; % a3 - cell array of structs with the different fields
-MyTree.a3{2}.c = 'john';
-Pref=[];
-Pref.CellItem = true;
-Pref.Debug = true;
-xml_write('test.xml', MyTree, 'MyTree',Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a1>
-      <item>
-         <b>jack</b>
-      </item>
-   </a1>
-   <a2>
-      <item>
-         <b>jack</b>
-      </item>
-      <item>
-         <b>john</b>
-      </item>
-   </a2>
-   <a3>
-      <item>
-         <b>jack</b>
-      </item>
-      <item>
-         <c>john</c>
-      </item>
-   </a3>
-</MyTree>
-

Read above file with "Pref.CellItem = true" (default) All outputs are in cell format

Pref=[];
-Pref.NoCells  = false;  % allow cell output
-Pref.CellItem = true;   % keep 'item' arrays as cells
-gen_object_display(xml_read('test.xml', Pref))
-
    a1: [1x1 cell] = 
-        b: 'jack'
-
-    a2: [1x1 cell] = 
-            [1x1 struct]    [1x1 struct]
-
-    a3: [1x1 cell] = 
-            [1x1 struct]    [1x1 struct]
-
-

Read above file with "Pref.CellItem = false" Outputs format is determined by content

Pref=[];
-Pref.NoCells  = false; % allow cell output
-Pref.CellItem = false; % allow 'item' arrays to beheave like other fields
-gen_object_display(xml_read('test.xml', Pref))
-
    a1: [1x1 struct]
-        b: 'jack'
-    a2: [2x1 struct]
-        b: 'jack'
-
-        b: 'john'
-
-    a3: [1x2 cell] = 
-        b: 'jack'
-
-        c: 'john'
-
-

Read above file with "Pref.CellItem = false" and "Pref.NoCells = true" All outputs are in struct format

Pref=[];
-Pref.NoCells  = true;  % don't allow cell output
-Pref.CellItem = false; % allow 'item' arrays to beheave like other fields
-gen_object_display(xml_read('test.xml', Pref))
-
    a1: [1x1 struct]
-        b: 'jack'
-    a2: [2x1 struct]
-        b: 'jack'
-
-        b: 'john'
-
-    a3: [2x1 struct]
-        b: 'jack'
-        c: [0x0 double]
-
-        b: [0x0 double]
-        c: 'john'
-
-

"Pref.CellTable" flag in "xml_write" (controls 2D arrays of cells)

Create a structure with 2D arrays of cells

MyTree = [];
-MyTree.M = {[1,2;3,4], 'M12'; struct('a','jack'), {11, 'N12'; 21, 'N22'}};
-gen_object_display(MyTree)
-
    M: [2x2 cell] = 
-           [2x2 double]    'M12'
-           [1x1 struct]    {2x2 cell}
-

Write XML with "CellTable = 'Html" (default). This option mimics use of HTML "tr" and "td" tags to encode 2D tables. Tag names can be changed using TableName parameter (see below)

wPref = [];
-wPref.CellTable = 'Html';
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-rPref=[]; rPref.NoCells=false;
-gen_object_display(xml_read('test.xml', rPref))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <M>
-      <tr>
-         <td>[1 2;3 4]</td>
-         <td>M12</td>
-      </tr>
-      <tr>
-         <td>
-            <a>jack</a>
-         </td>
-         <td>
-            <tr>
-               <td>11</td>
-               <td>N12</td>
-            </tr>
-            <tr>
-               <td>21</td>
-               <td>N22</td>
-            </tr>
-         </td>
-      </tr>
-   </M>
-</MyTree>
-
-xml_read output:
-    M: [2x2 cell] = 
-           [2x2 double]    'M12'
-           [1x1 struct]    {2x2 cell}
-

Write XML with "CellTable = 'Vector'". Converts 2D arrays to 1D array and item or regular notation. This option is mostly provided for backward compatibility since this was the behavior in prior verions of the code

wPref = [];
-wPref.CellTable = 'Vector';
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-rPref=[]; rPref.NoCells=false;
-gen_object_display(xml_read('test.xml', rPref))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <M>
-      <item>[1 2;3 4]</item>
-      <item>
-         <a>jack</a>
-      </item>
-      <item>M12</item>
-      <item>
-         <item>11</item>
-         <item>21</item>
-         <item>N12</item>
-         <item>N22</item>
-      </item>
-   </M>
-</MyTree>
-
-xml_read output:
-    M: [1x4 cell] = 
-            1     2
-     3     4
-
-       a: 'jack'
-
-       M12
-
-           [11]    [21]    'N12'    'N22'
-
-

Create a simpler structure without struct's

MyTree = [];
-MyTree.M = {[1,2;3,4], 'M12'; 'M21', {11, 'N12'; 21, 'N22'}};
-gen_object_display(MyTree)
-
    M: [2x2 cell] = 
-           [2x2 double]    'M12'
-           'M21'    {2x2 cell}
-

Write XML with "CellTable = 'Matlab". This option encodes tables consisting of numbers, strings and other cell arrays as MATLAB command string. Unlike 'Html' option it does not work if one of the cells is a struct

wPref = [];
-wPref.CellTable = 'Matlab';
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-rPref=[]; rPref.NoCells=false;
-gen_object_display(xml_read('test.xml', rPref))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <M>{[1 2;3 4],'M12';'M21',{11,'N12';21,'N22';};}</M>
-</MyTree>
-
-xml_read output:
-    M: [2x2 cell] = 
-           [2x2 double]    'M12'
-           'M21'    {2x2 cell}
-

Write 2D cell array in HTML format

MyTree = [];
-MyTree.table.ATTRIBUTE.border=1;
-MyTree.table.CONTENT = {'Apples', '44%'; 'Bannanas', '23%'; 'Oranges', '13%'; 'Other', '10%'};
-xml_write('html/test.html', MyTree);
-type('html/test.html')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <table border="1">
-      <tr>
-         <td>Apples</td>
-         <td>44%</td>
-      </tr>
-      <tr>
-         <td>Bannanas</td>
-         <td>23%</td>
-      </tr>
-      <tr>
-         <td>Oranges</td>
-         <td>13%</td>
-      </tr>
-      <tr>
-         <td>Other</td>
-         <td>10%</td>
-      </tr>
-   </table>
-</MyTree>
-

Click on test.html to opened this file with a web brouwser

"Pref.StructTable" flag in "xml_write" (controls 2D arrays of structs)

Create a simple structure with arrays of struct's

MyTree = [];
-MyTree.a(1,1).b = 'jack';
-MyTree.a(1,2).b = 'john';
-MyTree.a(2,1).b = 'jim';
-MyTree.a(2,2).b = 'jill';
-gen_object_display(MyTree)
-
    a: [2x2 struct]
-      a(1,1) =
-        b: 'jack'
-      a(1,2) =
-        b: 'john'
-      a(2,1) =
-        b: 'jim'
-      a(2,2) =
-        b: 'jill'
-

Write XML with "StructTable = 'Html" (default). This option mimics use of HTML "tr" and "td" tags to encode 2D tables. Tag names can be changed using TableName parameter (see below)

wPref = [];
-wPref.StructTable = 'Html';
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-gen_object_display(xml_read ('test.xml'))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <tr>
-         <td>
-            <b>jack</b>
-         </td>
-         <td>
-            <b>john</b>
-         </td>
-      </tr>
-      <tr>
-         <td>
-            <b>jim</b>
-         </td>
-         <td>
-            <b>jill</b>
-         </td>
-      </tr>
-   </a>
-</MyTree>
-
-xml_read output:
-    a: [2x2 struct]
-      a(1,1) =
-        b: 'jack'
-      a(1,2) =
-        b: 'john'
-      a(2,1) =
-        b: 'jim'
-      a(2,2) =
-        b: 'jill'
-

Write XML with "CellTable = 'Vector'". Converts 2D arrays to 1D array and item or regular notation. This option is mostly provided for backward compatibility since this was the behavior in prior verions of the code

wPref = [];
-wPref.StructTable = 'Vector';
-xml_write('test.xml', MyTree, 'MyTree',wPref);
-type('test.xml')
-fprintf('\nxml_read output:\n')
-gen_object_display(xml_read ('test.xml'))
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <a>
-      <item>
-         <b>jack</b>
-      </item>
-      <item>
-         <b>jim</b>
-      </item>
-      <item>
-         <b>john</b>
-      </item>
-      <item>
-         <b>jill</b>
-      </item>
-   </a>
-</MyTree>
-
-xml_read output:
-    a: [4x1 struct]
-       b: 'jack'
-
-       b: 'jim'
-
-       b: 'john'
-
-       b: 'jill'
-
-

"Pref.TableName" flag in "xml_write" (controls encoding tags used for 2D arrays)

Create a cell object

MyTree = [];
-MyTree.M = {[1,2;3,4], 'M12'; 21, {11, 'N12'; 21, 'N22'}};
-gen_object_display(MyTree);
-
    M: [2x2 cell] = 
-           [2x2 double]    'M12'
-           [21]    {2x2 cell}
-

Save it to xml file, using 'Html' notation but with different names for rows and cells

Pref=[]; Pref.TableName = {'row','cell'};
-xml_write('test.xml', MyTree, 'MyTree',Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <M>
-      <row>
-         <cell>[1 2;3 4]</cell>
-         <cell>M12</cell>
-      </row>
-      <row>
-         <cell>21</cell>
-         <cell>
-            <row>
-               <cell>11</cell>
-               <cell>N12</cell>
-            </row>
-            <row>
-               <cell>21</cell>
-               <cell>N22</cell>
-            </row>
-         </cell>
-      </row>
-   </M>
-</MyTree>
-

"Pref.TableName" flag in "xml_read"

Read above file with default settings ("Pref.TableName = {'tr','td'}") The results do not match the original structure

Pref=[]; Pref.NoCells  = false;
-gen_object_display(xml_read('test.xml', Pref))
-
    M: [1x1 struct]
-       row: [2x1 struct]
-            cell: [1x2 cell] = 
-                       1     2
-     3     4
-
-                  M12
-
-
-            cell: [1x2 cell] = 
-                      21
-
-                  row: [2x1 struct]
-                       cell: [1x2 cell] = 
-                                 11
-
-                             N12
-
-
-                       cell: [1x2 cell] = 
-                                 21
-
-                             N22
-
-
-
-
-

Read above file with "Pref.TableName = {'row','cell'}" - now saved and read MATLAB structures are the same

Pref=[];
-Pref.TableName = {'row','cell'};
-Pref.NoCells  = false;
-gen_object_display(xml_read('test.xml', Pref))
-
    M: [2x2 cell] = 
-           [2x2 double]    'M12'
-           [21]    {2x2 cell}
-

"Pref.Str2Num" flag in xml_read (control conversion to numbers while reading)

Create a cell/struct mixture object

MyTree = [];
-MyTree.str     = 'sphere';
-MyTree.num1    =  123;
-MyTree.num2    = '123';
-MyTree.num3    = '[Inf,NaN]';
-MyTree.calc    = '1+2+3+4';
-MyTree.func    = 'sin(pi)/2';
-MyTree.String1 = '[2003 10 30]';
-MyTree.String2 = '2003 10 30';   % array resembling date
-MyTree.ISO8601 = '2003-10-30';   % date in ISO 8601 format
-MyTree.US_date = '2003/10/30';   % US style date format
-MyTree.complex = '2003i-10e-30'; % complex number resembling a date
-gen_object_display(MyTree);
-
        str: 'sphere'
-       num1: [123]
-       num2: '123'
-       num3: '[Inf,NaN]'
-       calc: '1+2+3+4'
-       func: 'sin(pi)/2'
-    String1: '[2003 10 30]'
-    String2: '2003 10 30'
-    ISO8601: '2003-10-30'
-    US_date: '2003/10/30'
-    complex: '2003i-10e-30'
-

Save it to xml file

xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <str>sphere</str>
-   <num1>123</num1>
-   <num2>123</num2>
-   <num3>[Inf,NaN]</num3>
-   <calc>1+2+3+4</calc>
-   <func>sin(pi)/2</func>
-   <String1>[2003 10 30]</String1>
-   <String2>2003 10 30</String2>
-   <ISO8601>2003-10-30</ISO8601>
-   <US_date>2003/10/30</US_date>
-   <complex>2003i-10e-30</complex>
-</MyTree>
-

Read above file with default settings ("Pref.Str2Num = true" or "Pref.Str2Num = 'smart'"). Under this setting all strings that look like numbers are converted to numbers, except for strings that are recognized by MATLAB 'datenum' function as dates

gen_object_display(xml_read('test.xml'))
-
        str: 'sphere'
-       num1: [123]
-       num2: [123]
-       num3: [Inf  NaN]
-       calc: [10]
-       func: 'sin(pi)/2'
-    String1: [2003    10    30]
-    String2: [2003    10    30]
-    ISO8601: '2003-10-30'
-    US_date: '2003/10/30'
-    complex: [-1e-029+2003i]
-

Note that all the fields of 'MyTree' can be converted to numbers (even 'sphere') but by default the function is trying to 'judge' if a string should be converted to a number or not

MyCell = {'sphere','1+2+3+4','sin(pi)/2','2003 10 30','2003-10-30','2003/10/30','2003i-10e-30'};
-cellfun(@str2num, MyCell, 'UniformOutput', false)
-
ans = 
-  Columns 1 through 6
-    [21x21 double]    [10]    [6.1232e-017]    [1x3 double]    [1963]    [6.6767]
-  Column 7
-    [-1.0000e-029 +2.0030e+003i]
-

Read above file with "Pref.Str2Num = false" or "Pref.Str2Num = 'never'" to keep all the fields in string format

Pref=[]; Pref.Str2Num = false;
-gen_object_display(xml_read('test.xml', Pref))
-
        str: 'sphere'
-       num1: '123'
-       num2: '123'
-       num3: '[Inf,NaN]'
-       calc: '1+2+3+4'
-       func: 'sin(pi)/2'
-    String1: '[2003 10 30]'
-    String2: '2003 10 30'
-    ISO8601: '2003-10-30'
-    US_date: '2003/10/30'
-    complex: '2003i-10e-30'
-

Read above file with "Pref.Str2Num = always" to convert all strings that look like numbers to numbers note the likelly unintendet conversion of 'ISO8601'

Pref=[]; Pref.Str2Num   = 'always';
-gen_object_display(xml_read('test.xml', Pref))
-
        str: 'sphere'
-       num1: [123]
-       num2: [123]
-       num3: [Inf  NaN]
-       calc: [10]
-       func: 'sin(pi)/2'
-    String1: [2003    10    30]
-    String2: [2003    10    30]
-    ISO8601: [1963]
-    US_date: '2003/10/30'
-    complex: [-1e-029+2003i]
-

Notice that all three settings will produce the same output for "num1" and "num2" and there is no way to reproduce the original "MyTree" structure.

"Pref.PreserveSpace" flag in xml_write (control handling of strings with leading/trailing spaces)

Create a struct with strings

MyTree=[];
-MyTree.Empty     = '';
-MyTree.OneSpace  = ' ';
-MyTree.TwoSpaces = '  ';
-MyTree.String1   = ' Hello      World ';
-

Write XML with "PreserveSpace = false" (default).

Pref=[]; Pref.PreserveSpace = false; % (default setting)
-xml_write('test.xml', MyTree, [], Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <Empty/>
-   <OneSpace/>
-   <TwoSpaces/>
-   <String1>Hello World</String1>
-</MyTree>
-

Write XML with "PreserveSpace = true".

Pref=[]; Pref.PreserveSpace = true;
-xml_write('test.xml', MyTree, [], Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <Empty/>
-   <OneSpace> </OneSpace>
-   <TwoSpaces>  </TwoSpaces>
-   <String1> Hello      World </String1>
-</MyTree>
-

"Pref.PreserveSpace" flag in xml_read

Read file while using "PreserveSpace = false" (default).

Pref=[]; Pref.PreserveSpace = false; % (default setting)
-gen_object_display(xml_read('test.xml',Pref))
-
        Empty: [0x0 double]
-     OneSpace: [0x0 double]
-    TwoSpaces: [0x0 double]
-      String1: 'Hello      World'
-

Read file while using "PreserveSpace = true".

Pref=[]; Pref.PreserveSpace = true;
-gen_object_display(xml_read('test.xml',Pref))
-
        Empty: [0x0 double]
-     OneSpace: ' '
-    TwoSpaces: '  '
-      String1: ' Hello      World '
-

Write XML files with ATTRIBUTEs

In order to add node attributes a special ATTRIBUTE field is used. ATTRIBUTEs have to be of simple types like numbers or strings (not struct or cells). Attributes are easy to attach to structs nodes like MyTree below.

MyTree=[];
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World'; % simple case
-MyTree.ATTRIBUTE.Num = 2;
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree Num="2">
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

In case when one needs to attach attributes to nodes which are not structs (for example strings, numbers or calls) then special CONTENT field needs to be used to make the node a struct node.

MyTree=[];
-MyTree.MyNumber = 13;
-MyTree.MyString.CONTENT = 'Hello World'; % simple case
-MyTree.MyString.ATTRIBUTE.Num = 2;
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString Num="2">Hello World</MyString>
-</MyTree>
-

"Pref.Str2Num" flag in file with ATTRIBUTEs

Create a cell/struct mixture object

MyTree = [];
-MyTree.X.ATTRIBUTE.str     = 'sphere';
-MyTree.X.ATTRIBUTE.num1    =  123;
-MyTree.X.ATTRIBUTE.num2    = '123';
-MyTree.X.ATTRIBUTE.num3    = '[Inf,NaN]';
-MyTree.X.ATTRIBUTE.calc    = '1+2+3+4';
-MyTree.X.ATTRIBUTE.func    = 'sin(pi)/2';
-MyTree.X.ATTRIBUTE.String1 = '[2003 10 30]';
-MyTree.X.ATTRIBUTE.String2 = '2003 10 30';   % array resembling date
-MyTree.X.ATTRIBUTE.ISO8601 = '2003-10-30';   % date in ISO 8601 format
-MyTree.X.ATTRIBUTE.US_date = '2003/10/30';   % US style date format
-MyTree.X.ATTRIBUTE.complex = '2003i-10e-30'; % complex number resembling a date
-gen_object_display(MyTree);
-
    X: [1x1 struct]
-       ATTRIBUTE: [1x1 struct]
-                      str: 'sphere'
-                     num1: [123]
-                     num2: '123'
-                     num3: '[Inf,NaN]'
-                     calc: '1+2+3+4'
-                     func: 'sin(pi)/2'
-                  String1: '[2003 10 30]'
-                  String2: '2003 10 30'
-                  ISO8601: '2003-10-30'
-                  US_date: '2003/10/30'
-                  complex: '2003i-10e-30'
-

Save it to xml file

xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <X ISO8601="2003-10-30" String1="[2003 10 30]" String2="2003 10 30" US_date="2003/10/30" calc="1+2+3+4" complex="2003i-10e-30" func="sin(pi)/2" num1="123" num2="123" num3="[Inf,NaN]" str="sphere"/>
-</MyTree>
-

Read above file with default settings ("Pref.Str2Num = true" or "Pref.Str2Num = 'smart'"). Under this setting all strings that look like numbers are converted to numbers, except for strings that are recognized by MATLAB 'datenum' function as dates

gen_object_display(xml_read('test.xml'))
-
    X: [1x1 struct]
-         CONTENT: [0x0 double]
-       ATTRIBUTE: [1x1 struct]
-                  ISO8601: '2003-10-30'
-                  String1: '[2003 10 30]'
-                  String2: '2003 10 30'
-                  US_date: '2003/10/30'
-                     calc: '1+2+3+4'
-                  complex: [-1e-029+2003i]
-                     func: 'sin(pi)/2'
-                     num1: [123]
-                     num2: [123]
-                     num3: '[Inf,NaN]'
-                      str: 'sphere'
-

Read above file with "Pref.Str2Num = false" or "Pref.Str2Num = 'never'" to keep all the fields in string format

Pref=[]; Pref.Str2Num = false;
-gen_object_display(xml_read('test.xml', Pref))
-
    X: [1x1 struct]
-         CONTENT: [0x0 double]
-       ATTRIBUTE: [1x1 struct]
-                  ISO8601: '2003-10-30'
-                  String1: '[2003 10 30]'
-                  String2: '2003 10 30'
-                  US_date: '2003/10/30'
-                     calc: '1+2+3+4'
-                  complex: '2003i-10e-30'
-                     func: 'sin(pi)/2'
-                     num1: '123'
-                     num2: '123'
-                     num3: '[Inf,NaN]'
-                      str: 'sphere'
-

Read above file with "Pref.Str2Num = always" to convert all strings that look like numbers to numbers

Pref=[]; Pref.Str2Num   = 'always';
-gen_object_display(xml_read('test.xml', Pref))
-
    X: [1x1 struct]
-         CONTENT: [0x0 double]
-       ATTRIBUTE: [1x1 struct]
-                  ISO8601: '2003-10-30'
-                  String1: '[2003 10 30]'
-                  String2: '2003 10 30'
-                  US_date: '2003/10/30'
-                     calc: '1+2+3+4'
-                  complex: [-1e-029+2003i]
-                     func: 'sin(pi)/2'
-                     num1: [123]
-                     num2: [123]
-                     num3: '[Inf,NaN]'
-                      str: 'sphere'
-

Notice that all three settings will produce the same output for "num1" and "num2" and there is no way to reproduce the original "MyTree" structure.

Write XML files with COMMENTs

Insertion of Comments is done with help of special COMMENT field. Note that MATLAB's xmlwrite is less readable due to lack of end-of-line characters around comment section.

MyTree=[];
-MyTree.COMMENT = 'This is a comment';
-MyTree.MyNumber = 13;
-MyTree.MyString.CONTENT = 'Hello World';
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree><!--This is a comment-->
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Same operation using Apache Xerces XML engine gives the same result

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, 'MyTree', Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<MyTree>
-    <!--This is a comment-->
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTree>
-
-

Comments in XML top level (method #1) This method uses cell array

MyTree=[];
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World';
-xml_write('test.xml', MyTree, {'MyTree', [], 'This is a global comment'});
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?><!--This is a global comment-->
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Same operation using Apache Xerces XML engine gives even nicer results.

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, {'MyTree', [], 'This is a global comment'}, Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<!--This is a global comment-->
-<MyTree>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTree>
-
-

Comments in XML top level (method #2) This method adds an extra top layer to the struct 'tree' and sets "Pref.RootOnly = false", which informs the function about the extra layer. Notice that RootName is also saved as a part of the 'tree', and does not have to be passed in separately.

MyTree=[];
-MyTree.COMMENT = 'This is a global comment';
-MyTree.MyTest.MyNumber = 13;
-MyTree.MyTest.MyString = 'Hello World';
-Pref=[]; Pref.RootOnly = false;
-xml_write('test.xml', MyTree, [], Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?><!--This is a global comment-->
-<MyTest>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTest>
-

Same operation using Apache Xerces XML engine

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-Pref.RootOnly  = false;
-xml_write('test.xml', MyTree, [], Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<!--This is a global comment-->
-<MyTest>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTest>
-
-

Write XML files with PROCESSING_INSTRUCTIONs

Insertion of Processing Instructions is done through use of special PROCESSING_INSTRUCTION field, which stores the instruction string. The string has to be in 'target data' format separated by space.

MyTree=[];
-MyTree.PROCESSING_INSTRUCTION = 'xml-stylesheet type="a" href="foo"';
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World';
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree><?xml-stylesheet type="a" href="foo"?>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Same operation using Apache Xerces XML engine

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, 'MyTree', Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<MyTree><?xml-stylesheet type="a" href="foo"?>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTree>
-
-

PROCESSING_INSTRUCTIONs in XML top level (method #1) This method uses cell array

MyTree=[];
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World';
-xml_write('test.xml', MyTree, {'MyTree', 'xml-stylesheet type="a" href="foo"'});
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="a" href="foo"?>
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Same operation using Apache Xerces XML engine

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, {'MyTree', 'xml-stylesheet type="a" href="foo"'}, Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<?xml-stylesheet type="a" href="foo"?>
-<MyTree>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTree>
-
-

PROCESSING_INSTRUCTIONs in XML top level (method #2) This method adds an extra top layer to the struct 'tree' and sets pref.RootOnly=false, which informs the function about the extra layer. Notice that RootName is also saved as a part of the 'tree', and does not have to be passed in separately.

MyTree=[];
-MyTree.PROCESSING_INSTRUCTION =  'xml-stylesheet type="a" href="foo"';
-MyTree.MyTest.MyNumber = 13;
-MyTree.MyTest.MyString = 'Hello World';
-Pref=[]; Pref.RootOnly = false;
-xml_write('test.xml', MyTree, [], Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="a" href="foo"?>
-<MyTest>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTest>
-

Same operation using Apache Xerces XML engine

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-Pref.RootOnly  = false;
-xml_write('test.xml', MyTree, 'MyTree', Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<?xml-stylesheet type="a" href="foo"?>
-<MyTest>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTest>
-
-

Write XML files with CDATA Sections

"In an XML document a CDATA (Character DATA) section is a section of element content that is marked for the parser to interpret as only character data, not markup." (from Wikipedia) To insert CDATA Sections one use special CDATA_SECTION field, which stores the instruction string. Note that MATLAB's xmlwrite created wrong xml code for CDATA section

MyTree=[];
-MyTree.CDATA_SECTION = '<A>txt</A>';
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World';
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>&lt;A&gt;txt&lt;/A&gt;<MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Same operation using Apache Xerces XML engine produces correct results

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, 'MyTree', Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<MyTree><![CDATA[<A>txt</A>]]><MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTree>
-
-

Write XML files with special characters in TAG names

The input to xml_write requires that all tags one wants in XML document have to be encoded as field names of MATLAB's struct's. Matlab has a lot of restrictions on variable names. This section is about XML tags with names not allowed as MATLAB variables, or more specifically with characters allowed as xml tag names but not allowed as MATLAB variable names. Characters like that can be replaced by their hexadecimal representation just as it is done by genvarname function. Alternative way of writing the first example is:

MyTree=[];
-MyTree.('MyNumber') = 13;               % same as MyTree.MyNumber = 13;
-MyTree.MyString.CONTENT = 'Hello World';
-MyTree.MyString.ATTRIBUTE.('Num') = 2;  % same as MyTree.MyString.ATTRIBUTE.Num = 2;
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString Num="2">Hello World</MyString>
-</MyTree>
-

This approach fails for some characters like dash '-', colon ':', and international characters.

MyTree=[];
-try
-  MyTree.('My-Number') = 13;
-  MyTree.MyString.CONTENT = 'Hello World';
-  MyTree.MyString.ATTRIBUTE.('Num_ö') = 2;
-catch  %#ok<CTCH>
-  err = lasterror; %#ok<LERR>
-  disp(err.message);
-end
-
Invalid field name: 'My-Number'.
-

It can be overcome by replacing offending characters with their hexadecimal representation. That can be done manually or with use of genvarname function. Note that MATLAB 'type' function does not show correctly 'ö' letter in xml file, but opening the file in editor shows that it is correct.

MyTree=[];
-MyTree.(genvarname('My-Number')) = 13;
-MyTree.MyString.CONTENT = 'Hello World';
-MyTree.MyString.ATTRIBUTE.Num_0xF6 = 2;
-gen_object_display(MyTree);
-xml_write('test.xml', MyTree);
-type('test.xml')
-
    My0x2DNumber: [13]
-        MyString: [1x1 struct]
-                    CONTENT: 'Hello World'
-                  ATTRIBUTE: [1x1 struct]
-                             Num_0xF6: [2]
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <My-Number>13</My-Number>
-   <MyString Num_ö="2">Hello World</MyString>
-</MyTree>
-

Also two of the characters '-' and ':' can be encoded by a special strings: '_DASH_' and '_COLON_' respectively

MyTree=[];
-MyTree.My_DASH_Number = 13;
-MyTree.MyString.CONTENT = 'Hello World';
-MyTree.MyString.ATTRIBUTE.Num0xF6 = 2;
-xml_write('test.xml', MyTree);
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <My-Number>13</My-Number>
-   <MyString Numö="2">Hello World</MyString>
-</MyTree>
-

Write XML files with Namespaces

No extra special fields are needed to define XML namespaces, only colon character written using '0x3A' or '_COLON_'. Below is an example of a namespace definition

MyTree=[];
-MyTree.f_COLON_child.ATTRIBUTE.xmlns_COLON_f = 'http://www.foo.com';
-MyTree.f_COLON_child.f_COLON_MyNumber = 13;
-MyTree.f_COLON_child.f_COLON_MyString = 'Hello World';
-xml_write('test.xml', MyTree, 'MyTree');
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <f:child xmlns:f="http://www.foo.com">
-      <f:MyNumber>13</f:MyNumber>
-      <f:MyString>Hello World</f:MyString>
-   </f:child>
-</MyTree>
-

Same operation using Apache Xerces XML engine

Pref=[]; Pref.XmlEngine = 'Xerces';  % use Xerces xml generator directly
-xml_write('test.xml', MyTree, 'f_COLON_MyTree', Pref);
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<f:MyTree>
-    <f:child xmlns:f="http://www.foo.com">
-        <f:MyNumber>13</f:MyNumber>
-        <f:MyString>Hello World</f:MyString>
-    </f:child>
-</f:MyTree>
-
-

"Pref.KeepNS" flag in "xml_read"

Thise option allow keeping or exclusion of namespaces in tag names. By default the namespace data is kept but it produces much longer field names in the output structure. Ignoring namespace will produce more readible output. Perform default read of file with namespace

tree = xml_read('test.xml');
-gen_object_display(tree);
-
    f_COLON_child: [1x1 struct]
-                   f_COLON_MyNumber: [13]
-                   f_COLON_MyString: 'Hello World'
-                          ATTRIBUTE: [1x1 struct]
-                                     xmlns_COLON_f: 'http://www.foo.com'
-

Now the same operation with KeepNS = false.

Pref=[]; Pref.KeepNS = false; % do not read attributes
-tree = xml_read('test.xml', Pref);
-gen_object_display(tree);
-
    child: [1x1 struct]
-            MyNumber: [13]
-            MyString: 'Hello World'
-           ATTRIBUTE: [1x1 struct]
-                      f: 'http://www.foo.com'
-

Read XML files with special node types

Display and read the file, then show the data structure. Note that MATLAB 'type' function shows 'ö' letter incorrectly as 'A¶' in xml file, but opening the file in editor shows that it is correct.

fprintf('Test xml file:\n');
-type('test_file.xml')
-
Test xml file:
-
-<?xml version="1.0" encoding="utf-8" ?> 
-<?xml-stylesheet type="text/css" href="foo.css"?>
-<!-- This is a Global Comment -->
-<aaa xmlns:xsi="http://www.foo.org">
-  <?ProcInst type="local processing instruction"?>
-  <!-- local comment 1 -->
-  bbb
-  <!-- local comment 2 -->
-  ccc
-  <matrix bad-name='fff'>
-    5e3+2*i, Inf
-    NaN,     pi
-  </matrix>
-  <ee_e> ee_e </ee_e>
-  <ff-f> ff-f </ff-f>
-  <ggög> ggög </ggög>
-  <![CDATA[
-    Here <ddd>xml</ddd> tags are treated as ...
-    ... text
-	]]>
-</aaa>
-
-
-

Read only the Root Element (default)

[tree GlobalTextNodes] = xml_read('test_file.xml');
-fprintf('Global Data (Root name, Global Processing Instructions and Global Comments):\n');
-disp(GlobalTextNodes')
-fprintf('\nStructure read from the file (uncludes COMMENT and CDATA sections):\n');
-gen_object_display(tree);
-
Global Data (Root name, Global Processing Instructions and Global Comments):
-    'aaa'
-    'xml-stylesheet type="text/css" href="foo.css"'
-    'This is a Global Comment'
-
-Structure read from the file (uncludes COMMENT and CDATA sections):
-    PROCESSING_INSTRUCTION: 'ProcInst type="local processing instruction"'
-                   COMMENT: [1x2 cell] = 
-                            local comment 1
-
-                            local comment 2
-
-                   CONTENT: [1x2 cell] = 
-                            bbb
-
-                            ccc
-
-                    matrix: [1x1 struct]
-                              CONTENT: [2x2 double]
-                            ATTRIBUTE: [1x1 struct]
-                                       bad_DASH_name: 'fff'
-                      ee_e: 'ee_e'
-                 ff_DASH_f: 'ff-f'
-                   gg0xF6g: 'ggög'
-             CDATA_SECTION: 'Here <ddd>xml</ddd> tags are treated as ...
-    ... text'
-                 ATTRIBUTE: [1x1 struct]
-                            xmlns_COLON_xsi: 'http://www.foo.org'
-

Read the whole tree including global Comments and Processing Instructions

Pref=[]; Pref.RootOnly = false;
-[tree GlobalTextNodes] = xml_read('test_file.xml', Pref);
-fprintf('Global Data (Root name, Global Processing Instructions and Global Comments):\n');
-disp(GlobalTextNodes')
-fprintf('\nStructure read from the file (uncludes COMMENT and CDATA sections):\n');
-gen_object_display(tree);
-
Global Data (Root name, Global Processing Instructions and Global Comments):
-    'aaa'
-    'xml-stylesheet type="text/css" href="foo.css"'
-    'This is a Global Comment'
-
-Structure read from the file (uncludes COMMENT and CDATA sections):
-    PROCESSING_INSTRUCTION: 'xml-stylesheet type="text/css" href="foo.css"'
-                   COMMENT: 'This is a Global Comment'
-                       aaa: [1x1 struct]
-                            PROCESSING_INSTRUCTION: 'ProcInst type="local processing instruction"'
-                                           COMMENT: [1x2 cell] = 
-                                                    local comment 1
-
-                                                    local comment 2
-
-                                           CONTENT: [1x2 cell] = 
-                                                    bbb
-
-                                                    ccc
-
-                                            matrix: [1x1 struct]
-                                                      CONTENT: [2x2 double]
-                                                    ATTRIBUTE: [1x1 struct]
-                                                               bad_DASH_name: 'fff'
-                                              ee_e: 'ee_e'
-                                         ff_DASH_f: 'ff-f'
-                                           gg0xF6g: 'ggög'
-                                     CDATA_SECTION: 'Here <ddd>xml</ddd> tags are treated as ...
-    ... text'
-                                         ATTRIBUTE: [1x1 struct]
-                                                    xmlns_COLON_xsi: 'http://www.foo.org'
-

"Pref.ReadAttr" flag in "xml_read" (control handling of nodes with attributes)

Those option allow exclusion of attributes

Pref=[]; Pref.ReadAttr = false; % do not read attributes
-tree = xml_read('test_file.xml', Pref);
-gen_object_display(tree);
-
    PROCESSING_INSTRUCTION: 'ProcInst type="local processing instruction"'
-                   COMMENT: [1x2 cell] = 
-                            local comment 1
-
-                            local comment 2
-
-                   CONTENT: [1x2 cell] = 
-                            bbb
-
-                            ccc
-
-                    matrix: [2x2 double]
-                      ee_e: 'ee_e'
-                 ff_DASH_f: 'ff-f'
-                   gg0xF6g: 'ggög'
-             CDATA_SECTION: 'Here <ddd>xml</ddd> tags are treated as ...
-    ... text'
-

"Pref.ReadSpec" flag in "xml_read"

Those option allow exclusion of special nodes, like comments, processing instructions, CData sections, etc.

Pref=[]; Pref.ReadSpec = false; % do not read special node types
-tree = xml_read('test_file.xml', Pref);
-gen_object_display(tree);
-
      CONTENT: [1x2 cell] = 
-               bbb
-
-               ccc
-
-       matrix: [1x1 struct]
-                 CONTENT: [2x2 double]
-               ATTRIBUTE: [1x1 struct]
-                          bad_DASH_name: 'fff'
-         ee_e: 'ee_e'
-    ff_DASH_f: 'ff-f'
-      gg0xF6g: 'ggög'
-    ATTRIBUTE: [1x1 struct]
-               xmlns_COLON_xsi: 'http://www.foo.org'
-

"Pref.RootOnly" flag in "xml_read"

As it was shown in previous examples RootOnly parameter can be used to capture global (top level) special nodes (like COMMENTs and PROCESSING_INSTRUCTIONs) which are ignored by default

Pref=[]; Pref.RootOnly = false; % do not read special node types
-tree = xml_read('test_file.xml', Pref);
-gen_object_display(tree);
-
    PROCESSING_INSTRUCTION: 'xml-stylesheet type="text/css" href="foo.css"'
-                   COMMENT: 'This is a Global Comment'
-                       aaa: [1x1 struct]
-                            PROCESSING_INSTRUCTION: 'ProcInst type="local processing instruction"'
-                                           COMMENT: [1x2 cell] = 
-                                                    local comment 1
-
-                                                    local comment 2
-
-                                           CONTENT: [1x2 cell] = 
-                                                    bbb
-
-                                                    ccc
-
-                                            matrix: [1x1 struct]
-                                                      CONTENT: [2x2 double]
-                                                    ATTRIBUTE: [1x1 struct]
-                                                               bad_DASH_name: 'fff'
-                                              ee_e: 'ee_e'
-                                         ff_DASH_f: 'ff-f'
-                                           gg0xF6g: 'ggög'
-                                     CDATA_SECTION: 'Here <ddd>xml</ddd> tags are treated as ...
-    ... text'
-                                         ATTRIBUTE: [1x1 struct]
-                                                    xmlns_COLON_xsi: 'http://www.foo.org'
-

"Pref.RootOnly" flag in "xml_write"

Writing previously read tree with default "Pref.RootOnly = true" gives wrong output file

Pref=[]; Pref.RootOnly = true; % do not read special node types
-xml_write('test.xml', tree, [], Pref);
-fprintf('Test xml file:\n');
-type('test.xml')
-
Test xml file:
-
-<?xml version="1.0" encoding="utf-8"?>
-<tree><?xml-stylesheet type="text/css" href="foo.css"?><!--This is a Global Comment-->
-   <aaa xmlns:xsi="http://www.foo.org"><?ProcInst type="local processing instruction"?><!--local comment 1--><!--local comment 2-->
-      <item>bbb</item>
-      <item>ccc</item>
-      <matrix bad-name="fff">[5000+i*2 Inf;NaN 3.14159265358979]</matrix>
-      <ee_e>ee_e</ee_e>
-      <ff-f>ff-f</ff-f>
-      <ggög>ggög</ggög>Here &lt;ddd&gt;xml&lt;/ddd&gt; tags are treated as ...
-    ... text</aaa>
-</tree>
-

Writing the same tree with "Pref.RootOnly = false" gives correct output

Pref=[]; Pref.RootOnly = false; % do not read special node types
-xml_write('test.xml', tree, [], Pref);
-fprintf('Test xml file:\n');
-type('test.xml')
-
Test xml file:
-
-<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/css" href="foo.css"?><!--This is a Global Comment-->
-<aaa xmlns:xsi="http://www.foo.org"><?ProcInst type="local processing instruction"?><!--local comment 1--><!--local comment 2-->
-   <item>bbb</item>
-   <item>ccc</item>
-   <matrix bad-name="fff">[5000+i*2 Inf;NaN 3.14159265358979]</matrix>
-   <ee_e>ee_e</ee_e>
-   <ff-f>ff-f</ff-f>
-   <ggög>ggög</ggög>Here &lt;ddd&gt;xml&lt;/ddd&gt; tags are treated as ...
-    ... text</aaa>
-

"Pref.NumLevels" flag in "xml_read"

This parameter allows user to skip parts of the tree in order to save time and memory. Usefull only in a rare case when a small portion of large XML file is needed.

Create test tile

MyTree = [];
-MyTree.Level1 = 1;
-MyTree.Level1_.Level2 = 2;
-MyTree.Level1_.Level2_.Level3 = 3;
-MyTree.Level1_.Level2_.Level3_.Level4 = 4;
-xml_write('test.xml', MyTree);
-fprintf('Test xml file:\n');
-type('test.xml')
-
Test xml file:
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <Level1>1</Level1>
-   <Level1_>
-      <Level2>2</Level2>
-      <Level2_>
-         <Level3>3</Level3>
-         <Level3_>
-            <Level4>4</Level4>
-         </Level3_>
-      </Level2_>
-   </Level1_>
-</MyTree>
-

Use Default ("Pref.NumLevels = infinity") setting

tree = xml_read('test.xml');
-gen_object_display(tree);
-
     Level1: [1]
-    Level1_: [1x1 struct]
-              Level2: [2]
-             Level2_: [1x1 struct]
-                       Level3: [3]
-                      Level3_: [1x1 struct]
-                               Level4: [4]
-

Limit the read to only 2 levels

Pref=[]; Pref.NumLevels = 2;
-tree = xml_read('test.xml', Pref);
-gen_object_display(tree);
-
     Level1: [1]
-    Level1_: [1x1 struct]
-              Level2: [2]
-             Level2_: [0x0 double]
-

Create DOM object based on a Struct using "xml_write"

Create Struct tree

MyTree=[];
-MyTree.MyNumber = 13;
-MyTree.MyString = 'Hello World';
-

Convert Struct to DOM object using xml_write

DOM = xml_write([], MyTree);
-xmlwrite('test.xml', DOM);   % Save DOM object using MATLAB function
-type('test.xml')
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Convert DOM object to Struct using "xml_read"

DOM = xmlread('test.xml');       % Read DOM object using MATLAB function
-[tree treeName] = xml_read(DOM); % Convert DOM object to Struct
-disp([treeName{1} ' ='])
-gen_object_display(tree)
-
MyTree =
-    MyNumber: [13]
-    MyString: 'Hello World'
-

Write XML file based on a DOM using "xml_write_xerces"

xmlwrite_xerces('test.xml', DOM); % Save DOM object using Xerces library
-type('test.xml')
-
-<?xml version="1.0" encoding="UTF-8"?>
-<MyTree>
-    <MyNumber>13</MyNumber>
-    <MyString>Hello World</MyString>
-</MyTree>
-
-

Write XML to string instead of a file

DOM = xml_write([], MyTree);
-str = xmlwrite(DOM);
-disp(str)
-
<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <MyNumber>13</MyNumber>
-   <MyString>Hello World</MyString>
-</MyTree>
-

Write XML file with embedded binary data encoded as Base64 (using java version)

fid = fopen('football.jpg', 'rb');
-raw1 = uint8(fread(fid, 'uint8'));                % read image file as a raw binary
-fclose(fid);
-
-MyTree=[];
-MyTree.Size = 13;
-MyTree.MyString = 'Hello World'; % simple case
-MyTree.MyImage.ATTRIBUTE.EncodingMIMEType = 'base64';
-MyTree.MyImage.CONTENT = base64encode(raw1,'java');% perform base64 encoding of the binary data
-xml_write('test.xml', MyTree);             % write xml file
-

Read XML file with embedded binary data encoded as Base64 (using java version)

tree = xml_read('test.xml', Pref);         % read xml file
-raw  = base64decode(tree.MyImage.CONTENT, '', 'java');   % convert xml image to raw binary
-fid = fopen('MyFootball.jpg', 'wb');
-fwrite(fid, raw, 'uint8');                 % dumb the raw binary to the hard disk
-fclose(fid);
-I = imread('MyFootball.jpg');              % read it as an image
-imshow(I);
-

Write XML file with embedded binary data encoded as Base64 (simpler version using only matlab code

Notice that process of writing to xml stripped all end-of-lie characters from base64 code.

isChunked = true; % break into chunks 76 characters long
-url_safe  = true; % 'base64url' encoding
-code = base64encode('license.txt', 'matlab', isChunked, url_safe);
-disp(code)
-MyTree=[];
-MyTree.Size = 13;
-MyTree.MyString = 'Hello World';
-MyTree.MyImage.ATTRIBUTE.EncodingMIMEType = 'base64';
-MyTree.MyImage.CONTENT = code;   % perform base64 encoding of the binary data
-xml_write('test.xml', MyTree);   % write xml file
-type('test.xml')
-
Q29weXJpZ2h0IChjKSAyMDA3LCBKYXJvc2xhdyBUdXN6eW5za2kKQWxsIHJpZ2h0cyByZXNlcnZl
-ZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdp
-dGggb3Igd2l0aG91dCAKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQg
-dGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZSAKbWV0OgoKICAgICogUmVkaXN0cmlidXRpb25z
-IG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgCiAgICAgIG5v
-dGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1l
-ci4KICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRo
-ZSBhYm92ZSBjb3B5cmlnaHQgCiAgICAgIG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMg
-YW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiAKICAgICAgdGhlIGRvY3VtZW50YXRpb24g
-YW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24KICAg
-ICAgClRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFO
-RCBDT05UUklCVVRPUlMgIkFTIElTIiAKQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFO
-VElFUywgSU5DTFVESU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFRIRSAKSU1QTElFRCBXQVJSQU5U
-SUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBP
-U0UgCkFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIE9XTkVS
-IE9SIENPTlRSSUJVVE9SUyBCRSAKTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5D
-SURFTlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiAKQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJ
-TkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgClNVQlNUSVRVVEUg
-R09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJ
-TkVTUyAKSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBM
-SUFCSUxJVFksIFdIRVRIRVIgSU4gCkNPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JU
-IChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIApBUklTSU5HIElOIEFOWSBXQVkg
-T1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSAK
-UE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCg==
-
-
-<?xml version="1.0" encoding="utf-8"?>
-<MyTree>
-   <Size>13</Size>
-   <MyString>Hello World</MyString>
-   <MyImage EncodingMIMEType="base64">Q29weXJpZ2h0IChjKSAyMDA3LCBKYXJvc2xhdyBUdXN6eW5za2kKQWxsIHJpZ2h0cyByZXNlcnZl ZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdp dGggb3Igd2l0aG91dCAKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQg dGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZSAKbWV0OgoKICAgICogUmVkaXN0cmlidXRpb25z IG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgCiAgICAgIG5v dGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1l ci4KICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRo ZSBhYm92ZSBjb3B5cmlnaHQgCiAgICAgIG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMg YW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiAKICAgICAgdGhlIGRvY3VtZW50YXRpb24g YW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24KICAg ICAgClRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFO RCBDT05UUklCVVRPUlMgIkFTIElTIiAKQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFO VElFUywgSU5DTFVESU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFRIRSAKSU1QTElFRCBXQVJSQU5U SUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBP U0UgCkFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIE9XTkVS IE9SIENPTlRSSUJVVE9SUyBCRSAKTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5D SURFTlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiAKQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJ TkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgClNVQlNUSVRVVEUg R09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJ TkVTUyAKSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBM SUFCSUxJVFksIFdIRVRIRVIgSU4gCkNPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JU IChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIApBUklTSU5HIElOIEFOWSBXQVkg T1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSAK UE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCg==</MyImage>
-</MyTree>
-

Read XML file with embedded binary data encoded as Base64 (simpler version using only matlab code

tree = xml_read('test.xml', Pref);         % read xml file
-base64decode(tree.MyImage.CONTENT, 'license2.txt', 'matlab'); % save xml image as raw binary
-type('license2.txt')
-
-Copyright (c) 2007, Jaroslaw Tuszynski
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without 
-modification, are permitted provided that the following conditions are 
-met:
-
-    * Redistributions of source code must retain the above copyright 
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright 
-      notice, this list of conditions and the following disclaimer in 
-      the documentation and/or other materials provided with the distribution
-      
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
-POSSIBILITY OF SUCH DAMAGE.
-
-
\ No newline at end of file diff --git a/private/xml_io_tools/xml_tutorial_script.m b/private/xml_io_tools/xml_tutorial_script.m deleted file mode 100644 index 3a0d7ab..0000000 --- a/private/xml_io_tools/xml_tutorial_script.m +++ /dev/null @@ -1,914 +0,0 @@ -%% Tutorial for xml_io_tools Package -% *By Jarek Tuszynski* -% -% Package xml_io_tools can read XML files into MATLAB struct and writes -% MATLAB data types to XML files with help of simple interface to -% MATLAB's xmlwrite and xmlread functions. -% -% Two function to simplify reading and writing XML files from MATLAB: -% -% * Function xml_read first calls MATLAB's xmlread function and than -% converts its output ('Document Object Model' tree of Java objects) -% to tree of MATLAB struct's. The output is in the format of nested -% structs and cells. In the output data structure field names are based on -% XML tags. -% -% * Function xml_write first convert input tree of MATLAB structs and cells -% and other types to tree of 'Document Object Model' nodes, and then writes -% resulting object to XML file using MATLAB's xmlwrite function. . -% -%% This package can: -% * Read most XML files, created inside and outside of MATLAB environment, -% and convert them to MATLAB data structures. -% * Write any MATLAB's struct tree to XML file -% * Handle XML attributes and special XML nodes like comments, processing -% instructions and CDATA sections -% * Supports base64 encoding and decoding to allow handling embeded binary -% data -% * Be studied, modified, customized, rewritten and used in other packages -% without any limitations. All code is included and documented. Software -% is distributed under BSD Licence (included). -% -%% This package does not: -% * Guarantee to recover the same Matlab objects that were saved. If you -% need to be able to recover carbon copy of the structure that was saved -% than you will have to use one of the packages that uses special set of -% tags saved as xml attributes that help to guide the parsing of XML code. -% This package does not use those tags. -% * Guarantee to work with older versions of MATLAB. Functions do not work -% with versions of MATLAB prior to 7.1 (26-Jul-2005). -% -%% Change History -% * 2006-11-06 - original version -% * 2006-11-26 - corrected xml_write to handle writing Matlab's column -% arrays to xml files. Bug discovered and diagnosed by Kalyan Dutta. -% * 2006-11-28 - made changes to handle special node types like: -% COMMENTS and CDATA sections -% * 2007-03-12 - Writing CDATA sections still did not worked. The problem -% was diagnosed and fixed by Alberto Amaro. The fix involved rewriting -% xmlwrite to use Apache Xerces java files directly instead of MATLAB's -% XMLUtils java class. -% * 2007-06-21 - Fixed problem reported by Anna Kelbert in Reviews about -% not writing attributes of ROOT node. Also: added support for Processing -% Instructions, added support for global text nodes: Processing -% Instructions and comments, allowed writing tag names with special -% characters -% * 2007-07-20 - Added tutorial script file. Extended support for global -% text nodes. Added more Preference fields. -% * 2008-01-23 - Fixed problem reported by Anna Krewet of converting dates -% in format '2007-01-01' to numbers. Improved and added warning messages. -% Added detection of old Matlab versions incompatible with the library. -% Expanded documentation. -% * 2008-06-23 - Fixed problem with writing 1D array reported by Mark Neil. -% Extended xml_read's Pref.Num2Str to 3 settings (never, smart and always) -% for better control. Added parameter Pref.KeepNS for keeping or ignoring -% namespace data when reading. Fixed a bug related to writing 2D cell -% arrays brought up by Andrej's Mosat review. -% * 2008-09-11 - Resubmitting last upload - zip file is still old -% * 2009-02-26 - Small changes. More error handling. More robust in case of -% large binary objects. Added support for Base64 encoding/decoding of -% binary objects (using functions by Peter J. Acklam). -% * 2009-06-26 - changes to xml_read: added CellItem parameter to allow -% better control of reading files with 'item' notation (see comment by -% Shlomi); changed try-catch statements so xml_read would work for mablab -% versions prior to 7.5 (see Thomas Pilutti comment) -% * 2009-12-03 - added PreserveSpace parameter for contolling empty string -% handling as suggested by Sebastiaan. Fix suggested by Michael Murphy. -% Fixed number recognition code as suggested by Yuan Ren. -% * 2010-05-04 - implemented fixes suggested by Dylan Reynolds from Airbus. -% * 2010-07-28 - implemented support for 2D arrays of cells and structs -% suggested by Rodney Behn from MIT Lincoln Laboratory. Also attempted -% large scale cleanup of xml_write function -% * 2010-08-18 - minor extension to allow better handling of logical -% scalars and arrays and function handles suggested by Andreas Richter -% and others -% * 2010-09-20 - allow reading and writing of sparse matrices. Improve -% reading of 1D boolean arrays. -% * 2010-11-05 - Fix problem with empty cells reported by Richard Cotton; -% fixed issues with reading boolean arrays reported by Zohar Bar-Yehuda; -% Improved speed of base64 coding and decoding by switching to java based -% code. -%% Licence -% The package is distributed under BSD License -format compact; % viewing preference -clear variables; -type('license.txt') - -%% Write XML file based on a Struct using "xml_write" -% Any MATLAB data struct can be saved to XML file. -MyTree=[]; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; -xml_write('test.xml', MyTree); -type('test.xml') - -%% Read XML file producing a Struct using "xml_read" -[tree treeName] = xml_read ('test.xml'); -disp([treeName{1} ' =']) -gen_object_display(tree) - -%% "Pref.XmlEngine" flag in "xml_write" -% Occasionaly some operations are performed better by Apache Xerces XML -% engine than default xmlread function. That is why xml_write provide an -% option for choosing the underlaying xml engine. Code below performs the -% same operation as the previous section but using Apache Xerces XML engine. -% Notice that in this case name of root element -% was passed as variable and not extracted from the variable name. -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, 'TreeOfMine', Pref); -type('test.xml') - -%% Writing Struct with different type MATLAB arrays -MyTree=[]; -MyTree.Empty = []; % Empty variable -MyTree.Num_1x1 = 13; % simple scalar -MyTree.Vec_1x3 = [1 2 3]; % horizontal vector -MyTree.Vec_4x1 = [1; 2; 3; 4]; % vertical vector -MyTree.Mat_2x2 = [1, 2; 3, 4]; % 2D matrix -MyTree.Cube_3D = reshape(1:8,[2 2 2]); % 3D array -MyTree.String1 = '[2003 10 30]'; % number string with [] brackets -MyTree.String2 = ' 2003 10 30 '; % number string without [] brackets -MyTree.Logical_1x1 = false; % single logical -MyTree.Logical_2x2 = [false, true; true, false]; % 2D matrix of logicals -MyTree.Logical_Str = 'False False True True'; -MyTree.Int_2x2 = uint8([1 2;3 4]); % 2D matrix of uint8 integers -MyTree.Complex_1x1 = complex(1, 7); % complex scalar -MyTree.Complex_2x2 = complex([1 2;3 4],[2 2;7 7]); % 2D matrix of complex numbers -MyTree.Sparse_9x9 = sparse(1:9,1:9,1); % sparse 9x9 matrix -MyTree.Function = @sum; % function handle -xml_write('test.xml', MyTree); -type('test.xml') - -%% Read Struct with MATLAB arrays -% Notice that 'Cube_3D' did not preserve original dimentions -[tree treeName] = xml_read ('test.xml'); -disp([treeName{1} ' =']) -gen_object_display(tree) - -%% "Pref.StructItem" flag in "xml_write" (controls 1D arrays of structs) -% *Create a simple structure with 1D array of struct's* -MyTree = []; -MyTree.a(1).b = 'jack'; -MyTree.a(2).b = 'john'; -gen_object_display(MyTree) -%% -% *Write XML with "StructItem = true" (default). Notice single 'a' -% section and multiple 'item' sub-sections. Those subsections are used -% to store array elements* -wPref.StructItem = true; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -gen_object_display(xml_read ('test.xml')) -%% -% *Write XML with "StructItem = false". Notice multiple 'a' sections* -wPref.StructItem = false; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -gen_object_display(xml_read ('test.xml')) -%% -% *Notice that xml_read function produced the same struct when reading both files* -%% -% *Potential problems with "StructItem = true":* -wPref.StructItem = true; -MyTree1 = []; MyTree1.a.b = 'jack'; -MyTree2 = []; MyTree2.a(1).b = 'jack'; -MyTree3 = []; MyTree3.a(2).b = 'jack'; -xml_write('test.xml', MyTree1, [], wPref); type('test.xml'); -xml_write('test.xml', MyTree2, [], wPref); type('test.xml'); -xml_write('test.xml', MyTree3, [], wPref); type('test.xml'); -%% -% *Notice that MyTree1 and MyTree2 produce identical files with no 'items', -% while MyTree2 and MyTree3 produce very different file structures. It was -% pointed out to me that files produced from MyTree2 and MyTree3 can not -% belong to the same schema, which can be a problem. The solution is to use -% cells.* -wPref.CellItem = true; -wPref.NoCells = true; -MyTree2 = []; MyTree2.a{1}.b = 'jack'; -MyTree3 = []; MyTree3.a{2}.b = 'jack'; -xml_write('test.xml', MyTree2, [], wPref); type('test.xml'); -xml_write('test.xml', MyTree3, [], wPref); type('test.xml'); - - -%% "Pref.CellItem" flag in "xml_write" (controls 1D arrays of cells) -% *Create a simple structure with cell arrays* -MyTree = []; -MyTree.a = {'jack', 'john'}; -disp(MyTree) -%% -% *Write XML with "CellItem = true" (default). Notice single 'a' -% section and multiple 'item' sections* -Pref=[]; Pref.CellItem = true; -xml_write('test.xml', MyTree, 'MyTree',Pref); -type('test.xml') -fprintf('\nxml_read output:\n'); -disp(xml_read ('test.xml')) -%% -% *Write XML with "CellItem = false". Notice multiple 'a' sections* -Pref=[]; Pref.CellItem = false; -xml_write('test.xml', MyTree, 'MyTree',Pref); -type('test.xml') -fprintf('\nxml_read output:\n'); -disp(xml_read ('test.xml')) -%% -% *Notice that xml_read function produced the same struct when reading both files* - -%% "Pref.NoCells" flag in "xml_read" -% *Create a cell/struct mixture object* -MyTree = []; -MyTree.a{1}.b = 'jack'; -MyTree.a{2}.b = []; -MyTree.a{2}.c = 'john'; -gen_object_display(MyTree); -%% -% *Save it to xml file* -Pref=[]; Pref.CellItem = false; -xml_write('test.xml', MyTree, 'MyTree',Pref); -type('test.xml') -%% -% *Read above file with "Pref.NoCells=true" (default) - output is quite different then input* -% By default program is trying to convert everything to struct's and arrays -% of structs. In case arrays of structs all the structs in array need to have the -% same fields, and if they are not than MATLAB creates empty fields. -Pref=[]; Pref.NoCells=true; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.NoCells=false" - now input and output are the same* -% Cell arrays of structs allow structs in array to have different fields. -Pref=[]; Pref.NoCells=false; -gen_object_display(xml_read('test.xml', Pref)) - -%% "Pref.ItemName" flag in "xml_write" (customize 1D arrays of structs and cells) -% *Create a cell/struct mixture object* -MyTree = []; -MyTree.a{1}.b = 'jack'; -MyTree.a{2}.c = 'john'; -gen_object_display(MyTree); -%% -% *Save it to xml file, using 'item' notation but with different name* -Pref=[]; -Pref.CellItem = true; -Pref.ItemName = 'MyItem'; -xml_write('test.xml', MyTree, 'MyTree',Pref); -type('test.xml') - -%% "Pref.ItemName" flag in "xml_read" -% *Read above file with default settings ("Pref.ItemName = 'item'")* -% The results do not match the original structure -Pref=[]; Pref.NoCells = false; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.ItemName = 'MyItem'" - now saved and read -% MATLAB structures are the same* -Pref=[]; -Pref.ItemName = 'MyItem'; -Pref.NoCells = false; -gen_object_display(xml_read('test.xml', Pref)) - -%% "Pref.CellItem" flag in "xml_read" -% "Pref.ItemName" is used to create xml files with clearly marked arrays -% "Pref.CellItem" flag in "xml_read" ensures that they are always read as -% arrays by forcing output to stay in cell format. In cell format s{1} is -% different than s, while s(1) is indistinguishable from s. -%% -% *Create a test file* -MyTree = []; -MyTree.a1{1}.b = 'jack'; % a1 - single struct -MyTree.a2{1}.b = 'jack'; % a2 - cell array of structs with the same fields -MyTree.a2{2}.b = 'john'; -MyTree.a3{1}.b = 'jack'; % a3 - cell array of structs with the different fields -MyTree.a3{2}.c = 'john'; -Pref=[]; -Pref.CellItem = true; -Pref.Debug = true; -xml_write('test.xml', MyTree, 'MyTree',Pref); -type('test.xml') -%% -% *Read above file with "Pref.CellItem = true" (default)* -% All outputs are in cell format -Pref=[]; -Pref.NoCells = false; % allow cell output -Pref.CellItem = true; % keep 'item' arrays as cells -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.CellItem = false"* -% Outputs format is determined by content -Pref=[]; -Pref.NoCells = false; % allow cell output -Pref.CellItem = false; % allow 'item' arrays to beheave like other fields -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.CellItem = false" and "Pref.NoCells = true"* -% All outputs are in struct format -Pref=[]; -Pref.NoCells = true; % don't allow cell output -Pref.CellItem = false; % allow 'item' arrays to beheave like other fields -gen_object_display(xml_read('test.xml', Pref)) - -%% "Pref.CellTable" flag in "xml_write" (controls 2D arrays of cells) -% *Create a structure with 2D arrays of cells* -MyTree = []; -MyTree.M = {[1,2;3,4], 'M12'; struct('a','jack'), {11, 'N12'; 21, 'N22'}}; -gen_object_display(MyTree) -%% -% *Write XML with "CellTable = 'Html" (default). This option mimics use of -% HTML "tr" and "td" tags to encode 2D tables. Tag names can -% be changed using TableName parameter (see below)* -wPref = []; -wPref.CellTable = 'Html'; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -rPref=[]; rPref.NoCells=false; -gen_object_display(xml_read('test.xml', rPref)) -%% -% *Write XML with "CellTable = 'Vector'".* -% Converts 2D arrays to 1D array and item or regular notation. This option -% is mostly provided for backward compatibility since this was the -% behavior in prior verions of the code -wPref = []; -wPref.CellTable = 'Vector'; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -rPref=[]; rPref.NoCells=false; -gen_object_display(xml_read('test.xml', rPref)) -%% -% *Create a simpler structure without struct's* -MyTree = []; -MyTree.M = {[1,2;3,4], 'M12'; 'M21', {11, 'N12'; 21, 'N22'}}; -gen_object_display(MyTree) -%% -% *Write XML with "CellTable = 'Matlab". This option encodes tables -% consisting of numbers, strings and other cell arrays as MATLAB command -% string. Unlike 'Html' option it does not work if one of the cells is -% a struct* -wPref = []; -wPref.CellTable = 'Matlab'; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -rPref=[]; rPref.NoCells=false; -gen_object_display(xml_read('test.xml', rPref)) - -%% Write 2D cell array in HTML format -MyTree = []; -MyTree.table.ATTRIBUTE.border=1; -MyTree.table.CONTENT = {'Apples', '44%'; 'Bannanas', '23%'; 'Oranges', '13%'; 'Other', '10%'}; -xml_write('html/test.html', MyTree); -type('html/test.html') -%% -% Click on to opened this file with a web brouwser - -%% "Pref.StructTable" flag in "xml_write" (controls 2D arrays of structs) -% *Create a simple structure with arrays of struct's* -MyTree = []; -MyTree.a(1,1).b = 'jack'; -MyTree.a(1,2).b = 'john'; -MyTree.a(2,1).b = 'jim'; -MyTree.a(2,2).b = 'jill'; -gen_object_display(MyTree) -%% -% *Write XML with "StructTable = 'Html" (default). This option mimics use of -% HTML "tr" and "td" tags to encode 2D tables. Tag names can -% be changed using TableName parameter (see below)* -wPref = []; -wPref.StructTable = 'Html'; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -gen_object_display(xml_read ('test.xml')) -%% -% *Write XML with "CellTable = 'Vector'".* -% Converts 2D arrays to 1D array and item or regular notation. This option -% is mostly provided for backward compatibility since this was the -% behavior in prior verions of the code -wPref = []; -wPref.StructTable = 'Vector'; -xml_write('test.xml', MyTree, 'MyTree',wPref); -type('test.xml') -fprintf('\nxml_read output:\n') -gen_object_display(xml_read ('test.xml')) - -%% "Pref.TableName" flag in "xml_write" (controls encoding tags used for 2D arrays) -% *Create a cell object* -MyTree = []; -MyTree.M = {[1,2;3,4], 'M12'; 21, {11, 'N12'; 21, 'N22'}}; -gen_object_display(MyTree); -%% -% *Save it to xml file, using 'Html' notation but with different names for -% rows and cells* -Pref=[]; Pref.TableName = {'row','cell'}; -xml_write('test.xml', MyTree, 'MyTree',Pref); -type('test.xml') - -%% "Pref.TableName" flag in "xml_read" -% *Read above file with default settings ("Pref.TableName = {'tr','td'}")* -% The results do not match the original structure -Pref=[]; Pref.NoCells = false; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.TableName = {'row','cell'}" - now saved and read -% MATLAB structures are the same* -Pref=[]; -Pref.TableName = {'row','cell'}; -Pref.NoCells = false; -gen_object_display(xml_read('test.xml', Pref)) - -%% "Pref.Str2Num" flag in xml_read (control conversion to numbers while reading) -% *Create a cell/struct mixture object* -MyTree = []; -MyTree.str = 'sphere'; -MyTree.num1 = 123; -MyTree.num2 = '123'; -MyTree.num3 = '[Inf,NaN]'; -MyTree.calc = '1+2+3+4'; -MyTree.func = 'sin(pi)/2'; -MyTree.String1 = '[2003 10 30]'; -MyTree.String2 = '2003 10 30'; % array resembling date -MyTree.ISO8601 = '2003-10-30'; % date in ISO 8601 format -MyTree.US_date = '2003/10/30'; % US style date format -MyTree.complex = '2003i-10e-30'; % complex number resembling a date -gen_object_display(MyTree); -%% -% *Save it to xml file* -xml_write('test.xml', MyTree); -type('test.xml') -%% -% *Read above file with default settings* -% ("Pref.Str2Num = true" or "Pref.Str2Num = 'smart'"). Under this setting all -% strings that look like numbers are converted to numbers, except for -% strings that are recognized by MATLAB 'datenum' function as dates -gen_object_display(xml_read('test.xml')) -%% -% *Note that all the fields of 'MyTree' can be converted to numbers (even -% 'sphere') but by default the function is trying to 'judge' if a string -% should be converted to a number or not* -MyCell = {'sphere','1+2+3+4','sin(pi)/2','2003 10 30','2003-10-30','2003/10/30','2003i-10e-30'}; -cellfun(@str2num, MyCell, 'UniformOutput', false) -%% -% *Read above file with "Pref.Str2Num = false" or "Pref.Str2Num = 'never'" -% to keep all the fields in string format* -Pref=[]; Pref.Str2Num = false; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.Str2Num = always" -% to convert all strings that look like numbers to numbers* note the likelly -% unintendet conversion of 'ISO8601' -Pref=[]; Pref.Str2Num = 'always'; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Notice that all three settings will produce the same output for "num1" and -% "num2" and there is no way to reproduce the original "MyTree" structure.* - -%% "Pref.PreserveSpace" flag in xml_write (control handling of strings with leading/trailing spaces) -% *Create a struct with strings* -MyTree=[]; -MyTree.Empty = ''; -MyTree.OneSpace = ' '; -MyTree.TwoSpaces = ' '; -MyTree.String1 = ' Hello World '; -%% -% *Write XML with "PreserveSpace = false" (default).* -Pref=[]; Pref.PreserveSpace = false; % (default setting) -xml_write('test.xml', MyTree, [], Pref); -type('test.xml') -%% -% *Write XML with "PreserveSpace = true".* -Pref=[]; Pref.PreserveSpace = true; -xml_write('test.xml', MyTree, [], Pref); -type('test.xml') - -%% "Pref.PreserveSpace" flag in xml_read -% *Read file while using "PreserveSpace = false" (default).* -Pref=[]; Pref.PreserveSpace = false; % (default setting) -gen_object_display(xml_read('test.xml',Pref)) -%% -% *Read file while using "PreserveSpace = true".* -Pref=[]; Pref.PreserveSpace = true; -gen_object_display(xml_read('test.xml',Pref)) - - -%% Write XML files with ATTRIBUTEs -% In order to add node attributes a special ATTRIBUTE field is used. -% ATTRIBUTEs have to be of simple types like numbers or strings (not -% struct or cells). Attributes are easy to attach to structs nodes like -% MyTree below. -MyTree=[]; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; % simple case -MyTree.ATTRIBUTE.Num = 2; -xml_write('test.xml', MyTree); -type('test.xml') - -%% -% In case when one needs to attach attributes to nodes which are not -% structs (for example strings, numbers or calls) then special CONTENT -% field needs to be used to make the node a struct node. -MyTree=[]; -MyTree.MyNumber = 13; -MyTree.MyString.CONTENT = 'Hello World'; % simple case -MyTree.MyString.ATTRIBUTE.Num = 2; -xml_write('test.xml', MyTree); -type('test.xml') - -%% "Pref.Str2Num" flag in file with ATTRIBUTEs -% *Create a cell/struct mixture object* -MyTree = []; -MyTree.X.ATTRIBUTE.str = 'sphere'; -MyTree.X.ATTRIBUTE.num1 = 123; -MyTree.X.ATTRIBUTE.num2 = '123'; -MyTree.X.ATTRIBUTE.num3 = '[Inf,NaN]'; -MyTree.X.ATTRIBUTE.calc = '1+2+3+4'; -MyTree.X.ATTRIBUTE.func = 'sin(pi)/2'; -MyTree.X.ATTRIBUTE.String1 = '[2003 10 30]'; -MyTree.X.ATTRIBUTE.String2 = '2003 10 30'; % array resembling date -MyTree.X.ATTRIBUTE.ISO8601 = '2003-10-30'; % date in ISO 8601 format -MyTree.X.ATTRIBUTE.US_date = '2003/10/30'; % US style date format -MyTree.X.ATTRIBUTE.complex = '2003i-10e-30'; % complex number resembling a date -gen_object_display(MyTree); -%% -% *Save it to xml file* -xml_write('test.xml', MyTree); -type('test.xml') -%% -% *Read above file with default settings* -% ("Pref.Str2Num = true" or "Pref.Str2Num = 'smart'"). Under this setting all -% strings that look like numbers are converted to numbers, except for -% strings that are recognized by MATLAB 'datenum' function as dates -gen_object_display(xml_read('test.xml')) - -%% -% *Read above file with "Pref.Str2Num = false" or "Pref.Str2Num = 'never'" -% to keep all the fields in string format* -Pref=[]; Pref.Str2Num = false; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Read above file with "Pref.Str2Num = always" -% to convert all strings that look like numbers to numbers* -Pref=[]; Pref.Str2Num = 'always'; -gen_object_display(xml_read('test.xml', Pref)) -%% -% *Notice that all three settings will produce the same output for "num1" and -% "num2" and there is no way to reproduce the original "MyTree" structure.* - - -%% Write XML files with COMMENTs -% Insertion of Comments is done with help of special COMMENT field. -% Note that MATLAB's xmlwrite is less readable due to lack of end-of-line -% characters around comment section. -MyTree=[]; -MyTree.COMMENT = 'This is a comment'; -MyTree.MyNumber = 13; -MyTree.MyString.CONTENT = 'Hello World'; -xml_write('test.xml', MyTree); -type('test.xml') - -%% -% *Same operation using Apache Xerces XML engine* -% gives the same result -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, 'MyTree', Pref); -type('test.xml') - -%% -% *Comments in XML top level (method #1)* -% This method uses cell array -MyTree=[]; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; -xml_write('test.xml', MyTree, {'MyTree', [], 'This is a global comment'}); -type('test.xml') -%% -% *Same operation using Apache Xerces XML engine* -% gives even nicer results. -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, {'MyTree', [], 'This is a global comment'}, Pref); -type('test.xml') - -%% -% *Comments in XML top level (method #2)* -% This method adds an extra top layer to the struct 'tree' and sets -% "Pref.RootOnly = false", which informs the function about the extra -% layer. Notice that RootName is also saved as a part of -% the 'tree', and does not have to be passed in separately. -MyTree=[]; -MyTree.COMMENT = 'This is a global comment'; -MyTree.MyTest.MyNumber = 13; -MyTree.MyTest.MyString = 'Hello World'; -Pref=[]; Pref.RootOnly = false; -xml_write('test.xml', MyTree, [], Pref); -type('test.xml') -%% -% *Same operation using Apache Xerces XML engine* -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -Pref.RootOnly = false; -xml_write('test.xml', MyTree, [], Pref); -type('test.xml') - -%% Write XML files with PROCESSING_INSTRUCTIONs -% Insertion of Processing Instructions is done through use of special -% PROCESSING_INSTRUCTION field, which stores the instruction string. The -% string has to be in 'target data' format separated by space. -MyTree=[]; -MyTree.PROCESSING_INSTRUCTION = 'xml-stylesheet type="a" href="foo"'; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; -xml_write('test.xml', MyTree); -type('test.xml') - -%% -% *Same operation using Apache Xerces XML engine* -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, 'MyTree', Pref); -type('test.xml') - -%% -% *PROCESSING_INSTRUCTIONs in XML top level (method #1)* -% This method uses cell array -MyTree=[]; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; -xml_write('test.xml', MyTree, {'MyTree', 'xml-stylesheet type="a" href="foo"'}); -type('test.xml') -%% -% *Same operation using Apache Xerces XML engine* -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, {'MyTree', 'xml-stylesheet type="a" href="foo"'}, Pref); -type('test.xml') - -%% -% *PROCESSING_INSTRUCTIONs in XML top level (method #2)* -% This method adds an extra top layer to the struct 'tree' and sets -% pref.RootOnly=false, which informs the function about the extra -% layer. Notice that RootName is also saved as a part of -% the 'tree', and does not have to be passed in separately. -MyTree=[]; -MyTree.PROCESSING_INSTRUCTION = 'xml-stylesheet type="a" href="foo"'; -MyTree.MyTest.MyNumber = 13; -MyTree.MyTest.MyString = 'Hello World'; -Pref=[]; Pref.RootOnly = false; -xml_write('test.xml', MyTree, [], Pref); -type('test.xml') -%% -% *Same operation using Apache Xerces XML engine* -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -Pref.RootOnly = false; -xml_write('test.xml', MyTree, 'MyTree', Pref); -type('test.xml') - -%% Write XML files with CDATA Sections -% "In an XML document a CDATA (Character DATA) section is a section of -% element content that is marked for the parser to interpret as only -% character data, not markup." (from Wikipedia) -% To insert CDATA Sections one use special CDATA_SECTION field, -% which stores the instruction string. Note that MATLAB's xmlwrite created -% wrong xml code for CDATA section -MyTree=[]; -MyTree.CDATA_SECTION = 'txt'; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; -xml_write('test.xml', MyTree); -type('test.xml') -%% -% *Same operation using Apache Xerces XML engine produces correct results* -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, 'MyTree', Pref); -type('test.xml') - -%% Write XML files with special characters in TAG names -% The input to xml_write requires that all tags one wants in XML document -% have to be encoded as field names of MATLAB's struct's. Matlab has a lot -% of restrictions on variable names. This section is about XML tags with -% names not allowed as MATLAB variables, or more specifically with -% characters allowed as xml tag names but not allowed as MATLAB variable -% names. Characters like that can be replaced by their hexadecimal -% representation just as it is done by genvarname function. Alternative way -% of writing the first example is: -MyTree=[]; -MyTree.('MyNumber') = 13; % same as MyTree.MyNumber = 13; -MyTree.MyString.CONTENT = 'Hello World'; -MyTree.MyString.ATTRIBUTE.('Num') = 2; % same as MyTree.MyString.ATTRIBUTE.Num = 2; -xml_write('test.xml', MyTree); -type('test.xml') - -%% -% *This approach fails for some characters like dash '-', colon ':', and -% international characters.* -MyTree=[]; -try - MyTree.('My-Number') = 13; - MyTree.MyString.CONTENT = 'Hello World'; - MyTree.MyString.ATTRIBUTE.('Num_ö') = 2; -catch %#ok - err = lasterror; %#ok - disp(err.message); -end - -%% -% It can be overcome by replacing offending characters with their -% hexadecimal representation. That can be done manually or with use of -% genvarname function. Note that MATLAB 'type' function does not show -% correctly 'ö' letter in xml file, but opening the file in editor shows -% that it is correct. -MyTree=[]; -MyTree.(genvarname('My-Number')) = 13; -MyTree.MyString.CONTENT = 'Hello World'; -MyTree.MyString.ATTRIBUTE.Num_0xF6 = 2; -gen_object_display(MyTree); -xml_write('test.xml', MyTree); -type('test.xml') - -%% -% *Also two of the characters '-' and ':' can be encoded by a special strings: -% '_DASH_' and '_COLON_' respectively* -MyTree=[]; -MyTree.My_DASH_Number = 13; -MyTree.MyString.CONTENT = 'Hello World'; -MyTree.MyString.ATTRIBUTE.Num0xF6 = 2; -xml_write('test.xml', MyTree); -type('test.xml') - -%% Write XML files with Namespaces -% No extra special fields are needed to define XML namespaces, only colon -% character written using '0x3A' or '_COLON_'. Below is an -% example of a namespace definition -MyTree=[]; -MyTree.f_COLON_child.ATTRIBUTE.xmlns_COLON_f = 'http://www.foo.com'; -MyTree.f_COLON_child.f_COLON_MyNumber = 13; -MyTree.f_COLON_child.f_COLON_MyString = 'Hello World'; -xml_write('test.xml', MyTree, 'MyTree'); -type('test.xml') -%% -% *Same operation using Apache Xerces XML engine* -Pref=[]; Pref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -xml_write('test.xml', MyTree, 'f_COLON_MyTree', Pref); -type('test.xml') - -%% "Pref.KeepNS" flag in "xml_read" -% Thise option allow keeping or exclusion of namespaces in tag names. -% By default the namespace data is kept but it produces much longer field -% names in the output structure. Ignoring namespace will produce more -% readible output. -% Perform default read of file with namespace -tree = xml_read('test.xml'); -gen_object_display(tree); - -%% -% Now the same operation with KeepNS = false. -Pref=[]; Pref.KeepNS = false; % do not read attributes -tree = xml_read('test.xml', Pref); -gen_object_display(tree); - -%% Read XML files with special node types -% Display and read the file, then show the data structure. Note that -% MATLAB 'type' function shows 'ö' letter incorrectly as 'A¶' in xml file, -% but opening the file in editor shows that it is correct. -fprintf('Test xml file:\n'); -type('test_file.xml') -%% -% Read only the Root Element (default) -[tree GlobalTextNodes] = xml_read('test_file.xml'); -fprintf('Global Data (Root name, Global Processing Instructions and Global Comments):\n'); -disp(GlobalTextNodes') -fprintf('\nStructure read from the file (uncludes COMMENT and CDATA sections):\n'); -gen_object_display(tree); -%% -% Read the whole tree including global Comments and Processing Instructions -Pref=[]; Pref.RootOnly = false; -[tree GlobalTextNodes] = xml_read('test_file.xml', Pref); -fprintf('Global Data (Root name, Global Processing Instructions and Global Comments):\n'); -disp(GlobalTextNodes') -fprintf('\nStructure read from the file (uncludes COMMENT and CDATA sections):\n'); -gen_object_display(tree); - -%% "Pref.ReadAttr" flag in "xml_read" (control handling of nodes with attributes) -% Those option allow exclusion of attributes -Pref=[]; Pref.ReadAttr = false; % do not read attributes -tree = xml_read('test_file.xml', Pref); -gen_object_display(tree); - -%% "Pref.ReadSpec" flag in "xml_read" -% Those option allow exclusion of special nodes, like -% comments, processing instructions, CData sections, etc. -Pref=[]; Pref.ReadSpec = false; % do not read special node types -tree = xml_read('test_file.xml', Pref); -gen_object_display(tree); - -%% "Pref.RootOnly" flag in "xml_read" -% As it was shown in previous examples RootOnly parameter can be used to -% capture global (top level) special nodes (like COMMENTs and -% PROCESSING_INSTRUCTIONs) which are ignored by default -Pref=[]; Pref.RootOnly = false; % do not read special node types -tree = xml_read('test_file.xml', Pref); -gen_object_display(tree); - -%% "Pref.RootOnly" flag in "xml_write" -% Writing previously read tree with default "Pref.RootOnly = true" gives -% wrong output file -Pref=[]; Pref.RootOnly = true; % do not read special node types -xml_write('test.xml', tree, [], Pref); -fprintf('Test xml file:\n'); -type('test.xml') -%% -% Writing the same tree with "Pref.RootOnly = false" gives correct output -Pref=[]; Pref.RootOnly = false; % do not read special node types -xml_write('test.xml', tree, [], Pref); -fprintf('Test xml file:\n'); -type('test.xml') - -%% "Pref.NumLevels" flag in "xml_read" -% This parameter allows user to skip parts of the tree in order to save -% time and memory. Usefull only in a rare case when a small portion of -% large XML file is needed. -% -% Create test tile -MyTree = []; -MyTree.Level1 = 1; -MyTree.Level1_.Level2 = 2; -MyTree.Level1_.Level2_.Level3 = 3; -MyTree.Level1_.Level2_.Level3_.Level4 = 4; -xml_write('test.xml', MyTree); -fprintf('Test xml file:\n'); -type('test.xml') -%% -% *Use Default ("Pref.NumLevels = infinity") setting* -tree = xml_read('test.xml'); -gen_object_display(tree); -%% -% *Limit the read to only 2 levels* -Pref=[]; Pref.NumLevels = 2; -tree = xml_read('test.xml', Pref); -gen_object_display(tree); - - - - -%% Create DOM object based on a Struct using "xml_write" -% *Create Struct tree* -MyTree=[]; -MyTree.MyNumber = 13; -MyTree.MyString = 'Hello World'; -%% -% *Convert Struct to DOM object using xml_write* -DOM = xml_write([], MyTree); -xmlwrite('test.xml', DOM); % Save DOM object using MATLAB function -type('test.xml') - -%% Convert DOM object to Struct using "xml_read" -DOM = xmlread('test.xml'); % Read DOM object using MATLAB function -[tree treeName] = xml_read(DOM); % Convert DOM object to Struct -disp([treeName{1} ' =']) -gen_object_display(tree) - -%% Write XML file based on a DOM using "xml_write_xerces" -xmlwrite_xerces('test.xml', DOM); % Save DOM object using Xerces library -type('test.xml') - -%% Write XML to string instead of a file -DOM = xml_write([], MyTree); -str = xmlwrite(DOM); -disp(str) - -%% Write XML file with embedded binary data encoded as Base64 (using java version) -fid = fopen('football.jpg', 'rb'); -raw1 = uint8(fread(fid, 'uint8')); % read image file as a raw binary -fclose(fid); - -MyTree=[]; -MyTree.Size = 13; -MyTree.MyString = 'Hello World'; % simple case -MyTree.MyImage.ATTRIBUTE.EncodingMIMEType = 'base64'; -MyTree.MyImage.CONTENT = base64encode(raw1,'java');% perform base64 encoding of the binary data -xml_write('test.xml', MyTree); % write xml file - -%% Read XML file with embedded binary data encoded as Base64 (using java version) -tree = xml_read('test.xml', Pref); % read xml file -raw = base64decode(tree.MyImage.CONTENT, '', 'java'); % convert xml image to raw binary -fid = fopen('MyFootball.jpg', 'wb'); -fwrite(fid, raw, 'uint8'); % dumb the raw binary to the hard disk -fclose(fid); -I = imread('MyFootball.jpg'); % read it as an image -imshow(I); - -%% Write XML file with embedded binary data encoded as Base64 (simpler version using only matlab code -% Notice that process of writing to xml stripped all end-of-lie characters -% from base64 code. -isChunked = true; % break into chunks 76 characters long -url_safe = true; % 'base64url' encoding -code = base64encode('license.txt', 'matlab', isChunked, url_safe); -disp(code) -MyTree=[]; -MyTree.Size = 13; -MyTree.MyString = 'Hello World'; -MyTree.MyImage.ATTRIBUTE.EncodingMIMEType = 'base64'; -MyTree.MyImage.CONTENT = code; % perform base64 encoding of the binary data -xml_write('test.xml', MyTree); % write xml file -type('test.xml') - -%% Read XML file with embedded binary data encoded as Base64 (simpler version using only matlab code -tree = xml_read('test.xml', Pref); % read xml file -base64decode(tree.MyImage.CONTENT, 'license2.txt', 'matlab'); % save xml image as raw binary -type('license2.txt') \ No newline at end of file diff --git a/private/xml_io_tools/xml_read.m b/private/xml_read.m similarity index 97% rename from private/xml_io_tools/xml_read.m rename to private/xml_read.m index 845efd0..03c590d 100644 --- a/private/xml_io_tools/xml_read.m +++ b/private/xml_read.m @@ -1,550 +1,550 @@ -function [tree, RootName, DOMnode] = xml_read(xmlfile, Pref) -%XML_READ reads xml files and converts them into Matlab's struct tree. -% -% DESCRIPTION -% tree = xml_read(xmlfile) reads 'xmlfile' into data structure 'tree' -% -% tree = xml_read(xmlfile, Pref) reads 'xmlfile' into data structure 'tree' -% according to your preferences -% -% [tree, RootName, DOMnode] = xml_read(xmlfile) get additional information -% about XML file -% -% INPUT: -% xmlfile URL or filename of xml file to read -% Pref Preferences: -% Pref.ItemName - default 'item' - name of a special tag used to itemize -% cell arrays -% Pref.ReadAttr - default true - allow reading attributes -% Pref.ReadSpec - default true - allow reading special nodes -% Pref.Str2Num - default 'smart' - convert strings that look like numbers -% to numbers. Options: "always", "never", and "smart" -% Pref.KeepNS - default true - keep or strip namespace info -% Pref.NoCells - default true - force output to have no cell arrays -% Pref.Debug - default false - show mode specific error messages -% Pref.NumLevels- default infinity - how many recursive levels are -% allowed. Can be used to speed up the function by prunning the tree. -% Pref.RootOnly - default true - output variable 'tree' corresponds to -% xml file root element, otherwise it correspond to the whole file. -% Pref.CellItem - default 'true' - leave 'item' nodes in cell notation. -% OUTPUT: -% tree tree of structs and/or cell arrays corresponding to xml file -% RootName XML tag name used for root (top level) node. -% Optionally it can be a string cell array storing: Name of -% root node, document "Processing Instructions" data and -% document "comment" string -% DOMnode output of xmlread -% -% DETAILS: -% Function xml_read first calls MATLAB's xmlread function and than -% converts its output ('Document Object Model' tree of Java objects) -% to tree of MATLAB struct's. The output is in format of nested structs -% and cells. In the output data structure field names are based on -% XML tags, except in cases when tags produce illegal variable names. -% -% Several special xml node types result in special tags for fields of -% 'tree' nodes: -% - node.CONTENT - stores data section of the node if other fields are -% present. Usually data section is stored directly in 'node'. -% - node.ATTRIBUTE.name - stores node's attribute called 'name'. -% - node.COMMENT - stores node's comment section (string). For global -% comments see "RootName" output variable. -% - node.CDATA_SECTION - stores node's CDATA section (string). -% - node.PROCESSING_INSTRUCTIONS - stores "processing instruction" child -% node. For global "processing instructions" see "RootName" output variable. -% - other special node types like: document fragment nodes, document type -% nodes, entity nodes, notation nodes and processing instruction nodes -% will be treated like regular nodes -% -% EXAMPLES: -% MyTree=[]; -% MyTree.MyNumber = 13; -% MyTree.MyString = 'Hello World'; -% xml_write('test.xml', MyTree); -% [tree treeName] = xml_read ('test.xml'); -% disp(treeName) -% gen_object_display() -% % See also xml_examples.m -% -% See also: -% xml_write, xmlread, xmlwrite -% -% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com -% References: -% - Function inspired by Example 3 found in xmlread function. -% - Output data structures inspired by xml_toolbox structures. - -%% default preferences -DPref.TableName = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays -DPref.ItemName = 'item'; % name of a special tag used to itemize 1D cell arrays -DPref.CellItem = false; % leave 'item' nodes in cell notation -DPref.ReadAttr = true; % allow reading attributes -DPref.ReadSpec = true; % allow reading special nodes: comments, CData, etc. -DPref.KeepNS = true; % Keep or strip namespace info -DPref.Str2Num = 'smart';% convert strings that look like numbers to numbers -DPref.NoCells = true; % force output to have no cell arrays -DPref.NumLevels = 1e10; % number of recurence levels -DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? -RootOnly = true; % return root node with no top level special nodes -Debug = false; % show specific errors (true) or general (false)? -tree = []; -RootName = []; - -%% Check Matlab Version -v = ver('MATLAB'); -version = str2double(regexp(v.Version, '\d.\d','match','once')); -if (version<7.1) - error('Your MATLAB version is too old. You need version 7.1 or newer.'); -end - -%% read user preferences -if (nargin>1) - if (isfield(Pref, 'TableName')), DPref.TableName = Pref.TableName; end - if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end - if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end - if (isfield(Pref, 'Str2Num' )), DPref.Str2Num = Pref.Str2Num ; end - if (isfield(Pref, 'NoCells' )), DPref.NoCells = Pref.NoCells ; end - if (isfield(Pref, 'NumLevels')), DPref.NumLevels = Pref.NumLevels; end - if (isfield(Pref, 'ReadAttr' )), DPref.ReadAttr = Pref.ReadAttr; end - if (isfield(Pref, 'ReadSpec' )), DPref.ReadSpec = Pref.ReadSpec; end - if (isfield(Pref, 'KeepNS' )), DPref.KeepNS = Pref.KeepNS; end - if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end - if (isfield(Pref, 'Debug' )), Debug = Pref.Debug ; end - if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end -end -if ischar(DPref.Str2Num), % convert from character description to numbers - DPref.Str2Num = find(strcmpi(DPref.Str2Num, {'never', 'smart', 'always'}))-1; - if isempty(DPref.Str2Num), DPref.Str2Num=1; end % 1-smart by default -end - -%% read xml file using Matlab function -if isa(xmlfile, 'org.apache.xerces.dom.DeferredDocumentImpl'); - % if xmlfile is a DOMnode than skip the call to xmlread - try - try - DOMnode = xmlfile; - catch ME - error('Invalid DOM node: \n%s.', getReport(ME)); - end - catch %#ok catch for mablab versions prior to 7.5 - error('Invalid DOM node. \n'); - end -else % we assume xmlfile is a filename - if (Debug) % in debuging mode crashes are allowed - DOMnode = xmlread(xmlfile); - else % in normal mode crashes are not allowed - try - try - DOMnode = xmlread(xmlfile); - catch ME - error('Failed to read XML file %s: \n%s',xmlfile, getReport(ME)); - end - catch %#ok catch for mablab versions prior to 7.5 - error('Failed to read XML file %s\n',xmlfile); - end - end -end -Node = DOMnode.getFirstChild; - -%% Find the Root node. Also store data from Global Comment and Processing -% Instruction nodes, if any. -GlobalTextNodes = cell(1,3); -GlobalProcInst = []; -GlobalComment = []; -GlobalDocType = []; -while (~isempty(Node)) - if (Node.getNodeType==Node.ELEMENT_NODE) - RootNode=Node; - elseif (Node.getNodeType==Node.PROCESSING_INSTRUCTION_NODE) - data = strtrim(char(Node.getData)); - target = strtrim(char(Node.getTarget)); - GlobalProcInst = [target, ' ', data]; - GlobalTextNodes{2} = GlobalProcInst; - elseif (Node.getNodeType==Node.COMMENT_NODE) - GlobalComment = strtrim(char(Node.getData)); - GlobalTextNodes{3} = GlobalComment; - % elseif (Node.getNodeType==Node.DOCUMENT_TYPE_NODE) - % GlobalTextNodes{4} = GlobalDocType; - end - Node = Node.getNextSibling; -end - -%% parse xml file through calls to recursive DOMnode2struct function -if (Debug) % in debuging mode crashes are allowed - [tree RootName] = DOMnode2struct(RootNode, DPref, 1); -else % in normal mode crashes are not allowed - try - try - [tree RootName] = DOMnode2struct(RootNode, DPref, 1); - catch ME - error('Unable to parse XML file %s: \n %s.',xmlfile, getReport(ME)); - end - catch %#ok catch for mablab versions prior to 7.5 - error('Unable to parse XML file %s.',xmlfile); - end -end - -%% If there were any Global Text nodes than return them -if (~RootOnly) - if (~isempty(GlobalProcInst) && DPref.ReadSpec) - t.PROCESSING_INSTRUCTION = GlobalProcInst; - end - if (~isempty(GlobalComment) && DPref.ReadSpec) - t.COMMENT = GlobalComment; - end - if (~isempty(GlobalDocType) && DPref.ReadSpec) - t.DOCUMENT_TYPE = GlobalDocType; - end - t.(RootName) = tree; - tree=t; -end -if (~isempty(GlobalTextNodes)) - GlobalTextNodes{1} = RootName; - RootName = GlobalTextNodes; -end - - -%% ======================================================================= -% === DOMnode2struct Function =========================================== -% ======================================================================= -function [s TagName LeafNode] = DOMnode2struct(node, Pref, level) - -%% === Step 1: Get node name and check if it is a leaf node ============== -[TagName LeafNode] = NodeName(node, Pref.KeepNS); -s = []; % initialize output structure - -%% === Step 2: Process Leaf Nodes (nodes with no children) =============== -if (LeafNode) - if (LeafNode>1 && ~Pref.ReadSpec), LeafNode=-1; end % tags only so ignore special nodes - if (LeafNode>0) % supported leaf node types - try - try % use try-catch: errors here are often due to VERY large fields (like images) that overflow java memory - s = char(node.getData); - if (isempty(s)), s = ' '; end % make it a string - % for some reason current xmlread 'creates' a lot of empty text - % fields with first chatacter=10 - those will be deleted. - if (~Pref.PreserveSpace || s(1)==10) - if (isspace(s(1)) || isspace(s(end))), s = strtrim(s); end % trim speces is any - end - if (LeafNode==1), s=str2var(s, Pref.Str2Num, 0); end % convert to number(s) if needed - catch ME % catch for mablab versions 7.5 and higher - warning('xml_io_tools:read:LeafRead', ... - 'This leaf node could not be read and was ignored. '); - getReport(ME) - end - catch %#ok catch for mablab versions prior to 7.5 - warning('xml_io_tools:read:LeafRead', ... - 'This leaf node could not be read and was ignored. '); - end - end - if (LeafNode==3) % ProcessingInstructions need special treatment - target = strtrim(char(node.getTarget)); - s = [target, ' ', s]; - end - return % We are done the rest of the function deals with nodes with children -end -if (level>Pref.NumLevels+1), return; end % if Pref.NumLevels is reached than we are done - -%% === Step 3: Process nodes with children =============================== -if (node.hasChildNodes) % children present - Child = node.getChildNodes; % create array of children nodes - nChild = Child.getLength; % number of children - - % --- pass 1: how many children with each name ----------------------- - f = []; - for iChild = 1:nChild % read in each child - [cname cLeaf] = NodeName(Child.item(iChild-1), Pref.KeepNS); - if (cLeaf<0), continue; end % unsupported leaf node types - if (~isfield(f,cname)), - f.(cname)=0; % initialize first time I see this name - end - f.(cname) = f.(cname)+1; % add to the counter - end % end for iChild - % text_nodes become CONTENT & for some reason current xmlread 'creates' a - % lot of empty text fields so f.CONTENT value should not be trusted - if (isfield(f,'CONTENT') && f.CONTENT>2), f.CONTENT=2; end - - % --- pass 2: store all the children as struct of cell arrays ---------- - for iChild = 1:nChild % read in each child - [c cname cLeaf] = DOMnode2struct(Child.item(iChild-1), Pref, level+1); - if (cLeaf && isempty(c)) % if empty leaf node than skip - continue; % usually empty text node or one of unhandled node types - elseif (nChild==1 && cLeaf==1) - s=c; % shortcut for a common case - else % if normal node - if (level>Pref.NumLevels), continue; end - n = f.(cname); % how many of them in the array so far? - if (~isfield(s,cname)) % encountered this name for the first time - if (n==1) % if there will be only one of them ... - s.(cname) = c; % than save it in format it came in - else % if there will be many of them ... - s.(cname) = cell(1,n); - s.(cname){1} = c; % than save as cell array - end - f.(cname) = 1; % initialize the counter - else % already have seen this name - s.(cname){n+1} = c; % add to the array - f.(cname) = n+1; % add to the array counter - end - end - end % for iChild -end % end if (node.hasChildNodes) - -%% === Step 4: Post-process struct's created for nodes with children ===== -if (isstruct(s)) - fields = fieldnames(s); - nField = length(fields); - - % Detect structure that looks like Html table and store it in cell Matrix - if (nField==1 && strcmpi(fields{1},Pref.TableName{1})) - tr = s.(Pref.TableName{1}); - fields2 = fieldnames(tr{1}); - if (length(fields2)==1 && strcmpi(fields2{1},Pref.TableName{2})) - % This seems to be a special structure such that for - % Pref.TableName = {'tr','td'} 's' corresponds to - % M11 M12 - % M12 M22 - % Recognize it as encoding for 2D struct - nr = length(tr); - for r = 1:nr - row = tr{r}.(Pref.TableName{2}); - Table(r,1:length(row)) = row; %#ok - end - s = Table; - end - end - - % --- Post-processing: convert 'struct of cell-arrays' to 'array of structs' - % Example: let say s has 3 fields s.a, s.b & s.c and each field is an - % cell-array with more than one cell-element and all 3 have the same length. - % Then change it to array of structs, each with single cell. - % This way element s.a{1} will be now accessed through s(1).a - vec = zeros(size(fields)); - for i=1:nField, vec(i) = f.(fields{i}); end - if (numel(vec)>1 && vec(1)>1 && var(vec)==0) % convert from struct of - s = cell2struct(struct2cell(s), fields, 1); % arrays to array of struct - end % if anyone knows better way to do above conversion please let me know. - -end - -%% === Step 5: Process nodes with attributes ============================= -if (node.hasAttributes && Pref.ReadAttr) - if (~isstruct(s)), % make into struct if is not already - ss.CONTENT=s; - s=ss; - end - Attr = node.getAttributes; % list of all attributes - for iAttr = 1:Attr.getLength % for each attribute - name = char(Attr.item(iAttr-1).getName); % attribute name - name = str2varName(name, Pref.KeepNS); % fix name if needed - value = char(Attr.item(iAttr-1).getValue); % attribute value - value = str2var(value, Pref.Str2Num, 1); % convert to number if possible - s.ATTRIBUTE.(name) = value; % save again - end % end iAttr loop -end % done with attributes -if (~isstruct(s)), return; end %The rest of the code deals with struct's - -%% === Post-processing: fields of "s" -% convert 'cell-array of structs' to 'arrays of structs' -fields = fieldnames(s); % get field names -nField = length(fields); -for iItem=1:length(s) % for each struct in the array - usually one - for iField=1:length(fields) - field = fields{iField}; % get field name - % if this is an 'item' field and user want to leave those as cells - % than skip this one - if (strcmpi(field, Pref.ItemName) && Pref.CellItem), continue; end - x = s(iItem).(field); - if (iscell(x) && all(cellfun(@isstruct,x(:))) && numel(x)>1) % it's cell-array of structs - % numel(x)>1 check is to keep 1 cell-arrays created when Pref.CellItem=1 - try % this operation fails sometimes - % example: change s(1).a{1}.b='jack'; s(1).a{2}.b='john'; to - % more convinient s(1).a(1).b='jack'; s(1).a(2).b='john'; - s(iItem).(field) = [x{:}]'; %#ok % converted to arrays of structs - catch %#ok - % above operation will fail if s(1).a{1} and s(1).a{2} have - % different fields. If desired, function forceCell2Struct can force - % them to the same field structure by adding empty fields. - if (Pref.NoCells) - s(iItem).(field) = forceCell2Struct(x); %#ok - end - end % end catch - end - end -end - -%% === Step 4: Post-process struct's created for nodes with children ===== - -% --- Post-processing: remove special 'item' tags --------------------- -% many xml writes (including xml_write) use a special keyword to mark -% arrays of nodes (see xml_write for examples). The code below converts -% s.item to s.CONTENT -ItemContent = false; -if (isfield(s,Pref.ItemName)) - s.CONTENT = s.(Pref.ItemName); - s = rmfield(s,Pref.ItemName); - ItemContent = Pref.CellItem; % if CellItem than keep s.CONTENT as cells -end - -% --- Post-processing: clean up CONTENT tags --------------------- -% if s.CONTENT is a cell-array with empty elements at the end than trim -% the length of this cell-array. Also if s.CONTENT is the only field than -% remove .CONTENT part and store it as s. -if (isfield(s,'CONTENT')) - if (iscell(s.CONTENT) && isvector(s.CONTENT)) - x = s.CONTENT; - for i=numel(x):-1:1, if ~isempty(x{i}), break; end; end - if (i==1 && ~ItemContent) - s.CONTENT = x{1}; % delete cell structure - else - s.CONTENT = x(1:i); % delete empty cells - end - end - if (nField==1) - if (ItemContent) - ss = s.CONTENT; % only child: remove a level but ensure output is a cell-array - s=[]; s{1}=ss; - else - s = s.CONTENT; % only child: remove a level - end - end -end - - - -%% ======================================================================= -% === forceCell2Struct Function ========================================= -% ======================================================================= -function s = forceCell2Struct(x) -% Convert cell-array of structs, where not all of structs have the same -% fields, to a single array of structs - -%% Convert 1D cell array of structs to 2D cell array, where each row -% represents item in original array and each column corresponds to a unique -% field name. Array "AllFields" store fieldnames for each column -AllFields = fieldnames(x{1}); % get field names of the first struct -CellMat = cell(length(x), length(AllFields)); -for iItem=1:length(x) - fields = fieldnames(x{iItem}); % get field names of the next struct - for iField=1:length(fields) % inspect all fieldnames and find those - field = fields{iField}; % get field name - col = find(strcmp(field,AllFields),1); - if isempty(col) % no column for such fieldname yet - AllFields = [AllFields; field]; %#ok - col = length(AllFields); % create a new column for it - end - CellMat{iItem,col} = x{iItem}.(field); % store rearanged data - end -end -%% Convert 2D cell array to array of structs -s = cell2struct(CellMat, AllFields, 2); - -%% ======================================================================= -% === str2var Function ================================================== -% ======================================================================= -function val=str2var(str, option, attribute) -% Can this string 'str' be converted to a number? if so than do it. -val = str; -len = numel(str); -if (len==0 || option==0), return; end % Str2Num="never" of empty string -> do not do enything -if (len>10000 && option==1), return; end % Str2Num="smart" and string is very long -> probably base64 encoded binary -digits = '(Inf)|(NaN)|(pi)|[\t\n\d\+\-\*\.ei EI\[\]\;\,]'; -s = regexprep(str, digits, ''); % remove all the digits and other allowed characters -if (~all(~isempty(s))) % if nothing left than this is probably a number - if (~isempty(strfind(str, ' '))), option=2; end %if str has white-spaces assume by default that it is not a date string - if (~isempty(strfind(str, '['))), option=2; end % same with brackets - str(strfind(str, '\n')) = ';';% parse data tables into 2D arrays, if any - if (option==1) % the 'smart' option - try % try to convert to a date, like 2007-12-05 - datenum(str); % if successful than leave it as string - catch %#ok % if this is not a date than ... - option=2; % ... try converting to a number - end - end - if (option==2) - if (attribute) - num = str2double(str); % try converting to a single number using sscanf function - if isnan(num), return; end % So, it wasn't really a number after all - else - num = str2num(str); %#ok % try converting to a single number or array using eval function - end - if(isnumeric(num) && numel(num)>0), val=num; end % if convertion to a single was succesful than save - end -elseif ((str(1)=='[' && str(end)==']') || (str(1)=='{' && str(end)=='}')) % this looks like a (cell) array encoded as a string - try - val = eval(str); - catch %#ok - val = str; - end -elseif (~attribute) % see if it is a boolean array with no [] brackets - str1 = lower(str); - str1 = strrep(str1, 'false', '0'); - str1 = strrep(str1, 'true' , '1'); - s = regexprep(str1, '[01 \;\,]', ''); % remove all 0/1, spaces, commas and semicolons - if (~all(~isempty(s))) % if nothing left than this is probably a boolean array - num = str2num(str1); %#ok - if(isnumeric(num) && numel(num)>0), val = (num>0); end % if convertion was succesful than save as logical - end -end - - -%% ======================================================================= -% === str2varName Function ============================================== -% ======================================================================= -function str = str2varName(str, KeepNS) -% convert a sting to a valid matlab variable name -if(KeepNS) - str = regexprep(str,':','_COLON_', 'once', 'ignorecase'); -else - k = strfind(str,':'); - if (~isempty(k)) - str = str(k+1:end); - end -end -str = regexprep(str,'-','_DASH_' ,'once', 'ignorecase'); -if (~isvarname(str)) && (~iskeyword(str)) - str = genvarname(str); -end - -%% ======================================================================= -% === NodeName Function ================================================= -% ======================================================================= -function [Name LeafNode] = NodeName(node, KeepNS) -% get node name and make sure it is a valid variable name in Matlab. -% also get node type: -% LeafNode=0 - normal element node, -% LeafNode=1 - text node -% LeafNode=2 - supported non-text leaf node, -% LeafNode=3 - supported processing instructions leaf node, -% LeafNode=-1 - unsupported non-text leaf node -switch (node.getNodeType) - case node.ELEMENT_NODE - Name = char(node.getNodeName);% capture name of the node - Name = str2varName(Name, KeepNS); % if Name is not a good variable name - fix it - LeafNode = 0; - case node.TEXT_NODE - Name = 'CONTENT'; - LeafNode = 1; - case node.COMMENT_NODE - Name = 'COMMENT'; - LeafNode = 2; - case node.CDATA_SECTION_NODE - Name = 'CDATA_SECTION'; - LeafNode = 2; - case node.DOCUMENT_TYPE_NODE - Name = 'DOCUMENT_TYPE'; - LeafNode = 2; - case node.PROCESSING_INSTRUCTION_NODE - Name = 'PROCESSING_INSTRUCTION'; - LeafNode = 3; - otherwise - NodeType = {'ELEMENT','ATTRIBUTE','TEXT','CDATA_SECTION', ... - 'ENTITY_REFERENCE', 'ENTITY', 'PROCESSING_INSTRUCTION', 'COMMENT',... - 'DOCUMENT', 'DOCUMENT_TYPE', 'DOCUMENT_FRAGMENT', 'NOTATION'}; - Name = char(node.getNodeName);% capture name of the node - warning('xml_io_tools:read:unkNode', ... - 'Unknown node type encountered: %s_NODE (%s)', NodeType{node.getNodeType}, Name); - LeafNode = -1; -end - - +function [tree, RootName, DOMnode] = xml_read(xmlfile, Pref) +%XML_READ reads xml files and converts them into Matlab's struct tree. +% +% DESCRIPTION +% tree = xml_read(xmlfile) reads 'xmlfile' into data structure 'tree' +% +% tree = xml_read(xmlfile, Pref) reads 'xmlfile' into data structure 'tree' +% according to your preferences +% +% [tree, RootName, DOMnode] = xml_read(xmlfile) get additional information +% about XML file +% +% INPUT: +% xmlfile URL or filename of xml file to read +% Pref Preferences: +% Pref.ItemName - default 'item' - name of a special tag used to itemize +% cell arrays +% Pref.ReadAttr - default true - allow reading attributes +% Pref.ReadSpec - default true - allow reading special nodes +% Pref.Str2Num - default 'smart' - convert strings that look like numbers +% to numbers. Options: "always", "never", and "smart" +% Pref.KeepNS - default true - keep or strip namespace info +% Pref.NoCells - default true - force output to have no cell arrays +% Pref.Debug - default false - show mode specific error messages +% Pref.NumLevels- default infinity - how many recursive levels are +% allowed. Can be used to speed up the function by prunning the tree. +% Pref.RootOnly - default true - output variable 'tree' corresponds to +% xml file root element, otherwise it correspond to the whole file. +% Pref.CellItem - default 'true' - leave 'item' nodes in cell notation. +% OUTPUT: +% tree tree of structs and/or cell arrays corresponding to xml file +% RootName XML tag name used for root (top level) node. +% Optionally it can be a string cell array storing: Name of +% root node, document "Processing Instructions" data and +% document "comment" string +% DOMnode output of xmlread +% +% DETAILS: +% Function xml_read first calls MATLAB's xmlread function and than +% converts its output ('Document Object Model' tree of Java objects) +% to tree of MATLAB struct's. The output is in format of nested structs +% and cells. In the output data structure field names are based on +% XML tags, except in cases when tags produce illegal variable names. +% +% Several special xml node types result in special tags for fields of +% 'tree' nodes: +% - node.CONTENT - stores data section of the node if other fields are +% present. Usually data section is stored directly in 'node'. +% - node.ATTRIBUTE.name - stores node's attribute called 'name'. +% - node.COMMENT - stores node's comment section (string). For global +% comments see "RootName" output variable. +% - node.CDATA_SECTION - stores node's CDATA section (string). +% - node.PROCESSING_INSTRUCTIONS - stores "processing instruction" child +% node. For global "processing instructions" see "RootName" output variable. +% - other special node types like: document fragment nodes, document type +% nodes, entity nodes, notation nodes and processing instruction nodes +% will be treated like regular nodes +% +% EXAMPLES: +% MyTree=[]; +% MyTree.MyNumber = 13; +% MyTree.MyString = 'Hello World'; +% xml_write('test.xml', MyTree); +% [tree treeName] = xml_read ('test.xml'); +% disp(treeName) +% gen_object_display() +% % See also xml_examples.m +% +% See also: +% xml_write, xmlread, xmlwrite +% +% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com +% References: +% - Function inspired by Example 3 found in xmlread function. +% - Output data structures inspired by xml_toolbox structures. + +%% default preferences +DPref.TableName = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays +DPref.ItemName = 'item'; % name of a special tag used to itemize 1D cell arrays +DPref.CellItem = false; % leave 'item' nodes in cell notation +DPref.ReadAttr = true; % allow reading attributes +DPref.ReadSpec = true; % allow reading special nodes: comments, CData, etc. +DPref.KeepNS = true; % Keep or strip namespace info +DPref.Str2Num = 'smart';% convert strings that look like numbers to numbers +DPref.NoCells = true; % force output to have no cell arrays +DPref.NumLevels = 1e10; % number of recurence levels +DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? +RootOnly = true; % return root node with no top level special nodes +Debug = false; % show specific errors (true) or general (false)? +tree = []; +RootName = []; + +%% Check Matlab Version +v = ver('MATLAB'); +version = str2double(regexp(v.Version, '\d.\d','match','once')); +if (version<7.1) + error('Your MATLAB version is too old. You need version 7.1 or newer.'); +end + +%% read user preferences +if (nargin>1) + if (isfield(Pref, 'TableName')), DPref.TableName = Pref.TableName; end + if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end + if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end + if (isfield(Pref, 'Str2Num' )), DPref.Str2Num = Pref.Str2Num ; end + if (isfield(Pref, 'NoCells' )), DPref.NoCells = Pref.NoCells ; end + if (isfield(Pref, 'NumLevels')), DPref.NumLevels = Pref.NumLevels; end + if (isfield(Pref, 'ReadAttr' )), DPref.ReadAttr = Pref.ReadAttr; end + if (isfield(Pref, 'ReadSpec' )), DPref.ReadSpec = Pref.ReadSpec; end + if (isfield(Pref, 'KeepNS' )), DPref.KeepNS = Pref.KeepNS; end + if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end + if (isfield(Pref, 'Debug' )), Debug = Pref.Debug ; end + if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end +end +if ischar(DPref.Str2Num), % convert from character description to numbers + DPref.Str2Num = find(strcmpi(DPref.Str2Num, {'never', 'smart', 'always'}))-1; + if isempty(DPref.Str2Num), DPref.Str2Num=1; end % 1-smart by default +end + +%% read xml file using Matlab function +if isa(xmlfile, 'org.apache.xerces.dom.DeferredDocumentImpl'); + % if xmlfile is a DOMnode than skip the call to xmlread + try + try + DOMnode = xmlfile; + catch ME + error('Invalid DOM node: \n%s.', getReport(ME)); + end + catch %#ok catch for mablab versions prior to 7.5 + error('Invalid DOM node. \n'); + end +else % we assume xmlfile is a filename + if (Debug) % in debuging mode crashes are allowed + DOMnode = xmlread(xmlfile); + else % in normal mode crashes are not allowed + try + try + DOMnode = xmlread(xmlfile); + catch ME + error('Failed to read XML file %s: \n%s',xmlfile, getReport(ME)); + end + catch %#ok catch for mablab versions prior to 7.5 + error('Failed to read XML file %s\n',xmlfile); + end + end +end +Node = DOMnode.getFirstChild; + +%% Find the Root node. Also store data from Global Comment and Processing +% Instruction nodes, if any. +GlobalTextNodes = cell(1,3); +GlobalProcInst = []; +GlobalComment = []; +GlobalDocType = []; +while (~isempty(Node)) + if (Node.getNodeType==Node.ELEMENT_NODE) + RootNode=Node; + elseif (Node.getNodeType==Node.PROCESSING_INSTRUCTION_NODE) + data = strtrim(char(Node.getData)); + target = strtrim(char(Node.getTarget)); + GlobalProcInst = [target, ' ', data]; + GlobalTextNodes{2} = GlobalProcInst; + elseif (Node.getNodeType==Node.COMMENT_NODE) + GlobalComment = strtrim(char(Node.getData)); + GlobalTextNodes{3} = GlobalComment; + % elseif (Node.getNodeType==Node.DOCUMENT_TYPE_NODE) + % GlobalTextNodes{4} = GlobalDocType; + end + Node = Node.getNextSibling; +end + +%% parse xml file through calls to recursive DOMnode2struct function +if (Debug) % in debuging mode crashes are allowed + [tree RootName] = DOMnode2struct(RootNode, DPref, 1); +else % in normal mode crashes are not allowed + try + try + [tree RootName] = DOMnode2struct(RootNode, DPref, 1); + catch ME + error('Unable to parse XML file %s: \n %s.',xmlfile, getReport(ME)); + end + catch %#ok catch for mablab versions prior to 7.5 + error('Unable to parse XML file %s.',xmlfile); + end +end + +%% If there were any Global Text nodes than return them +if (~RootOnly) + if (~isempty(GlobalProcInst) && DPref.ReadSpec) + t.PROCESSING_INSTRUCTION = GlobalProcInst; + end + if (~isempty(GlobalComment) && DPref.ReadSpec) + t.COMMENT = GlobalComment; + end + if (~isempty(GlobalDocType) && DPref.ReadSpec) + t.DOCUMENT_TYPE = GlobalDocType; + end + t.(RootName) = tree; + tree=t; +end +if (~isempty(GlobalTextNodes)) + GlobalTextNodes{1} = RootName; + RootName = GlobalTextNodes; +end + + +%% ======================================================================= +% === DOMnode2struct Function =========================================== +% ======================================================================= +function [s TagName LeafNode] = DOMnode2struct(node, Pref, level) + +%% === Step 1: Get node name and check if it is a leaf node ============== +[TagName LeafNode] = NodeName(node, Pref.KeepNS); +s = []; % initialize output structure + +%% === Step 2: Process Leaf Nodes (nodes with no children) =============== +if (LeafNode) + if (LeafNode>1 && ~Pref.ReadSpec), LeafNode=-1; end % tags only so ignore special nodes + if (LeafNode>0) % supported leaf node types + try + try % use try-catch: errors here are often due to VERY large fields (like images) that overflow java memory + s = char(node.getData); + if (isempty(s)), s = ' '; end % make it a string + % for some reason current xmlread 'creates' a lot of empty text + % fields with first chatacter=10 - those will be deleted. + if (~Pref.PreserveSpace || s(1)==10) + if (isspace(s(1)) || isspace(s(end))), s = strtrim(s); end % trim speces is any + end + if (LeafNode==1), s=str2var(s, Pref.Str2Num, 0); end % convert to number(s) if needed + catch ME % catch for mablab versions 7.5 and higher + warning('xml_io_tools:read:LeafRead', ... + 'This leaf node could not be read and was ignored. '); + getReport(ME) + end + catch %#ok catch for mablab versions prior to 7.5 + warning('xml_io_tools:read:LeafRead', ... + 'This leaf node could not be read and was ignored. '); + end + end + if (LeafNode==3) % ProcessingInstructions need special treatment + target = strtrim(char(node.getTarget)); + s = [target, ' ', s]; + end + return % We are done the rest of the function deals with nodes with children +end +if (level>Pref.NumLevels+1), return; end % if Pref.NumLevels is reached than we are done + +%% === Step 3: Process nodes with children =============================== +if (node.hasChildNodes) % children present + Child = node.getChildNodes; % create array of children nodes + nChild = Child.getLength; % number of children + + % --- pass 1: how many children with each name ----------------------- + f = []; + for iChild = 1:nChild % read in each child + [cname cLeaf] = NodeName(Child.item(iChild-1), Pref.KeepNS); + if (cLeaf<0), continue; end % unsupported leaf node types + if (~isfield(f,cname)), + f.(cname)=0; % initialize first time I see this name + end + f.(cname) = f.(cname)+1; % add to the counter + end % end for iChild + % text_nodes become CONTENT & for some reason current xmlread 'creates' a + % lot of empty text fields so f.CONTENT value should not be trusted + if (isfield(f,'CONTENT') && f.CONTENT>2), f.CONTENT=2; end + + % --- pass 2: store all the children as struct of cell arrays ---------- + for iChild = 1:nChild % read in each child + [c cname cLeaf] = DOMnode2struct(Child.item(iChild-1), Pref, level+1); + if (cLeaf && isempty(c)) % if empty leaf node than skip + continue; % usually empty text node or one of unhandled node types + elseif (nChild==1 && cLeaf==1) + s=c; % shortcut for a common case + else % if normal node + if (level>Pref.NumLevels), continue; end + n = f.(cname); % how many of them in the array so far? + if (~isfield(s,cname)) % encountered this name for the first time + if (n==1) % if there will be only one of them ... + s.(cname) = c; % than save it in format it came in + else % if there will be many of them ... + s.(cname) = cell(1,n); + s.(cname){1} = c; % than save as cell array + end + f.(cname) = 1; % initialize the counter + else % already have seen this name + s.(cname){n+1} = c; % add to the array + f.(cname) = n+1; % add to the array counter + end + end + end % for iChild +end % end if (node.hasChildNodes) + +%% === Step 4: Post-process struct's created for nodes with children ===== +if (isstruct(s)) + fields = fieldnames(s); + nField = length(fields); + + % Detect structure that looks like Html table and store it in cell Matrix + if (nField==1 && strcmpi(fields{1},Pref.TableName{1})) + tr = s.(Pref.TableName{1}); + fields2 = fieldnames(tr{1}); + if (length(fields2)==1 && strcmpi(fields2{1},Pref.TableName{2})) + % This seems to be a special structure such that for + % Pref.TableName = {'tr','td'} 's' corresponds to + % M11 M12 + % M12 M22 + % Recognize it as encoding for 2D struct + nr = length(tr); + for r = 1:nr + row = tr{r}.(Pref.TableName{2}); + Table(r,1:length(row)) = row; %#ok + end + s = Table; + end + end + + % --- Post-processing: convert 'struct of cell-arrays' to 'array of structs' + % Example: let say s has 3 fields s.a, s.b & s.c and each field is an + % cell-array with more than one cell-element and all 3 have the same length. + % Then change it to array of structs, each with single cell. + % This way element s.a{1} will be now accessed through s(1).a + vec = zeros(size(fields)); + for i=1:nField, vec(i) = f.(fields{i}); end + if (numel(vec)>1 && vec(1)>1 && var(vec)==0) % convert from struct of + s = cell2struct(struct2cell(s), fields, 1); % arrays to array of struct + end % if anyone knows better way to do above conversion please let me know. + +end + +%% === Step 5: Process nodes with attributes ============================= +if (node.hasAttributes && Pref.ReadAttr) + if (~isstruct(s)), % make into struct if is not already + ss.CONTENT=s; + s=ss; + end + Attr = node.getAttributes; % list of all attributes + for iAttr = 1:Attr.getLength % for each attribute + name = char(Attr.item(iAttr-1).getName); % attribute name + name = str2varName(name, Pref.KeepNS); % fix name if needed + value = char(Attr.item(iAttr-1).getValue); % attribute value + value = str2var(value, Pref.Str2Num, 1); % convert to number if possible + s.ATTRIBUTE.(name) = value; % save again + end % end iAttr loop +end % done with attributes +if (~isstruct(s)), return; end %The rest of the code deals with struct's + +%% === Post-processing: fields of "s" +% convert 'cell-array of structs' to 'arrays of structs' +fields = fieldnames(s); % get field names +nField = length(fields); +for iItem=1:length(s) % for each struct in the array - usually one + for iField=1:length(fields) + field = fields{iField}; % get field name + % if this is an 'item' field and user want to leave those as cells + % than skip this one + if (strcmpi(field, Pref.ItemName) && Pref.CellItem), continue; end + x = s(iItem).(field); + if (iscell(x) && all(cellfun(@isstruct,x(:))) && numel(x)>1) % it's cell-array of structs + % numel(x)>1 check is to keep 1 cell-arrays created when Pref.CellItem=1 + try % this operation fails sometimes + % example: change s(1).a{1}.b='jack'; s(1).a{2}.b='john'; to + % more convinient s(1).a(1).b='jack'; s(1).a(2).b='john'; + s(iItem).(field) = [x{:}]'; %#ok % converted to arrays of structs + catch %#ok + % above operation will fail if s(1).a{1} and s(1).a{2} have + % different fields. If desired, function forceCell2Struct can force + % them to the same field structure by adding empty fields. + if (Pref.NoCells) + s(iItem).(field) = forceCell2Struct(x); %#ok + end + end % end catch + end + end +end + +%% === Step 4: Post-process struct's created for nodes with children ===== + +% --- Post-processing: remove special 'item' tags --------------------- +% many xml writes (including xml_write) use a special keyword to mark +% arrays of nodes (see xml_write for examples). The code below converts +% s.item to s.CONTENT +ItemContent = false; +if (isfield(s,Pref.ItemName)) + s.CONTENT = s.(Pref.ItemName); + s = rmfield(s,Pref.ItemName); + ItemContent = Pref.CellItem; % if CellItem than keep s.CONTENT as cells +end + +% --- Post-processing: clean up CONTENT tags --------------------- +% if s.CONTENT is a cell-array with empty elements at the end than trim +% the length of this cell-array. Also if s.CONTENT is the only field than +% remove .CONTENT part and store it as s. +if (isfield(s,'CONTENT')) + if (iscell(s.CONTENT) && isvector(s.CONTENT)) + x = s.CONTENT; + for i=numel(x):-1:1, if ~isempty(x{i}), break; end; end + if (i==1 && ~ItemContent) + s.CONTENT = x{1}; % delete cell structure + else + s.CONTENT = x(1:i); % delete empty cells + end + end + if (nField==1) + if (ItemContent) + ss = s.CONTENT; % only child: remove a level but ensure output is a cell-array + s=[]; s{1}=ss; + else + s = s.CONTENT; % only child: remove a level + end + end +end + + + +%% ======================================================================= +% === forceCell2Struct Function ========================================= +% ======================================================================= +function s = forceCell2Struct(x) +% Convert cell-array of structs, where not all of structs have the same +% fields, to a single array of structs + +%% Convert 1D cell array of structs to 2D cell array, where each row +% represents item in original array and each column corresponds to a unique +% field name. Array "AllFields" store fieldnames for each column +AllFields = fieldnames(x{1}); % get field names of the first struct +CellMat = cell(length(x), length(AllFields)); +for iItem=1:length(x) + fields = fieldnames(x{iItem}); % get field names of the next struct + for iField=1:length(fields) % inspect all fieldnames and find those + field = fields{iField}; % get field name + col = find(strcmp(field,AllFields),1); + if isempty(col) % no column for such fieldname yet + AllFields = [AllFields; field]; %#ok + col = length(AllFields); % create a new column for it + end + CellMat{iItem,col} = x{iItem}.(field); % store rearanged data + end +end +%% Convert 2D cell array to array of structs +s = cell2struct(CellMat, AllFields, 2); + +%% ======================================================================= +% === str2var Function ================================================== +% ======================================================================= +function val=str2var(str, option, attribute) +% Can this string 'str' be converted to a number? if so than do it. +val = str; +len = numel(str); +if (len==0 || option==0), return; end % Str2Num="never" of empty string -> do not do enything +if (len>10000 && option==1), return; end % Str2Num="smart" and string is very long -> probably base64 encoded binary +digits = '(Inf)|(NaN)|(pi)|[\t\n\d\+\-\*\.ei EI\[\]\;\,]'; +s = regexprep(str, digits, ''); % remove all the digits and other allowed characters +if (~all(~isempty(s))) % if nothing left than this is probably a number + if (~isempty(strfind(str, ' '))), option=2; end %if str has white-spaces assume by default that it is not a date string + if (~isempty(strfind(str, '['))), option=2; end % same with brackets + str(strfind(str, '\n')) = ';';% parse data tables into 2D arrays, if any + if (option==1) % the 'smart' option + try % try to convert to a date, like 2007-12-05 + datenum(str); % if successful than leave it as string + catch %#ok % if this is not a date than ... + option=2; % ... try converting to a number + end + end + if (option==2) + if (attribute) + num = str2double(str); % try converting to a single number using sscanf function + if isnan(num), return; end % So, it wasn't really a number after all + else + num = str2num(str); %#ok % try converting to a single number or array using eval function + end + if(isnumeric(num) && numel(num)>0), val=num; end % if convertion to a single was succesful than save + end +elseif ((str(1)=='[' && str(end)==']') || (str(1)=='{' && str(end)=='}')) % this looks like a (cell) array encoded as a string + try + val = eval(str); + catch %#ok + val = str; + end +elseif (~attribute) % see if it is a boolean array with no [] brackets + str1 = lower(str); + str1 = strrep(str1, 'false', '0'); + str1 = strrep(str1, 'true' , '1'); + s = regexprep(str1, '[01 \;\,]', ''); % remove all 0/1, spaces, commas and semicolons + if (~all(~isempty(s))) % if nothing left than this is probably a boolean array + num = str2num(str1); %#ok + if(isnumeric(num) && numel(num)>0), val = (num>0); end % if convertion was succesful than save as logical + end +end + + +%% ======================================================================= +% === str2varName Function ============================================== +% ======================================================================= +function str = str2varName(str, KeepNS) +% convert a sting to a valid matlab variable name +if(KeepNS) + str = regexprep(str,':','_COLON_', 'once', 'ignorecase'); +else + k = strfind(str,':'); + if (~isempty(k)) + str = str(k+1:end); + end +end +str = regexprep(str,'-','_DASH_' ,'once', 'ignorecase'); +if (~isvarname(str)) && (~iskeyword(str)) + str = genvarname(str); +end + +%% ======================================================================= +% === NodeName Function ================================================= +% ======================================================================= +function [Name LeafNode] = NodeName(node, KeepNS) +% get node name and make sure it is a valid variable name in Matlab. +% also get node type: +% LeafNode=0 - normal element node, +% LeafNode=1 - text node +% LeafNode=2 - supported non-text leaf node, +% LeafNode=3 - supported processing instructions leaf node, +% LeafNode=-1 - unsupported non-text leaf node +switch (node.getNodeType) + case node.ELEMENT_NODE + Name = char(node.getNodeName);% capture name of the node + Name = str2varName(Name, KeepNS); % if Name is not a good variable name - fix it + LeafNode = 0; + case node.TEXT_NODE + Name = 'CONTENT'; + LeafNode = 1; + case node.COMMENT_NODE + Name = 'COMMENT'; + LeafNode = 2; + case node.CDATA_SECTION_NODE + Name = 'CDATA_SECTION'; + LeafNode = 2; + case node.DOCUMENT_TYPE_NODE + Name = 'DOCUMENT_TYPE'; + LeafNode = 2; + case node.PROCESSING_INSTRUCTION_NODE + Name = 'PROCESSING_INSTRUCTION'; + LeafNode = 3; + otherwise + NodeType = {'ELEMENT','ATTRIBUTE','TEXT','CDATA_SECTION', ... + 'ENTITY_REFERENCE', 'ENTITY', 'PROCESSING_INSTRUCTION', 'COMMENT',... + 'DOCUMENT', 'DOCUMENT_TYPE', 'DOCUMENT_FRAGMENT', 'NOTATION'}; + Name = char(node.getNodeName);% capture name of the node + warning('xml_io_tools:read:unkNode', ... + 'Unknown node type encountered: %s_NODE (%s)', NodeType{node.getNodeType}, Name); + LeafNode = -1; +end + + diff --git a/private/xml_io_tools/xml_write.m b/private/xml_write.m similarity index 97% rename from private/xml_io_tools/xml_write.m rename to private/xml_write.m index 10a18de..8ed29d5 100644 --- a/private/xml_io_tools/xml_write.m +++ b/private/xml_write.m @@ -1,447 +1,447 @@ -function DOMnode = xml_write(filename, tree, RootName, Pref) -%XML_WRITE Writes Matlab data structures to XML file -% -% DESCRIPTION -% xml_write( filename, tree) Converts Matlab data structure 'tree' containing -% cells, structs, numbers and strings to Document Object Model (DOM) node -% tree, then saves it to XML file 'filename' using Matlab's xmlwrite -% function. Optionally one can also use alternative version of xmlwrite -% function which directly calls JAVA functions for XML writing without -% MATLAB middleware. This function is provided as a patch to existing -% bugs in xmlwrite (in R2006b). -% -% xml_write(filename, tree, RootName, Pref) allows you to specify -% additional preferences about file format -% -% DOMnode = xml_write([], tree) same as above except that DOM node is -% not saved to the file but returned. -% -% INPUT -% filename file name -% tree Matlab structure tree to store in xml file. -% RootName String with XML tag name used for root (top level) node -% Optionally it can be a string cell array storing: Name of -% root node, document "Processing Instructions" data and -% document "comment" string -% Pref Other preferences: -% Pref.ItemName - default 'item' - name of a special tag used to -% itemize cell or struct arrays -% Pref.XmlEngine - let you choose the XML engine. Currently default is -% 'Xerces', which is using directly the apache xerces java file. -% Other option is 'Matlab' which uses MATLAB's xmlwrite and its -% XMLUtils java file. Both options create identical results except in -% case of CDATA sections where xmlwrite fails. -% Pref.CellItem - default 'true' - allow cell arrays to use 'item' -% notation. See below. -% Pref.RootOnly - default true - output variable 'tree' corresponds to -% xml file root element, otherwise it correspond to the whole file. -% Pref.StructItem - default 'true' - allow arrays of structs to use -% 'item' notation. For example "Pref.StructItem = true" gives: -% -% -% ... <\item> -% ... <\item> -% <\b> -% <\a> -% while "Pref.StructItem = false" gives: -% -% ... <\b> -% ... <\b> -% <\a> -% -% -% Several special xml node types can be created if special tags are used -% for field names of 'tree' nodes: -% - node.CONTENT - stores data section of the node if other fields -% (usually ATTRIBUTE are present. Usually data section is stored -% directly in 'node'. -% - node.ATTRIBUTE.name - stores node's attribute called 'name'. -% - node.COMMENT - create comment child node from the string. For global -% comments see "RootName" input variable. -% - node.PROCESSING_INSTRUCTIONS - create "processing instruction" child -% node from the string. For global "processing instructions" see -% "RootName" input variable. -% - node.CDATA_SECTION - stores node's CDATA section (string). Only works -% if Pref.XmlEngine='Xerces'. For more info, see comments of F_xmlwrite. -% - other special node types like: document fragment nodes, document type -% nodes, entity nodes and notation nodes are not being handled by -% 'xml_write' at the moment. -% -% OUTPUT -% DOMnode Document Object Model (DOM) node tree in the format -% required as input to xmlwrite. (optional) -% -% EXAMPLES: -% MyTree=[]; -% MyTree.MyNumber = 13; -% MyTree.MyString = 'Hello World'; -% xml_write('test.xml', MyTree); -% type('test.xml') -% %See also xml_tutorial.m -% -% See also -% xml_read, xmlread, xmlwrite -% -% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com - -%% Check Matlab Version -v = ver('MATLAB'); -v = str2double(regexp(v.Version, '\d.\d','match','once')); -if (v<7) - error('Your MATLAB version is too old. You need version 7.0 or newer.'); -end - -%% default preferences -DPref.TableName = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays -DPref.ItemName = 'item'; % name of a special tag used to itemize 1D cell arrays -DPref.StructItem = true; % allow arrays of structs to use 'item' notation -DPref.CellItem = true; % allow cell arrays to use 'item' notation -DPref.StructTable= 'Html'; -DPref.CellTable = 'Html'; -DPref.XmlEngine = 'Matlab'; % use matlab provided XMLUtils -%DPref.XmlEngine = 'Xerces'; % use Xerces xml generator directly -DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? -RootOnly = true; % Input is root node only -GlobalProcInst = []; -GlobalComment = []; -GlobalDocType = []; - -%% read user preferences -if (nargin>3) - if (isfield(Pref, 'TableName' )), DPref.TableName = Pref.TableName; end - if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end - if (isfield(Pref, 'StructItem')), DPref.StructItem = Pref.StructItem; end - if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end - if (isfield(Pref, 'CellTable')), DPref.CellTable = Pref.CellTable; end - if (isfield(Pref, 'StructTable')), DPref.StructTable= Pref.StructTable; end - if (isfield(Pref, 'XmlEngine' )), DPref.XmlEngine = Pref.XmlEngine; end - if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end - if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end -end -if (nargin<3 || isempty(RootName)), RootName=inputname(2); end -if (isempty(RootName)), RootName='ROOT'; end -if (iscell(RootName)) % RootName also stores global text node data - rName = RootName; - RootName = char(rName{1}); - if (length(rName)>1), GlobalProcInst = char(rName{2}); end - if (length(rName)>2), GlobalComment = char(rName{3}); end - if (length(rName)>3), GlobalDocType = char(rName{4}); end -end -if(~RootOnly && isstruct(tree)) % if struct than deal with each field separatly - fields = fieldnames(tree); - for i=1:length(fields) - field = fields{i}; - x = tree(1).(field); - if (strcmp(field, 'COMMENT')) - GlobalComment = x; - elseif (strcmp(field, 'PROCESSING_INSTRUCTION')) - GlobalProcInst = x; - elseif (strcmp(field, 'DOCUMENT_TYPE')) - GlobalDocType = x; - else - RootName = field; - t = x; - end - end - tree = t; -end - -%% Initialize jave object that will store xml data structure -RootName = varName2str(RootName); -if (~isempty(GlobalDocType)) - % n = strfind(GlobalDocType, ' '); - % if (~isempty(n)) - % dtype = com.mathworks.xml.XMLUtils.createDocumentType(GlobalDocType); - % end - % DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName, dtype); - warning('xml_io_tools:write:docType', ... - 'DOCUMENT_TYPE node was encountered which is not supported yet. Ignoring.'); -end -DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName); - - -%% Use recursive function to convert matlab data structure to XML -root = DOMnode.getDocumentElement; -struct2DOMnode(DOMnode, root, tree, DPref.ItemName, DPref); - -%% Remove the only child of the root node -root = DOMnode.getDocumentElement; -Child = root.getChildNodes; % create array of children nodes -nChild = Child.getLength; % number of children -if (nChild==1) - node = root.removeChild(root.getFirstChild); - while(node.hasChildNodes) - root.appendChild(node.removeChild(node.getFirstChild)); - end - while(node.hasAttributes) % copy all attributes - root.setAttributeNode(node.removeAttributeNode(node.getAttributes.item(0))); - end -end - -%% Save exotic Global nodes -if (~isempty(GlobalComment)) - DOMnode.insertBefore(DOMnode.createComment(GlobalComment), DOMnode.getFirstChild()); -end -if (~isempty(GlobalProcInst)) - n = strfind(GlobalProcInst, ' '); - if (~isempty(n)) - proc = DOMnode.createProcessingInstruction(GlobalProcInst(1:(n(1)-1)),... - GlobalProcInst((n(1)+1):end)); - DOMnode.insertBefore(proc, DOMnode.getFirstChild()); - end -end -% Not supported yet as the code below does not work -% if (~isempty(GlobalDocType)) -% n = strfind(GlobalDocType, ' '); -% if (~isempty(n)) -% dtype = DOMnode.createDocumentType(GlobalDocType); -% DOMnode.insertBefore(dtype, DOMnode.getFirstChild()); -% end -% end - -%% save java DOM tree to XML file -if (~isempty(filename)) - if (strcmpi(DPref.XmlEngine, 'Xerces')) - xmlwrite_xerces(filename, DOMnode); - else - xmlwrite(filename, DOMnode); - end -end - - -%% ======================================================================= -% === struct2DOMnode Function =========================================== -% ======================================================================= -function [] = struct2DOMnode(xml, parent, s, TagName, Pref) -% struct2DOMnode is a recursive function that converts matlab's structs to -% DOM nodes. -% INPUTS: -% xml - jave object that will store xml data structure -% parent - parent DOM Element -% s - Matlab data structure to save -% TagName - name to be used in xml tags describing 's' -% Pref - preferenced -% OUTPUT: -% parent - modified 'parent' - -% perform some conversions -if (ischar(s) && min(size(s))>1) % if 2D array of characters - s=cellstr(s); % than convert to cell array -end -% if (strcmp(TagName, 'CONTENT')) -% while (iscell(s) && length(s)==1), s = s{1}; end % unwrap cell arrays of length 1 -% end -TagName = varName2str(TagName); - -%% == node is a 2D cell array == -% convert to some other format prior to further processing -nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? -if (iscell(s) && nDim==2 && strcmpi(Pref.CellTable, 'Matlab')) - s = var2str(s, Pref.PreserveSpace); -end -if (nDim==2 && (iscell (s) && strcmpi(Pref.CellTable, 'Vector')) || ... - (isstruct(s) && strcmpi(Pref.StructTable, 'Vector'))) - s = s(:); -end -if (nDim>2), s = s(:); end % can not handle this case well -nItem = numel(s); -nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? - -%% == node is a cell == -if (iscell(s)) % if this is a cell or cell array - if ((nDim==2 && strcmpi(Pref.CellTable,'Html')) || (nDim< 2 && Pref.CellItem)) - % if 2D array of cells than can use HTML-like notation or if 1D array - % than can use item notation - if (strcmp(TagName, 'CONTENT')) % CONTENT nodes already have ... - array2DOMnode(xml, parent, s, Pref.ItemName, Pref ); % recursive call - else - node = xml.createElement(TagName); % ... - array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call - parent.appendChild(node); - end - else % use ...<\TagName> ...<\TagName> notation - array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call - end -%% == node is a struct == -elseif (isstruct(s)) % if struct than deal with each field separatly - if ((nDim==2 && strcmpi(Pref.StructTable,'Html')) || (nItem>1 && Pref.StructItem)) - % if 2D array of structs than can use HTML-like notation or - % if 1D array of structs than can use 'items' notation - node = xml.createElement(TagName); - array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call - parent.appendChild(node); - elseif (nItem>1) % use ...<\TagName> ...<\TagName> notation - array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call - else % otherwise save each struct separatelly - fields = fieldnames(s); - node = xml.createElement(TagName); - for i=1:length(fields) % add field by field to the node - field = fields{i}; - x = s.(field); - switch field - case {'COMMENT', 'CDATA_SECTION', 'PROCESSING_INSTRUCTION'} - if iscellstr(x) % cell array of strings -> add them one by one - array2DOMnode(xml, node, x(:), field, Pref ); % recursive call will modify 'node' - elseif ischar(x) % single string -> add it - struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node' - else % not a string - Ignore - warning('xml_io_tools:write:badSpecialNode', ... - ['Struct field named ',field,' encountered which was not a string. Ignoring.']); - end - case 'ATTRIBUTE' % set attributes of the node - if (isempty(x)), continue; end - if (isstruct(x)) - attName = fieldnames(x); % get names of all the attributes - for k=1:length(attName) % attach them to the node - att = xml.createAttribute(varName2str(attName(k))); - att.setValue(var2str(x.(attName{k}),Pref.PreserveSpace)); - node.setAttributeNode(att); - end - else - warning('xml_io_tools:write:badAttribute', ... - 'Struct field named ATTRIBUTE encountered which was not a struct. Ignoring.'); - end - otherwise % set children of the node - struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node' - end - end % end for i=1:nFields - parent.appendChild(node); - end -%% == node is a leaf node == -else % if not a struct and not a cell than it is a leaf node - switch TagName % different processing depending on desired type of the node - case 'COMMENT' % create comment node - com = xml.createComment(s); - parent.appendChild(com); - case 'CDATA_SECTION' % create CDATA Section - cdt = xml.createCDATASection(s); - parent.appendChild(cdt); - case 'PROCESSING_INSTRUCTION' % set attributes of the node - OK = false; - if (ischar(s)) - n = strfind(s, ' '); - if (~isempty(n)) - proc = xml.createProcessingInstruction(s(1:(n(1)-1)),s((n(1)+1):end)); - parent.insertBefore(proc, parent.getFirstChild()); - OK = true; - end - end - if (~OK) - warning('xml_io_tools:write:badProcInst', ... - ['Struct field named PROCESSING_INSTRUCTION need to be',... - ' a string, for example: xml-stylesheet type="text/css" ', ... - 'href="myStyleSheet.css". Ignoring.']); - end - case 'CONTENT' % this is text part of already existing node - txt = xml.createTextNode(var2str(s, Pref.PreserveSpace)); % convert to text - parent.appendChild(txt); - otherwise % I guess it is a regular text leaf node - txt = xml.createTextNode(var2str(s, Pref.PreserveSpace)); - node = xml.createElement(TagName); - node.appendChild(txt); - parent.appendChild(node); - end -end % of struct2DOMnode function - -%% ======================================================================= -% === array2DOMnode Function ============================================ -% ======================================================================= -function [] = array2DOMnode(xml, parent, s, TagName, Pref) -% Deal with 1D and 2D arrays of cell or struct. Will modify 'parent'. -nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? -switch nDim - case 2 % 2D array - for r=1:size(s,1) - subnode = xml.createElement(Pref.TableName{1}); - for c=1:size(s,2) - v = s(r,c); - if iscell(v), v = v{1}; end - struct2DOMnode(xml, subnode, v, Pref.TableName{2}, Pref ); % recursive call - end - parent.appendChild(subnode); - end - case 1 %1D array - for iItem=1:numel(s) - v = s(iItem); - if iscell(v), v = v{1}; end - struct2DOMnode(xml, parent, v, TagName, Pref ); % recursive call - end - case 0 % scalar -> this case should never be called - if ~isempty(s) - if iscell(s), s = s{1}; end - struct2DOMnode(xml, parent, s, TagName, Pref ); - end -end - -%% ======================================================================= -% === var2str Function ================================================== -% ======================================================================= -function str = var2str(object, PreserveSpace) -% convert matlab variables to a string -switch (1) - case isempty(object) - str = ''; - case (isnumeric(object) || islogical(object)) - if ndims(object)>2, object=object(:); end % can't handle arrays with dimention > 2 - str=mat2str(object); % convert matrix to a string - % mark logical scalars with [] (logical arrays already have them) so the xml_read - % recognizes them as MATLAB objects instead of strings. Same with sparse - % matrices - if ((islogical(object) && isscalar(object)) || issparse(object)), - str = ['[' str ']']; - end - if (isinteger(object)), - str = ['[', class(object), '(', str ')]']; - end - case iscell(object) - if ndims(object)>2, object=object(:); end % can't handle cell arrays with dimention > 2 - [nr nc] = size(object); - obj2 = object; - for i=1:length(object(:)) - str = var2str(object{i}, PreserveSpace); - if (ischar(object{i})), object{i} = ['''' object{i} '''']; else object{i}=str; end - obj2{i} = [object{i} ',']; - end - for r = 1:nr, obj2{r,nc} = [object{r,nc} ';']; end - obj2 = obj2.'; - str = ['{' obj2{:} '}']; - case isstruct(object) - str=''; - warning('xml_io_tools:write:var2str', ... - 'Struct was encountered where string was expected. Ignoring.'); - case isa(object, 'function_handle') - str = ['[@' char(object) ']']; - case ischar(object) - str = object; - otherwise - str = char(object); -end - -%% string clean-up -str=str(:); str=str.'; % make sure this is a row vector of char's -if (~isempty(str)) - str(str<32|str==127)=' '; % convert no-printable characters to spaces - if (~PreserveSpace) - str = strtrim(str); % remove spaces from begining and the end - str = regexprep(str,'\s+',' '); % remove multiple spaces - end -end - -%% ======================================================================= -% === var2Namestr Function ============================================== -% ======================================================================= -function str = varName2str(str) -% convert matlab variable names to a sting -str = char(str); -p = strfind(str,'0x'); -if (~isempty(p)) - for i=1:length(p) - before = str( p(i)+(0:3) ); % string to replace - after = char(hex2dec(before(3:4))); % string to replace with - str = regexprep(str,before,after, 'once', 'ignorecase'); - p=p-3; % since 4 characters were replaced with one - compensate - end -end -str = regexprep(str,'_COLON_',':', 'once', 'ignorecase'); -str = regexprep(str,'_DASH_' ,'-', 'once', 'ignorecase'); - +function DOMnode = xml_write(filename, tree, RootName, Pref) +%XML_WRITE Writes Matlab data structures to XML file +% +% DESCRIPTION +% xml_write( filename, tree) Converts Matlab data structure 'tree' containing +% cells, structs, numbers and strings to Document Object Model (DOM) node +% tree, then saves it to XML file 'filename' using Matlab's xmlwrite +% function. Optionally one can also use alternative version of xmlwrite +% function which directly calls JAVA functions for XML writing without +% MATLAB middleware. This function is provided as a patch to existing +% bugs in xmlwrite (in R2006b). +% +% xml_write(filename, tree, RootName, Pref) allows you to specify +% additional preferences about file format +% +% DOMnode = xml_write([], tree) same as above except that DOM node is +% not saved to the file but returned. +% +% INPUT +% filename file name +% tree Matlab structure tree to store in xml file. +% RootName String with XML tag name used for root (top level) node +% Optionally it can be a string cell array storing: Name of +% root node, document "Processing Instructions" data and +% document "comment" string +% Pref Other preferences: +% Pref.ItemName - default 'item' - name of a special tag used to +% itemize cell or struct arrays +% Pref.XmlEngine - let you choose the XML engine. Currently default is +% 'Xerces', which is using directly the apache xerces java file. +% Other option is 'Matlab' which uses MATLAB's xmlwrite and its +% XMLUtils java file. Both options create identical results except in +% case of CDATA sections where xmlwrite fails. +% Pref.CellItem - default 'true' - allow cell arrays to use 'item' +% notation. See below. +% Pref.RootOnly - default true - output variable 'tree' corresponds to +% xml file root element, otherwise it correspond to the whole file. +% Pref.StructItem - default 'true' - allow arrays of structs to use +% 'item' notation. For example "Pref.StructItem = true" gives: +% +% +% ... <\item> +% ... <\item> +% <\b> +% <\a> +% while "Pref.StructItem = false" gives: +% +% ... <\b> +% ... <\b> +% <\a> +% +% +% Several special xml node types can be created if special tags are used +% for field names of 'tree' nodes: +% - node.CONTENT - stores data section of the node if other fields +% (usually ATTRIBUTE are present. Usually data section is stored +% directly in 'node'. +% - node.ATTRIBUTE.name - stores node's attribute called 'name'. +% - node.COMMENT - create comment child node from the string. For global +% comments see "RootName" input variable. +% - node.PROCESSING_INSTRUCTIONS - create "processing instruction" child +% node from the string. For global "processing instructions" see +% "RootName" input variable. +% - node.CDATA_SECTION - stores node's CDATA section (string). Only works +% if Pref.XmlEngine='Xerces'. For more info, see comments of F_xmlwrite. +% - other special node types like: document fragment nodes, document type +% nodes, entity nodes and notation nodes are not being handled by +% 'xml_write' at the moment. +% +% OUTPUT +% DOMnode Document Object Model (DOM) node tree in the format +% required as input to xmlwrite. (optional) +% +% EXAMPLES: +% MyTree=[]; +% MyTree.MyNumber = 13; +% MyTree.MyString = 'Hello World'; +% xml_write('test.xml', MyTree); +% type('test.xml') +% %See also xml_tutorial.m +% +% See also +% xml_read, xmlread, xmlwrite +% +% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com + +%% Check Matlab Version +v = ver('MATLAB'); +v = str2double(regexp(v.Version, '\d.\d','match','once')); +if (v<7) + error('Your MATLAB version is too old. You need version 7.0 or newer.'); +end + +%% default preferences +DPref.TableName = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays +DPref.ItemName = 'item'; % name of a special tag used to itemize 1D cell arrays +DPref.StructItem = true; % allow arrays of structs to use 'item' notation +DPref.CellItem = true; % allow cell arrays to use 'item' notation +DPref.StructTable= 'Html'; +DPref.CellTable = 'Html'; +DPref.XmlEngine = 'Matlab'; % use matlab provided XMLUtils +%DPref.XmlEngine = 'Xerces'; % use Xerces xml generator directly +DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? +RootOnly = true; % Input is root node only +GlobalProcInst = []; +GlobalComment = []; +GlobalDocType = []; + +%% read user preferences +if (nargin>3) + if (isfield(Pref, 'TableName' )), DPref.TableName = Pref.TableName; end + if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end + if (isfield(Pref, 'StructItem')), DPref.StructItem = Pref.StructItem; end + if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end + if (isfield(Pref, 'CellTable')), DPref.CellTable = Pref.CellTable; end + if (isfield(Pref, 'StructTable')), DPref.StructTable= Pref.StructTable; end + if (isfield(Pref, 'XmlEngine' )), DPref.XmlEngine = Pref.XmlEngine; end + if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end + if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end +end +if (nargin<3 || isempty(RootName)), RootName=inputname(2); end +if (isempty(RootName)), RootName='ROOT'; end +if (iscell(RootName)) % RootName also stores global text node data + rName = RootName; + RootName = char(rName{1}); + if (length(rName)>1), GlobalProcInst = char(rName{2}); end + if (length(rName)>2), GlobalComment = char(rName{3}); end + if (length(rName)>3), GlobalDocType = char(rName{4}); end +end +if(~RootOnly && isstruct(tree)) % if struct than deal with each field separatly + fields = fieldnames(tree); + for i=1:length(fields) + field = fields{i}; + x = tree(1).(field); + if (strcmp(field, 'COMMENT')) + GlobalComment = x; + elseif (strcmp(field, 'PROCESSING_INSTRUCTION')) + GlobalProcInst = x; + elseif (strcmp(field, 'DOCUMENT_TYPE')) + GlobalDocType = x; + else + RootName = field; + t = x; + end + end + tree = t; +end + +%% Initialize jave object that will store xml data structure +RootName = varName2str(RootName); +if (~isempty(GlobalDocType)) + % n = strfind(GlobalDocType, ' '); + % if (~isempty(n)) + % dtype = com.mathworks.xml.XMLUtils.createDocumentType(GlobalDocType); + % end + % DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName, dtype); + warning('xml_io_tools:write:docType', ... + 'DOCUMENT_TYPE node was encountered which is not supported yet. Ignoring.'); +end +DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName); + + +%% Use recursive function to convert matlab data structure to XML +root = DOMnode.getDocumentElement; +struct2DOMnode(DOMnode, root, tree, DPref.ItemName, DPref); + +%% Remove the only child of the root node +root = DOMnode.getDocumentElement; +Child = root.getChildNodes; % create array of children nodes +nChild = Child.getLength; % number of children +if (nChild==1) + node = root.removeChild(root.getFirstChild); + while(node.hasChildNodes) + root.appendChild(node.removeChild(node.getFirstChild)); + end + while(node.hasAttributes) % copy all attributes + root.setAttributeNode(node.removeAttributeNode(node.getAttributes.item(0))); + end +end + +%% Save exotic Global nodes +if (~isempty(GlobalComment)) + DOMnode.insertBefore(DOMnode.createComment(GlobalComment), DOMnode.getFirstChild()); +end +if (~isempty(GlobalProcInst)) + n = strfind(GlobalProcInst, ' '); + if (~isempty(n)) + proc = DOMnode.createProcessingInstruction(GlobalProcInst(1:(n(1)-1)),... + GlobalProcInst((n(1)+1):end)); + DOMnode.insertBefore(proc, DOMnode.getFirstChild()); + end +end +% Not supported yet as the code below does not work +% if (~isempty(GlobalDocType)) +% n = strfind(GlobalDocType, ' '); +% if (~isempty(n)) +% dtype = DOMnode.createDocumentType(GlobalDocType); +% DOMnode.insertBefore(dtype, DOMnode.getFirstChild()); +% end +% end + +%% save java DOM tree to XML file +if (~isempty(filename)) + if (strcmpi(DPref.XmlEngine, 'Xerces')) + xmlwrite_xerces(filename, DOMnode); + else + xmlwrite(filename, DOMnode); + end +end + + +%% ======================================================================= +% === struct2DOMnode Function =========================================== +% ======================================================================= +function [] = struct2DOMnode(xml, parent, s, TagName, Pref) +% struct2DOMnode is a recursive function that converts matlab's structs to +% DOM nodes. +% INPUTS: +% xml - jave object that will store xml data structure +% parent - parent DOM Element +% s - Matlab data structure to save +% TagName - name to be used in xml tags describing 's' +% Pref - preferenced +% OUTPUT: +% parent - modified 'parent' + +% perform some conversions +if (ischar(s) && min(size(s))>1) % if 2D array of characters + s=cellstr(s); % than convert to cell array +end +% if (strcmp(TagName, 'CONTENT')) +% while (iscell(s) && length(s)==1), s = s{1}; end % unwrap cell arrays of length 1 +% end +TagName = varName2str(TagName); + +%% == node is a 2D cell array == +% convert to some other format prior to further processing +nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? +if (iscell(s) && nDim==2 && strcmpi(Pref.CellTable, 'Matlab')) + s = var2str(s, Pref.PreserveSpace); +end +if (nDim==2 && (iscell (s) && strcmpi(Pref.CellTable, 'Vector')) || ... + (isstruct(s) && strcmpi(Pref.StructTable, 'Vector'))) + s = s(:); +end +if (nDim>2), s = s(:); end % can not handle this case well +nItem = numel(s); +nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? + +%% == node is a cell == +if (iscell(s)) % if this is a cell or cell array + if ((nDim==2 && strcmpi(Pref.CellTable,'Html')) || (nDim< 2 && Pref.CellItem)) + % if 2D array of cells than can use HTML-like notation or if 1D array + % than can use item notation + if (strcmp(TagName, 'CONTENT')) % CONTENT nodes already have ... + array2DOMnode(xml, parent, s, Pref.ItemName, Pref ); % recursive call + else + node = xml.createElement(TagName); % ... + array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call + parent.appendChild(node); + end + else % use ...<\TagName> ...<\TagName> notation + array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call + end +%% == node is a struct == +elseif (isstruct(s)) % if struct than deal with each field separatly + if ((nDim==2 && strcmpi(Pref.StructTable,'Html')) || (nItem>1 && Pref.StructItem)) + % if 2D array of structs than can use HTML-like notation or + % if 1D array of structs than can use 'items' notation + node = xml.createElement(TagName); + array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call + parent.appendChild(node); + elseif (nItem>1) % use ...<\TagName> ...<\TagName> notation + array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call + else % otherwise save each struct separatelly + fields = fieldnames(s); + node = xml.createElement(TagName); + for i=1:length(fields) % add field by field to the node + field = fields{i}; + x = s.(field); + switch field + case {'COMMENT', 'CDATA_SECTION', 'PROCESSING_INSTRUCTION'} + if iscellstr(x) % cell array of strings -> add them one by one + array2DOMnode(xml, node, x(:), field, Pref ); % recursive call will modify 'node' + elseif ischar(x) % single string -> add it + struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node' + else % not a string - Ignore + warning('xml_io_tools:write:badSpecialNode', ... + ['Struct field named ',field,' encountered which was not a string. Ignoring.']); + end + case 'ATTRIBUTE' % set attributes of the node + if (isempty(x)), continue; end + if (isstruct(x)) + attName = fieldnames(x); % get names of all the attributes + for k=1:length(attName) % attach them to the node + att = xml.createAttribute(varName2str(attName(k))); + att.setValue(var2str(x.(attName{k}),Pref.PreserveSpace)); + node.setAttributeNode(att); + end + else + warning('xml_io_tools:write:badAttribute', ... + 'Struct field named ATTRIBUTE encountered which was not a struct. Ignoring.'); + end + otherwise % set children of the node + struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node' + end + end % end for i=1:nFields + parent.appendChild(node); + end +%% == node is a leaf node == +else % if not a struct and not a cell than it is a leaf node + switch TagName % different processing depending on desired type of the node + case 'COMMENT' % create comment node + com = xml.createComment(s); + parent.appendChild(com); + case 'CDATA_SECTION' % create CDATA Section + cdt = xml.createCDATASection(s); + parent.appendChild(cdt); + case 'PROCESSING_INSTRUCTION' % set attributes of the node + OK = false; + if (ischar(s)) + n = strfind(s, ' '); + if (~isempty(n)) + proc = xml.createProcessingInstruction(s(1:(n(1)-1)),s((n(1)+1):end)); + parent.insertBefore(proc, parent.getFirstChild()); + OK = true; + end + end + if (~OK) + warning('xml_io_tools:write:badProcInst', ... + ['Struct field named PROCESSING_INSTRUCTION need to be',... + ' a string, for example: xml-stylesheet type="text/css" ', ... + 'href="myStyleSheet.css". Ignoring.']); + end + case 'CONTENT' % this is text part of already existing node + txt = xml.createTextNode(var2str(s, Pref.PreserveSpace)); % convert to text + parent.appendChild(txt); + otherwise % I guess it is a regular text leaf node + txt = xml.createTextNode(var2str(s, Pref.PreserveSpace)); + node = xml.createElement(TagName); + node.appendChild(txt); + parent.appendChild(node); + end +end % of struct2DOMnode function + +%% ======================================================================= +% === array2DOMnode Function ============================================ +% ======================================================================= +function [] = array2DOMnode(xml, parent, s, TagName, Pref) +% Deal with 1D and 2D arrays of cell or struct. Will modify 'parent'. +nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? +switch nDim + case 2 % 2D array + for r=1:size(s,1) + subnode = xml.createElement(Pref.TableName{1}); + for c=1:size(s,2) + v = s(r,c); + if iscell(v), v = v{1}; end + struct2DOMnode(xml, subnode, v, Pref.TableName{2}, Pref ); % recursive call + end + parent.appendChild(subnode); + end + case 1 %1D array + for iItem=1:numel(s) + v = s(iItem); + if iscell(v), v = v{1}; end + struct2DOMnode(xml, parent, v, TagName, Pref ); % recursive call + end + case 0 % scalar -> this case should never be called + if ~isempty(s) + if iscell(s), s = s{1}; end + struct2DOMnode(xml, parent, s, TagName, Pref ); + end +end + +%% ======================================================================= +% === var2str Function ================================================== +% ======================================================================= +function str = var2str(object, PreserveSpace) +% convert matlab variables to a string +switch (1) + case isempty(object) + str = ''; + case (isnumeric(object) || islogical(object)) + if ndims(object)>2, object=object(:); end % can't handle arrays with dimention > 2 + str=mat2str(object); % convert matrix to a string + % mark logical scalars with [] (logical arrays already have them) so the xml_read + % recognizes them as MATLAB objects instead of strings. Same with sparse + % matrices + if ((islogical(object) && isscalar(object)) || issparse(object)), + str = ['[' str ']']; + end + if (isinteger(object)), + str = ['[', class(object), '(', str ')]']; + end + case iscell(object) + if ndims(object)>2, object=object(:); end % can't handle cell arrays with dimention > 2 + [nr nc] = size(object); + obj2 = object; + for i=1:length(object(:)) + str = var2str(object{i}, PreserveSpace); + if (ischar(object{i})), object{i} = ['''' object{i} '''']; else object{i}=str; end + obj2{i} = [object{i} ',']; + end + for r = 1:nr, obj2{r,nc} = [object{r,nc} ';']; end + obj2 = obj2.'; + str = ['{' obj2{:} '}']; + case isstruct(object) + str=''; + warning('xml_io_tools:write:var2str', ... + 'Struct was encountered where string was expected. Ignoring.'); + case isa(object, 'function_handle') + str = ['[@' char(object) ']']; + case ischar(object) + str = object; + otherwise + str = char(object); +end + +%% string clean-up +str=str(:); str=str.'; % make sure this is a row vector of char's +if (~isempty(str)) + str(str<32|str==127)=' '; % convert no-printable characters to spaces + if (~PreserveSpace) + str = strtrim(str); % remove spaces from begining and the end + str = regexprep(str,'\s+',' '); % remove multiple spaces + end +end + +%% ======================================================================= +% === var2Namestr Function ============================================== +% ======================================================================= +function str = varName2str(str) +% convert matlab variable names to a sting +str = char(str); +p = strfind(str,'0x'); +if (~isempty(p)) + for i=1:length(p) + before = str( p(i)+(0:3) ); % string to replace + after = char(hex2dec(before(3:4))); % string to replace with + str = regexprep(str,before,after, 'once', 'ignorecase'); + p=p-3; % since 4 characters were replaced with one - compensate + end +end +str = regexprep(str,'_COLON_',':', 'once', 'ignorecase'); +str = regexprep(str,'_DASH_' ,'-', 'once', 'ignorecase'); + diff --git a/private/xml_io_tools/xmlwrite_xerces.m b/private/xmlwrite_xerces.m similarity index 97% rename from private/xml_io_tools/xmlwrite_xerces.m rename to private/xmlwrite_xerces.m index 3625c73..56dd30f 100644 --- a/private/xml_io_tools/xmlwrite_xerces.m +++ b/private/xmlwrite_xerces.m @@ -1,109 +1,109 @@ -function varargout=xmlwrite_xerces(varargin) -%XMLWRITE_XERCES Serialize an XML Document Object Model node using Xerces parser. -% xmlwrite_xerces(FILENAME,DOMNODE) serializes the DOMNODE to file FILENAME. -% -% The function xmlwrite_xerces is very similar the Matlab function xmlwrite -% but works directly with the XERCES java classes (written by Apache XML -% Project) instead of the XMLUtils class created by Mathworks. Xerces files -% are provided in standard MATLAB instalation and live in root\java\jarext -% directory. -% -% Written by A.Amaro (02-22-2007) and generously donated to xml_io_tools. -% This function is needed as a work-around for a bug in XMLUtils library -% which can not write CDATA SECTION nodes correctly. Also Xerces and -% XMLUtils libraries handle namespaces differently. -% -% Examples: -% % See xmlwrite examples this function have almost identical behavior. -% -% Advanced use: -% FILENAME can also be a URN, java.io.OutputStream or java.io.Writer object -% SOURCE can also be a SAX InputSource, JAXP Source, InputStream, or -% Reader object - -returnString = false; -if length(varargin)==1 - returnString = true; - result = java.io.StringWriter; - source = varargin{1}; -else - result = varargin{1}; - if ischar(result) - % Using the XERCES classes directly, is not needed to modify the - % filename string. So I have commented this next line - % result = F_xmlstringinput(result,false); - end - - source = varargin{2}; - if ischar(source) - source = F_xmlstringinput(source,true); - end -end - -% SERIALIZATION OF THE DOM DOCUMENT USING XERCES CLASSES DIRECTLY - -% 1) create the output format according to the document definitions -% and type -objOutputFormat = org.apache.xml.serialize.OutputFormat(source); -set(objOutputFormat,'Indenting','on'); - -% 2) create the output stream. In this case: an XML file -objFile = java.io.File(result); -objOutputStream = java.io.FileOutputStream(objFile); - -% 3) Create the Xerces Serializer object -objSerializer= org.apache.xml.serialize.XMLSerializer(objOutputStream,objOutputFormat); - -% 4) Serialize to the XML files -javaMethod('serialize',objSerializer,source); - -% 5) IMPORTANT! Delete the objects to liberate the XML file created -objOutputStream.close; - -if returnString - varargout{1}=char(result.toString); -end - -%% ======================================================================== - function out = F_xmlstringinput(xString,isFullSearch,varargin) -% The function F_xmlstringinput is a copy of the private function: -% 'xmlstringinput' that the original xmlwrite function uses. - -if isempty(xString) - error('Filename is empty'); -elseif ~isempty(findstr(xString,'://')) - %xString is already a URL, most likely prefaced by file:// or http:// - out = xString; - return; -end - -xPath=fileparts(xString); -if isempty(xPath) - if nargin<2 || isFullSearch - out = which(xString); - if isempty(out) - error('xml:FileNotFound','File %s not found',xString); - end - else - out = fullfile(pwd,xString); - end -else - out = xString; - if (nargin<2 || isFullSearch) && ~exist(xString,'file') - %search to see if xString exists when isFullSearch - error('xml:FileNotFound','File %s not found',xString); - end -end - -%Return as a URN -if strncmp(out,'\\',2) - % SAXON UNC filepaths need to look like file:///\\\server-name\ - out = ['file:///\',out]; -elseif strncmp(out,'/',1) - % SAXON UNIX filepaths need to look like file:///root/dir/dir - out = ['file://',out]; -else - % DOS filepaths need to look like file:///d:/foo/bar - out = ['file:///',strrep(out,'\','/')]; -end - +function varargout=xmlwrite_xerces(varargin) +%XMLWRITE_XERCES Serialize an XML Document Object Model node using Xerces parser. +% xmlwrite_xerces(FILENAME,DOMNODE) serializes the DOMNODE to file FILENAME. +% +% The function xmlwrite_xerces is very similar the Matlab function xmlwrite +% but works directly with the XERCES java classes (written by Apache XML +% Project) instead of the XMLUtils class created by Mathworks. Xerces files +% are provided in standard MATLAB instalation and live in root\java\jarext +% directory. +% +% Written by A.Amaro (02-22-2007) and generously donated to xml_io_tools. +% This function is needed as a work-around for a bug in XMLUtils library +% which can not write CDATA SECTION nodes correctly. Also Xerces and +% XMLUtils libraries handle namespaces differently. +% +% Examples: +% % See xmlwrite examples this function have almost identical behavior. +% +% Advanced use: +% FILENAME can also be a URN, java.io.OutputStream or java.io.Writer object +% SOURCE can also be a SAX InputSource, JAXP Source, InputStream, or +% Reader object + +returnString = false; +if length(varargin)==1 + returnString = true; + result = java.io.StringWriter; + source = varargin{1}; +else + result = varargin{1}; + if ischar(result) + % Using the XERCES classes directly, is not needed to modify the + % filename string. So I have commented this next line + % result = F_xmlstringinput(result,false); + end + + source = varargin{2}; + if ischar(source) + source = F_xmlstringinput(source,true); + end +end + +% SERIALIZATION OF THE DOM DOCUMENT USING XERCES CLASSES DIRECTLY + +% 1) create the output format according to the document definitions +% and type +objOutputFormat = org.apache.xml.serialize.OutputFormat(source); +set(objOutputFormat,'Indenting','on'); + +% 2) create the output stream. In this case: an XML file +objFile = java.io.File(result); +objOutputStream = java.io.FileOutputStream(objFile); + +% 3) Create the Xerces Serializer object +objSerializer= org.apache.xml.serialize.XMLSerializer(objOutputStream,objOutputFormat); + +% 4) Serialize to the XML files +javaMethod('serialize',objSerializer,source); + +% 5) IMPORTANT! Delete the objects to liberate the XML file created +objOutputStream.close; + +if returnString + varargout{1}=char(result.toString); +end + +%% ======================================================================== + function out = F_xmlstringinput(xString,isFullSearch,varargin) +% The function F_xmlstringinput is a copy of the private function: +% 'xmlstringinput' that the original xmlwrite function uses. + +if isempty(xString) + error('Filename is empty'); +elseif ~isempty(findstr(xString,'://')) + %xString is already a URL, most likely prefaced by file:// or http:// + out = xString; + return; +end + +xPath=fileparts(xString); +if isempty(xPath) + if nargin<2 || isFullSearch + out = which(xString); + if isempty(out) + error('xml:FileNotFound','File %s not found',xString); + end + else + out = fullfile(pwd,xString); + end +else + out = xString; + if (nargin<2 || isFullSearch) && ~exist(xString,'file') + %search to see if xString exists when isFullSearch + error('xml:FileNotFound','File %s not found',xString); + end +end + +%Return as a URN +if strncmp(out,'\\',2) + % SAXON UNC filepaths need to look like file:///\\\server-name\ + out = ['file:///\',out]; +elseif strncmp(out,'/',1) + % SAXON UNIX filepaths need to look like file:///root/dir/dir + out = ['file://',out]; +else + % DOS filepaths need to look like file:///d:/foo/bar + out = ['file:///',strrep(out,'\','/')]; +end +