Jacoco XML 解析

1 XML解析器对比

1. DOM解析器:

○ 优点:易于使用,提供完整的文档树,可以方便地修改和遍历XML文档。
○ 缺点:对大型文档消耗内存较多,加载整个文档可能会变慢。
○ 适用场景:适合小型XML文档或需要多次访问和修改XML内容的情况。

2. SAX解析器:

○ 优点:逐个处理XML元素,节省内存,适用于一次性遍历大型XML文档。
○ 缺点:编写处理事件的代码可能会相对复杂,不适合需要频繁修改XML内容的情况。
○ 适用场景:适合大型XML文档的读取、分析和提取数据。

3. StAX解析器:

○ 优点:结合了DOM和SAX的优点,提供了灵活的编程模型,适用于流式处理和部分内存加载。
○ 缺点:可能比纯粹的SAX稍微复杂一些。
○ 适用场景:适合需要处理中等大小的XML文档,同时保持较低内存占用的情况。

4. XPath解析器:

○ 优点:提供了强大的查询功能,能够方便地定位XML文档中的元素和数据。
○ 缺点:可能会稍微降低性能,特别是在复杂的查询情况下。
○ 适用场景:适合需要定位和提取特定数据的情况,可以减少手动遍历和解析的工作。

5. JSON对应XML解析器:

○ 优点:可以将XML数据转换为JSON格式,利用JSON解析器处理。
○ 缺点:可能会导致数据结构转换复杂性,不是所有情况下都适用。
○ 适用场景:当您的应用程序使用JSON格式处理数据,但需要处理XML数据时,可以考虑此类解析器。

解析的jacoco XML数据量较多 行数至几十万行 XML大小为 数十M
选择SAX解析器进行解析

2 解析思路

SAX可以逐行处理标签
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述





/**
 * @author xieyan
 * @date 2023-08-22 16:13
 */
public class JacocoXmlConstant {
    public static final String COUNTER = "counter";

    public static final String METHOD = "method";

    public static final String CLASS = "class";

    public static final String SOURCEFILE = "sourcefile";

    public static final String SOURCEFILE_NAME = "name";

    public static final String COUNTER_TYPE = "type";

    public static final String COUNTER_MISSED = "missed";

    public static final String COUNTER_COVERED = "covered";

    public static final String METHOD_NAME = "name";

    public static final String METHOD_DESC = "desc";

    public static final String CLASS_NAME = "name";

    public static final String CLASS_SOURCEFILENAME = "sourcefilename";

    public static final String REPORT_NAME = "name";

    public static final String PACKAGE_NAME = "name";

    public static final String REPORT = "report";

    public static final String PACKAGE = "package";

    public static final String SESSIONINFO = "sessioninfo";

    public static final String LINE = "line";

    public static final String INSTRUCTION_TYPE = "INSTRUCTION";

    public static final String BRANCH_TYPE = "BRANCH";

    public static final String LINE_TYPE = "LINE";

    public static final String COMPLEXITY_TYPE = "COMPLEXITY";

    public static final String METHOD_TYPE = "METHOD";

    public static final String CLASS_TYPE = "CLASS";


}


import lombok.Getter;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import java.util.List;

import static com.jacoco.xml.JacocoXmlConstant.*;

/**
 * 解析xml文件
 *
 * @author xieyan
 */
@Getter
public class ParseXmlHandler extends DefaultHandler {

    private Report report;

    private Package currentPackage;

    private Clazz currentClass;

    private Method currentMethod;

    private Counter currentCounter;

    private String sourcefile;

    /**
     * 开始解析元素时触发
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        switch (qName) {
            // 跳过 sessioninfo 和 line
            case SESSIONINFO:
            case LINE:
                return;
            // 解析report
            case REPORT:
                report = new Report();
                report.setName(attributes.getValue(REPORT_NAME));
                break;
            // 解析package
            case PACKAGE:
                currentPackage = new Package();
                currentPackage.setName(attributes.getValue(PACKAGE_NAME));
                if (report != null) {
                    report.addPackage(currentPackage);
                }
                break;
            // 解析class
            case CLASS:
                currentClass = new Clazz();
                currentClass.setName(attributes.getValue(CLASS_NAME));
                currentClass.setSourcefilename(attributes.getValue(CLASS_SOURCEFILENAME));
                if (currentPackage != null) {
                    currentPackage.addClass(currentClass);
                }
                break;
            // 解析method
            case METHOD:
                currentMethod = new Method();
                currentMethod.setName(attributes.getValue(METHOD_NAME));
                currentMethod.setDesc(attributes.getValue(METHOD_DESC));
                if (currentClass != null) {
                    currentClass.addMethod(currentMethod);
                }
                break;
            // 解析sourcefile
            case SOURCEFILE:
                sourcefile = attributes.getValue(SOURCEFILE_NAME);
                break;
            // 解析counter
            case COUNTER:
                currentCounter = new Counter();
                currentCounter.setType(attributes.getValue(COUNTER_TYPE));
                currentCounter.setMissed(Integer.parseInt(attributes.getValue(COUNTER_MISSED)));
                currentCounter.setCovered(Integer.parseInt(attributes.getValue(COUNTER_COVERED)));
                // 绑定counter 到对应的元素
                bindCounter();
                break;
            default:
                break;
        }

    }


    /**
     * 结束解析元素时触发
     */
    @Override
    public void endElement(String uri, String localName, String qName) {
        switch (qName) {
            case SESSIONINFO:
            case LINE:
                // 跳过 sessioninfo 和 line
                return;
            case SOURCEFILE:
                // 避免重复加入 counter 到 page
                sourcefile = null;
                break;
            case REPORT:
                // 计算总的覆盖率
                Coverage coverageReport = caculateCoverage(report.getReportCounters());
                report.setReportCoverage(coverageReport);
                break;
            case PACKAGE:
                // 计算 package 覆盖率
                if (currentPackage != null) {
                    Coverage coveragePackage = caculateCoverage(currentPackage.getPackageCounters());
                    currentPackage.setPackageCoverage(coveragePackage);
                    currentPackage = null;
                }
                break;
            case CLASS:
                // 计算 class 覆盖率
                if (currentClass != null) {
                    Coverage coverageClass = caculateCoverage(currentClass.getClazzCounters());
                    currentClass.setClazzCoverage(coverageClass);
                    currentClass = null;
                }
                break;
            case METHOD:
                // 计算 method 覆盖率
                if (currentMethod != null) {
                    Coverage coverageMethod = caculateCoverage(currentMethod.getMethodCounters());
                    currentMethod.setMethodCoverage(coverageMethod);
                    currentMethod = null;
                }
                break;
            case COUNTER:
                currentCounter = null;
                break;
            default:
                break;
        }
    }


    /**
     * 绑定counter 到对应的元素
     */
    private void bindCounter() {
        // counter 属于 method
        if (currentMethod != null) {
            currentMethod.addCounter(currentCounter);
        }
        // counter 属于 class
        else if (currentClass != null) {
            currentClass.addCounter(currentCounter);
        }
        // counter 属于 package
        else if (currentPackage != null) {
            // 跳过sourcefile里的counter 避免重复加入
            if (sourcefile == null) {
                currentPackage.addCounter(currentCounter);
            }
        }
        // counter 属于 report
        else if (report != null) {
            report.addCounter(currentCounter);
        }
    }

    /**
     * 计算覆盖率
     */
    private Coverage caculateCoverage(List<Counter> counterList) {
        Coverage result = new Coverage();
        for (Counter counter : counterList) {
            String type = counter.getType().toUpperCase();
            String coverage = processResult(counter);
            setCoverageByType(result, type, coverage);
        }
        return result;
    }

    private void setCoverageByType(Coverage result, String type, String coverage) {
        switch (type) {
            // 指令覆盖率
            case INSTRUCTION_TYPE:
                result.setInstructionCoverage(coverage);
                break;
            // 行覆盖率
            case LINE_TYPE:
                result.setLineCoverage(coverage);
                break;
            // 分支覆盖率
            case BRANCH_TYPE:
                result.setBranchCoverage(coverage);
                break;
            // 圈复杂度覆盖率
            case COMPLEXITY_TYPE:
                result.setComplexityCoverage(coverage);
                break;
            // 方法覆盖率
            case METHOD_TYPE:
                result.setMethodCoverage(coverage);
                break;
            // 类覆盖率
            case CLASS_TYPE:
                result.setClassCoverage(coverage);
                break;
            default:
                break;
        }
    }

    /**
     * 处理覆盖率结果
     * 保留两位小数 例如 99.99%
     */
    private String processResult(Counter counter) {
        int missed = counter.getMissed();
        int covered = counter.getCovered();
        double coverage = (double) covered / (missed + covered);
        double coveragePercentage = coverage * 100;
        return String.format("%.2f%%", coveragePercentage);
    }

}


import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * @author xieyan
 */
@Data
public class Clazz {

    private String name;

    private String sourcefilename;

    private final List<Method> methods = new ArrayList<>();

    private final List<Counter> clazzCounters = new ArrayList<>();

    private Coverage clazzCoverage;


    public void addMethod(Method method) {
        methods.add(method);
    }

    public void addCounter(Counter counter) {
        clazzCounters.add(counter);
    }

}

import lombok.Data;

/**
 * @author xieyan
 */
@Data
public class Counter {

    private String type;

    private int missed;

    private int covered;

}

import lombok.Data;

/**
 * 覆盖率统计
 * @author xieyan
 * @date 2021-08-22 16:13
 */
@Data
public class Coverage {

    /*** 指令覆盖率*/
    private String instructionCoverage;

    /** * 分支覆盖率*/
    private String branchCoverage;

    /*** 行覆盖率*/
    private String lineCoverage;

    /*** 圈复杂度覆盖率*/
    private String complexityCoverage;

    /*** 方法覆盖率 */
    private String methodCoverage;

    /*** 类覆盖率*/
    private String classCoverage;
}

Method
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * @author xieyan
 */
@Data
public class Method {

    private String name;

    private String desc;

    private final List<Counter> methodCounters = new ArrayList<>();

    private Coverage methodCoverage;

    public void addCounter(Counter counter) {
        methodCounters.add(counter);
    }

}


import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * @author xieyan
 */
@Data
public class Package {

    private String name;

    private final List<Clazz> classes = new ArrayList<>();

    private final List<Counter> packageCounters = new ArrayList<>();

    private Coverage packageCoverage;

    public void addClass(Clazz clazz) {
        classes.add(clazz);
    }

    public void addCounter(Counter counter) {
        packageCounters.add(counter);
    }

}

@Data
public class Report {

    private String name;

    private final List<Package> packages = new ArrayList<>();

    private final List<Counter> reportCounters = new ArrayList<>();

    private Coverage reportCoverage;


    public void addPackage(Package pkg) {
        packages.add(pkg);
    }

    public void addCounter(Counter counter) {
        reportCounters.add(counter);
    }

}

import lombok.extern.slf4j.Slf4j;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * 解析jacoco覆盖率报告工具类
 * @author xieyan
 * @date 2023-08-22 16:38
 */
@Slf4j
public class ParseJacocoXmlUtil {
    /**
     * 解析jacoco覆盖率报告
     *
     * @param xmlPath jacoco覆盖率报告路径
     * @return 解析后的报告对象,解析失败时返回null
     */
    public static Report parse(String xmlPath) {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // 禁用DTD验证
            factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

            SAXParser saxParser = factory.newSAXParser();

            // 读取xml文件
            File xmlFile = new File(xmlPath);

            ParseXmlHandler handler = new ParseXmlHandler();

            try (InputStream inputStream = xmlFile.toURI().toURL().openStream()) {
                // 解析XML文件
                saxParser.parse(inputStream, handler);
                return handler.getReport();
            }
        } catch (ParserConfigurationException | SAXException | IOException e) {
            log.error("解析jacoco xml 覆盖率报告失败", e);
        }
        return null;
    }
}


public class SaxParsingToJavaObjectExample {
    public static void main(String[] args) {
        // 指定XML文件路径
        String xmlFilePath = "D:\\tmp\\jacoco.xml";
        //String xmlFilePath = "F:\\temp\\a.xml";
        Report report = ParseJacocoXmlUtil.parse(xmlFilePath);
        Optional.ofNullable(report).ifPresent(SaxParsingToJavaObjectExample::export);
    }



    public static void export(Report report) {
        for (Package pkg : report.getPackages()) {
            System.out.println("Package Name: " + pkg.getName());

            for (Clazz clazz : pkg.getClasses()) {
                System.out.println("Class Name: " + clazz.getName());

                for (Method method : clazz.getMethods()) {
                    System.out.println("Method Name: " + method.getName());
                    System.out.println("Method Description: " + method.getDesc());

                    for (Counter counter : method.getMethodCounters()) {
                        System.out.println("Counter Type: " + counter.getType());
                        System.out.println("Counter Missed: " + counter.getMissed());
                        System.out.println("Counter Covered: " + counter.getCovered() + "\n");
                    }
                    System.out.println("==================== Method ==========================================");
                }
            }
        }

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/91573.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数组习题答案

基础题目 第一题&#xff1a;需求实现 模拟大乐透号码&#xff1a; 一组大乐透号码由10个1-99之间的数字组成定义方法&#xff0c;打印大乐透号码信息 代码实现&#xff0c;效果如图所示&#xff1a; 开发提示&#xff1a; 使用数组保存录入的号码 参考答案&#xff1a; p…

使用 SQLStudio 进行数据库管理并通过 Docker Compose 进行部署

在现代软件开发中&#xff0c;数据库管理是一个至关重要的环节。SQLStudio 是一个强大的工具&#xff0c;可以帮助开发人员轻松管理数据库&#xff0c;现在改名成SQLynx&#xff0c;我们用的是旧的镜像&#xff0c;本文还是用SQLStudio这个名称。同时&#xff0c;使用 Docker C…

live555server环境搭建

live555环境搭建详解&#xff08;ubuntu18.04&#xff09; 1.环境依赖 openssl可选安不安 安装&#xff08;选择好版本&#xff09; sudo apt-get update sudo apt-get install openssl sudo apt-get install libssl-dev使用头文件是否可用时编译测试时记得链接&#xff08…

【Go 基础篇】Go语言中的数组:初识与应用

Go语言以其简洁、高效和强大的特性在编程界广受欢迎。数组作为一种基本的数据结构&#xff0c;在各种应用场景中扮演着重要角色。本文将引入Go语言中的数组&#xff0c;介绍其特点、创建、初始化以及基本应用&#xff0c;为你打开数组的大门。 前言 数组是一种固定大小的数据…

c++ qt--事件过滤(第七部分)

c qt–事件过滤&#xff08;第七部分&#xff09; 一.为什么要用事件过滤 上一篇博客中我们用到了事件来进行一些更加细致的操作&#xff0c;如监控鼠标的按下与抬起&#xff0c;但是我们发现如果有很多的组件那每个组件都要创建一个类&#xff0c;这样就显得很麻烦&#xff…

学习设计模式之享元模式,但是宝可梦

前言 作者在准备秋招中&#xff0c;学习设计模式&#xff0c;做点小笔记&#xff0c;用宝可梦为场景举例&#xff0c;有错误欢迎指出。 享元模式 1 介绍 享元模式很好理解&#xff0c;它主要是为了减少创建对象的数量&#xff0c;属于结构型设计模式 目的&#xff1a;减少…

高效多用的群集-Haproxy搭建Web集群

Haproxy搭建 Web 群集 一、Haproxy前言 HAProxy是一个使用c语言编写的自由及开放源代码软件&#xff0c;其提供高可用性、负载均衡&#xff0c;以及基于TcP和HrrP的应用程序代理。HAProxy特别适用于那些负载特大的web站点&#xff0c;这些站点通常又需要会话保持或七层处理。…

Leetcode77. 组合

给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 回溯剪枝 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 class Solution {public List<List<Integer>> combine(int n, i…

Android App的设计规范

Android App 设计规范是为开发者和设计师提供的一系列准则和建议&#xff0c;以确保应用在 Android 设备上的外观、交互和用户体验保持一致。以下是一些常见的 Android App 设计规范要点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开…

新生报到:无压力的数字自我介绍

&#x1f338; 新生报到&#xff1a;无压力的数字自我介绍 &#x1f338; 开学季又来临&#xff0c;每个学校、每个班级都迎来了一批新鲜面孔。作为新生&#xff0c;面对陌生的环境和同学&#xff0c;首次的自我介绍无疑是一个让许多人感到紧张和迷茫的挑战。你是否曾因为害羞…

学习高等数学需要的初等数学知识

文章目录 名词解释常用希腊字符读音幂、根式和对数常用的三角函数值三角函数变换一元二次方程求解充分条件和必要条件切线方程、斜率和法线隐函数极坐标排列组合 名词解释 教材中存在着许多熟悉且陌生的词汇&#xff0c;作者在此进行了整理&#xff1a; 概念&#xff1a;概念…

【Linux】【驱动】驱动挂载的时候给驱动传递参数

【Linux】【驱动】驱动挂载的时候给驱动传递参数 绪论1.什么是驱动传参驱动传参就是传递参数给我们的驱动举例:2.驱动传参数有什么作用呢?3. 传递单个参数使用如下的数组4. 传递数组使用以下函数&#xff1a; 传递数字值代码指令 传递数组代码传递数组指令 绪论 1.什么是驱动…

【C进阶】指针(一)

大家好&#xff0c;我是深鱼~ 【前言】&#xff1a; 指针的主题&#xff0c;在初阶指针章节已经接触过了&#xff0c;我们知道了指针的概念&#xff1a; 1.指针就是个变量&#xff0c;用来存放地址&#xff0c;地址的唯一标识一块内存空间&#xff08;指针变量&#xff09;&a…

战略在集体学习过程中涌现

战略学习派&#xff1a;战略是涌现的学习过程&#xff0c;中国人的话&#xff0c;要交学习费&#xff01;【安志强趣讲269期】 趣讲大白话&#xff1a;出来混总要交学费 **************************** 中国人有这个意识 新进一个领域&#xff0c;要交学费&#xff0c;有学习过程…

虚拟机中Ubuntu 16.04 设置网络

1、打开虚拟机的“编辑”选项&#xff0c;选择“虚拟网络编辑器”&#xff0c;修改网络配置 2、同时打开 Windows下面的命令行&#xff0c;输入ipconfig&#xff0c;看到虚拟网络适配器的地址 1-虚拟机-设置-网络适配器 2-编辑-虚拟网络编辑器-VMnet0 3-编辑-虚拟网络编辑…

线性代数的本质笔记(3B1B课程)

文章目录 前言向量矩阵行列式线性方程非方阵点积叉积基变换特征向量与特征值抽象向量空间 前言 最近在复习线代&#xff0c;李永乐的基础课我刷了一下&#xff0c;感觉讲的不够透彻&#xff0c;和我当年学线代的感觉一样&#xff0c;就是不够形象。 比如&#xff0c;行列式为…

KVM虚拟化平台安装及创建虚拟机

文章目录 一、KVM 简介二、安装KVM虚拟化平台1、方式一&#xff1a;安装操作系统时&#xff0c;添加虚拟化功能2、方式二&#xff1a;基于现有系统&#xff0c;安装虚拟化功能3、验证KVM安装是否无误 三、创建虚拟机1、创建虚拟机前环境准备工作2、创建CentOS7.5系统虚拟机 一、…

数据结构(Java实现)LinkedList与链表(上)

链表 逻辑结构 无头单向非循环链表&#xff1a;结构简单&#xff0c;一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构&#xff0c;如哈希桶、图的邻接表等等。 无头双向链表&#xff1a;在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。 链表的…

数据库系统课设——基于python+pyqt5+mysql的酒店管理系统(可直接运行)--GUI编程

几个月之前写的一个项目&#xff0c;通过这个项目&#xff0c;你能学到关于数据库的触发器知识&#xff0c;python的基本语法&#xff0c;python一些第三方库的使用&#xff0c;包括python如何将前后端连接起来&#xff08;界面和数据&#xff09;&#xff0c;还有界面的设计等…

实战项目 在线学院springcloud调用篇3(nacos,feging,hystrix,gateway)

一 springcloud与springboot的关系 1.1 关系 1.2 版本关系 1.3 list转json串 public class Test {public static void main(String[] args) {List<String> dataListnew ArrayList<String>();dataList.add("12");dataList.add("45");dataLi…