typora笔记中图片路径批量修改成相对路径

所有演示均在typora环境下,其他markdown软件不清楚是否都能用,使用之前建议备份一遍,防止出现问题。

前置操作

打开typora,进入文件->偏好设置->图像
按照下面的图片勾选被填写图片相对路径(方便以后使用)
在这里插入图片描述相对路径:

./pictures/${filename}.assets

Java代码

Main.java

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入笔记文件目录路径:");
        String srcNotePath,outNotepath;
        while(true){
            srcNotePath= sc.next();
            File temp=new File(srcNotePath);
            if(!temp.exists())
                System.out.println("该目录不存在");
            else
                break;
        }
        System.out.println("请输入笔记文件输出目录: ");
        while(true){
            outNotepath = sc.next();
            File temp=new File(outNotepath);
            if(!temp.exists())
                System.out.println("该目录不存在");
            else
                break;
        }
        FileProcess fileProcess = new FileProcess(srcNotePath,outNotepath);
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        File file=new File(outNotepath+File.separator+simpleDateFormat.format(new Date())+".log");
        if(file.exists()){
            FileWriter fw=new FileWriter(file);
            fw.write("");
            fw.flush();
        }else {
            file.createNewFile();
        }
        PrintStream out=System.out;
        System.setOut(new PrintStream(new FileOutputStream(file)));
        long start = System.currentTimeMillis();
        fileProcess.start();
        long end = System.currentTimeMillis();
        System.setOut(out);
        System.out.println("文件处理完成,耗费时间:"+String.format("%.2f",(end-start)/1000.0)+"s,详细日志地址:"+file.getAbsolutePath());
    }
}

FileProcess.java


import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FileProcess {
    private String text;
    private final String srcNoteDir;
    private final String outNoteDir;
    private final static String outImgDir ="pictures/${fileName}.assets";

    public FileProcess(String srcNoteDir, String outNoteDir) {
        this.srcNoteDir = srcNoteDir;
        this.outNoteDir = outNoteDir;
    }
    //启动器
    public void start(){
        //存储md文件绝对路径
        Set<String> filesPathSet=new HashSet<>();
        File file = new File(srcNoteDir);
        //找到该目录下的所有md文件
        if(file.isDirectory()){
            File[] files=file.listFiles();
            if(files==null){
                System.out.println("目录["+srcNoteDir+"]下没有文件");
                return;
            }
            for (File file1 : files) {
                if(file1.isDirectory())
                    continue;
                String name=file1.getName().trim().toLowerCase();
                if(name.lastIndexOf(".md")==name.length()-3){
                    filesPathSet.add(file1.getAbsolutePath());
                }
            }
        }else {
            System.out.println("路径"+srcNoteDir+"只能是目录 ");
            return;
        }
        filesPathSet.forEach((value)->{
            //value:md文件绝对路径
            System.out.println("----------------------------------------------------------------------------");
            System.out.println("开始对["+value+"]文件进行处理");
            //从该文件中读取出文本
            text = this.read(value);
            if(text==null) return;
            Map<String, String> imgMap = process(value);
            String folderName=new File(value).getName();
            int end = folderName.toLowerCase().lastIndexOf(".md");
            //获取md文件的文件名,去掉后缀
            folderName=folderName.substring(0,end).trim();
            //获取该md文件中图片最终存储的路径
            String imgOutPath=outNoteDir +File.separator+ outImgDir.replace("${fileName}",folderName);
            //复制图片
            if(writeImg(imgMap,imgOutPath,new File(value).getParent())){
                //将修改后的md文件内容写入到新的文件
                writeMd(new File(value).getName());
            }
            System.out.println("结束对["+value+"]文件的处理");
            System.out.println("----------------------------------------------------------------------------");
        });
    }

    //从笔记路径[srcNotePath]读取出文本
    private String read(String srcNotePath){
        File file=new File(srcNotePath);
        StringBuilder text=new StringBuilder();
        try(FileInputStream fis=new FileInputStream(file);
            BufferedReader reader=new BufferedReader(new InputStreamReader(fis))){
            while(reader.ready())
                text.append((char)reader.read());
        }catch (IOException e){
            System.out.println("文件["+srcNotePath+"]读取失败");
            return null;
        }
        return text.toString();
    }

    //对不同语法显示的图片进行处理,并返回图片原路径与对应的图片相对路径的哈希表,Map<String,String> key:原图片的路径地址(绝对或相对) value:目标图片相对路径
    private Map<String,String> process(String srcNotePath){
        final Map<String,String> imgMap=new HashMap<>();
        handleTypeOne(imgMap,srcNotePath);
        handleTypeTwo(imgMap,srcNotePath);
        handleTypeThree(imgMap,srcNotePath);
        return imgMap;
    }

    //匹配: ![]() 语法的图片
    private void handleTypeOne(Map<String,String> imgMap,String srcNotePath){
        String reg="!\\[.*?]\\(.*?\\)";
        Pattern pattern=Pattern.compile(reg);
        Matcher matcher=pattern.matcher(text);

        File file=new File(srcNotePath);
        String  fileName=file.getName();
        int end=fileName.lastIndexOf(".md");
        fileName=fileName.substring(0,end);

        while(matcher.find()){
            String srcPath = matcher.group();
            int start = srcPath.indexOf("(");
            int lastIndexOf = srcPath.lastIndexOf(")");
            srcPath=srcPath.substring(start+1,lastIndexOf).trim();
            if(imgMap.containsKey(srcPath)) continue;
            if("".equals(srcPath))
                continue;
            File temp=new File(srcPath);
            if(!temp.isAbsolute()){
                temp = new File(file.getParent()+File.separator+srcPath);
            }
            if(!temp.exists()||temp.isDirectory()){
                System.out.println("图片["+srcPath+"]不存在");
                continue;
            }
            String targetPath= outImgDir.replace("${fileName}",fileName)+File.separator+temp.getName();
            imgMap.put(srcPath,targetPath);
        }
    }

    //匹配 []: 语法
    private void handleTypeTwo(Map<String,String> imgMap,String srcNotePath){
        String reg="\\[.*?]:.*?\\s";
        Pattern pattern=Pattern.compile(reg);
        Matcher matcher=pattern.matcher(text);

        File file=new File(srcNotePath);
        String  fileName=file.getName();
        int end=fileName.lastIndexOf(".md");
        fileName=fileName.substring(0,end);

        while(matcher.find()){
            String srcPath = matcher.group();
            int start = srcPath.indexOf(":");
            srcPath=srcPath.substring(start+1).trim();
            Pattern p=Pattern.compile("\\s");
            Matcher m=p.matcher(srcPath);
            if(m.find())
                srcPath=srcPath.substring(0,m.start()).trim();
            if(imgMap.containsKey(srcPath))
                continue;
            File temp=new File(srcPath);
            if(!temp.isAbsolute()){
                temp = new File(file.getParent()+File.separator+srcPath);
            }
            if(!temp.exists()||temp.isDirectory()){
                System.out.println("图片["+srcPath+"]不存在");
                continue;
            }
            String targetPath= outImgDir.replace("${fileName}",fileName)+File.separator+temp.getName();
            imgMap.put(srcPath,targetPath);
        }
    }

    //匹配img标签图片
    private void handleTypeThree(Map<String,String> imgMap,String srcNotePath){
        String reg="<img.*?src=[\"|']?(.*?)[\"|']\\s*.*?>";
        Pattern pattern=Pattern.compile(reg);
        Matcher matcher=pattern.matcher(text);

        File file=new File(srcNotePath);
        String  fileName=file.getName();
        int end=fileName.lastIndexOf(".md");
        fileName=fileName.substring(0,end);

        while(matcher.find()){
            String srcPath = matcher.group();
            Pattern p=Pattern.compile("src=[\"|']?(.*?)[\"|']");
            Matcher m=p.matcher(srcPath);
            if(m.find())srcPath=srcPath.substring(m.start(),m.end()+1);
            int start=srcPath.indexOf("'");
            if(start<0) start=srcPath.indexOf("\"");
            end=Math.max(srcPath.lastIndexOf("'"),srcPath.lastIndexOf("\""));
            srcPath=srcPath.substring(start+1,end);
            if(imgMap.containsKey(srcPath))
                continue;
            File temp=new File(srcPath);
            if(!temp.isAbsolute()){
                temp = new File(file.getParent()+File.separator+srcPath);
            }
            if(!temp.exists()||temp.isDirectory()){
                System.out.println("图片["+srcPath+"]不存在");
                continue;
            }
            String targetPath= outImgDir.replace("${fileName}",fileName)+File.separator+temp.getName();
            imgMap.put(srcPath,targetPath);
        }
    }

    //将当前笔记文本中的srcPath路径字符串替换成相对路径targetPath
    private void replace(String srcPath,String targetPath){
        srcPath=srcPath.replaceAll("\\\\","\\\\\\\\");
        targetPath=targetPath.replaceAll("\\\\","/");
        text=text.replaceAll(srcPath,targetPath);
        System.out.println(srcPath+"替换成"+targetPath.replaceAll("\\\\","/"));
    }
    //递归清空文件夹及删除文件夹本身
    private void clearFiles(File file){
        if(!file.exists()) return;
        File[] list = file.listFiles();  //无法做到list多层文件夹数据
        if (list != null) {
            for (File temp : list) {     //先去递归删除子文件夹及子文件
                clearFiles(temp);   //注意这里是递归调用
            }
        }
        file.delete();
    }

    //批量在新目录下创建图片,key:原图相对地址或绝对地址,value目标图片相对路径,currImgOutDir:当前md文件中图片的最终存储路径,parentPath:当前md文件的父级路径
    private boolean writeImg(Map<String,String> imgMap,String currImgOutDir,String parentPath){
        File file=new File(currImgOutDir);
        clearFiles(file);
        try {
            Files.createDirectories(Paths.get(currImgOutDir));
        } catch (IOException e) {
            System.out.println("文件夹["+currImgOutDir+"]创建失败");
            return false;
        }
        for(Map.Entry<String,String> entry:imgMap.entrySet()){
            File srcImg=new File(entry.getKey());
            if(!srcImg.isAbsolute()) srcImg=new File(parentPath+File.separator+entry.getKey());
            File targetImg =new File(currImgOutDir +File.separator+srcImg.getName());
            if(!targetImg.exists()) {
                try {
                    targetImg.createNewFile();
                    replace(entry.getKey(),entry.getValue());
                } catch (IOException e) {
                    System.out.println("创建图片失败,可能是文件权限不足");
                }
            }else{
                //图片重名时加number后缀
                int count=0;
                String prefixName="",suffixName="";
                int index=srcImg.getName().lastIndexOf(".");
                if(index<0) index=srcImg.getName().length();
                prefixName=srcImg.getName().substring(0,index);
                suffixName=srcImg.getName().substring(index);
                do{
                    ++count;
                    targetImg=new File(currImgOutDir+File.separator+prefixName+"("+count+")"+suffixName);
                }while(targetImg.exists());
                replace(entry.getKey(),entry.getValue().replace(srcImg.getName(),prefixName+"("+count+")"+suffixName));
            }
            copyImg(srcImg,targetImg);
        }
        return true;
    }

    //重新写入新的md文档
    private void writeMd(String currentMdName){
        File file=new File(outNoteDir+File.separator+currentMdName);
        try(FileWriter fileWriter=new FileWriter(file)){
            fileWriter.write("");
            fileWriter.flush();
            fileWriter.write(text);
            System.out.println("文件["+file.getAbsolutePath()+"]写入成功");
        } catch (IOException e) {
            System.out.println("文件["+file.getAbsolutePath()+"]写入失败");
        }
    }

    //将图片srcImg复制到targetImg
    private void  copyImg(File srcImg,File targetImg){
        try (InputStream is=new FileInputStream(srcImg);
            OutputStream os = new FileOutputStream(targetImg)){
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) > 0) {
                os.write(buffer, 0, length);
            }
            System.out.println("图片["+srcImg.getAbsolutePath()+"]成功复制到["+targetImg.getAbsolutePath()+"]");
        } catch (IOException e) {
            System.out.println("图片["+srcImg.getAbsolutePath()+"]移动失败");
        }
    }
}

使用步骤:直接运行Main.java中的main方法,先输入笔记文件所在的目录,后输入笔记的输出目录(没有创建的先创建),然后程序会开始执行,该程序不修改原笔记内容和图片,只是将修改的内容重新写入到新的文件里面,图片复制到指定目录,原图片不会删除

注意事项:

  • 网络图片(URL)不做处理
  • 只处理笔记目录下的一层子文件(笔记),超过一层不会处理
  • 输出目录建议不要是原笔记目录
  • 原笔记建议备份一遍
  • 程序执行完毕之后,在输出目录里面有个log文件,为程序执行的日志,会说明程序期间进行了哪些操作,可以删除

版权声明:本文为qq_16525829原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。