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

2023-11-11

所有演示均在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文件,为程序执行的日志,会说明程序期间进行了哪些操作,可以删除
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

typora笔记中图片路径批量修改成相对路径 的相关文章

  • 带路径压缩算法的加权 Quick-Union

    有一种 带路径压缩的加权快速联合 算法 代码 public class WeightedQU private int id private int iz public WeightedQU int N id new int N iz new
  • 两个整数乘积的模

    我必须找到c c a b mod m a b c m 是 32 位整数 但 a b 可以超过 32 位 我正在尝试找出一种计算 c 的方法 而不使用 long 或任何 gt 32 位的数据类型 有任何想法吗 如果m是质数 事情可以简化吗 注
  • 与 Eclipse 中的 Java Content Assist 交互

    作为我的插件项目的一部分 我正在考虑与 Eclipse 在 Java 文件上显示的内容辅助列表进行交互 我正在尝试根据一些外部数据对列表进行重新排序 我看过一些有关创建新内容辅助的教程 但没有看到有关更改现有内容辅助的教程 这可能吗 如果是
  • eclipse行号状态行贡献项是如何实现的?

    我需要更新状态行编辑器特定的信息 我已经有了自己的实现 但我想看看 eclipse 贡献项是如何实现的 它显示状态行中的行号 列位置 谁能指点一下 哪里可以找到源代码 提前致谢 亚历克斯 G 我一直在研究它 它非常复杂 我不确定我是否了解完
  • 什么是抽象类? [复制]

    这个问题在这里已经有答案了 当我了解抽象类时 我说 WT H 问题 创建一个无法实例化的类有什么意义呢 为什么有人想要这样的课程 什么情况下需要抽象类 如果你明白我的意思 最常见的是用作基类或接口 某些语言有单独的interface构建 有
  • Android中如何使用JNI获取设备ID?

    我想从 c 获取 IMEIJNI 我使用下面的代码 但是遇到了未能获取的错误cls 它总是返回NULL 我检查了环境和上下文 它们都没有问题 为什么我不能得到Context班级 我在网上搜索了一下 有人说我们应该使用java lang Ob
  • 提供节点名或服务名,或未知 Java

    最近我尝试运行我的 Java 项目 每当我运行它并将其打开到我得到的服务器地址时 Unable to determine host name java net UnknownHostException Caused by java net
  • Android 无法解析日期异常

    当尝试解析发送到我的 Android 客户端的日期字符串时 我得到一个无法解析的日期 这是例外 java text ParseException 无法解析的日期 2018 09 18T00 00 00Z 位于 偏移量 19 在 java t
  • 如何仅从 Firestore 获取最新更新的数据?

    在 Firestore 上发现任何更改时始终获取整个文档 如何只获取最近更新的数据 这是我的数据 我需要在第一次加载时在聊天中按对象顺序 例如 2018 09 17 30 40 msg和sendby 并且如果数据更新则仅获取新的msg和se
  • 将人类日期(当地时间 GMT)转​​换为日期

    我正在服务器上工作 服务器正在向我发送 GMT 本地日期的日期 例如Fri Jun 22 09 29 29 NPT 2018在字符串格式上 我将其转换为日期 如下所示 SimpleDateFormat simpleDateFormat ne
  • 如何使用 JMagick 转换色彩空间?

    如何使用 JMagick API 转换色彩空间 例如 CMYK gt RGB 和 RGB gt CMYK None
  • 蓝牙发送和接收文本数据

    我是 Android 开发新手 我想制作一个使用蓝牙发送和接收文本的应用程序 我得到了有关发送文本的所有内容逻辑工作 但是当我尝试在手机中测试它时 我看不到界面 这是Main Activity Code import android sup
  • 使用 Elastic Beanstalk 进行 Logback

    我在使用 Elastic Beanstalk 记录应用程序日志时遇到问题 我正在 AWS Elastic Beanstalk 上的 Tomcat 8 5 with Corretto 11 running on 64bit Amazon Li
  • Android S8+ 警告消息“不支持当前的显示尺寸设置,可能会出现意外行为”

    我在 Samsung S8 Android 7 中收到此警告消息 APP NAME 不支持当前的显示尺寸设置 可能会 行为出乎意料 它意味着什么以及如何删除它 谢谢 通过添加解决supports screens 机器人 xlargeScre
  • Hibernate 本机查询 - char(3) 列

    我在 Oracle 中有一个表 其中列 SC CUR CODE 是 CHAR 3 当我做 Query q2 em createNativeQuery select sc cur code sc amount from sector cost
  • 在java中以原子方式获取多个锁

    我有以下代码 注意 为了可读性 我尽可能简化了代码 如果我忘记了任何关键部分 请告诉我 public class User private Relations relations public User relations new Rela
  • partitioningBy 必须生成一个包含 true 和 false 条目的映射吗?

    The 分区依据 https docs oracle com javase 8 docs api java util stream Collectors html partitioningBy java util function Pred
  • 子类构造函数(JAVA)中的重写函数[重复]

    这个问题在这里已经有答案了 为什么在派生类构造函数中调用超类构造函数时 id 0 当创建子对象时 什么时候在堆中为该对象分配内存 在基类构造函数运行之后还是之前 class Parent int id 10 Parent meth void
  • 抛出 Java 异常时是否会生成堆栈跟踪?

    这是假设我们不调用 printstacktrace 方法 只是抛出和捕获 我们正在考虑这样做是为了解决一些性能瓶颈 不 堆栈跟踪是在构造异常对象时生成的 而不是在抛出异常对象时生成的 Throwable 构造函数调用 fillInStack
  • java'assert'和'if(){}else exit;'之间的区别

    java和java有什么区别assert and if else exit 我可以用吗if else exit代替assert 也许有点谷歌 您应该记住的主要事情是 if else 语句应该用于程序流程控制 而assert 关键字应该仅用于

随机推荐

  • intelli idea中配置Tomcat找不到的解决办法

    这两天新入职一家公司 公司用的是intelli idea 以前用习惯了eclipse 感觉到有点不太习惯 当然 intelli idea也有自己的强大之处 在开始配置Tomact之前 按照网上的说法 发现点击 号之后没有Tomcat 于是乎
  • printf 和scanf

    1 printf 简介 1 1 printf 的格式 printf 函数的原型为 include
  • Xbox One 升级后黑屏修复

    好久没用Xbox了 近期突然想要利用体感游戏进行锻炼 结果打开后提示需要更新 更新包有4G左右 随手选了更新后就让xbox后台更新了 过了一段时间切回hdmi信号发现一片黑屏 手柄的xbox键可以唤出关闭菜单 但是其他操作都无效 经过一番研
  • 20230322 元宇宙

    VR AR MR XR的区别和联系 知乎
  • 解决Kaggele无法下载输出output文件夹下的文件

    import os os chdir kaggle working print os getcwd print os listdir kaggle working from IPython display import FileLink F
  • 支付宝给个人账号转账付款

    一 说明 转账到支付宝账户是为了满足支付宝商户向其他支付宝账户进行单笔转账的需求 针对具备开发能力的商户 提供通过 API 接口完成单笔转账的功能 商家只需输入另一个正确的支付宝账号 即可将单笔资金从本人的支付宝账户转账至另一个支付宝账户
  • GPT带我学-设计模式-代理模式

    什么是代理模式 代理模式 Proxy Pattern 是设计模式中的一种结构型模式 它为其他对象提供一种代理以控制对这个对象的访问 代理模式有三个主要角色 抽象主题 Subject 真实主题 Real Subject 和代理 Proxy 抽
  • Bootstrap使用方法(个人经验,仅限参考)

    下载Bootstrap 将Bootstrap导入到所需要的项目中 将Bootstrap的css和js的路径引入到所需要的页面 选择所需要的插件 粘贴到所需要的位置 菜鸟教程实例 Bootstrap的JavaScript脚本带有监听事件方法
  • kl散度matlab实现,Kullback–Leibler divergence KL散度

    In probability theory and information theory the Kullback Leibler divergence 1 2 3 also information divergence informati
  • Bash中分号“;”、与“&&“、或(

    linux 中 bash 下执行多个命令时 操作符的区别 cmd1 cmd2 cmd1 和 cmd2 都会 被执行 cmd1 cmd2 如果 cmd1 执行 成功 则执行 cmd2 cmd1 cmd2 如果 cmd1 执行 失败 则执行 c
  • 2022-12-30 Ubuntu 运行qt creator提示qt.qpa.plugin: Could not load the Qt platform plugin “xcb“

    一 Ubuntu 运行qt creator提示qt qpa plugin Could not load the Qt platform plugin xcb qt qpa plugin Could not load the Qt platf
  • python弹球游戏彩蛋

    在上一期 我们已经实现了基本弹球的功能 先总结一下上期的代码 import pygame pygame init width 800 height 600 screen pygame display set mode width heigh
  • OpenGL ES2.0粒子系统(附有源码)

    http blog csdn net cxy200927099 article details 38584487 刚学OpenGL 2个多星期 也算是入门了吧 在看了老外写的书 OpenGL ES 2 for Android A Quick
  • 雅特力at421f串口2串口1互发透传

    void USART1 IRQHandler void static u8 k USART ClearFlag USART1 USART FLAG TC 清除USARTx的待处理标志位 if USART GetITStatus USART1
  • 目标检测:SSD算法原理综述

    SSD Single Shot Detection 是一个流行且强大的目标检测网络 网络结构包含了基础网络 Base Network 辅助卷积层 Auxiliary Convolutions 和预测卷积层 Predicton Convolu
  • NAT穿越原理——STUN

    STUN是RFC3489规定的一种NAT穿透方式 它采用辅助的方法探测NAT的IP和端口 毫无疑问的 它对穿越早期的NAT起了巨大的作用 并且还将继续在ANT穿透中占有一席之地 STUN的探测过程需要有一个公网IP的STUN server
  • Vscode中C++/c按F5调试不了,但是可以右键run code运行的问题

    V s c o d e 里 配
  • 想知道你的Mac是否支持macOS Monterey吗?

    Apple年度WWDC大会已经结束 在WWDC2021上宣布了新系统 macOS Monterey 它为Safari和Mail等应用带来了重大更新 你的Mac是否支持新系统呢 一起跟小编看看吧 https www macv com mac
  • Spark使用场景有哪些?大数据spark教程

    Spark 是一种与 Hadoop 相似的开源集群计算环境 是专为大规模数据处理而设计的快速通用的计算引擎 现已形成一个高速发展应用广泛的生态系统 主要应用场景如下 1 Spark是基于内存的迭代计算框架 适用于需要多次操作特定数据集的应用
  • typora笔记中图片路径批量修改成相对路径

    所有演示均在typora环境下 其他markdown软件不清楚是否都能用 使用之前建议备份一遍 防止出现问题 前置操作 打开typora 进入文件 gt 偏好设置 gt 图像 按照下面的图片勾选被填写图片相对路径 方便以后使用 相对路径 p