前言:
好久没有写博文了,最近有几位csdn的网友想找过来借鉴开发的管理系统,因为工作调整我已经不在公司管理干部了,整套系统当时本身就是为了方便自己工作捣鼓的,现在也没人用了。牵涉到打架java运行环境,数据库,整合企业微信登录等等,要用起来也不方便,所以干脆就重新把最核心的转换审批表到实体类,然后再转换为word,以及提取简历功能用c#写了winform程序了。
程序开源地址:
干部任免审批报转换为word: 将干部任免审批表批量转换为word格式
首先声明本人不是专业的程序员,代码是比较烂我自己也知道,纯属给大家提供一个解决问题的思路。
程序实现的主要代码:
一、Lrmx文件解析:
本质上中组部任免审批表的lrmx文件就是一个xml格式文档,只是后缀为lrmx格式,核心思想就是解析xml文件,让文件中的内容与我们自己所定义的类对应。
1. 用到的依赖包:
1.1 Nlog 及 Nlog.from 日志记录用
1.2 NPOI 用于操作word和excel的。用这个包最大的好处就是你不需要去考虑客户端是否有安装office。
1.3 System.Xml.XDocument 和 System.Xml.ReaderWriter xml的解析工具了。
2. 定义的几个核心实体类
2.1 最核心的GanBu类
public class GanBu { public String XingMing; //姓名 1,0,0 1,0,1 public String XingBie; //性别 1,0,2 1,0,3 public String ChuShengNianYue; //出生年月 YYYYMM格式 1,0,4 1,0,5 public String MinZu; //民族 1,1,0 1,1,1 public String JiGuan; //籍贯 1,1,2 1,1,3 public String ChuShengDi;// 出生地 1,1,4 1,1,5 public String RuDangShiJian;// 入党时间 1,2,0 public String CanJiaGongZuoShiJian;//参加工作时间 1,2,2 public String JianKangZhuangKuang;//健康状况 1,2,4 public String ZhuanYeJiShuZhiWu;//专业技术职务 1,3,0 public String ShuXiZhuanYeYouHeZhuanChang;//熟悉专业有何专长 1,3,2 public String QuanRiZhiJiaoYu_XueLi;//全日制教育学历 1,4,2 public String QuanRiZhiJiaoYu_XueWei;//全日制教育学位 1,4,2 public String QuanRiZhiJiaoYu_XueLi_BiYeYuanXiaoXi;//全日制毕业学校 1,4,4 public String QuanRiZhiJiaoYu_XueWei_BiYeYuanXiaoXi;//全日制毕业院校专业 1,5,4 public String ZaiZhiJiaoYu_XueLi;//在职教育学历 1,6,2 public String ZaiZhiJiaoYu_XueWei;//在职教育学位 1,6,2 public String ZaiZhiJiaoYu_XueLi_BiYeYuanXiaoXi;//在职教育毕业院校 1,6,4 public String ZaiZhiJiaoYu_XueWei_BiYeYuanXiaoXi;//在职教育毕业院校专业 1,7,4 public String XianRenZhiWu;//现任职务 1,8,0 public String NiRenZhiWu;//拟任职务 1,9,0 public String NiMianZhiWu;//拟免职务 1,10,0 public List<JianLi> JianLi; //简历 1,11,0 public String JiangChengQingKuang;//奖惩情况 2,0,0 public String NianDuKaoHeJieGuo;//年度考核结果 2,1,0 public String RenMianLiYou;//任免理由 2,2,0 public List<JiaTingChengYuan> JiaTingChengYuan;//家庭成员 2,4,1 称谓 2 姓名 3 年龄 4 政治面貌 5 工作单位及职务 public String ChengBaoDanWei;//承报单位 public String JiSuanNianLingShiJian;//计算年龄时间 public String TianBiaoShiJian;//填报时间 public String TianBiaoRen;//填报人 public String ShenFenZheng;//身份证号码 public String ZhaoPian;// 照片 public String Version;//版本 public int NianLing; //年龄 }
2.2 JianLi 类
public class JianLi { public String KaiShiNianYue; public String JieSuNianYue; public String JingLi; //经历 }
2.3 JiaTingChengYuan 类
public class JiaTingChengYuan { public String ChengWei;//称谓 public String XingMing;//姓名 public String ChuShengRiQi;//出生日期 YYYYMM public String ZhengZhiMianMao;//政治面貌 public String GongZuoDanWeiJiZhiWu;//工作单位及职务 public int NianLing; //年龄 }
3 解析lrmx文件过程
代码就是读取xml文件,将对应的内容解析赋值给GanBu类
3.1 完整转换为类的代码
private GanBu LrmxToClass(string filename) { GanBu ganBu = new GanBu(); Type type = ganBu.GetType(); try { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(filename); XmlNodeList nl = xmlDoc.DocumentElement.ChildNodes; foreach (XmlNode node in nl) { //Debug.WriteLine(node.Name); if (node.Name != "JianLi" && node.Name != "JiaTingChengYuan") { FieldInfo fieldInfo = type.GetField(node.Name); fieldInfo.SetValue(ganBu, node.InnerText); } if (node.Name == "JianLi") { List<JianLi> jianLiList = new List<JianLi>(); string jianli = node.InnerText; int rowNums = 0; string[] JianLiArrayTmp = jianli.Split('\n'); List<String> JianLiArray = new List<string>(); foreach (string s in JianLiArrayTmp) { if (s.Replace("\n", "").Replace("\r","").Trim() != "") { JianLiArray.Add(s.Replace("\r","")); } } foreach (string line in JianLiArray) { if (line.Trim() != "") { if (!Tools.isStartWithNumber(line.Trim())) { jianLiList[rowNums - 1].JingLi = jianLiList[rowNums - 1].JingLi + " " + line.Trim(); } else { JianLi jl = new JianLi(); String[] partArray = Regex.Split(line, " "); int tmpNum = 0; String jingli = ""; foreach (string s in partArray) { if (tmpNum == 0) { //第一列分割的用来做时间拆分 string[] a = Regex.Split(s, "--|——"); if (a.Length == 1) { a = Regex.Split(s, "-|—"); } jl.KaiShiNianYue = a[0].Trim().Replace(" ",""); if (a.Length == 1) { jl.JieSuNianYue = ""; } else { jl.JieSuNianYue += a[1].Trim().Replace(" ", ""); } tmpNum++; } else { jingli += s.Trim(); } } jl.JingLi = jingli; jianLiList.Add(jl); rowNums++; } } } ganBu.JianLi = jianLiList; } if (node.Name == "JiaTingChengYuan") { XmlNodeList node2list = node.ChildNodes; List<JiaTingChengYuan> jtcyList = new List<JiaTingChengYuan>(); foreach (XmlNode node2 in node2list) { JiaTingChengYuan jtcy = new JiaTingChengYuan(); XmlNodeList node3List = node2.ChildNodes; Type t2 = jtcy.GetType(); foreach (XmlNode node3 in node3List) { FieldInfo fi = t2.GetField(node3.Name); fi.SetValue(jtcy, node3.InnerText.Trim()); } jtcy.ChuShengRiQi = jtcy.ChuShengRiQi.Trim().Replace("\n", "").Replace("-", ""); if (jtcy.ChuShengRiQi.Length == 6 || jtcy.ChuShengRiQi.Length == 8) { jtcy.NianLing = Tools.JiSuanNianLing(jtcy.ChuShengRiQi, Tools.NullToEmpty(ganBu.JiSuanNianLingShiJian), Tools.NullToEmpty(ganBu.TianBiaoShiJian)); } jtcyList.Add(jtcy); } ganBu.JiaTingChengYuan = jtcyList; } } //20240718 增加身份证提取出生日期 if (ganBu.ChuShengNianYue.Length == 6 || ganBu.ChuShengNianYue.Length == 8) { ganBu.NianLing = Tools.JiSuanNianLing(ganBu.ChuShengNianYue, Tools.NullToEmpty(ganBu.JiSuanNianLingShiJian), Tools.NullToEmpty(ganBu.TianBiaoShiJian)); } else { if (!string.IsNullOrEmpty(ganBu.ShenFenZheng)) { ganBu.ChuShengNianYue = Tools.GetDate(ganBu.ShenFenZheng); ganBu.NianLing = Tools.JiSuanNianLing(ganBu.ChuShengNianYue, Tools.NullToEmpty(ganBu.JiSuanNianLingShiJian), Tools.NullToEmpty(ganBu.TianBiaoShiJian)); } } logger.Info(filename + "转换为类成功!"); return ganBu; }catch(Exception ex) { logger.Error(ex.ToString()); logger.Error(filename + "转换为类失败!"); return ganBu; } }
3.2 转换过程中一些要点:
3.2.1 对于如何处理简历我的实现思路如下: 因为一条简历有可能会分行的,首先把简历的内容按行存到数组中,如果这行的内容不是数字开头(因为一条简历都是以年份开始的)那么这行就不是一条新的简历,而是一条简历分行了。如果再严格点可以取前四位是否为数字。
3.2.2 对于干部年龄的计算,如果有填写计算年龄时间那么就用该字段取,如果没有取填表时间,如果都没有取当前时间。如果没有填写出生日期,那么看是否有填写身份证号码。
二、 转换为word过程:
转换为word的过程采用的是按照模板来进行内容填充,本来想采用占位符替换的方式,发现有些问题,后来决定还是老老实实通过代码来实现。 审批表核心部分内容实际是一个大大表格,那么就可以按照excel单元格填充的思路来做处理了。
1. 完整代码
try { GanBu ganBu = LrmxToClass(filename); string[] tmpArr = filename.Split('\\'); string newFileName = tmpArr[tmpArr.Length - 1]; string filePath = Path.GetDirectoryName(filename); String targetFilePath = ".\\docx\\" + filePath.Substring(3, filePath.Length - 3) + "\\" + newFileName.Substring(0, newFileName.Length -5) + "-任免审批表.docx"; if (File.Exists(targetFilePath)) { File.Delete(targetFilePath); } XWPFDocument docx; using (FileStream stream = File.OpenRead(".\\spbmb.docx")) { docx = new XWPFDocument(stream); } foreach (XWPFParagraph ph in docx.Paragraphs) { foreach (XWPFRun r in ph.Runs) { if (r.Text == "填表人:") { r.SetText("填表人:" + ganBu.TianBiaoRen); } } } if (!string.IsNullOrEmpty(ganBu.ZhaoPian.Replace("\r", "").Replace("\n", ""))) { XWPFTableCell imageCell = docx.Tables[1].Rows[0].GetTableCells()[6]; XWPFParagraph pg = imageCell.Paragraphs[0]; XWPFRun run = pg.CreateRun(); byte[] b = Convert.FromBase64String(ganBu.ZhaoPian); InputStream sbs = new ByteArrayInputStream(b); run.AddPicture(new ByteArrayInputStream(b), (int)NPOI.SS.UserModel.PictureType.JPEG, "", 363 * 3600, 490 * 3600); } Boolean ifFirst = true; int num = 0; foreach (XWPFTable table in docx.Tables) { for (int i = 0; i < table.Rows.Count; i++) { for (int j = 0; j < table.Rows[i].GetTableCells().Count; j++) { switch (table.Rows[i].GetTableCells()[j].GetText().Trim().Replace(" ", "").Replace("\r\n","")) { case "姓名": if (num == 1) { table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.XingMing); } break; case "性别": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.XingBie); break; case "出生年月(岁)": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.ChuShengNianYue.Substring(0, 4) + "." + ganBu.ChuShengNianYue.Substring(4, 2) + "(" + ganBu.NianLing + "岁)"); break; case "民族": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.MinZu); break; case "籍贯": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.JiGuan); break; case "出生地": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.ChuShengDi); break; case "入党时间": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.RuDangShiJian); break; case "参加工作时间": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.CanJiaGongZuoShiJian); break; case "健康状况": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.JianKangZhuangKuang); break; case "专业技术职务": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.ZhuanYeJiShuZhiWu); break; case "熟悉专业有何专长": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.ShuXiZhuanYeYouHeZhuanChang); break; case "全日制教育": XWPFParagraph pg = table.Rows[i].GetTableCells()[j + 1].Paragraphs[0]; XWPFRun run = pg.CreateRun(); run.SetText(ganBu.QuanRiZhiJiaoYu_XueLi); run.AddBreak(); run.SetText(ganBu.QuanRiZhiJiaoYu_XueWei); break; case "毕业院校系及专业": if (ifFirst) { XWPFParagraph pg2 = table.Rows[i].GetTableCells()[j + 1].Paragraphs[0]; XWPFRun run2 = pg2.CreateRun(); run2.SetText(ganBu.QuanRiZhiJiaoYu_XueLi_BiYeYuanXiaoXi); run2.AddBreak(); run2.SetText(ganBu.QuanRiZhiJiaoYu_XueWei_BiYeYuanXiaoXi); ifFirst = false; } else { XWPFParagraph pg3 = table.Rows[i].GetTableCells()[j + 1].Paragraphs[0]; XWPFRun run3 = pg3.CreateRun(); run3.SetText(ganBu.ZaiZhiJiaoYu_XueLi_BiYeYuanXiaoXi); run3.AddBreak(); run3.SetText(ganBu.ZaiZhiJiaoYu_XueWei_BiYeYuanXiaoXi); ifFirst = true; } break; case "在职教育": XWPFParagraph pg4 = table.Rows[i].GetTableCells()[j + 1].Paragraphs[0]; XWPFRun run4 = pg4.CreateRun(); run4.SetText(ganBu.ZaiZhiJiaoYu_XueLi); run4.AddBreak(); run4.SetText(ganBu.ZaiZhiJiaoYu_XueWei); break; case "现任职务": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.XianRenZhiWu); break; case "拟任职务": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.NiRenZhiWu); break; case "拟免职务": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.NiMianZhiWu); break; case "简历": int lastLine = 1; XWPFParagraph pg5 = table.Rows[i].GetTableCells()[j + 1].Paragraphs[0]; XWPFRun run5 = pg5.CreateRun(); foreach (JianLi jl in ganBu.JianLi) { if (lastLine != ganBu.JianLi.Count) { if (ifFirst) { run5.SetText(jl.KaiShiNianYue + "--" + jl.JieSuNianYue + " " + jl.JingLi); ifFirst = false; } else { table.Rows[i].GetTableCells()[j + 1].AddParagraph().CreateRun().SetText(jl.KaiShiNianYue + "--" + jl.JieSuNianYue + " " + jl.JingLi); } lastLine++; } else { table.Rows[i].GetTableCells()[j + 1].AddParagraph().CreateRun().SetText(jl.KaiShiNianYue + "--" + " " + jl.JieSuNianYue + " " + jl.JingLi); } } break; case "奖惩情况": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.JiangChengQingKuang); break; case "年核度结考果": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.NianDuKaoHeJieGuo); break; case "任免理由": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.RenMianLiYou); break; case "家庭主要成员及重要社会关系": List<JiaTingChengYuan> jtcyList = ganBu.JiaTingChengYuan; for (int m = 0; m < jtcyList.Count(); m++) { XWPFTableRow tr = table.Rows[i + m + 1]; tr.GetTableCells()[1].SetText(jtcyList[m].ChengWei); tr.GetTableCells()[2].SetText(jtcyList[m].XingMing); tr.GetTableCells()[3].SetText(jtcyList[m].NianLing.ToString()); tr.GetTableCells()[4].SetText(jtcyList[m].ZhengZhiMianMao); tr.GetTableCells()[5].SetText(jtcyList[m].GongZuoDanWeiJiZhiWu); } break; case "呈报单位": table.Rows[i].GetTableCells()[j + 1].SetText(ganBu.ChengBaoDanWei); break; case "行任政免机意关见": table.Rows[i].GetTableCells()[j + 1].SetText(" 同意 " ); break; } } } num++; } System.IO.FileStream output = new System.IO.FileStream(targetFilePath, System.IO.FileMode.Create, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite); //写入文件 docx.Write(output); output.Close(); output.Dispose(); logger.Info(targetFilePath + "文件生成成功!"); } catch (Exception ex) { logger.Error(filename + "转换不成功"); logger.Error(ex.ToString()); }
2 核心思路和注意点
2.1 需要填充内容位置判断:
通过遍历表格中的文字内容来判断,填充的位置就是文字所在单元格位置横向+1 了。
2.2 需要注意点:
年度考核结果实际在解析模板中对应字符串为 "年核度结考果", 同样的还有行政任免机关意见对应为“行任政免机意关见” ,这个是个坑。
总结:
有些点说通了,会发现实现起来就没什么难度了。批量转换以及子目录这些我就不阐述了。 东西写得也比较粗,如果有需要的朋友可以留言给我,一起完善这个软件了。