Java利用mpxj解析mpp格式文件

article/2025/11/8 23:50:57

转载请注明来源:http://blog.csdn.net/loongshawn/article/details/51038051

  • 《Java利用mpxj解析mpp格式文件》
  • 《SpringBoot添加Email发送功能》
  • 《SpringBoot配置log4j输出日志》
  • 《SpringBoot定时任务说明》
  • 《SpringBoot接口服务处理Whitelabel Error Page》
  • 《构建基于阿里云OSS文件上传服务》

1、mpp文件介绍

MPP是Microsoft Project项目管理软件的文件扩展名,此软件旨在帮助个人跟踪,组织或维护项目。

2、mpp显示效果

这里写图片描述

可以通过在线的mpp阅读工具打开的,该工具免费,但限制了上传文件大小不超过2M。
https://www.projectplan365.com/projectviewernow/tViews.aspx#

3、mpp结构说明

通过上图可以看出,文件主体内容就是一条一条的记录,记录内容包括:任务ID、任务名、Duration、Start日期、Finish日期、Predecessors业务流、自定义字段。

接下来,我们先来了解下mpp文档的解析工具类,目前主要是通过MPXJ工具类来解析该文件。

MPXJ官方网站:http://www.mpxj.org/index.html

MPXJ官方API说明文档:http://www.mpxj.org/apidocs/index.html

实例中引入的maven依赖版本:

<dependency><groupId>net.sf.mpxj</groupId><artifactId>mpxj</artifactId><version>5.2.2</version>
</dependency>

Package net.sf.mpxj.mpp
上面这个包是处理MPP文件解析的主要集合,通过net.sf.mpxj.mpp.MPPReader类来构建读文件管道。

每条具体的任务都需要通过这个类net.sf.mpxj.Task来解析。

// 普通任务ID
Integer task_id =task.getID();
// 独立任务ID
Integer task_unique_id =task.getUniqueID();
// 大纲ID
Integer task_outline_level =task.getOutlineLevel();
// 任务周期
double task_duration =task.getDuration().getDuration();
// 任务名
String task_name = task.getName();
// 任务开始日期
Date task_start_date = task.getStart();
// 任务结束日期
Date task_finish_date = task.getFinish();
// 任务流
List<Relation> task_predecessors = task.getPredecessors();		   

4、mpp解析代码

解析线上mpp文件,同时获取TaskInfo

// NO.1 解析mpp文件,同时获取TaskInfopublic static List<TaskInfo> readInputStream(InputStream in,String fileName){List<TaskInfo> taskList = new ArrayList<TaskInfo>();InputStream ins = in;try{		    MPPReader mppRead = new MPPReader();ProjectFile pf = mppRead.read(in);logger.info("MPXJUtils.method [readInputStream]: fileName-" + fileName);List<Task> tasks = pf.getAllTasks();logger.info("MPXJUtils.method [readInputStream]: taskSize-" + tasks.size());for (int i = 0; i < tasks.size(); i++) {Task task = tasks.get(i);Integer task_id = task.getID();Integer task_unique_id = task.getUniqueID();Integer task_outline_level = task.getOutlineLevel();double task_duration = task.getDuration().getDuration();String task_name = task.getName();Date task_start_date = task.getStart();Date task_finish_date = task.getFinish();List<Relation> task_predecessors = task.getPredecessors();		    			    	logger.info("MPXJUtils.method [readInputStream] taskInfo:" + task_id + "|" + task_unique_id + "|" + task_outline_level + "|" + task_duration + "|" + task_start_date + "|" + task_finish_date + "|" + task_predecessors);// 封装TaskInfojava.sql.Date sqlStartDate = Str2Date.getUKDate(task_start_date.toString());				// StartDate转换java.sql.Date sqlFinishDate = Str2Date.getUKDate(task_finish_date.toString());				// FinishDate转换StringBuffer sb = new StringBuffer();if(task_predecessors != null){if(task_predecessors.size() > 0){for(Relation relation : task_predecessors){Integer targetTaskId = relation.getTargetTask().getID();if(sb.length() == 0){sb.append(targetTaskId);}else{sb.append(","+targetTaskId);}}}}String task_predecessors_str = sb.toString();												// 任务流文本TaskInfo taskInfo = new TaskInfo();taskInfo.setTask_id(task_id);taskInfo.setTask_unique_id(task_unique_id);taskInfo.setTask_outline_level(task_outline_level);taskInfo.setTask_name(task_name);taskInfo.setTask_duration(task_duration);taskInfo.setTask_start_date(sqlStartDate);taskInfo.setTask_finish_date(sqlFinishDate);taskInfo.setTask_predecessors(task_predecessors_str);taskList.add(taskInfo);		    	}		        }catch (MPXJException e) {logger.info("MPXJUtils.method [readInputStream]: MPXJException-" + e);return null;  } catch (Exception e) { logger.info("MPXJUtils.method [readInputStream]: MPXJException-" + e);return null;  } finally {			try {ins.close();} catch (IOException e) {// TODO Auto-generated catch blocklogger.info("MPXJUtils.method [readInputStream]: IOException-" + e);return null;  }}return taskList;}

封装过的TaskInfo

public class TaskInfo {private int project_id;							// 所属项目IDprivate int task_id;							// 任务IDprivate int task_unique_id;						// 任务唯一IDprivate int parent_id;							// 父任务IDprivate int task_outline_level;					// 任务级别private String task_name;						// 任务名称private double task_duration;					// 任务工期private java.sql.Date task_start_date;			// 任务开始时间private java.sql.Date task_finish_date;			// 任务结束时间private String task_predecessors;				// 任务流private String task_operator;					// 负责人public int getProject_id() {return project_id;}public void setProject_id(int project_id) {this.project_id = project_id;}public int getTask_id() {return task_id;}public void setTask_id(int task_id) {this.task_id = task_id;}public int getTask_unique_id() {return task_unique_id;}public void setTask_unique_id(int task_unique_id) {this.task_unique_id = task_unique_id;}public int getParent_id() {return parent_id;}public void setParent_id(int parent_id) {this.parent_id = parent_id;}public int getTask_outline_level() {return task_outline_level;}public void setTask_outline_level(int task_outline_level) {this.task_outline_level = task_outline_level;}public double getTask_duration() {return task_duration;}public void setTask_duration(double task_duration) {this.task_duration = task_duration;}public Date getTask_start_date() {return task_start_date;}public void setTask_start_date(Date task_start_date) {this.task_start_date = task_start_date;}public Date getTask_finish_date() {return task_finish_date;}public void setTask_finish_date(Date task_finish_date) {this.task_finish_date = task_finish_date;}public String getTask_predecessors() {return task_predecessors;}public void setTask_predecessors(String task_predecessors) {this.task_predecessors = task_predecessors;}public String getTask_operator() {return task_operator;}public void setTask_operator(String task_operator) {this.task_operator = task_operator;}public String getTask_name() {return task_name;}public void setTask_name(String task_name) {this.task_name = task_name;}
}

解析本地mpp文件方法,供测试使用

public static List<TaskInfo> readFile(){List<TaskInfo> taskList = new ArrayList<TaskInfo>();try{	File file = new File("/Users/ffff/Downloads/计划(含月度版)V0.10-20160222.mpp");MPPReader mppRead = new MPPReader();ProjectFile pf = mppRead.read(file);logger.info("MPXJUtils.method [readFile]: fileName-" + file.getName());List<Task> tasks = pf.getAllTasks();logger.info("MPXJUtils.method [readFile]: taskSize-" + tasks.size());for (int i = 0; i < tasks.size(); i++) {Task task = tasks.get(i);Integer task_id = task.getID();Integer task_unique_id = task.getUniqueID();Integer task_outline_level = task.getOutlineLevel();double task_duration = task.getDuration().getDuration();Date task_start_date = task.getStart();Date task_finish_date = task.getFinish();List<Relation> task_predecessors = task.getPredecessors();		    			    	logger.info("MPXJUtils.method [readFile] taskInfo:" + task_id + "|" + task_unique_id + "|" + task_outline_level + "|" + task_duration + "|" + task_start_date + "|" + task_finish_date + "|" + task_predecessors);// 封装TaskInfojava.sql.Date sqlStartDate = Str2Date.getUKDate(task_start_date.toString());			// StartDate转换java.sql.Date sqlFinishDate = Str2Date.getUKDate(task_finish_date.toString());			// FinishDate转换StringBuffer sb = new StringBuffer();if(task_predecessors != null){if(task_predecessors.size() > 0){for(Relation relation : task_predecessors){Integer targetTaskId = relation.getTargetTask().getID();if(sb.length() == 0){sb.append(targetTaskId);}else{sb.append(","+targetTaskId);}}}}String task_predecessors_str = sb.toString();											// 任务流文本TaskInfo taskInfo = new TaskInfo();taskInfo.setTask_id(task_id);taskInfo.setTask_unique_id(task_unique_id);taskInfo.setTask_outline_level(task_outline_level);taskInfo.setTask_duration(task_duration);taskInfo.setTask_start_date(sqlStartDate);taskInfo.setTask_finish_date(sqlFinishDate);taskInfo.setTask_predecessors(task_predecessors_str);taskList.add(taskInfo);		    	}		        }catch (MPXJException e) {logger.info("MPXJUtils.method [readFile]: MPXJException-" + e);return null;  } catch (Exception e) { logger.info("MPXJUtils.method [readFile]: MPXJException-" + e);return null;  } 		return taskList;}

本地测试,输出mpp文件结果:
这里写图片描述

获取子任务间的所属父子关系

// NO.2 获取TaskInfo之间的父子关联关系public static List<TaskInfo> refreshTaskInfo(List<TaskInfo> taskList){List<Map<String,Integer>> tempTaskOutLine = new ArrayList<Map<String,Integer>>();for(TaskInfo taskInfo : taskList){int taskId = taskInfo.getTask_id();int taskOutLineLevel = taskInfo.getTask_outline_level();			int listSize = tempTaskOutLine.size();logger.info("MPXJUtils.method [refreshTaskInfo1]: taskId-" + taskId + ",taskOutLineLevel-" + taskOutLineLevel + ",listSize-" + listSize);// 初始化taskOutLineLevelif(listSize > 2){				if(taskOutLineLevel == 1){					for(int i=listSize;i>2;i--){tempTaskOutLine.remove(i-1);}listSize = 2;	logger.info("MPXJUtils.method [refreshTaskInfo2]: taskId-" + taskId + ",taskOutLineLevel-" + taskOutLineLevel + ",listSize-" + listSize);}				}Map<String,Integer> map = new HashMap<String,Integer>();map.put("taskId", taskId);map.put("taskOutLineLevel", taskOutLineLevel);if(listSize == 0){if(taskOutLineLevel == 0){tempTaskOutLine.add(map);}else{return null;}}else{Map<String,Integer> lastMap = tempTaskOutLine.get(listSize-1);int lastTaskId = lastMap.get("taskId");int lastTaskOutLineLevel = lastMap.get("taskOutLineLevel");if(taskOutLineLevel > lastTaskOutLineLevel){tempTaskOutLine.add(map);taskInfo.setParent_id(lastTaskId);}else if(taskOutLineLevel == lastTaskOutLineLevel){	tempTaskOutLine.set(taskOutLineLevel, map);Map<String,Integer> lastMap1 = tempTaskOutLine.get(taskOutLineLevel-1);int lastTaskId1 = lastMap1.get("taskId");taskInfo.setParent_id(lastTaskId1);}else if(taskOutLineLevel < lastTaskOutLineLevel){					tempTaskOutLine.set(taskOutLineLevel, map);Map<String,Integer> lastMap2 = tempTaskOutLine.get(taskOutLineLevel-1);int lastTaskId2 = lastMap2.get("taskId");taskInfo.setParent_id(lastTaskId2);}}						}return taskList;}	

5、数据存储

将解析的结果,经过子任务排序,即获取每条子任务的父任务,大家可以理解为书本目录的大纲。
这里写图片描述

下面看看数据存储到数据库后的效果:
这里写图片描述

6、自定义字段读取

有时候用户会自定义字段来使事件描述得更加清晰,那如何读取自定义字段呢?

首先,需要知道这个自定义字段的Index,如何查看,我以网页编辑器为例:

双击负责人这一列,会弹出下列窗口:

这里写图片描述

弹出窗口:

这里写图片描述

上面这个弹出窗口里面有如下标识:负责人 ( Text2 ),其中Text2标识表示这个字段存在Index为2的Text里面。因此可以通过如下方法读取内容:

String task_operator = task.getText(2);

读取结果:

这里写图片描述

最后,我说说我这边没有读取自定义字段的原因,当前我们这边生产mpp文件不规范,不同人操作可能造成自定义字段下标不一致,经过验证也确实存在不一致的情况,这种随机的情况很容易造成错误,所以在没有规范流程前尽量不要去读取自定义字段。

若有同学在实际工作中发现mpxj解析mpp还存在其他的问题,请不吝赐教,谢谢!


http://chatgpt.dhexx.cn/article/9747OJJs.shtml

相关文章

java解析mpp文件(包含层级关系)

我用的是递归循环的&#xff0c;不限制有多少子级关系都可以拿到 首先引入解析mpp所需依赖 <dependency><groupId>net.sf.mpxj</groupId><artifactId>mpxj</artifactId><version>7.1.0</version></dependency>解析所用实体类…

Date转换年月日

timebasic.js //时间戳转年月日 export function format(shijianchuo) {//shijianchuo是整数&#xff0c;否则要parseInt转换var time new Date(shijianchuo);var y time.getFullYear();var m time.getMonth() 1;m m < 10 ? "0" m : m;var d time.getDate…

C# 接口中DateTime类型字段返回年月日格式,去掉时分秒的数据

背景 在我们平时写接口的时候&#xff0c;避免不了这样一个问题&#xff0c;数据库中存的字段类型为datetime,代码中对应的实体类也是DateTime类型的字段&#xff0c;于是在读取数据库内容之后返回的数据也是DateTime类型的值&#xff0c;比如2022-10-24 18:34:56.110&#xf…

vue3-用dayjs将时间戳转为年月日格式

已知&#xff0c;格式化时间&#xff1a;dayjs(cellValue).format(YYYY-MM-DD) 用法&#xff1a; import dayjs from dayjs;dayjs(时间戳).format(YYYY-MM-DD HH:mm:ss); 如&#xff0c;在get请求中使用&#xff1a; service.get(/trace/sourceSearchInput.value).then(res …

Java但中获取时间将时间转换成字符串格式(年月日格式)

一:直接上马拿走&#xff1a; package cn.wyj.one;import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date;/*** 测试时间对象和字符串之间的相互转化* DateFormat抽象类和SimpleDateFormat实现类的使用* author 86155**/public class Demo2…

Excel修改日期格式:日月年-年月日

最近处理数据&#xff0c;遇到需要处理一下日期格式&#xff0c;记一下。。。 1、原格式 2、新建Excel表&#xff0c;复制到表中&#xff0c;选择列&#xff0c;数据-分列&#xff0c;下一步…,选择列格式为“DMY”,点击完成 3、效果(若不成功&#xff0c;可以试一试其他的)

各种加密证书

证书相关知识 PFX文件属于数字证书。pfx数字证书既包含有公钥又包含私钥&#xff0c;cer | crt数字证书只包含公钥。参考 JKS&#xff08;Java Key Store&#xff09;就是利用Java Keytool 工具生成的Keystore文件&#xff0c;JKS文件由公钥和密钥构成&#xff0c;其中的公钥…

公钥,私钥,数字签名,证书

今天&#xff0c;我读到一篇好文章。 它用图片通俗易懂地解释了&#xff0c;"数字签名"&#xff08;digital signature&#xff09;和"数字证书"&#xff08;digital certificate&#xff09;到底是什么。 我对这些问题的理解&#xff0c;一直是模模糊糊…

国密SSL证书保障网站安全

国内很多网站为了网站安全都会部署SSL证书&#xff0c;目前市面上申请到的SSL服务器证书基本都是采用RSA国际算法&#xff0c;市场上80%的SSL服务器证书都是由国外CA尤其是美国为主的CA签发的证书。 网络安全就是国家安全&#xff0c;网络安全的对手也已经不仅仅是黑客&#xf…

构建用于签名/加密双证书测试体系的可执行命令

注意事项 生成证书请求的填写 范例Subject: C CN, ST Beijing, L Beijing, O MSI, OU msi, CN ca, emailAddress cagmssl.com 前面的步骤存在错误&#xff0c;后面改用脚本进行证书生成&#xff0c;阅读时请跳过前面错误的内容 错误的内容 -> 开始 CA 生成私钥 o…

来此加密证书申请,验证,自动部署

之前用certbot, 后来一直不报错, 证书不管用, 就想着干脆直接使用来此加密, 不要中间商了, 就有了直接到来此加密注册之旅 注册地址: 来此加密https://letsencrypt.osfipin.com/user-0408/order/list附上这两年的"战绩" 申请这么多证书主要原因是, 测试域名太多, 一…

加密和数字证书

目录 一 KPI概述二 KPI应用1 内容安全加密2 加密文件3 使用非对称加密对称加密密钥4 非对称加密的缺点5 数字签名6 数字证书7 时钟服务8 私钥使用者认证9 总结附&#xff1a;U盾的工作原理介绍 三 详解公钥、私钥、数字证书的概念1 加密和认证2 公钥和私钥3 证书4 总结5 签名证…

加密解密和CA证书杂记

最近两三个月&#xff0c;断断续续的一直在处理CA证书相关的事情。CA证书本质上也是一种加解密&#xff0c;因此就自然而然的涉及到一些加密和解密的技术&#xff0c;这就让我在了解CA的同时&#xff0c;也对加密和解密有了更进一步的认识和理解。 以下便是一个比较杂&#xff…

证书和加解密

刚进公司&#xff0c;在实习期需要了解很多关于加解密算法和证书相关的东西&#xff0c;我以写博客的方式把我近1个多月了解的东西整理出来传授给大家&#xff0c;大家觉得可以的话请不要吝啬你们的赞。 目录 什么是PKI 证书申请流程 加密与解密 签名认证 数字信封 数字…

HTTPS(对称加密+非对称加密+证书)

目录 1. 加密和解密 HTTPS工作过程 2. 对称加密 3. 对称加密 4. 既然都有非对称加密了,那为啥还要有对称加密 5. 中间人攻击 6. 引入证书 HTTPS 也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层. HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在…

非对称加密与数字证书

文章目录 1 非对称加密2 数字签名3 数字证书4 数字签名和数字证书的区别5 CA 认证中心如何保证权威性6 HTTPS 协议7 HTTPS 与 SSL8 为什么不一直使用HTTPS 1 非对称加密 非对称加密&#xff0c;是指不能从加密密钥推算出解密密钥。加密密钥不需要保密&#xff0c;可以公开&…

安全和加密CA证书

一、介绍 1、为什么要加密 ※ 不加密流量的易受攻击性 ● 密码/数据嗅探 ● 数据操作 ● 验证操作 ● 相当于邮寄明信片 ※ 不安全的传统协议 --明文 ● telnet、FTP、POP3等等&#xff1b;不安全密码 ● http.smtp、…

安全-加密与证书

对称加密 在对称加密中&#xff0c;加密和解密使用的是同一个密钥&#xff0c;即&#xff1a;使用相同的密钥对密文进行加密和解密 比如&#xff1a;A和B&#xff0c;A和B保存同一个密钥&#xff0c;A使用这个密钥对明文进行加密&#xff0c;发送给B&#xff0c;B再使用这个密…

加密、签名、证书的基础概念和流程

常用加密算法类型&#xff1a; 加密算法&#xff1a;对称加密 &#xff08;可逆&#xff09; 常用算法 DES&#xff08;Data Encryption Standard&#xff09;&#xff1a;数据加密标准&#xff0c;速度较快&#xff0c;适用于加密大量数据的场合&#xff1b;&#xff08;已…

ssl证书加密方式有哪些?

SSL证书是HTTP明文协议书升级HTTPS加密协议必需的ca证书。ssl证书主要是通过https加密方式实现网站及用户的安全性。那么&#xff0c;你可知道ssl证书加密方式有哪些&#xff1f;一起来看看。 ssl证书加密方式有哪些 ssl证书加密方式1.Base64位数据加密&#xff08;可加密解密…