Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

文件 File 递归复制 修改后缀名 生成Markdown目录 字符串解析 MD


目录

使用方式

工具类的作用:生成GitHub上仓库MyAndroidBlogsREANME.MD中博客的目录。

使用步骤式:

1、通过为知笔记导出指定目录下的所有文件,导出为U8格式的纯文本

2、执行以下代码

  1. String DIR = "D:/为知笔记导出/导出的笔记";
  2. MDUtils.modifyFile(new File(DIR)); //递归修改文件后缀名,同时删除不匹配的文件,并修改文件中不正常的字符
  3. TOCUtils.insertToc(new File(DIR), false); //递归为 markdown 文件生成目录
  4. DirUtils.getFormatFilesNames(new File(DIR)); //递归获取文件名,并将格式化后的内容复制到粘贴板

3、将粘贴板中的内容粘贴到REANME.MD

设计

一些常量

  1. private static final String DIR = "D:/为知笔记导出/MyAndroidBlogs";
  2. private static final String WEBSITE = "https://github.com/baiqiantao/MyAndroidBlogs/blob/master/";
  3. private static final String FILTER = "MD";
  4. private static final String SUFFIX_MD_TXT = ".md.txt";
  5. private static final String SUFFIX_TXT = ".txt";
  6. private static final String SUFFIX_MD = ".md";
  7. private static final String SPACE = " ";
  8. private static final String SPACE_FORMAT = "%20";
  9. private static final String[] EXCLUDE_FILES = new String[]{".git", "README.md"};
  10. private static final String[] REPLACE_STRINGS = new String[] { " " };//特殊字符
  11. private static final String BLANK = " ";//空格

文件目录递归修改

递归修改文件后缀名并修改文件内容

  1. /**
  2. * 递归修改文件后缀名并修改文件内容
  3. */
  4. public static void modifyFile(File from) {
  5. if (from == null || !from.exists()) {
  6. throw new RuntimeException("源目录或文件不存在");
  7. }
  8. if (from.isFile()) {
  9. if (from.getName().contains(FILTER)) {//只处理带指定标识的文件
  10. copyAndModifyFileContent(from, new File(from.getParent(), getNewName(from)));
  11. }
  12. from.delete();//删除源文件
  13. return;
  14. }
  15. File[] listFiles = from.listFiles();
  16. if (listFiles == null || listFiles.length == 0) {
  17. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  18. from.delete();//删除空文件夹
  19. return;
  20. }
  21. for (File file : listFiles) {
  22. if (file.isDirectory()) {
  23. modifyFile(file);//递归
  24. } else if (file.isFile()) {
  25. if (file.getName().contains(FILTER)) {//只处理带指定标识的文件
  26. copyAndModifyFileContent(file, new File(file.getParent(), getNewName(file)));
  27. }
  28. file.delete();
  29. }
  30. }
  31. listFiles = from.listFiles();
  32. if (listFiles == null || listFiles.length == 0) {
  33. System.out.println("----------------------" + from.getName() + "的文件全部不符合要求");
  34. from.delete();//删除空文件夹
  35. }
  36. }

递归修改文件后缀名

  1. /**
  2. * 递归修改文件后缀名
  3. */
  4. public static void modifyFileSuffix(File from) {
  5. if (from == null || !from.exists()) {
  6. throw new RuntimeException("源目录或文件不存在");
  7. }
  8. if (from.isFile()) {
  9. if (from.getName().contains(FILTER)) {//只处理带指定标识的文件
  10. from.renameTo(new File(from.getParent(), getNewName(from)));
  11. } else {
  12. from.delete();//删除源文件
  13. }
  14. return;
  15. }
  16. File[] listFiles = from.listFiles();
  17. if (listFiles == null || listFiles.length == 0) {
  18. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  19. from.delete();//删除空文件夹
  20. return;
  21. }
  22. for (File file : listFiles) {
  23. if (file.isDirectory()) {
  24. modifyFileSuffix(file);//递归
  25. } else if (file.isFile()) {
  26. if (file.getName().contains(FILTER)) {//只处理带指定标识的文件
  27. file.renameTo(new File(file.getParent(), getNewName(file)));
  28. } else {
  29. file.delete();
  30. }
  31. }
  32. }
  33. listFiles = from.listFiles();
  34. if (listFiles == null || listFiles.length == 0) {
  35. System.out.println("----------------------" + from.getName() + "的文件全部不符合要求");
  36. from.delete();//删除空文件夹
  37. }
  38. }
  1. /**
  2. * 获取重命名的文件名
  3. */
  4. private static String getNewName(File file) {
  5. String name = file.getName();
  6. if (name.endsWith(SUFFIX_MD_TXT)) { //处理指定后缀的文件
  7. name = name.substring(0, name.indexOf(SUFFIX_MD_TXT) + SUFFIX_MD.length());
  8. } else if (name.endsWith(SUFFIX_TXT)) {
  9. name = name.substring(0, name.indexOf(SUFFIX_TXT)) + SUFFIX_MD;
  10. }
  11. return name;
  12. }

递归获取格式化后的文件名

  1. /**
  2. * 获取指定目录及其子目录下的文件的文件名,并对文件名进行格式化,并存储到一个指定的集合中,并将内容复制到粘贴板
  3. */
  4. public static List<String> getFormatFilesNames(File from) {
  5. List<String> filePathList = new ArrayList<>();
  6. getDirFormatFilesNames(filePathList, from, 0);
  7. StringBuilder sBuilder = new StringBuilder();
  8. for (String string : filePathList) {
  9. System.out.print(string);
  10. sBuilder.append(string);
  11. }
  12. setSysClipboardText(sBuilder.toString());
  13. return filePathList;
  14. }
  1. /**
  2. * 递归获取指定目录及其子目录下的文件的文件名,并对文件名进行格式化,并存储到一个指定的集合中
  3. *
  4. * @param filePathList 将结果保存到指定的集合中
  5. * @param from 要遍历的目录
  6. * @param curLeval 记录当前递归所在层级
  7. */
  8. private static void getDirFormatFilesNames(List<String> filePathList, File from, int curLeval) {
  9. if (from == null || !from.exists()) {
  10. throw new RuntimeException("源目录或文件不存在");
  11. } else if (isExcludeFile(from.getName())) {
  12. System.out.println("----------------------" + "忽略文件" + from.getName());
  13. return;
  14. } else {
  15. filePathList = filePathList == null ? new ArrayList<String>() : filePathList;
  16. filePathList.add(getTitle(curLeval, from));
  17. }
  18. curLeval++;
  19. File[] files = from.listFiles();
  20. if (files == null || files.length == 0) {
  21. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  22. return;
  23. }
  24. for (File file : files) {
  25. if (file.isDirectory()) {
  26. getDirFormatFilesNames(filePathList, file, curLeval);//递归
  27. } else if (file.isFile() && !isExcludeFile(file.getName())) {
  28. filePathList.add(getTitle(curLeval, file));
  29. }
  30. }
  31. }
  1. /**
  2. * 判断是否是忽略的文件
  3. */
  4. private static boolean isExcludeFile(String fileName) {
  5. for (String name : EXCLUDE_FILES) {
  6. if (name.equals(fileName)) {
  7. return true;
  8. }
  9. }
  10. return false;
  11. }

文件复制

递归复制目录下的所有文件

  1. /**
  2. * 递归复制目录下的所有文件
  3. */
  4. public static void copyFiles(File from, File to) {
  5. if (from == null || !from.exists() || to == null) {
  6. throw new RuntimeException("源文件或目标文件不存在");
  7. }
  8. if (from.equals(to)) {
  9. System.out.println("----------------------源文件和目标文件是同一个" + from.getAbsolutePath());
  10. }
  11. if (from.isDirectory()) {
  12. if (to.isFile()) {
  13. throw new RuntimeException("目录不能复制为文件");
  14. }
  15. if (!to.exists()) {
  16. to.mkdirs();//创建目录
  17. }
  18. } else {
  19. if (to.isDirectory()) {
  20. throw new RuntimeException("文件不能复制为目录");
  21. }
  22. copyFile(from, to);//文件的话直接复制
  23. return;
  24. }
  25. File[] files = from.listFiles();
  26. if (files == null || files.length == 0) {
  27. System.out.println(from.getName() + "中不存在任何文件");
  28. return;
  29. }
  30. for (File file : files) {
  31. if (file.isDirectory()) {
  32. File copyDir = new File(to, file.getName());
  33. if (!copyDir.exists()) {
  34. copyDir.mkdirs();
  35. System.out.println("创建子目录\t\t" + copyDir.getAbsolutePath());
  36. }
  37. copyFiles(file, copyDir);//递归
  38. } else if (file.getName().contains(FILTER)) {
  39. copyFile(file, new File(to, file.getName()));
  40. }
  41. }
  42. }

复制一个文件或一个目录

  1. /**
  2. * 复制一个文件或一个目录(不会递归复制目录下的文件)
  3. */
  4. public static void copyFile(File from, File to) {
  5. if (from == null || !from.exists() || to == null) {
  6. throw new RuntimeException("源文件或目标文件不存在");
  7. }
  8. if (from.equals(to)) {
  9. System.out.println("----------------------源文件和目标文件是同一个" + from.getAbsolutePath());
  10. }
  11. if (from.isDirectory()) {
  12. if (to.isFile()) {
  13. throw new RuntimeException("目录不能复制为文件");
  14. }
  15. if (!to.exists()) {
  16. to.mkdirs();//创建目录
  17. }
  18. return; //没有下面的刘操作
  19. } else {
  20. if (to.isDirectory()) {
  21. throw new RuntimeException("文件不能复制为目录");
  22. }
  23. if (!to.getParentFile().exists()) {
  24. to.getParentFile().mkdirs(); //复制父目录
  25. }
  26. }
  27. try {
  28. BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(from));
  29. BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(to));
  30. int ch;
  31. while ((ch = bufis.read()) != -1) {
  32. bufos.write(ch);
  33. }
  34. bufis.close();
  35. bufos.close();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. }

复制一个文件并修改文件内容

  1. /**
  2. * 复制一个文件或目录(不会复制目录下的文件),复制的时候会修改文件中的一些内容
  3. */
  4. public static void copyAndModifyFileContent(File from, File to) {
  5. if (from == null || !from.exists() || to == null) {
  6. throw new RuntimeException("源文件或目标文件不存在");
  7. }
  8. if (from.equals(to)) {
  9. System.out.println("----------------------源文件和目标文件是同一个" + from.getAbsolutePath());
  10. }
  11. if (from.isDirectory()) {
  12. if (to.isFile()) {
  13. throw new RuntimeException("目录不能复制为文件");
  14. }
  15. if (!to.exists()) {
  16. to.mkdirs();//创建目录
  17. }
  18. return; //没有下面的刘操作
  19. } else {
  20. if (to.isDirectory()) {
  21. throw new RuntimeException("文件不能复制为目录");
  22. }
  23. if (!to.getParentFile().exists()) {
  24. to.getParentFile().mkdirs(); //复制父目录
  25. }
  26. }
  27. try {
  28. BufferedReader bufr = new BufferedReader(new FileReader(from));
  29. BufferedWriter bufw = new BufferedWriter(new FileWriter(to));
  30. String line;
  31. //另外开辟一个缓冲区,存储读取的一行数据,返回包含该行内容的字符串,不包含换行符,如果已到达流末尾,则返回【 null】
  32. while ((line = bufr.readLine()) != null) {
  33. for (String string : REPLACE_STRINGS) {
  34. line = line.replace(string, BLANK);//替换为空格
  35. }
  36. bufw.write(line);
  37. bufw.write(BLANK + BLANK);//加两个空格
  38. bufw.newLine();// 写入一个行分隔符
  39. bufw.flush();
  40. }
  41. bufr.close();
  42. bufw.close();
  43. } catch (IOException e) {
  44. e.printStackTrace();
  45. }
  46. }

其他工具方法

将字符串复制到剪切板

  1. /**
  2. * 将字符串复制到剪切板
  3. */
  4. public static void setSysClipboardText(String writeMe) {
  5. Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
  6. Transferable tText = new StringSelection(writeMe);
  7. clip.setContents(tText, null);
  8. }

生成格式化的 Markdown 目录

  1. /***
  2. * 格式化目录
  3. */
  4. private static String getTitle(int level, File file) {
  5. StringBuilder sb = new StringBuilder();
  6. StringBuilder parentPath = new StringBuilder();
  7. File parent = file;
  8. for (int x = 1; x < level; x++) {
  9. sb.append("\t");
  10. parent = parent.getParentFile();//逐级获取父文件
  11. parentPath.insert(0, parent.getName() + "/");//在前面插入父文件的文件名
  12. }
  13. sb.append("-").append(" ")//无序列表
  14. .append("[")//超链接显示的字符
  15. .append(file.getName().endsWith(SUFFIX_MD) ? file.getName().substring(0, file.getName().lastIndexOf(SUFFIX_MD)) : file.getName())//
  16. .append("]")//
  17. .append("(")//拼接超链接
  18. .append(WEBSITE)//前缀
  19. .append(parentPath.toString())//父目录
  20. .append(file.getName().replaceAll(SPACE, SPACE_FORMAT))//文件名
  21. .append(")")//
  22. .append("\n");
  23. return sb.toString();
  24. }

生成 Markdown 的目录

用到的常量

  1. private static final String HEADER_STRING = "#";
  2. private static final char HEADER_CHAR = '#';
  3. private static final String TOC_TITLE = "";//在目录前插入的内容
  4. private static final String REPLACE_TOC = "[TOC]"; //目录要替换的内容
  5. private static final String HEADER_1 = "="; //一级目录
  6. private static final String HEADER_2 = "--"; //二级目录
  7. private static final String PATTERN = "%s- [%s](#%s)";//标题的格式
  8. private static final String SPACES = " ";
  9. private static final String CODES = "%([abcdef]|\\d){2,2}";
  10. private static final String SPECIAL_CHARS = "[\\/?!:\\[\\]`.,()*\"';{}+=<>~\\$|#]";
  11. private static final String DASH = "-";
  12. private static final String EMPTY = "";
  13. private static final String AFFIX = "\t";

递归为文件生成Markdown目录

  1. /**
  2. * 递归为文件生成Markdown目录
  3. */
  4. public static void insertToc(File from) {
  5. if (from == null || !from.exists()) {
  6. throw new RuntimeException("源目录或文件不存在");
  7. }
  8. if (from.isFile()) {
  9. insertTocIntoFile(from, from, REPLACE_TOC, Integer.MAX_VALUE);
  10. return;
  11. }
  12. File[] listFiles = from.listFiles();
  13. if (listFiles == null || listFiles.length == 0) {
  14. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  15. return;
  16. }
  17. for (File file : listFiles) {
  18. if (file.isDirectory()) {
  19. insertToc(file);//递归
  20. } else if (file.isFile()) {
  21. insertTocIntoFile(file, file, REPLACE_TOC, Integer.MAX_VALUE);
  22. }
  23. }
  24. }
  1. /**
  2. * 为文件生成Markdown目录
  3. */
  4. public static void insertTocIntoFile(File from, File to, String replaceAt, int deepLevel) {
  5. BufferedReader reader = null;
  6. try {
  7. reader = new BufferedReader(new InputStreamReader(new FileInputStream(from)));
  8. String line;//当前行的内容
  9. String previousLine = null;
  10. List<String> contentList = new ArrayList<>();//每一行的内容集合
  11. List<String> titleList = new ArrayList<>();//标题集合
  12. int currentLine = 0;
  13. int patternLineNumber = 1; //目录插在那一行
  14. while ((line = reader.readLine()) != null) {
  15. ++currentLine;
  16. boolean skipLine = false;
  17. String trimLineString = line.trim();
  18. if (trimLineString.startsWith(HEADER_STRING)) { //检测到标题
  19. int count = getCharCount(trimLineString, HEADER_CHAR);
  20. if (count < 1 || count > deepLevel) { //层级控制
  21. System.out.println("----------------------------超过最大层级 " + deepLevel);
  22. previousLine = line;
  23. continue;
  24. }
  25. String headerName = trimLineString.substring(count).trim(); //去掉层级后的文字
  26. titleList.add(getTitle(count, headerName));
  27. } else if (line.startsWith(HEADER_1) && isNotEmpty(previousLine) && line.replaceAll(HEADER_1, "").isEmpty()) {
  28. titleList.add(getTitle(1, previousLine));
  29. } else if (line.startsWith(HEADER_2) && isNotEmpty(previousLine) && line.replaceAll(HEADER_2, "").isEmpty()) {
  30. titleList.add(getTitle(2, previousLine));
  31. } else if (patternLineNumber <= 1 && line.trim().equals(replaceAt)) {
  32. patternLineNumber = currentLine; //找到这个字符串时就插在这一行(替换这个字符串),找到之后就不再继续找了
  33. skipLine = true;//忽略这一行
  34. }
  35. if (!skipLine) {
  36. contentList.add(line);
  37. }
  38. previousLine = line;
  39. }
  40. closeStream(reader); //必须先关掉这个读取,流才能开启写入流
  41. if (patternLineNumber <= 0) {
  42. System.out.println("----------------------------目录插入位置有误");
  43. return;
  44. }
  45. contentList.add(patternLineNumber - 1, TOC_TITLE);//在指定位置插入目录前面的标题
  46. for (String title : titleList) {
  47. contentList.add(patternLineNumber, title);
  48. patternLineNumber++;
  49. }
  50. writeFile(to, contentList);
  51. } catch (Exception e) {
  52. e.printStackTrace();
  53. } finally {
  54. closeStream(reader);
  55. }
  56. }
  1. /**
  2. * 写内容到指定文件
  3. */
  4. private static void writeFile(File to, List<String> contentList) {
  5. PrintWriter writer = null;
  6. try {
  7. writer = new PrintWriter(new FileWriter(to));
  8. for (String string : contentList) {
  9. writer.append(string).append("\n");
  10. }
  11. writer.close();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } finally {
  15. closeStream(writer);
  16. }
  17. }

插入的目录格式

  1. /**
  2. * 插入的目录的模型
  3. */
  4. private static String getTitle(int deepLevel, String headerName) {
  5. StringBuilder title = new StringBuilder();
  6. if (deepLevel > 1) {
  7. for (int i = 0; i < deepLevel - 1; i++) {
  8. title.append(AFFIX);
  9. }
  10. }
  11. String headerLink = headerName.trim()//
  12. .replaceAll(SPACES, DASH)//替换特殊字符,比如必须将空格换成'-'
  13. .replaceAll(CODES, EMPTY)//
  14. .replaceAll(SPECIAL_CHARS, EMPTY).toLowerCase();
  15. title.append(DASH).append(SPACES)//格式为【%s- [%s](#%s)】或【缩进- [标题][#链接]】
  16. .append("[").append(headerName).append("]")//
  17. .append("(").append("#")//
  18. .append(headerLink)//
  19. .append(")");
  20. return title.toString();
  21. }

其他一些用到的工具方法

  1. /**
  2. * 关闭流
  3. */
  4. public static void closeStream(Closeable... closeable) {
  5. for (Closeable c : closeable) {
  6. if (c != null) {
  7. try {
  8. c.close();
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. }
  1. /**
  2. * 计算字符串中字符的个数
  3. */
  4. private static int getCharCount(String string, char c) {
  5. int count = 0;
  6. for (int i = 0; i < string.length(); i++) {
  7. if (string.charAt(i) == c) {
  8. ++count;
  9. } else {
  10. break;
  11. }
  12. }
  13. return count;
  14. }
  1. /**
  2. * 获取文件行数
  3. */
  4. public static int getFileLines(File file) {
  5. LineNumberReader reader = null;
  6. try {
  7. reader = new LineNumberReader(new FileReader(file));
  8. reader.skip(Long.MAX_VALUE);
  9. return reader.getLineNumber() + 1;
  10. } catch (IOException ignored) {
  11. } finally {
  12. closeStream(reader);
  13. }
  14. return -1;
  15. }
  1. /**
  2. * 判断字符串非空
  3. */
  4. public static boolean isNotEmpty(String string) {
  5. return string != null && !string.isEmpty();
  6. }

源码

MDUtils

  1. public class MDUtils {
  2. private static final String FILTER = "MD";
  3. private static final String SUFFIX_MD_TXT = ".md.txt";
  4. private static final String SUFFIX_TXT = ".txt";
  5. private static final String SUFFIX_MD = ".md";
  6. private static final String[] REPLACE_STRINGS = new String[] { " " };//特殊字符
  7. private static final String BLANK = " ";//空格
  8. /**
  9. * 递归修改文件后缀名并修改文件内容
  10. */
  11. public static void modifyFile(File from) {
  12. if (from == null || !from.exists()) {
  13. throw new RuntimeException("源目录或文件不存在");
  14. }
  15. if (from.isFile()) {
  16. if (from.getName().contains(FILTER)) {//只处理带指定标识的文件
  17. copyAndModifyFileContent(from, new File(from.getParent(), getNewName(from)));
  18. }
  19. from.delete();//删除源文件
  20. return;
  21. }
  22. File[] listFiles = from.listFiles();
  23. if (listFiles == null || listFiles.length == 0) {
  24. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  25. from.delete();//删除空文件夹
  26. return;
  27. }
  28. for (File file : listFiles) {
  29. if (file.isDirectory()) {
  30. modifyFile(file);//递归
  31. } else if (file.isFile()) {
  32. if (file.getName().contains(FILTER)) {//只处理带指定标识的文件
  33. copyAndModifyFileContent(file, new File(file.getParent(), getNewName(file)));
  34. }
  35. file.delete();
  36. }
  37. }
  38. listFiles = from.listFiles();
  39. if (listFiles == null || listFiles.length == 0) {
  40. System.out.println("----------------------" + from.getName() + "的文件全部不符合要求");
  41. from.delete();//删除空文件夹
  42. }
  43. }
  44. /**
  45. * 复制一个文件或目录(不会复制目录下的文件),复制的时候会修改文件中的一些内容
  46. */
  47. private static void copyAndModifyFileContent(File from, File to) {
  48. if (from == null || !from.exists() || to == null) {
  49. throw new RuntimeException("源文件或目标文件不存在");
  50. }
  51. if (from.equals(to)) {
  52. System.out.println("----------------------源文件和目标文件是同一个" + from.getAbsolutePath());
  53. }
  54. if (from.isDirectory()) {
  55. if (to.isFile()) {
  56. throw new RuntimeException("目录不能复制为文件");
  57. }
  58. if (!to.exists()) {
  59. to.mkdirs();//创建目录
  60. }
  61. return; //没有下面的刘操作
  62. } else {
  63. if (to.isDirectory()) {
  64. throw new RuntimeException("文件不能复制为目录");
  65. }
  66. if (!to.getParentFile().exists()) {
  67. to.getParentFile().mkdirs(); //复制父目录
  68. }
  69. }
  70. try {
  71. BufferedReader bufr = new BufferedReader(new FileReader(from));
  72. BufferedWriter bufw = new BufferedWriter(new FileWriter(to));
  73. String line;
  74. //另外开辟一个缓冲区,存储读取的一行数据,返回包含该行内容的字符串,不包含换行符,如果已到达流末尾,则返回【 null】
  75. while ((line = bufr.readLine()) != null) {
  76. for (String string : REPLACE_STRINGS) {
  77. line = line.replace(string, BLANK);//替换为空格
  78. }
  79. bufw.write(line);
  80. bufw.write(BLANK + BLANK);//加两个空格
  81. bufw.newLine();// 写入一个行分隔符
  82. bufw.flush();
  83. }
  84. bufr.close();
  85. bufw.close();
  86. } catch (IOException e) {
  87. e.printStackTrace();
  88. }
  89. }
  90. /**
  91. * 获取重命名的文件名
  92. */
  93. private static String getNewName(File file) {
  94. String name = file.getName();
  95. if (name.endsWith(SUFFIX_MD_TXT)) { //处理指定后缀的文件
  96. name = name.substring(0, name.indexOf(SUFFIX_MD_TXT) + SUFFIX_MD.length());
  97. } else if (name.endsWith(SUFFIX_TXT)) {
  98. name = name.substring(0, name.indexOf(SUFFIX_TXT)) + SUFFIX_MD;
  99. }
  100. return name;
  101. }
  102. }

TOCUtils

  1. public class TOCUtils {
  2. private static final String HEADER_STRING = "#";
  3. private static final char HEADER_CHAR = '#';
  4. private static final String TOC_TITLE = "";//在目录前插入的内容
  5. private static final String REPLACE_TOC = "[TOC]"; //目录要替换的内容
  6. private static final String HEADER_1 = "="; //一级目录
  7. private static final String HEADER_2 = "--"; //二级目录
  8. private static final String SPACES = " ";
  9. private static final String CODES = "%([abcdef]|\\d){2,2}";
  10. private static final String SPECIAL_CHARS = "[\\/?!:\\[\\]`.,()*\"';{}+=<>~\\$|#]";
  11. private static final String DASH = "-";
  12. private static final String EMPTY = "";
  13. private static final String AFFIX = "\t";
  14. private static String ROOT_DIR = null;
  15. /**
  16. * 递归为为文件生成目录
  17. */
  18. public static void insertToc(File from, boolean addHeader) {
  19. if (from == null || !from.exists()) {
  20. throw new RuntimeException("源目录或文件不存在");
  21. } else if (ROOT_DIR == null) {
  22. ROOT_DIR = from.getAbsolutePath();
  23. }
  24. if (from.isFile()) {
  25. insertTocIntoFile(from, from, REPLACE_TOC, Integer.MAX_VALUE, addHeader);
  26. return;
  27. }
  28. File[] listFiles = from.listFiles();
  29. if (listFiles == null || listFiles.length == 0) {
  30. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  31. return;
  32. }
  33. for (File file : listFiles) {
  34. if (file.isDirectory()) {
  35. insertToc(file, addHeader);//递归
  36. } else if (file.isFile()) {
  37. insertTocIntoFile(file, file, REPLACE_TOC, Integer.MAX_VALUE, addHeader);
  38. }
  39. }
  40. }
  41. /**
  42. * 为文件生成目录
  43. */
  44. private static void insertTocIntoFile(File from, File to, String replaceAt, int deepLevel, boolean addHeader) {
  45. BufferedReader reader = null;
  46. try {
  47. reader = new BufferedReader(new InputStreamReader(new FileInputStream(from)));
  48. String line;//当前行的内容
  49. String previousLine = null;
  50. List<String> contentList = new ArrayList<>();//每一行的内容集合
  51. List<String> titleList = new ArrayList<>();//标题集合
  52. int currentLine = 0;
  53. int patternLineNumber = 1; //目录插在那一行
  54. while ((line = reader.readLine()) != null) {
  55. ++currentLine;
  56. boolean skipLine = false;
  57. String trimLineString = line.trim();
  58. if (trimLineString.startsWith(HEADER_STRING)) { //检测到标题
  59. int count = getCharCount(trimLineString, HEADER_CHAR);
  60. if (count < 1 || count > deepLevel) { //层级控制
  61. System.out.println("----------------------------超过最大层级 " + deepLevel);
  62. previousLine = line;
  63. continue;
  64. }
  65. String headerName = trimLineString.substring(count).trim(); //去掉层级后的文字
  66. System.out.println("【" + headerName + "】");
  67. titleList.add(getTitle(count, headerName));
  68. } else if (line.startsWith(HEADER_1) && isNotEmpty(previousLine) && line.replaceAll(HEADER_1, "").isEmpty()) {
  69. //titleList.add(getTitle(1, previousLine.trim()));//暂时不需要
  70. } else if (line.startsWith(HEADER_2) && isNotEmpty(previousLine) && line.replaceAll(HEADER_2, "").isEmpty()) {
  71. //titleList.add(getTitle(2, previousLine.trim()));//暂时不需要
  72. } else if (patternLineNumber <= 1 && line.trim().equals(replaceAt)) {
  73. patternLineNumber = currentLine; //找到这个字符串时就插在这一行(替换这个字符串),找到之后就不再继续找了
  74. skipLine = true;//忽略这一行
  75. }
  76. if (!skipLine) {
  77. contentList.add(line);
  78. }
  79. previousLine = line;
  80. }
  81. closeStream(reader); //必须先关掉这个读取,流才能开启写入流
  82. if (patternLineNumber <= 0) {
  83. System.out.println("----------------------------目录插入位置有误");
  84. return;
  85. }
  86. contentList.add(patternLineNumber - 1, TOC_TITLE);//在指定位置插入目录前面的标题
  87. for (String title : titleList) {
  88. contentList.add(patternLineNumber, title);
  89. patternLineNumber++;
  90. }
  91. writeFile(to, contentList, addHeader);
  92. } catch (Exception e) {
  93. e.printStackTrace();
  94. } finally {
  95. closeStream(reader);
  96. }
  97. }
  98. /**
  99. * 写内容到指定文件
  100. */
  101. private static void writeFile(File file, List<String> contentList, boolean addHeader) {
  102. PrintWriter writer = null;
  103. try {
  104. writer = new PrintWriter(new FileWriter(file));
  105. if (addHeader) {
  106. String title = file.getName().substring(0, file.getName().indexOf(".md"));
  107. String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified()));
  108. String bqtdir = file.getParent().substring(ROOT_DIR.length() + 1).replace("\\", "/");
  109. writer.append("---").append("\n")//
  110. .append("title: ").append(title).append("\n")//标题
  111. .append("date: ").append(date).append("\n")//生成日期
  112. .append("bqtdir: ").append(bqtdir).append("\n")//自定义路径
  113. .append("categories:").append("\n");//分类
  114. for (String dir : bqtdir.split("/")) {
  115. writer.append(" - ").append(dir).append("\n");//路径分类
  116. }
  117. writer.append("---").append("\n").append("\n");
  118. }
  119. for (String string : contentList) {
  120. writer.append(string).append("\n");
  121. }
  122. writer.close();
  123. } catch (IOException e) {
  124. e.printStackTrace();
  125. } finally {
  126. closeStream(writer);
  127. }
  128. }
  129. /**
  130. * 关闭流
  131. */
  132. private static void closeStream(Closeable... closeable) {
  133. for (Closeable c : closeable) {
  134. if (c != null) {
  135. try {
  136. c.close();
  137. } catch (IOException e) {
  138. e.printStackTrace();
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * 判断字符串非空
  145. */
  146. private static boolean isNotEmpty(String string) {
  147. return string != null && !string.isEmpty();
  148. }
  149. /**
  150. * 计算字符串中字符的个数
  151. */
  152. private static int getCharCount(String string, char c) {
  153. int count = 0;
  154. for (int i = 0; i < string.length(); i++) {
  155. if (string.charAt(i) == c) {
  156. ++count;
  157. } else {
  158. break;
  159. }
  160. }
  161. return count;
  162. }
  163. /**
  164. * 插入的目录的模型
  165. */
  166. private static String getTitle(int deepLevel, String headerName) {
  167. StringBuilder title = new StringBuilder();
  168. if (deepLevel > 1) {
  169. for (int i = 0; i < deepLevel - 1; i++) {
  170. title.append(AFFIX);
  171. }
  172. }
  173. String headerLink = headerName.trim()//
  174. .replaceAll(SPACES, DASH)//替换特殊字符,比如必须将空格换成'-'
  175. .replaceAll(CODES, EMPTY)//
  176. .replaceAll(SPECIAL_CHARS, EMPTY).toLowerCase();
  177. title.append(DASH).append(SPACES)//格式为【%s- [%s](#%s)】或【缩进- [标题][#链接]】
  178. .append("[").append(headerName).append("]")//
  179. .append("(").append("#")//
  180. .append(headerLink)//
  181. .append(")");
  182. return title.toString();
  183. }
  184. }

DirUtils

  1. public class DirUtils {
  2. private static final String WEBSITE = "https://github.com/baiqiantao/MyAndroidBlogs/blob/master/";
  3. private static final String SPACE = " ";
  4. private static final String SPACE_FORMAT = "%20";
  5. private static final String[] EXCLUDE_FILES = new String[] { ".git", "README.md" };
  6. private static final String SUFFIX_MD = ".md";
  7. /**
  8. * 获取指定目录及其子目录下的文件的文件名,并对文件名进行格式化,并存储到一个指定的集合中,并将内容复制到粘贴板
  9. */
  10. public static List<String> getFormatFilesNames(File from) {
  11. List<String> filePathList = new ArrayList<>();
  12. getDirFormatFilesNames(filePathList, from, 0);
  13. StringBuilder sBuilder = new StringBuilder();
  14. for (String string : filePathList) {
  15. System.out.print(string);
  16. sBuilder.append(string);
  17. }
  18. setSysClipboardText(sBuilder.toString());
  19. return filePathList;
  20. }
  21. /**
  22. * 获取指定目录及其子目录下的文件的文件名,并对文件名进行格式化,并存储到一个指定的集合中
  23. *
  24. * @param filePathList 将结果保存到指定的集合中
  25. * @param from 要遍历的目录
  26. * @param curLeval 记录当前递归所在层级
  27. */
  28. private static void getDirFormatFilesNames(List<String> filePathList, File from, int curLeval) {
  29. if (from == null || !from.exists()) {
  30. throw new RuntimeException("源目录或文件不存在");
  31. } else if (isExcludeFile(from.getName())) {
  32. System.out.println("----------------------" + "忽略文件" + from.getName());
  33. return;
  34. } else {
  35. filePathList = filePathList == null ? new ArrayList<String>() : filePathList;
  36. filePathList.add(getTitle(curLeval, from));
  37. }
  38. curLeval++;
  39. File[] files = from.listFiles();
  40. if (files == null || files.length == 0) {
  41. System.out.println("----------------------" + from.getName() + "中不存在任何文件");
  42. return;
  43. }
  44. for (File file : files) {
  45. if (file.isDirectory()) {
  46. getDirFormatFilesNames(filePathList, file, curLeval);//递归
  47. } else if (file.isFile() && !isExcludeFile(file.getName())) {
  48. filePathList.add(getTitle(curLeval, file));
  49. }
  50. }
  51. }
  52. /**
  53. * 是否是忽略的文件
  54. */
  55. private static boolean isExcludeFile(String fileName) {
  56. for (String name : EXCLUDE_FILES) {
  57. if (name.equals(fileName)) {
  58. return true;
  59. }
  60. }
  61. return false;
  62. }
  63. /**
  64. * 将字符串复制到剪切板
  65. */
  66. private static void setSysClipboardText(String writeMe) {
  67. Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
  68. Transferable tText = new StringSelection(writeMe);
  69. clip.setContents(tText, null);
  70. }
  71. /***
  72. * 格式化目录
  73. */
  74. private static String getTitle(int level, File file) {
  75. StringBuilder sb = new StringBuilder();
  76. StringBuilder parentPath = new StringBuilder();
  77. File parent = file;
  78. for (int x = 1; x < level; x++) {
  79. sb.append("\t");
  80. parent = parent.getParentFile();//逐级获取父文件
  81. parentPath.insert(0, parent.getName() + "/");//在前面插入父文件的文件名
  82. }
  83. sb.append("-").append(" ")//无序列表
  84. .append("[")//超链接显示的字符
  85. .append(file.getName().endsWith(SUFFIX_MD) ? file.getName().substring(0, file.getName().lastIndexOf(SUFFIX_MD)) : file.getName())//
  86. .append("]")//
  87. .append("(")//拼接超链接
  88. .append(WEBSITE)//前缀
  89. .append(parentPath.toString())//父目录
  90. .append(file.getName().replaceAll(SPACE, SPACE_FORMAT))//文件名
  91. .append(")")//
  92. .append("\n");
  93. return sb.toString();
  94. }
  95. }

2018-10-24

生成Markdown目录 字符串解析 MD的更多相关文章

  1. ADO.NET生成的数据库连接字符串解析

    1.概述 当我们使用ADO.NET数据实体模型生成的时候,在项目目下生成一个.edmx文件的同时,还会在app.config里面出现如下一个代码串: <?xml version="1. ...

  2. git - gitHub生成Markdown目录

    就是github-markdown-toc.go. github-markdown-toc.go Github地址 如果你有GO语言(又是你)的编译环境,可以尝试自己编译,如果没有,可以直接下载编译好 ...

  3. C#实现生成Markdown文档目录树

    前言 之前我写了一篇关于C#处理Markdown文档的文章:C#解析Markdown文档,实现替换图片链接操作 算是第一次尝试使用C#处理Markdown文档,然后最近又把博客网站的前台改了一下,目前 ...

  4. [Selenium2+python2.7][Scrap]爬虫和selenium方式下拉滚动条获取简书作者目录并且生成Markdown格式目录

    预计阅读时间: 15分钟 环境: win7 + Selenium2.53.6+python2.7 +Firefox 45.2  (具体配置参考 http://www.cnblogs.com/yoyok ...

  5. InfluxDB源码目录结构解析

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 influxdata主目录结构 [root@localhost ...

  6. 基于开源库jsoncpp的json字符串解析

    json(JavaScript Object Notation)是一种轻量级高效数据交换格式.相比于XML,其更加简洁,解析更加方便.在实习期间,我负责的程序模块,多次使用到json进行数据传输.由于 ...

  7. NET 5.0 Swagger API 自动生成MarkDown文档

    目录 1.SwaggerDoc引用 主要接口 接口实现 2.Startup配置 注册SwaggerDoc服务 注册Swagger服务 引用Swagger中间件 3.生成MarkDown 4.生成示例 ...

  8. Markdown 目录

    Markdown 目录 1. TOC TOC 全称为 Table of Content,自动列出全部标题. 用法: [toc] 在 Markdown 中,自动生成目录非常简单,只需要在恰当的位置添加 ...

  9. 好用的Markdown编辑器一览 readme.md 编辑查看

    https://github.com/pandao/editor.md https://pandao.github.io/editor.md/examples/index.html Editor.md ...

随机推荐

  1. C#开发Unity游戏教程之判断语句

    C#开发Unity游戏教程之判断语句 游戏执行路径的选择——判断 玩家在游戏时,无时无刻不在通过判断做出选择.例如,正是因为玩家做出的选择不同,才导致游戏朝着不同的剧情发展,因此一个玩家可以对一个游戏 ...

  2. BZOJ2160: 拉拉队排练

    Description 艾利斯顿商学院篮球队要参加一年一度的市篮球比赛了.拉拉队是篮球比赛的一个看点,好的拉拉队往往能帮助球队增加士气,赢得最终的比赛.所以作为拉拉队队长的楚雨荨同学知道,帮助篮球队训 ...

  3. 【BZOJ-4261】建设游乐场 最大费用最大流

    4261: 建设游乐场 Time Limit: 50 Sec  Memory Limit: 256 MBSubmit: 21  Solved: 8[Submit][Status][Discuss] D ...

  4. java php c# 三种语言的AES加密互转

    java php c# 三种语言的AES加密互转 最近做的项目中有一个领取优惠券的功能,项目是用php写得,不得不佩服,php自带的方法简洁而又方便好用.项目是为平台为其他公司发放优惠券,结果很囧的是 ...

  5. activiti流程

    package cn.demo.service.impl; import java.io.File; import java.io.FileInputStream; import java.io.Fi ...

  6. 在eclipse中查看Android源码

    声明:高手跳过此文章 当我们在eclipse中开发android程序的时候.往往须要看源码(可能是出于好奇,可能是读源码习惯),那么怎样查看Android源码呢? 比方以下这样的情况 图1 如果我们想 ...

  7. SysTick Software Timer

    #ifndef __SYSTEM_H__ #define __SYSTEM_H__ #include <stdint.h> #include <stddef.h> #inclu ...

  8. [Ubuntu] 编译安装 PHP 依赖库

    编译环境 sudo apt-get -y install build-essential xml sudo apt-get -y install libxml2-dev pcre sudo apt-g ...

  9. [Winform]缓存处理

    摘要 在对winform做的项目优化的时候,首先想到的是对查询,并不经常变化的数据进行缓存,但对web项目来说有System.Web.Caching.Cache类进行缓存,那么winform端该如何呢 ...

  10. PostgreSQL代码分析,查询优化部分,canonicalize_qual

    这里把规范谓词表达式的部分就整理完了.阅读的顺序例如以下: 一.PostgreSQL代码分析,查询优化部分,canonicalize_qual 二.PostgreSQL代码分析,查询优化部分,pull ...