许可优化
许可优化
产品
产品
解决方案
解决方案
服务支持
服务支持
关于
关于
软件库
当前位置:服务支持 >  软件文章 >  inputParser MATLAB兼容项目经验总结

inputParser MATLAB兼容项目经验总结

阅读数 3
点赞 0
article_banner


目标:将北太天元 inputParser 的行为完全对齐 MATLAB R2021b 文档语义

最终结果:95/95 有效用例 100% 通过,10 项缺陷全部修复,端到端验收通过,具备上线条件

代码块

PlainText


classdef inputParser < handle
%   inputParser 函数的输入解析器
%   
%   属性:
%   CaseSensitive 是否区分大小写,逻辑值
%   KeepUnmatched 是否保留未匹配的参数,逻辑值
%   StructExpand 是否展开结构体,逻辑值
%   Parameters 参数列表,字符向量元胞数组
%   Results 结果,结构体
%   Unmatched 未匹配的参数,结构体
%   UsingDefaults 使用默认值的参数列表,字符向量元胞数组
%   
%   函数:
%   addParameter 添加可选参数
%   addOptional 添加可选的位置参数
%   addRequired 添加必须的位置参数
%   parse 解析输入参数
%   
%   使用方法(handle 类,语句式与链式两种写法均可):
%   obj = inputParser;
% 
%   obj.addParameter(paramName, defaultVal, validationFcn);或
%   obj = obj.addParameter(paramName, defaultVal, validationFcn);
% 
%   obj.addOptional(paramName, defaultVal, validationFcn);或
%   obj = obj.addOptional(paramName, defaultVal, validationFcn);
% 
%   obj.addRequired(paramName, validationFcn);或
%   obj = obj.addRequired(paramName, validationFcn);
% 
%   obj.parse(varargin);或 obj = obj.parse(varargin); 其中 varargin 需要分开输
%   入到parse,不支持作为一个cell传入。
%   
%   输出:
%   obj.Results.paramName

    properties
        CaseSensitive = false
        KeepUnmatched = false
        StructExpand = true
        FunctionName = '';
        PartialMatching = true;
    end

    properties (SetAccess=private,GetAccess=public)
        Parameters = {};
        Results = struct();
        Unmatched = struct();
        UsingDefaults = {};
    end

    properties(Hidden)
        RequiredList = {};      % { {name, validator} }
        OptionalList = {};      % { {name, default, validator} }
        ParameterList = {};     % { {name, default, validator} }
    end

    methods
        function obj = set.CaseSensitive(obj, val)
            if ~isscalar(val) || ~islogical(val)
                error(message('CaseSensitive 必须是逻辑值标量。'));
            end
            obj.CaseSensitive = val;
        end

        function obj = set.KeepUnmatched(obj, val)
            if ~isscalar(val) || ~islogical(val)
                error(message('KeepUnmatched 必须是逻辑值标量。'));
            end
            obj.KeepUnmatched = val;
        end

        function obj = set.StructExpand(obj, val)
            if ~isscalar(val) || ~islogical(val)
                error(message('StructExpand 必须是逻辑值标量。'));
            end
            obj.StructExpand = val;
        end

        function obj = set.PartialMatching(obj, val)
            if ~isscalar(val) || ~islogical(val)
                error(message('PartialMatching 必须是逻辑值标量。'));
            end
            obj.PartialMatching = val;
        end

        function obj = set.FunctionName(obj, val)
            if ~ischar(val) && ~(isstring(val) && isscalar(val))
                error(message('FunctionName 必须是字符向量或字符串标量。'));
            end
            obj.FunctionName = char(val);
        end

        function obj = addParameter(obj, varargin)
            % 支持签名:
            %   addParameter(p, name, defaultVal)
            %   addParameter(p, name, defaultVal, validationFcn)
            %   addParameter(p, name, defaultVal, 'PartialMatchPriority', N)
            %   addParameter(p, name, defaultVal, validationFcn, 'PartialMatchPriority', N)
            narginchk(3, 6)

            paramName = varargin{1};
            defaultVal = varargin{2};
            validationFcn = [];
            priority = 1;   % PartialMatchPriority 默认值

            k = 3;
            if numel(varargin) >= 3
                a = varargin{3};
                if isempty(a) || isa(a, 'function_handle')
                    validationFcn = a;
                    k = 4;
                elseif ~ischar(a) && ~(isstring(a) && isscalar(a))
                    % 第3参既非验证器也非选项名起始
                    error(message('validator 必须是函数句柄类型。'));
                end
            end

            % 解析 Name-Value 选项(当前仅 PartialMatchPriority)
            rest = varargin(k:end);
            if mod(numel(rest), 2) ~= 0
                error(message('addParameter 的选项参数必须成对出现。'));
            end
            for i = 1:2:numel(rest)
                optName = rest{i};
                if ischar(optName) || (isstring(optName) && isscalar(optName))
                    if strcmp(char(optName), 'PartialMatchPriority')
                        priority = rest{i+1};
                        if ~isscalar(priority) || ~isnumeric(priority) || priority < 1 || floor(priority) ~= priority
                            error(message('PartialMatchPriority 必须是正整数标量。'));
                        end
                    else
                        error(message('无法识别的选项: %s。', char(optName)));
                    end
                else
                    error(message('选项名必须是字符向量或字符串标量。'));
                end
            end

            paramName = checkArgs(obj.Parameters, paramName, validationFcn);
            obj.ParameterList{end+1} = {paramName, defaultVal, validationFcn, priority};
            obj.Parameters{end+1} = paramName;
        end

        function obj = addParamValue(obj, varargin)
            % addParameter 的历史别名(MATLAB 标记为 Not recommended)
            obj = obj.addParameter(varargin{:});
        end

        function obj = addOptional(obj, paramName, defaultVal, validationFcn)
            narginchk(3, 4)

            if nargin < 4
                validationFcn = [];
            end
            paramName = checkArgs(obj.Parameters, paramName, validationFcn);
            obj.OptionalList{end+1} = {paramName, defaultVal, validationFcn};
            obj.Parameters{end+1} = paramName;
        end

        function obj = addRequired(obj, paramName, validationFcn)
            narginchk(2, 3)

            if nargin < 3
                validationFcn = [];
            end
            paramName = checkArgs(obj.Parameters, paramName, validationFcn);
            obj.RequiredList{end+1} = {paramName, validationFcn};
            obj.Parameters{end+1} = paramName;
        end


        function obj = parse(obj, varargin)
            % 解析输入参数,并将解析结果存储到 Results 属性中
            % 同时处理未匹配参数(Unmatched)和使用默认值的参数(UsingDefaults)

            obj.Results = struct();    % 初始化存储解析结果的结构体
            obj.Unmatched = struct();  % 初始化未匹配的参数集合
            obj.UsingDefaults = {};    % 初始化使用了默认值的参数名列表

            hasName = obj.makeHead();  % 设置函数名用于错误消息显示(若存在)

            % --- StructExpand 功能 ---
            % 如果启用 StructExpand 且仅传入一个结构体参数,则将其转换为名称-值对格式
            % 注意: 字段名仅做精确匹配(S-03a), 通过 structMode 局部标志实现,
            %       不再修改 obj.PartialMatching 属性(修复副作用缺陷)
            structMode = false;
            if obj.StructExpand && numel(varargin) == 1 && isstruct(varargin{1})
                structMode = true;
                s = varargin{1};
                fn = fieldnames(s);
                vargs = cell(1, 2 * numel(fn));
                for i = 1:numel(fn)
                    vargs{2*i-1} = fn{i};
                    vargs{2*i} = s.(fn{i});
                end
                varargin = vargs;  % 替换原始输入参数
            end

            pos = 1;  % 当前参数的位置指针

            try
                % 处理 Required(必需)参数
                for i = 1:numel(obj.RequiredList)
                    [name, validator] = obj.RequiredList{i}{:};
                    if pos > numel(varargin)
                        error(message("丢失必须参数: %s", name));
                    end
                    val = varargin{pos};
                    obj = processResult(obj,name,val,validator,true);
                    pos = pos + 1;
                end

                % 预设 Optional 和 Parameter 的默认值
                for i = 1:numel(obj.OptionalList)
                    name = obj.OptionalList{i}{1};
                    default = obj.OptionalList{i}{2};
                    obj.Results.(name) = default;
                    obj.UsingDefaults{end+1} = name;
                end

                for i = 1:numel(obj.ParameterList)
                    name = obj.ParameterList{i}{1};
                    default = obj.ParameterList{i}{2};
                    obj.Results.(name) = default;
                    obj.UsingDefaults{end+1} = name;
                end

                % 处理 Optional(可选位置参数)
                % 交界算法(对齐 MATLAB 语义, 见行为依据表 J-01/J-02):
                %   optional 位置遇到文本 v 时:
                %   1) 该 optional 带验证器且 v 通过验证 → 消费为该 optional 的值
                %      (即使 v 恰好是已注册的参数名)
                %   2) 否则 v 视为名值区起点, 本 optional 及其后的 optional
                %      均保持默认值, 剩余输入转入名值对解析
                %   名值区起点判定仅针对 addParameter 注册名, optional 名不参与
                for i = 1:numel(obj.OptionalList)
                    if pos > numel(varargin)
                        break;
                    end
                    spec = obj.OptionalList{i};
                    name = spec{1};
                    validator = spec{3};
                    val = varargin{pos};
                    isText = ischar(val) || (isstring(val) && isscalar(val));
                    consumed = false;
                    if ~isText
                        consumed = true;  % 非文本必为该 optional 的位置值
                    elseif ~isempty(validator)
                        try
                            validate(validator, name, val);
                            consumed = true;   % 文本通过验证 → 消费为值
                        catch
                            consumed = false;  % 未通过 → 转名值判定
                        end
                    end
                    if consumed
                        obj = processResult(obj, name, val, validator, false);
                        pos = pos + 1;
                    else
                        break;  % 转入名值区, 后续 optional 保持默认
                    end
                end

                % 处理 Parameter(名称-值对参数)
                remaining = varargin(pos:end);
                if mod(numel(remaining), 2) ~= 0
                    error(message("名称-值对组参数需要一个名称并后跟一个值。"));
                end
                pairs = numel(remaining) / 2;
                knownNames = cellfun(@(x) x{1},obj.ParameterList,'UniformOutput', false);  % 获取所有合法参数名
                prios = ones(1, numel(obj.ParameterList));   % 各参数的 PartialMatchPriority
                for i = 1:numel(obj.ParameterList)
                    prios(i) = obj.ParameterList{i}{4};
                end

                for i = 1:2:numel(remaining)
                    rawKey = remaining{i};
                    value = remaining{i+1};

                    checkName(rawKey)
                    key = char(rawKey);
                    nameList = knownNames;
                    if ~obj.CaseSensitive
                        key = lower(key);
                        nameList = lower(nameList);  % 不区分大小写时统一转小写
                    end

                    idx = obj.matchName(key, nameList, knownNames, prios, structMode);  % 查找匹配的参数名
                    if isempty(idx)
                        if obj.KeepUnmatched
                            % 保留未匹配参数(N-09 定案: 保留用户原始拼写,
                            % 依据官方 KeepUnmatched 示例输出 tName: 1 未被小写化;
                            % 不可用 lower 后的 key)
                            obj.Unmatched.(char(rawKey)) = value;
                        else
                            error(message("无法识别的参数名: %s", key));
                        end
                    else
                        % 匹配成功后,获取验证器并验证输入值
                        paramSpec = obj.ParameterList{idx};
                        validator = paramSpec{3};
                        paramName = paramSpec{1};
                        obj = processResult(obj,paramName,value,validator,false);
                    end

                    pairs = pairs - 1;
                end

                if pairs ~= 0
                    error(message("有额外的输出,请检查输入"));
                end

            catch ME
                % 错误处理:若指定函数名则使用 makeMsg 包装错误
                if hasName
                    obj.makeMsg(ME);
                else
                    error(ME);
                end
            end
        end
    end

    methods(Access=private,Hidden)
        function [hasName,head] = makeHead(obj)
            if ~isempty(obj.FunctionName)
                hasName = true;
                head = message('错误使用 %s\n', obj.FunctionName);
            else
                hasName = false;
                head = '';
            end
        end

        function idx = matchName(obj, key, nameList, knownNames, priorities, exactOnly)
            % exactOnly=true 时仅精确匹配(struct 展开字段名等场景, 不做前缀匹配)
            if nargin < 5
                priorities = [];
            end
            if nargin < 6
                exactOnly = false;
            end
            idx = find(strcmp(key, nameList), 1);
            if isempty(idx) && obj.PartialMatching && ~exactOnly
                matches = strncmp(key, nameList, length(key));
                matchIdx = find(matches);
                if isempty(matchIdx)
                    idx = [];
                elseif numel(matchIdx) > 1
                    % 依 PartialMatchPriority 消歧: 取优先级数值最小者
                    resolved = [];
                    if ~isempty(priorities)
                        prios = priorities(matchIdx);
                        cand = matchIdx(prios == min(prios));
                        if numel(cand) == 1
                            resolved = cand;
                            warning(message("'%s' 与多个参数名称匹配, 已按 PartialMatchPriority 选取 '%s'。", key, knownNames{resolved}));
                        end
                    end
                    if ~isempty(resolved)
                        idx = resolved;
                    elseif obj.KeepUnmatched
                        idx = [];
                    else
                        lists = join(knownNames(matchIdx),',');
                        error(message("'%1$s' 与多个参数名称匹配: %2$s。 为避免多义性,请指定参数的完整名称。",key,lists{1}));
                    end
                else
                    idx = matchIdx;
                end
            end
        end

        function makeMsg(obj, msg)
            [~,head] = obj.makeHead();
            msg = strcat(head, msg);
            error(msg);
        end

        function obj = processResult(obj,name,value,validator,isRequired)
            validate(validator, name, value);  % 调用验证函数进行检查
            obj.Results.(name) = value;        % 存入解析结果
            if ~isRequired
                % 移除该参数名出 UsingDefaults
                obj.UsingDefaults(strcmp(obj.UsingDefaults, name)) = [];
            end
        end
    end
end

function name = checkArgs(list, value, validator)
    % 注册层统一校验: 名称类型/合法变量名/重复注册/验证器类型
    % 返回规范化(char)后的参数名
    if ~ischar(value) && ~(isstring(value) && isscalar(value))
        error(message('参数名必须是字符向量或字符串标量。'));
    end
    name = char(value);
    if ~isNameOK(name)
        error(message('参数名 %s 不是有效的变量名。', name));
    end
    if ismember(name, list)
        error(message('参数名 %s 已存在,不允许重新定义。', name));
    end
    if ~isempty(validator) && ~isa(validator, 'function_handle')
        error(message('validator 必须是函数句柄类型。'));
    end
end

function tf = isNameOK(name)
    % 合法变量名 = isvarname 且非关键字
    % (注: 北太天元 isvarname 不拒绝关键字, 需叠加 iskeyword)
    tf = isvarname(name) && ~iskeyword(name);
end

function checkName(name)
    if ~ischar(name) && ~(isstring(name) && isscalar(name))
        error(message('参数名必须是字符向量或字符串标量。'));
    end
end

function validate(validator, name, value)
    % 双模式验证(修复 B-16):
    %   模式1 赋值式: 逻辑返回型验证器(@isnumeric、@(x)x>0)——返回 false 即失败
    %   模式2 语句式: 无返回值抛错型验证器(mustBe*)——赋值式调用会报
    %        "输出参数过多", 改以语句式调用: 抛错即失败, 无异常即通过;
    %        validateattributes 型匿名验证器(同样无返回值)自动落入此模式
    if ~isempty(validator)
        ok = 'NA';
        try
            ok = validator(value);   % 先按逻辑返回型尝试
        catch
            ok = 'NA';               % 赋值式失败: 无返回值型, 待语句式复核
        end
        if ischar(ok)
            % 模式2: 语句式复核
            try
                validator(value);   % 抛错即验证失败
            catch
                error(message("%1$s 参数验证失败。它必须满足函数: %2$s", name, func2str(validator)));
            end
        elseif ~all(ok(:))
            % 模式1: 逻辑返回 false 即失败
            error(message("%1$s 参数验证失败。它必须满足函数: %2$s", name, func2str(validator)));
        end
    end
end

      复制成功
     
     
     
     



免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

相关文章
技术文档
QR Code
微信扫一扫,欢迎咨询~
customer

online

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 board-phone 155-2731-8020
close1
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

姓名不为空

姓名不为空
手机不正确

手机不正确

手机不正确
公司不为空

公司不为空

公司不为空