前言 Halo是一个非常不错的博客程序,使用起来非常方便,但是在里面写的文章却不能导出为Markdown文件,那么我就实现了一个导出Halo文章为Markdown的程序
安装依赖 这个功能中,需要将HTML转换为Markdown,所以需要安装转换工具库,这个库的名称为league/html-to- markdown,使用命令安装这个库
1 composer require league/html-to-markdown
分析 如果你的博客使用了MySQL作为数据库,那么就可以比较方便的读取数据,如果是使用了默认的H2数据库,就需要导入到MySQL中。Halo的数据在数据库中存储比较特殊,采用了blob格式,我们能轻松读取数据,但是却难以修改,导出文章只要获取数据就足够了。Halo的数据全部存储于一张表:extension,每一行数据有name、data、version三个字段,其中name和data是我们要用到的字段。文章的name一般为:/registry/content.halo.run/posts/······,文章的data是一个JSON数据,数据中有一个字段为releaseSnapshot,存储着发布后的文章资源,我们需要读取这个资源并转换为Markdown。
编写代码 连接数据库 首先需要连接到博客的数据库,这里我采用了面向对象的写法
1 2 3 4 $db = new mysqli ($hostname ,$username ,$password ,$database );if ($db ->connect_error){die ("Connect error " .$db ->connect_error);}
获取文章列表 1 2 3 4 5 $sql = "select * from extensions where name like '/registry/content.halo.run/posts/%'" ;$result = $db ->query ($sql );while ($row = $result ->fetch_assoc ()) { }
这里的$row就是每一篇文章的数据
获取文章的标题 需要获取这行数据中的data->spec->title
1 $title = str_replace ("|" ,"-" ,json_decode ($row ["data" ],true )["spec" ]["title" ]);
如果文章标题有|符号,在写入文件的时候会出现错误,需要替换
获取文章发布ID 文章的发布ID在这行数据中的data->spec->releaseSnapshot
1 $release = str_replace ("|" ,"-" ,json_decode ($row ["data" ],true )["spec" ]["releaseSnapshot" ]);
获取文章内容 当拿到文章的发布ID后,就可以通过这个发布ID获取文章内容了
1 2 3 4 5 6 $releasePath = "/registry/content.halo.run/snapshots/$release " ;$sql = "select * from extensions where name='$releasePath ';" ;$result1 = $db ->query ($sql );while ($rowData = $result1 ->fetch_assoc ()) { }
注意这里$result1不能和前面的$result同名,$row1不能和$row同名,否则会出现问题
分析数据结构 文章的主要数据在这行数据的data->spec->rawPatch,需要获取这个数据并进行处理,如果你的博客导入过Markdown文章,那么可能会有所不同,存储格式会比较特殊。首先获取正常格式的文章
1 $rawPatch = json_decode ($rowData ["data" ], true )["spec" ]["rawPatch" ];
至于判断是否是正常文章,需要判断这个变量中的第一个字符是否为<,如果是就可以直接转Markdown,如果是[,那么是个JSON格式,里面是多行的数据需要进行拼接,如果都不是,那么文章可能是Markdown,就不需要进行转换,如果这个rawPatch不存在,说明文章没有发布。接下来处理JSON格式
1 2 3 if (str_starts_with ($rawPatch , "[" )) { $rawPatch = join ("\n" ,json_decode ($rawPatch , true )[0 ]["source" ]["lines" ]); }
将HTML转换为Markdown 将HTML转换为Markdown就需要用到开头提到的那个库了
1 2 3 4 include "vendor/autoload.php" ;use League \HTMLToMarkdown \HtmlConverter ;$converter = new HtmlConverter ();$rawPatch = $converter ->convert ($rawPatch );
将结果写入到文件中 1 2 3 @mkdir ("posts" ); @touch ("posts/$title .md" ); file_put_contents ("posts/$title .md" , $rawPatch );
这里我将文件存储到脚本目录同级的posts目录中
完整代码 注意 :使用这个代码需要检查数据库信息是否和博客数据库一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <?php $hostname = "127.0.0.1" ;$username = "root" ;$password = "123456" ;$database = "root" ;include "vendor/autoload.php" ;use League \HTMLToMarkdown \HtmlConverter ;$converter = new HtmlConverter ();$db = new mysqli ($hostname ,$username ,$password ,$database );if ($db ->connect_error){ die ("Connect error " .$db ->connect_error); } @mkdir ("posts" ); $sql = "select * from extensions where name like '/registry/content.halo.run/posts/%'" ;$result = $db ->query ($sql );while ($row = $result ->fetch_assoc ()) { msg ("Name {$row["name"]} " ); $data = json_decode ($row ["data" ], true ); $spec = $data ["spec" ]; $title = str_replace ("|" ,"-" ,$spec ["title" ]); msg ("Got $title " ); $release = $spec ["releaseSnapshot" ]; msg ("Release $release " ); $releasePath = "/registry/content.halo.run/snapshots/$release " ; $sql = "select * from extensions where name='$releasePath ';" ; $result1 = $db ->query ($sql ); while ($rowData = $result1 ->fetch_assoc ()) { $data = json_decode ($rowData ["data" ], true ); if ($spec = $data ["spec" ]) { if ($rawPatch = $spec ["rawPatch" ]) { if (str_starts_with ($rawPatch , "<" )) { $rawPatch = $converter ->convert ($rawPatch ); } else { if (str_starts_with ($rawPatch , "[" )) { $_ = json_decode ($rawPatch , true )[0 ]; if ($source = $_ ["source" ]) { if ($lines = $source ["lines" ]) { $rawPatch = join ("\n" , $lines ); $rawPatch = $converter ->convert ($rawPatch ); } else { if ($target = $_ ["target" ]) { if ($lines = $target ["lines" ]) { $rawPatch = join ("\n" , $lines ); $rawPatch = $converter ->convert ($rawPatch ); } else { die ("!!! No lines" ); } } else { die ("!!! No target" ); } } } else { die ("!!! No source" ); } } } @touch ("posts/$title .md" ); file_put_contents ("posts/$title .md" , $rawPatch ); msg ("Wrote $title .md" ); } else { msg ("文章未发布" ); } } else { die ("!!! No spec" ); } } } echo "All done" ;function msg ($message ) { if (@$_SERVER ["argc" ]) { echo "$message " . PHP_EOL; }else { echo "$message <br>" ; } }
运行代码后,将可以在脚本同级目录下看到posts目录,目录中就是文章的Markdown文件了
结尾 这篇文章到这里就结束了,感谢耐心阅读,如果遇到问题可以在下方评论区中讨论。