diff --git a/doc/ModelComparisonUtility_UserGuide.pdf b/doc/ModelComparisonUtility_UserGuide.pdf index a8394a3..9f7d7e4 100644 Binary files a/doc/ModelComparisonUtility_UserGuide.pdf and b/doc/ModelComparisonUtility_UserGuide.pdf differ diff --git a/doc/tex/ModelComparisonUtility_UserGuide.tex b/doc/tex/ModelComparisonUtility_UserGuide.tex index a4f17dd..7b737fe 100644 --- a/doc/tex/ModelComparisonUtility_UserGuide.tex +++ b/doc/tex/ModelComparisonUtility_UserGuide.tex @@ -482,9 +482,6 @@ \subsection{Highlighting Nodes in the Model} >> highlightNodes(added, mdl2); \end{lstlisting} -\section{Limitations} -Support for Stateflow comparison will be added in a future release of this tool. - \begin{thebibliography}{9} \bibitem{CompareXML} The MathWorks. diff --git a/src/find_node.m b/src/find_node.m index 15e0c85..2f14b4b 100755 --- a/src/find_node.m +++ b/src/find_node.m @@ -18,9 +18,10 @@ % pairs (not case-sensitive, except for NodeName). The following describes % the available constraint/value pairs: % -% NodeType ['block' | 'line' | 'port' | 'annotation' | 'mask' | 'block_diagram' | ...] +% NodeType ['block' | 'line' | 'port' | 'annotation' | 'mask' | 'block_diagram' | 'stateflow' | ...] % ChangeType ['added' | 'deleted' | 'modified' | 'renamed' | 'none'] % BlockType ['SubSystem' | 'Inport' | 'Outport' | ...] +% StateflowType ['Stateflow.Annotation' | 'Sateflow.Transition' | 'Sateflow.State' | ...] % NodeName % FirstOnly [('off'), 'on'] For changes with 2 nodes (e.g., none, modified), % returns the first 'before' node. This is @@ -107,6 +108,7 @@ nodeType = lower(getInput('NodeType', varargin)); changeType = lower(getInput('ChangeType', varargin)); blockType = lower(getInput('BlockType', varargin)); + stateflowType = lower(getInput('StateflowType', varargin)); nameValue = getInput('NodeName', varargin); % Check against varargin constraints @@ -148,6 +150,18 @@ end end + if ~isempty(stateflowType) && meetsConstraints + if iscell(stateflowType) + isStateflowType = ismember(stateflowType, lower(getStateflowType(node))); + else + isStateflowType = strcmpi(stateflowType, getStateflowType(node)); + end + + if ~any(isStateflowType) + meetsConstraints = false; + end + end + if ~isempty(nameValue) && meetsConstraints if iscell(nameValue) isMatchedName = ismember(nameValue, node.Name); diff --git a/src/getHandle.m b/src/getHandle.m index f908743..f667f7a 100755 --- a/src/getHandle.m +++ b/src/getHandle.m @@ -207,6 +207,13 @@ hdl = ports(i); return end - end + end + elseif strcmp(type, 'stateflow') + p = getPath(node, sys); + if ~isempty(p) + hdl = get_param(p, 'Handle'); + else + hdl = []; + end end end \ No newline at end of file diff --git a/src/getPath.m b/src/getPath.m index 35bf396..e804655 100755 --- a/src/getPath.m +++ b/src/getPath.m @@ -1,7 +1,7 @@ function path = getPath(node, sys) % GETPATH Get the path of a node in a model. Note: Not all elements in a -% model have a Path parameter (e.g. lines, annotations). If an empty cell -% array is returned, no valid path has been found. +% model have a Path parameter (e.g. lines, annotations, Sateflow transitions). +% If an empty cell array is returned, no valid path has been found. % % Inputs: % node xmlcomp.Node object, representing a block. @@ -24,6 +24,9 @@ end sys = char(sys); + if isStateflow(node) + node = getStateflowParent(node); + end path = assemblePath(sys, node); % Address R2016b bug diff --git a/src/getStateflowObj.m b/src/getStateflowObj.m new file mode 100644 index 0000000..ef5bd42 --- /dev/null +++ b/src/getStateflowObj.m @@ -0,0 +1,90 @@ +function obj = getStateflowObj(node) +% GETSTATEFLOWOBJ Get the model element corresponding to the Stateflow +% node. Stateflow objects don't have handles like Simulink, therefore this +% function returns the object itself. Note: The model needs to be open. +% +% Inputs: +% node xmlcomp.Node object. +% +% Outputs: +% obj Stateflow object. + + obj = ''; + chart_node = getStateflowParent(node); + chart = find(sfroot, '-isa', 'Stateflow.Chart', 'Name', chart_node.Name); + + for i = 1:length(chart) % Multiple charts with the same name can be found, so search all + + if ~isempty(node.Parameters) + %% Get type from model by following the SSID, name, or label string + paramNames = {node.Parameters.Name}; + paramVals = {node.Parameters.Value}; + idx_id = find(strcmpi(paramNames, 'ID')); + idx_ssid = find(strcmpi(paramNames, 'SSID')); + idx_lbl = find(strcmpi(paramNames, 'labelString')); + idx_name = find(strcmpi(paramNames, 'Name')); + idx_text = find(strcmp(paramNames, 'Text')); + idx_port = find(strcmp(paramNames, 'Port')); + idx_block = find(strcmp(paramNames, 'BlockType')); + + if numel(idx_name) > 1 % If both 'name' and 'Name' found, use 'Name' + idx_name = find(strcmp(paramNames, 'Name')); + end + + obj = ''; + if node == chart_node + % Node is the chart itself + obj = chart(i); + elseif ~isempty(idx_id) + % Try to find and object with the same SSID + obj = find(chart(i), 'ssid', str2num(paramVals{idx_id})); + elseif ~isempty(idx_ssid) + obj = find(chart(i), 'ssid', str2num(paramVals{idx_ssid})); + elseif ~isempty(idx_lbl) + obj = find(chart(i), 'LabelString', paramVals{idx_lbl}); + elseif ~isempty(idx_name) && (isempty(idx_port) && isempty(idx_block)) + obj = find(chart(i), '-isa', 'Stateflow.Data', 'Name', paramVals{idx_name}); + elseif ~isempty(idx_name) && ~isempty(idx_port) + obj = find(chart(i), '-isa', 'Stateflow.Data', 'Name', paramVals{idx_name}); + elseif ~isempty(idx_name) + name = paramVals{idx_name}; + obj = find(chart(i), 'Name', name); + if isempty(obj) + % Inports/outports may have () appended + name_stripped = regexprep(name, '\(.*?\)', ''); + obj = find(chart(i), 'Name', name_stripped); + end + if isempty(obj) + % May be a function + obj = find(chart(i), 'LabelString', name); + end + elseif ~isempty(idx_text) + % Check if annotation by comparing text + objs = find(chart(i), '-isa', 'Stateflow.Annotation'); + for j = 1:length(objs) + if contains(objs(j).Text, paramVals{idx_text}) + obj = objs(j); + break; + end + end + end + end + + if isempty(obj) + %% Get object from the node name only + if isempty(node.Name) + % Check if name matches name + obj = find(chart(i), '-not', '-isa', 'Stateflow.Chart', 'Name', node.Name); + + % Check if label matches name + if isempty(obj) + obj = find(chart(i), 'LabelString', node.Name); + end + end + end + + if ~isempty(obj) + break; + end + end +end \ No newline at end of file diff --git a/src/getStateflowParent.m b/src/getStateflowParent.m new file mode 100644 index 0000000..809e071 --- /dev/null +++ b/src/getStateflowParent.m @@ -0,0 +1,25 @@ +function parent = getStateflowParent(node) +% GETSTATEFLOWPARENT Get the parent node of the Stateflow object node. +% +% Inputs: +% node xmlcomp.Node object. +% +% Outputs: +% parent Parent node. + + parent = node; + while hasParent(parent) + if ~isempty(parent.Parameters) + paramNames = {parent.Parameters.Name}; + paramVals = {parent.Parameters.Value}; + idx = find(contains(paramNames, 'SFBlockType')); + if ~isempty(idx) && contains(paramVals{idx}, {'Chart', 'State Transition Table', 'Truth Table'}) + break + else + parent = parent.Parent; + end + else + parent = parent.Parent; + end + end +end \ No newline at end of file diff --git a/src/highlight/highlightNodes.m b/src/highlight/highlightNodes.m index 4d6c063..941278f 100755 --- a/src/highlight/highlightNodes.m +++ b/src/highlight/highlightNodes.m @@ -64,20 +64,26 @@ function highlightNodes(nodes, sys, varargin) bgColor = 'yellow'; end - % If given Nodes instead of handles, get the handles + % If given Nodes instead of handles, get the handles and/or Stateflow + % objects if isa(nodes, 'xmlcomp.Node') hdls = zeros(1, length(nodes)); + sfObj = []; for i = 1:length(nodes) try hdls(i) = getHandle(nodes(i), sys); catch hdls(i) = NaN; end + + if isStateflow(nodes(i)) + sfObj = vertcat(sfObj, getStateflowObj(nodes(i))); + end end nodes = hdls(isfinite(hdls(:))); % Remove invalid hdls end - % Highlight + % %Highlight Simulink if method colorRegular(nodes, fgColor, bgColor); else @@ -87,6 +93,14 @@ function highlightNodes(nodes, sys, varargin) 'BackgroundColor', bgColor)); hilite_system_notopen(nodes, 'user2'); end + + %% Highlight Stateflow + % Limitation: Only one Stateflow object can be highlighted at once. + % This will attempt to highlight all successively, but only the last + % one will appear. + for i = 1:numel(sfObj) + highlight(sfObj(i)); + end end function colorRegular(hdls, fg, bg) diff --git a/src/plot/editsToDigraph.m b/src/plot/editsToDigraph.m index 8a90998..2f58f8f 100644 --- a/src/plot/editsToDigraph.m +++ b/src/plot/editsToDigraph.m @@ -21,7 +21,7 @@ D.Nodes.Label = nodes; end -function [source, target, nodes] = createSourceTarget(root) +function [source, target, nodes] = createSourceTarget(root, varargin) % CREATESOURCETARGET Get the directed graph edges as (source, target) pairs for % a comparison tree. % (See www.mathworks.com/help/matlab/ref/digraph.html#mw_26035adf-ff90-4a33-a8f8-42048d7e39a6) @@ -34,17 +34,23 @@ % target Cell array of target nodes. % nodes Cell array of node labels. + % Handle input + omitParam = lower(getInput('OmitParam', varargin, 'on')); + + % Initialize source = {}; target = {}; nodes = {}; % Add the root node - source{end+1} = 'Edits'; + nodes{end+1} = 'Edits'; + source{end+1} = 'Edits'; target{end+1} = 'Comparison Root (before)'; - target{end+1} = 'Comparison Root (after)'; - nodes{end+1} = 'Edits'; nodes{end+1} = 'Comparison Root (before}'; + + source{end+1} = 'Edits'; + target{end+1} = 'Comparison Root (after)'; nodes{end+1} = 'Comparison Root (after}'; % Before @@ -60,9 +66,12 @@ suffix = ' (before)'; for k = 1:length(children) - source{end+1} = [parent suffix]; - target{end+1} = [getPathTree(children(k)) suffix]; - nodes{end+1} = char(children(k).Name); + if strcmp(omitParam, 'off') || ... + (strcmp(omitParam, 'on') && (~strcmp(getNodeType(children(k)), 'unknown') || children(k).Edited == 1)) + source{end+1} = [parent suffix]; + target{end+1} = [getPathTree(children(k)) suffix]; + nodes{end+1} = char(children(k).Name); + end end end end @@ -79,9 +88,12 @@ suffix = ' (after)'; for k = 1:length(children) - source{end+1} = [parent suffix]; - target{end+1} = [getPathTree(children(k)) suffix]; - nodes{end+1} = char(children(k).Name); + if strcmp(omitParam, 'off') || ... + (strcmp(omitParam, 'on') && (~strcmp(getNodeType(children(k)), 'unknown') || children(k).Edited == 1)) + source{end+1} = [parent suffix]; + target{end+1} = [getPathTree(children(k)) suffix]; + nodes{end+1} = char(children(k).Name); + end end end end diff --git a/src/type/isStateflow.m b/src/type/isStateflow.m new file mode 100644 index 0000000..451f287 --- /dev/null +++ b/src/type/isStateflow.m @@ -0,0 +1,29 @@ +function out = isStateflow(node) +% ISSTATEFLOW Determine if the node represents a Stateflow object. +% +% Inputs: +% node xmlcomp.Node object. +% +% Outputs: +% out Whether the node represents an Stateflow object(1) or not(0). + + out = false; + + % Inspect all parents of the node to see if it is contained in a Stateflow + % Chart or Stateflow table. + while hasParent(node) + if ~isempty(node.Parameters) + paramNames = {node.Parameters.Name}; + paramVals = {node.Parameters.Value}; + idx = find(contains(paramNames, 'SFBlockType')); + if ~isempty(idx) && contains(paramVals{idx}, {'Chart', 'State Transition Table', 'Truth Table'}) + out = true; + break + else + node = node.Parent; + end + else + node = node.Parent; + end + end +end \ No newline at end of file diff --git a/src/type/nodetype/getNodeType.m b/src/type/nodetype/getNodeType.m index d216b25..9fb5317 100755 --- a/src/type/nodetype/getNodeType.m +++ b/src/type/nodetype/getNodeType.m @@ -1,6 +1,6 @@ function type = getNodeType(node, varargin) % GETNODETYPE Determine the type of element that the node represents in the -% model: block, block_diagram, line, port, mask, or annotation. +% model: block, block_diagram, line, port, mask, annotation, or stateflow. % % In general, only elements which are Edited can have their type inferred % directly from the tree. For those that cannot be inferred, if sys is @@ -44,7 +44,11 @@ % First try to see if we can check the type without looking at the model. % If that does not work, check the model - if ~isempty(node.Parameters) && any(strcmp({node.Parameters.Name}, 'BlockType')) + if isStateflow(node) + type = 'stateflow'; + elseif strcmp(node.Name, 'Comparison Root') && isempty(node.Parent) + type = 'unknown'; % Tree artifact + elseif ~isempty(node.Parameters) && any(strcmp({node.Parameters.Name}, 'BlockType')) type = 'block'; elseif isBlockDiagram(node) type = 'block_diagram';