├── .gitattributes ├── .idea ├── .gitignore ├── compiler.xml ├── jarRepositories.xml └── misc.xml ├── README.md ├── SourceCodeDocxGenerator.exe ├── SourceCodeDocxGenerator.iml ├── SourceCodeDocxGenerator_1.1.jar ├── pom.xml ├── screenshot ├── docx.png ├── gui_input.png └── gui_unInput.png ├── src └── main │ └── java │ └── cocoas │ ├── CodeDocxGenerator.java │ ├── FileUtils.java │ ├── GenerateEvent.java │ ├── LogUtils.java │ ├── MsgHintUtil.java │ └── ParamsWindow.java └── 可运行程序 └── SourceCodeDocxGenerator1.0.exe /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SourceCodeDocxGenerator 2 | SourceCodeDocxGenerator是一个自动生成软著申请所需的项目源代码Word文档的工具,使用它可以很方便地生成60页的源代码文档,而不用手动撸。 3 | SourceCodeDocxGenerator基于Apache POI实现,详情请参考代码。 4 | 5 | ## 更新 6 | SourceCodeDocxGenerator目前已支持图形界面操作,可下载根目录下的[SourceCodeDocxGenerator.exe](https://github.com/CharlieJiang/SourceCodeDocxGenerator/blob/main/SourceCodeDocxGenerator.exe)文件直接运行。 7 | * **图形界面使用截图** 8 | ![图形界面未输入](screenshot/gui_unInput.png)![图形界面已输入](screenshot/gui_input.png) 9 | * **参数说明** 10 | **忽略目录**参数用于填写扫描时需要忽略的源代码目录,只需要输入目录名而无需输入目录的相对或绝对路径,多个目录间以空格分割,如"build debug release"。 11 | 其它参数请参考下方说明。 12 | 13 | ## 生成的源代码Word文档示例 14 | ![源代码Word文档截图示例](screenshot/docx.png) 15 | 16 | ## 注意 17 | 1. ~~此工具目前只支持生成一种文件类型的源代码文档,如.java,如果要过滤多种文件类型,可以考虑修改此项目源码。~~目前已支持生成多种文件类型的源代码文档,具体使用方法见下方。 18 | 2. 此项目是为了Android软件申请软著开发,所以在目录过滤时未考虑Java项目以及其他语言项目,可能会存在将项目自动生成的代码写进文档中的情况,后续会进行改进。 19 | 3. 为了保证源代码的完整性,最后一个准备写入Word的源代码文件的内容会被全部写入Word中,由此也会导致最终生成的源代码Word文档的页数可能会超过60页,这时就需要手动调整文档页数。 20 | 21 | ## 使用SourceCodeDocxGenerator.jar 22 | 本项目已打包成可运行的jar文件,即项目中的SourceCodeDocxGenerator.jar,可以直接在命令行中使用,使用命令格式如下: 23 | ``` 24 | java -Dfile.encoding=utf-8 -jar jar文件路径 项目路径 软件名称 版本号 是否分为前后各30页 源代码文件类型 25 | ``` 26 | 其中`-Dfile.encoding=utf-8`指定了JVM的字符集为UTF-8,以保证生成的Word文档中的中文不会出现乱码。 27 | `jar文件路径`是SourceCodeDocxGenerator.jar下载到本地的路径 28 | `项目路径`是源代码项目的本地路径 29 | `软件名称`和`版本号`是用于生成Word文档的页眉的 30 | `是否分为前后各30页`是区分要前后各30页写入源码(true)还是顺序写入60页源码(false) 31 | `源代码文件类型`是指要从项目中提取的源代码的文件类型,如.java等,目前已支持多种文件类型,以空格区分。如“.java .kt”。 32 | 使用SourceCodeDocxGenerator.jar的命令举例: 33 | ``` 34 | java -Dfile.encoding=utf-8 -jar D:\Github\SourceCodeDocxGenerator.jar D:\git_workspace\MerchantClient\app XX商户端 V1.0.7 true .java .kt 35 | ``` 36 | -------------------------------------------------------------------------------- /SourceCodeDocxGenerator.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CharlieJiang/SourceCodeDocxGenerator/e2ca57aecfdb91b513bdcebbdf05a96b8d6f26ef/SourceCodeDocxGenerator.exe -------------------------------------------------------------------------------- /SourceCodeDocxGenerator.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /SourceCodeDocxGenerator_1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CharlieJiang/SourceCodeDocxGenerator/e2ca57aecfdb91b513bdcebbdf05a96b8d6f26ef/SourceCodeDocxGenerator_1.1.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.cocoas 8 | SourceCodeDocxGenerator 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 8 17 | 8 18 | 19 | 20 | 21 | 22 | maven-assembly-plugin 23 | 2.4 24 | 25 | 26 | jar-with-dependencies 27 | 28 | 29 | 30 | com.cocoas.CodeDocxGenerator 31 | 32 | 33 | 34 | 35 | 36 | make-assembly 37 | package 38 | 39 | single 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.apache.poi 51 | poi-ooxml 52 | 3.17 53 | 54 | 55 | 56 | org.apache.poi 57 | ooxml-schemas 58 | 1.3 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /screenshot/docx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CharlieJiang/SourceCodeDocxGenerator/e2ca57aecfdb91b513bdcebbdf05a96b8d6f26ef/screenshot/docx.png -------------------------------------------------------------------------------- /screenshot/gui_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CharlieJiang/SourceCodeDocxGenerator/e2ca57aecfdb91b513bdcebbdf05a96b8d6f26ef/screenshot/gui_input.png -------------------------------------------------------------------------------- /screenshot/gui_unInput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CharlieJiang/SourceCodeDocxGenerator/e2ca57aecfdb91b513bdcebbdf05a96b8d6f26ef/screenshot/gui_unInput.png -------------------------------------------------------------------------------- /src/main/java/cocoas/CodeDocxGenerator.java: -------------------------------------------------------------------------------- 1 | package cocoas; 2 | 3 | import org.apache.poi.wp.usermodel.HeaderFooterType; 4 | import org.apache.poi.xwpf.usermodel.*; 5 | import org.apache.xmlbeans.impl.xb.xmlschema.SpaceAttribute; 6 | import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; 7 | 8 | import javax.swing.*; 9 | import java.io.*; 10 | import java.lang.reflect.Field; 11 | import java.math.BigInteger; 12 | import java.util.*; 13 | 14 | /** 15 | * 描述:使用Apache poi自动生成软著申请所需的项目源代码Word文档 16 | * 作者:蒋庆意 17 | * 日期时间:2020/12/16 13:53 18 | */ 19 | public class CodeDocxGenerator { 20 | 21 | private String PROJECT_PATH = "";// 项目路径 22 | private String DOC_SAVE_PATH ="";// 生成的源代码Word文档的保存路径 23 | private String HEADER = "";// 软件名称+版本号 24 | private List FILE_TYPES;// 需要查找的文件类型 25 | private int totalLines = 0;// 代码总行数 26 | private final int PAGE_LINES = 53;// 文档每页行数 27 | private final int MAX_LINES = PAGE_LINES * 60; // 限制代码的最大行数 28 | private final long PAGE_MARGIN_VERTICAL = 1080L;// 页面上下边距 29 | private final long PAGE_MARGIN_HORIZONTAL = 720L;// 页面左右边距 30 | private boolean IS_HALF = false;// 文档是否分为前后各30页 31 | private List IGNORE_DIRS;// 需要扫描时忽略的文件夹 32 | 33 | // public static void main(String[] args) { 34 | // CodeDocxGenerator cdg = new CodeDocxGenerator(); 35 | // cdg.start(args,null); 36 | // } 37 | 38 | public CodeDocxGenerator(){} 39 | 40 | /** 41 | * 开始 42 | * @param args 43 | */ 44 | public void start(String[] args, List ignoreDirs){ 45 | LogUtils.println("开始"); 46 | // 四个参数处理:项目源代码目录、软件名称、版本号、源码文件类型 47 | if(args == null || args.length < 5){ 48 | LogUtils.println("参数错误,请输入参数:源代码项目目录、软件名称、版本号、是否分为前后各30页(true/false)、源代码文件类型(以.开始,支持多个,以空格区分)。参数间以空格区分。"); 49 | System.exit(0); 50 | } 51 | PROJECT_PATH = args[0]; 52 | HEADER = args[1] + args[2]; 53 | IS_HALF = Boolean.parseBoolean(args[3]); 54 | FILE_TYPES = new ArrayList<>(); 55 | // 遍历获取选择的源码文件类型 56 | for(int i = 4;i < args.length;i++){ 57 | if(args[i] != null && !args[i].isEmpty()){ 58 | FILE_TYPES.add(args[i]); 59 | } 60 | } 61 | IGNORE_DIRS = ignoreDirs == null ? new ArrayList<>() : ignoreDirs; 62 | DOC_SAVE_PATH = PROJECT_PATH + "\\SourceCode.docx"; 63 | LogUtils.println("获取参数成功"); 64 | LogUtils.println("源代码项目目录:" + PROJECT_PATH); 65 | LogUtils.println("软件名称:" + args[1]); 66 | LogUtils.println("版本号:" + args[2]); 67 | LogUtils.print("源代码文件类型:" ); 68 | FILE_TYPES.stream().forEach(LogUtils::print); 69 | LogUtils.println(""); 70 | LogUtils.println("写入方式:" + (IS_HALF?"前后各30页":"顺序60页")); 71 | LogUtils.print("扫描忽略目录:" ); 72 | IGNORE_DIRS.stream().forEach(LogUtils::print); 73 | LogUtils.println(""); 74 | generateSourceCodeDocx(PROJECT_PATH); 75 | } 76 | 77 | /** 78 | * 生成源代码Word文档 79 | * @param projectPath 源代码目录 80 | */ 81 | private void generateSourceCodeDocx(String projectPath){ 82 | //扫描项目中符合要求的文件 83 | LogUtils.println("开始扫描文件"); 84 | List files = FileUtils.scanFiles(projectPath, FILE_TYPES,IGNORE_DIRS); 85 | LogUtils.println("扫描文件完成"); 86 | LogUtils.println("文件总数:" + files.size()); 87 | if(files.size() <= 0){ 88 | MsgHintUtil.showHint("未扫描到符合要求的文件"); 89 | return ; 90 | } 91 | // 创建一个Word:存放源代码 92 | XWPFDocument doc = new XWPFDocument(); 93 | // 设置Word的页边距:保证每页不少于50行代码,且尽量保证每行代码不换行 94 | setPageMargin(doc,PAGE_MARGIN_VERTICAL,PAGE_MARGIN_HORIZONTAL); 95 | // 迭代代码文件将源代码写入Word中 96 | LogUtils.println("开始写入Word文档"); 97 | if(IS_HALF){// 按前后各30页写入源码文档中 98 | // 先读取前30页 99 | files.forEach(f -> 100 | { 101 | if(totalLines < MAX_LINES/2){// 行数达到要求则不再写入 102 | writeFileToDocx(f,doc); 103 | } 104 | } 105 | ); 106 | //反转文件列表后继续读取后30页 107 | Collections.reverse(files); 108 | files.forEach(f -> 109 | { 110 | if(totalLines < MAX_LINES){// 行数达到要求则不再写入 111 | writeFileToDocx(f,doc); 112 | } 113 | } 114 | ); 115 | }else{ // 从开始写入60页 116 | files.forEach(f -> 117 | { 118 | if(totalLines < MAX_LINES){// 行数达到要求则不再写入 119 | writeFileToDocx(f,doc); 120 | } 121 | } 122 | ); 123 | } 124 | LogUtils.println("写入Word文档完成"); 125 | LogUtils.println("Word文档输出目录:" + DOC_SAVE_PATH); 126 | // 保存Word文档 127 | saveDocx(doc,DOC_SAVE_PATH); 128 | LogUtils.println("统计代码行数:" + totalLines); 129 | // Word添加页眉:显示软件名称、版本号和页码 130 | createPageHeader(HEADER); 131 | LogUtils.println("结束"); 132 | } 133 | 134 | /** 135 | * 创建页码:通过在页眉中插入Word中代表页码的域代码{PAGE \* MERGEFORMAT}来显示页码 136 | * @param paragraph 段落 137 | */ 138 | private void createPageNum(XWPFParagraph paragraph){ 139 | // Word中域代码的语法是 {域名称 指令 可选开关} ,其中大括号不能直接写,只能通过代码来生成或表示 140 | // 下面三个步骤就是创建左大括号{、域代码内容、右大括号} 141 | // 创建左大括号{ 142 | XWPFRun run = paragraph.createRun(); 143 | CTFldChar fldChar = run.getCTR().addNewFldChar(); 144 | fldChar.setFldCharType(STFldCharType.Enum.forString("begin")); 145 | 146 | // 创建域代码内容 147 | run = paragraph.createRun(); 148 | CTText ctText = run.getCTR().addNewInstrText(); 149 | ctText.setStringValue("PAGE \\* MERGEFORMAT"); 150 | ctText.setSpace(SpaceAttribute.Space.Enum.forString("preserve")); 151 | 152 | // 创建右大括号} 153 | fldChar = run.getCTR().addNewFldChar(); 154 | fldChar.setFldCharType(STFldCharType.Enum.forString("end")); 155 | } 156 | 157 | /** 158 | * Word添加页眉 159 | * @param header 页眉内容 160 | */ 161 | private void createPageHeader(String header){ 162 | try { 163 | // 以已存在的Word文件创建文档对象 164 | XWPFDocument doc = new XWPFDocument(new FileInputStream(new File(DOC_SAVE_PATH))); 165 | 166 | //生成偶数页的页眉 167 | createPageHeader(doc,HeaderFooterType.EVEN,header); 168 | 169 | //生成奇数页的页眉 170 | createPageHeader(doc,HeaderFooterType.DEFAULT,header); 171 | 172 | // 反射添加页眉 173 | Field filedSet = XWPFDocument.class.getDeclaredField("settings"); 174 | filedSet.setAccessible(true); 175 | XWPFSettings xwpfsettings = (XWPFSettings) filedSet.get(doc); 176 | 177 | Field filedCtSet = XWPFSettings.class.getDeclaredField("ctSettings"); 178 | filedCtSet.setAccessible(true); 179 | CTSettings ctSettings = (CTSettings) filedCtSet.get(xwpfsettings); 180 | ctSettings.addNewEvenAndOddHeaders(); 181 | 182 | // 获取文档页数 183 | // int pageNums = doc.getProperties().getExtendedProperties() 184 | // .getUnderlyingProperties().getPages();// 计算结果有问题 185 | int pageNums = totalLines/PAGE_LINES + 1;// 根据统计的代码行数计算总页数 186 | // 保存文档 187 | doc.write(new FileOutputStream(DOC_SAVE_PATH)); 188 | doc.close(); 189 | MsgHintUtil.showFinishHint(DOC_SAVE_PATH,pageNums,totalLines); 190 | }catch (Exception e){ 191 | LogUtils.error("Word添加页眉出错:" + e.getMessage()); 192 | } 193 | } 194 | 195 | /** 196 | * 创建页眉,页眉内容包含软件名称、版本号和页码。 197 | *
其中软件名称和版本号合并居左,页码居右 198 | * @param doc 199 | * @param type 页眉类型:决定创建的是奇数页页眉还是偶数页页眉 200 | * @param header 页眉显示的文本内容 201 | */ 202 | private void createPageHeader(XWPFDocument doc, HeaderFooterType type, String header){ 203 | // 创建页眉段落 204 | XWPFParagraph paragraph = doc.createHeader(type).createParagraph(); 205 | paragraph.setAlignment(ParagraphAlignment.LEFT);// 页眉内容左对齐 206 | paragraph.setVerticalAlignment(TextAlignment.CENTER);// 页眉内容垂直居中 207 | // paragraph.setBorderTop(Borders.THICK); 208 | 209 | // 创建tab,用于定位页码,让页码居右显示 210 | CTTabStop tabStop = paragraph.getCTP().getPPr().addNewTabs().addNewTab(); 211 | tabStop.setVal(STTabJc.RIGHT); 212 | int twipsPerInch = 720; 213 | tabStop.setPos(BigInteger.valueOf(15 * twipsPerInch)); 214 | 215 | // 创建显示header的XWPFRun,XWPFRun代表一个文本显示区域 216 | XWPFRun run = paragraph.createRun(); 217 | run.setText(header); 218 | run.addTab();// 在header后面追加一个tab,这样页码就只能在tab后面显示,也就是变相让页面居右 219 | createPageNum(paragraph);// 创建页码 220 | } 221 | 222 | /** 223 | * 设置Word的页边距:上下边距控制每页至少显示50行,左右边距控制每行代码尽量不会自动换行 224 | * @param doc 225 | * @param marginVertical 上下边距 226 | * @param marginHorizontal 左右边距 227 | */ 228 | private void setPageMargin(XWPFDocument doc,long marginVertical,long marginHorizontal){ 229 | CTSectPr sectPr = doc.getDocument().getBody().addNewSectPr(); 230 | CTPageMar pageMar = sectPr.addNewPgMar(); 231 | pageMar.setTop(BigInteger.valueOf(marginVertical)); 232 | pageMar.setBottom(BigInteger.valueOf(marginVertical)); 233 | pageMar.setLeft(BigInteger.valueOf(marginHorizontal)); 234 | pageMar.setRight(BigInteger.valueOf(marginHorizontal)); 235 | } 236 | 237 | /** 238 | * 单个源码文件写入Word 239 | * @param filePath 源码文件路径 240 | */ 241 | private void writeFileToDocx(String filePath, XWPFDocument doc){ 242 | LogUtils.println(filePath); 243 | // 写入文件标题 244 | XWPFParagraph titleP = doc.createParagraph();// 新建文件标题段落 245 | XWPFRun titleRun = titleP.createRun();// 创建段落文本 246 | titleRun.setText(FileUtils.getFileName(filePath)); 247 | totalLines++;// 文件名行计数 248 | 249 | // 写入文件内容 250 | XWPFParagraph paragraph = doc.createParagraph(); // 新建文件内容段落 251 | // 设置段落对齐方式 252 | paragraph.setAlignment(ParagraphAlignment.LEFT); 253 | paragraph.setSpacingLineRule(LineSpacingRule.EXACT); 254 | 255 | XWPFRun run ; 256 | List lines = FileUtils.readFile(filePath); 257 | for(int i = 0;i < lines.size();i++){// 将代码一行行写入Word中 258 | run = paragraph.createRun();// 创建段落文本 259 | run.setText(lines.get(i));// 设置段落文本 260 | if(i < lines.size() - 1){// 最后一行不用换行:防止两个源码文件间出现空行 261 | run.addBreak();// 设置换行 262 | } 263 | totalLines++;// 代码行计数 264 | if(lines.get(i).length() > 125){// 当一行代码的长度超过125时,应该会发生换行,一行代码在Word中可能会变成两行甚至更多行 265 | totalLines++;// 代码自动换行计数 266 | } 267 | } 268 | } 269 | 270 | /** 271 | * Word保存到本地 272 | * @param doc 273 | */ 274 | public void saveDocx(XWPFDocument doc, String savePath){ 275 | // 创建文件输出流:保存Word到本地 276 | try { 277 | FileOutputStream fout = new FileOutputStream(savePath); 278 | doc.write(fout); 279 | fout.close(); 280 | } catch (FileNotFoundException e) { 281 | LogUtils.error("保存Word文档到本地时发生错误:" + e.getMessage()); 282 | } catch (IOException e) { 283 | LogUtils.error("保存Word文档到本地时发生错误:" + e.getMessage()); 284 | } 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/cocoas/FileUtils.java: -------------------------------------------------------------------------------- 1 | package cocoas; 2 | 3 | import java.io.*; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | /** 9 | * 描述:文件操作帮助类 10 | * 作者:蒋庆意 11 | * 日期时间:2021/1/18 11:27 12 | *

13 | * cocoasjiang@foxmail.com 14 | */ 15 | public class FileUtils { 16 | 17 | /** 18 | * 获取文件名 19 | * @param filePath 文件路径 20 | * @return 21 | */ 22 | public static String getFileName(String filePath){ 23 | if(null == filePath || new File(filePath).isDirectory()){ 24 | return ""; 25 | } 26 | File file = new File(filePath); 27 | return file.getName(); 28 | } 29 | 30 | /** 31 | * 判断文件是否符合指定的文件类型 32 | * @param f 33 | * @param fileTypes 34 | * @return 35 | */ 36 | public static boolean matchFile(File f, List fileTypes){ 37 | if(f == null || f.isDirectory()){ 38 | return false; 39 | } 40 | for(String fileType : fileTypes){ 41 | if(f.getAbsolutePath().endsWith(fileType)){ 42 | return true; 43 | } 44 | } 45 | return false; 46 | } 47 | 48 | 49 | /** 50 | * 按行读取文件内容:过滤空行和注释 51 | * @param filePath 52 | * @return 53 | */ 54 | public static List readFile(String filePath){ 55 | if(null == filePath || new File(filePath).isDirectory()){ 56 | return new ArrayList<>(); 57 | } 58 | List lines = new ArrayList<>(); 59 | try { 60 | BufferedReader reader = new BufferedReader(new FileReader(new File(filePath))); 61 | String line = reader.readLine(); 62 | while(null != line){ 63 | if(filterLine(line)){// 过滤不符合要求的代码行 64 | lines.add(line); 65 | } 66 | line = reader.readLine(); 67 | } 68 | return lines; 69 | } catch (FileNotFoundException e) { 70 | LogUtils.error("读取文件<" + filePath + ">出错:" + e.getMessage()); 71 | } catch (IOException e) { 72 | LogUtils.error("读取文件<" + filePath + ">出错:" + e.getMessage()); 73 | } 74 | return new ArrayList<>(); 75 | } 76 | 77 | /** 78 | * 过滤不符合要求的代码行 79 | * @param line 80 | * @return 81 | */ 82 | public static boolean filterLine(String line){ 83 | // 过滤空行 84 | if(null == line || line.trim().length() == 0){ 85 | return false; 86 | } 87 | String lineTrim = line.trim(); 88 | // 过滤一般的行注释//、块注释/* */、文档注释/** */ 89 | if(lineTrim.startsWith("//") || lineTrim.startsWith("/*") 90 | || lineTrim.startsWith("*")){ 91 | return false; 92 | } 93 | // 过滤html、xml注释: 94 | if(lineTrim.startsWith("