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 ==========================================");
}
}
}
}
}