前言
Halo是一个非常不错的博客程序,使用起来非常方便,但是在里面写的文章却不能导出为Markdown文件,那么我就实现了一个导出Halo文章为Markdown的程序
安装依赖
这个功能中,需要将HTML转换为Markdown,所以需要安装转换工具库,这个库的名称为league/html-to- markdown
,使用命令安装这个库
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。
编写代码
连接数据库
首先需要连接到博客的数据库,这里我采用了面向对象的写法
$db = new mysqli($hostname,$username,$password,$database);
if ($db->connect_error){
die("Connect error ".$db->connect_error);
}
获取文章列表
$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
$title = str_replace("|","-",json_decode($row["data"],true)["spec"]["title"]);
如果文章标题有|
符号,在写入文件的时候会出现错误,需要替换
获取文章发布ID
文章的发布ID在这行数据中的data->spec->releaseSnapshot
$release = str_replace("|","-",json_decode($row["data"],true)["spec"]["releaseSnapshot"]);
获取文章内容
当拿到文章的发布ID后,就可以通过这个发布ID获取文章内容了
$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文章,那么可能会有所不同,存储格式会比较特殊。首先获取正常格式的文章
$rawPatch = json_decode($rowData["data"], true)["spec"]["rawPatch"];
至于判断是否是正常文章,需要判断这个变量中的第一个字符是否为<
,如果是就可以直接转Markdown,如果是[,那么是个JSON格式,里面是多行的数据需要进行拼接,如果都不是,那么文章可能是Markdown,就不需要进行转换,如果这个rawPatch不存在,说明文章没有发布。接下来处理JSON格式
if(str_starts_with($rawPatch, "[")) {
$rawPatch = join("\n",json_decode($rawPatch, true)[0]["source"]["lines"]);
}
将HTML转换为Markdown
将HTML转换为Markdown就需要用到开头提到的那个库了
include "vendor/autoload.php";
use League\HTMLToMarkdown\HtmlConverter;
$converter = new HtmlConverter();
$rawPatch = $converter->convert($rawPatch);
将结果写入到文件中
@mkdir("posts");
@touch("posts/$title.md");
file_put_contents("posts/$title.md", $rawPatch);
这里我将文件存储到脚本目录同级的posts目录中
完整代码
注意 :使用这个代码需要检查数据库信息是否和博客数据库一致
<?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文件了
结尾
这篇文章到这里就结束了,感谢耐心阅读,如果遇到问题可以在下方评论区中讨论。
评论区