antlr4 正则表达式生成器3解决排除字符处理

1、说明

对于 [^0-9] 正则要求式不能是字符0-9之间的数字,当前这种场景没有处理。

2、区间范围处理思路

可以使用排查法,总的字符选中范围,为0-0xffff 我们可以从中排除这个范围

即生成的字符范围为 [0 - '0'-1] 和 ['9'+1 - 0xffff] 的范围中随机选择。

为了简化处理过程,对于 [^0-9a-w] 我们就取这里的最小值,记为 min ,最大值记为 max

则最后的区间范围为 [0 - min-1] 和 [max+1 - 0xffff]。

3、代码的处理思路

1、分析

查看树的遍历过程 [^0-9a-w]

当遍历到 NegCharGroup 节点的时也就是匹配 ^ 字符时,我们要获取生成的随机字符的范围。 由于字符范围和其下面的子节点有关系,所以要使用变量来记住其下的子节点的遍历情况。 由于每个遍历函数都返回String,其参数都是固定类型,故不好传递参数,故定义全局变量。

2、解决办法

定义一个成员变量 List<String[]> recored 用来记录,后面的子节点的范围字符情况。

在访问到 NegCharGroup节点时,进行清除操作, 手动的访问其下面的子节点,在根据recored 来 生成最终的字符。

3、代码实现

ini 复制代码
/**
 * 用来记录,[^a-z]的范围情况
 */
private List<String[]> negRecords = new ArrayList<>();

visitCharClassExpr 函数调整

scss 复制代码
    // Character Class Expression
////    charClassExpr
////    : (NegCharGroup | NestedNegCharGroup | PosCharGroup | NestedPosCharGroup) charGroup EndCharGroup
//            ;
    @Override
    public String visitCharClassExpr(regexParser.CharClassExprContext ctx) {

        // 同时有 [^ 或者 [ 暂不支持
        if (ctx.NestedNegCharGroup() != null || ctx.NestedPosCharGroup() != null) {
            throw new RuntimeException("notSupportNestedCharGroup()");
        }
        // [^1-9]反字符的处理逻辑,
        if (ctx.NegCharGroup() != null) {
            // 清除集合
            negRecords.clear();
            // 手动调用,子集
            visitCharGroup(ctx.charGroup());
            return getNegDataChar();

        }
        return visitCharGroup(ctx.charGroup());
    }

    /**
     * 根据 negRecords记录来获取字符
     * @return
     */
    private String getNegDataChar() {
        List<Integer> nums = new ArrayList<>();
        for (String[] negRecord : negRecords) {
            for (int i = 0; i < negRecord.length; i++) {
                String data = negRecord[i];
                // 中文处理
                if (data.startsWith("\u")) {
                    String hexStr = data.replace("\u","");
                    nums.add(HexUtil.hexToInt(hexStr));
                } else {
                    // 普通字符,直接取ask码
                    nums.add((int) data.charAt(0));
                }
            }
        }
        // 获取最大值
        Integer max = nums.stream().max(Integer::compare).get();
        // 获取最小值
        Integer min = nums.stream().max(Integer::compare).get();
        // 对范围进行控制
        min = min<0?0:min;
        max = max>0xffff?0xffff:max;
        // 保存最随机生成的结果
        List<String> endDatas = new ArrayList<>();
        endDatas.add(randomChar(0,min));
        endDatas.add(randomChar(max+1,0xffff));
        // 随机选择一个结果进行返回
        return endDatas.get(R.nextInt(endDatas.size()));
    }

4、测试

csharp 复制代码
System.out.println(RegexUtil.gen("[^a-w0-9]{8}"));

打断点观察

值都顺利的添加进来了

运行结果如下:

css 复制代码
✱壸L醃胞I

5、完整代码

RegVistor2.java

java 复制代码
package com.regex;

import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.RandomUtil;
import com.regex.g4.regexParser;
import com.regex.g4.regexParserBaseVisitor;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * 遍历整个正则的语法分析树
 */
public class RegVistor2 extends regexParserBaseVisitor<String> {

    // 随机字符串
    private final static Random R = new Random();

    // 是否包含特殊字符
    private final static Boolean MAYBE_SPECIAL_CHAR = Boolean.TRUE;


    /**
     * 用来记录,[^a-z]的范围情况
     */
    private List<String[]> negRecords = new ArrayList<>();

    //    root
    //    : regExp EOF
    //            ;
    @Override
    public String visitRoot(regexParser.RootContext ctx) {
        // 直接访问子节点
        return visitRegExp(ctx.regExp());
    }


    //    regExp
//    : branch (PIPE branch)*
//    ;
    @Override
    public String visitRegExp(regexParser.RegExpContext ctx) {

        // 或者任意一个分支的结果即可
        int index = R.nextInt(ctx.branch().size());
        // 获取其中一个分支的结果
        return visitBranch(ctx.branch(index));
    }

    //    branch
//    : piece*
//    ;
    @Override
    public String visitBranch(regexParser.BranchContext ctx) {
        StringBuilder stringBuilder = new StringBuilder();
        // 遍历piece 结果,即可或者所有的结果
        for (regexParser.PieceContext pieceContext : ctx.piece()) {
            stringBuilder.append(visitPiece(pieceContext));
        }
        return stringBuilder.toString();
    }

    //    piece
//    : atom quantifier?
//    ;
    @Override
    public String visitPiece(regexParser.PieceContext ctx) {
        StringBuilder sb = new StringBuilder();
        // 获取字符
        regexParser.AtomContext atom = ctx.atom();
        // quantifier 次数
        regexParser.QuantifierContext quantifier = ctx.quantifier();
        if (quantifier != null) {
            String num = visitQuantifier(quantifier);
            Integer regCount = Integer.valueOf(num);
            for (Integer i = 0; i < regCount; i++) {
                String data = visitAtom(atom);
                sb.append(data);
            }
            return sb.toString();
        }
        // 只有字符就返回生成的字符
        return visitAtom(atom);
    }



    //    atom
//    : Char
//    | charClass
//    | (LPAREN regExp RPAREN)
//    ;
    @Override
    public String visitAtom(regexParser.AtomContext ctx) {
        // 字符直接匹配
        if (ctx.Char() != null) {
            return ctx.Char().getText();
        }
        // 其他访问子分支
        if (ctx.charClass() != null) {
            return visitCharClass(ctx.charClass());
        }

        if (ctx.regExp() != null) {
            return visitRegExp(ctx.regExp());
        }

        return "";
    }

    //    charClass
//    : charClassEsc
//    | charClassExpr
//    | WildcardEsc
//    ;
    @Override
    public String visitCharClass(regexParser.CharClassContext ctx) {
        // 都是优先处理终结符  匹配. 返回一个任意字符
        if (ctx.WildcardEsc() != null) {
            // 返回一个任意字符,
            int[][] range = {{0x4e00, 0x9fa5}, {0x30, 0x39}, {0x61, 0x7a}, {0x41, 0x5a}};
            return getRandomChar(range);
        }
        // 访问其他子节点
        if (ctx.charClassEsc() != null) {
            return visitCharClassEsc(ctx.charClassEsc());
        }

        if (ctx.charClassExpr() != null) {
            return visitCharClassExpr(ctx.charClassExpr());
        }
        return "";
    }

    // 生成随机字符串
    private String getRandomChar(int[][] range) {

        String specialCharStr = "!@#$%^&*()_+|\\/?'"',,<>,.·~`";
        // 包含特殊字符
        if (RegVistor2.MAYBE_SPECIAL_CHAR && R.nextBoolean()) {
            return String.valueOf(specialCharStr.charAt(R.nextInt(specialCharStr.length())));
        } else {
            // 随机选择一组
            int[] subRange = range[R.nextInt(range.length)];
            char charValue = (char) (subRange[0] + R.nextInt(subRange[1] - subRange[0] + 1));
            return String.valueOf(charValue);
        }
    }


    //    charClassEsc
//    : SingleCharEsc
//    | NestedSingleCharEsc
//    | MultiCharEsc
//    | NestedMultiCharEsc
//    | catEsc
//    | complEsc
//    ;
    @Override
    public String visitCharClassEsc(regexParser.CharClassEscContext ctx) {

        // 单字符
        if (ctx.SingleCharEsc() != null) {
            negRecords.add(new String[]{ctx.SingleCharEsc().getText(),ctx.SingleCharEsc().getText()});
            return ctx.SingleCharEsc().getText();
        }
        // 逻辑单字符
        if (ctx.NestedSingleCharEsc() != null) {
            return getNestedSingleCharEsc(ctx.NestedSingleCharEsc());

        }
        // /d /w 等处理
        if (ctx.MultiCharEsc() != null) {
            return getMultiCharEscStr(ctx.MultiCharEsc().getText());
        }
        // /D 反逻辑处理
        if (ctx.NestedMultiCharEsc() != null) {
            return getMultiCharEscStr(ctx.NestedMultiCharEsc().getText());
        }
        // 访问子节点
        if (ctx.catEsc() != null || ctx.complEsc() != null) {
            throw new RuntimeException("不支持catEsc,和 complEsc");
        }
        return "";
    }

    /**
     * 获取反逻辑的字符串数据
     * @param terminalNode 反逻辑字符串节点数据集
     * @return
     */
    private String getNestedSingleCharEsc(TerminalNode terminalNode) {
        return null;
    }

    /**
     * 获取反逻辑的字符串数据
     * @param text
     * @return
     */



    /**
     * 根据字符,来或者返回的字符
     * @param text
     * @return
     */
    private String getMultiCharEscStr(String text) {

        List<String> rangeList = new ArrayList();
        switch (text) {
            // 空白字符
            case "\\s":
                rangeList.add(randomChar(' ', ' '));
                rangeList.add(randomChar('\t', '\t'));
                rangeList.add(randomChar('\r', '\r'));
                rangeList.add(randomChar('\n', '\n'));
                break;
            case "\\S":

                rangeList.add(randomChar(0x4e00, 0x9fa5));
                rangeList.add(randomChar(0x30, 0x39));
                rangeList.add(randomChar(0x61, 0x7a));
                rangeList.add(randomChar(0x41, 0x5a));

                break;

            case "\\i":
            case "\\c":
            case "\\C":
            case "\\I":
                throw new  RuntimeException("notSupportExp_i_I_c_C");
            case "\\d":

                rangeList.add(randomChar(0x30, 0x39));
                break;


            case "\\D":
                // \D:表示非数字
                rangeList.add(randomChar(0x4e00, 0x8fa5));
                rangeList.add(randomChar(0x61, 0x7a));
                rangeList.add(randomChar(0x41, 0x5a));
                break;


            case "\\w":
                // \w 表示匹配大小写英文字母、数字以及下划线,等价于'[a-za-z0-9_]'。
                rangeList.add(randomChar(0x61, 0x7a));
                rangeList.add(randomChar(0x41, 0x5a));
                rangeList.add(randomChar(0x30, 0x39));

                break;
            case "\\W":
                // \W :匹配任何非单词字符,等价于 [^A-Z a-z 0-9_]
                rangeList.add(randomChar(0x4e00, 0x9fa5));
                break;

        }

        return rangeList.get(R.nextInt(rangeList.size()));
    }


    // Character Class Expression
////    charClassExpr
////    : (NegCharGroup | NestedNegCharGroup | PosCharGroup | NestedPosCharGroup) charGroup EndCharGroup
//            ;
    @Override
    public String visitCharClassExpr(regexParser.CharClassExprContext ctx) {

        // 同时有 [^ 或者 [ 暂不支持
        if (ctx.NestedNegCharGroup() != null || ctx.NestedPosCharGroup() != null) {
            throw new RuntimeException("notSupportNestedCharGroup()");
        }
        // [^1-9]反字符的处理逻辑,
        if (ctx.NegCharGroup() != null) {
            // 清除集合
            negRecords.clear();
            // 手动调用,子集
            visitCharGroup(ctx.charGroup());
            return getNegDataChar();

        }
        return visitCharGroup(ctx.charGroup());
    }

    /**
     * 根据 negRecords记录来获取
     * @return
     */
    private String getNegDataChar() {
        List<Integer> nums = new ArrayList<>();
        for (String[] negRecord : negRecords) {
            for (int i = 0; i < negRecord.length; i++) {
                String data = negRecord[i];
                // 中文处理
                if (data.startsWith("\\u")) {
                    String hexStr = data.replace("\\u","");
                    nums.add(HexUtil.hexToInt(hexStr));
                } else {
                    // 普通字符,直接取ask码
                    nums.add((int) data.charAt(0));
                }
            }
        }
        // 获取最大值
        Integer max = nums.stream().max(Integer::compare).get();
        // 获取最小值
        Integer min = nums.stream().max(Integer::compare).get();
        // 对范围进行控制
        min = min<0?0:min;
        max = max>0xffff?0xffff:max;
        // 保存最随机生成的结果
        List<String> endDatas = new ArrayList<>();
        endDatas.add(randomChar(0,min));
        endDatas.add(randomChar(max+1,0xffff));
        // 随机选择一个结果进行返回
        return endDatas.get(R.nextInt(endDatas.size()));
    }


    //    charGroup
//    : posCharGroup? DASH DASH charClassExpr
//    | posCharGroup DASH charClassExpr
//    | posCharGroup DASH?
//    | DASH
//    | Chinese_Unicode* // 添加了中文编码处理
//    ;
    @Override
    public String visitCharGroup(regexParser.CharGroupContext ctx) {

        List<String> datas = new ArrayList<>();
        // 对中文字符进行处理
        if (ctx.Chinese_Unicode() != null && !ctx.Chinese_Unicode().isEmpty()) {
            // 获取unicode 编码
            for (TerminalNode terminalNode : ctx.Chinese_Unicode()) {
                String text = terminalNode.getText();
                // 生成中文,使用了hutool库
                String chinese = UnicodeUtil.toString(text);
                // 添加到选择列表中
                datas.add(chinese);
                // 添加到记录中
                negRecords.add(new String[]{text,text});

            }
            // 处理完,就返回
            return datas.get(R.nextInt(datas.size()));
        }

        // 获取一个列表
        datas.add(visitPosCharGroup(ctx.posCharGroup()));

        // 获取任意一个
        return datas.get(R.nextInt(datas.size()));
    }


    // Positive Character Group
//    posCharGroup
//    : DASH? (charRange | charClassEsc)+
//    ;
    @Override
    public String visitPosCharGroup(regexParser.PosCharGroupContext ctx) {
        List<String> datas = new ArrayList<>();
        if(ctx.DASH() != null) {
            datas.add("-");
            // 添加到记录中
            negRecords.add(new String[]{"-","-"});
        }
        // 添加到可选项中
        for (regexParser.CharClassEscContext charClassEscContext : ctx.charClassEsc()) {
            datas.add(visitCharClassEsc(charClassEscContext));
        }
        //  添加到可选项中
        for (regexParser.CharRangeContext charRangeContext : ctx.charRange()) {
            datas.add(visitCharRange(charRangeContext));
        }
        // 获取任意一个
        return datas.get(R.nextInt(datas.size()));
    }



//    charRange
//    : seRange
//    | XmlChar
//    ;

    @Override
    public String visitCharRange(regexParser.CharRangeContext ctx) {

        if (ctx.XmlChar() != null) {

            negRecords.add(new String[]{ctx.XmlChar().getText(),ctx.XmlChar().getText()});

            return ctx.XmlChar().getText();
        }
        // 接着访问子节点
        return super.visitCharRange(ctx);
    }


//    charOrEsc
//    : Chinese_Unicode
//    | XmlChar
//    | SingleCharEsc
//    ;
    @Override
    public String visitSeRange(regexParser.SeRangeContext ctx) {

        // [a-w] [0-9]
        String start = ctx.charOrEsc(0).getText();
        String end = ctx.charOrEsc(1).getText();
        // 添加进去
        negRecords.add(new String[]{start,end});

        // 判断为中文处理
        if (start.startsWith("\\u")) {
            // 获取unicode,转为 16进制字符串
            String hexStartStr = start.replace("\\u", "");
            String hexEndStr = end.replace("\\u","");
            // 16进制转为10进制
            int hexStart = HexUtil.hexToInt(hexStartStr);
            int hexEnd = HexUtil.hexToInt(hexEndStr);
             // 使用hutool库生成字符串
            char chinese = (char)RandomUtil.randomInt(hexStart, hexEnd + 1);
            return new String(new char[]{chinese});
        }
        // 返回随机数
        return randomChar(start.charAt(0),end.charAt(0));
    }


    //    quantifier
//    : QUESTION
//    | STAR
//    | PLUS
//    | StartQuantity quantity EndQuantity
//    ;
    @Override
    public String visitQuantifier(regexParser.QuantifierContext ctx) {

        // 匹配?,就返回随机值,0或者1
        if (ctx.QUESTION() != null) {
            return String.valueOf(R.nextInt(2));
        }
        // 匹配 * 号,返回 0-15 之间的数字
        if (ctx.STAR() != null) {
            return String.valueOf(R.nextInt(15));
        }
        // 匹配+  1到21 之间的整数,
        if (ctx.PLUS() != null) {
            return String.valueOf(R.nextInt(20) + 1);
        }
        // 有进行处理
        if (ctx.quantity() != null) {
            return visitQuantity(ctx.quantity());
        }
        return "0";
    }

    //    quantity
//    : quantRange
//    | quantMin
//    | QuantExact
//    ;
    @Override
    public String visitQuantity(regexParser.QuantityContext ctx) {
        // 这种情况要进行特殊处理下
        if (ctx.QuantExact() != null) {
            return ctx.QuantExact().getText();
        }

        if (ctx.quantMin() != null) {
            return visitQuantMin(ctx.quantMin());
        }

        if (ctx.quantRange() != null) {
            return visitQuantRange(ctx.quantRange());
        }
        return "0";
    }

//    quantRange
//    : QuantExact COMMA QuantExact
//    ;  {4,9} 等

    @Override
    public String visitQuantRange(regexParser.QuantRangeContext ctx) {
        TerminalNode terminalNodeMin = ctx.QuantExact(0);
        TerminalNode terminalNodeMax = ctx.QuantExact(1);

        int min = Integer.parseInt(terminalNodeMin.getText());

        int max = Integer.parseInt(terminalNodeMax.getText());

        if (min > max) {
            throw new RuntimeException("范围错误");
        }
        // 从可选范围中,选中一个
        return String.valueOf(min + R.nextInt(max - min + 1));
    }


    //    quantMin
//    : QuantExact COMMA
//            ; {8,}
    @Override
    public String visitQuantMin(regexParser.QuantMinContext ctx) {

        if (ctx.QuantExact() != null) {
            TerminalNode terminalNode = ctx.QuantExact();
            String text = terminalNode.getText();
            int min = Integer.parseInt(text);
            // 最小数 min - min+10
            return String.valueOf(R.nextInt(10) + min);
        }
        return "0";
    }

    /**
     * 生成随机字符串
     * @param start
     * @param end
     * @return
     */
    public String randomChar(int start,int end) {
        Random random = new Random();
        return String.valueOf((char) (start + random.nextInt(end - start + 1)));
    }

}
相关推荐
uzong2 小时前
技术故障复盘模版
后端
GetcharZp2 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程3 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研3 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack5 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9655 小时前
pip install 已经不再安全
后端
寻月隐君6 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github