破公司的设计文档终于决定要从word改为markdown了。其中画UML的工具是plantuml,写个工具让我能够在linux侧编辑,windows侧看到效果。同时博客这边也支持一下这个功能,后面应该能掌握这个工具。
官方提供的转换工具是一个jar包,随便选择一个开源协议下载。
这个网站会弹窗通知权限,通过浏览器给你推送广告了。如果点了同意,记得在设置里取消。
演示一下如何使用,比如说以下的例子:
@startuml
start
if (legel) then (T)
:process;
else
:report error;
endif
end
@enduml
保存进一个文件里,然后使用命令行生成图片:
java -jar puml.jar tmp.txt --svg
# 使用流输入
cat tmp.txt |java -jar puml.jar --svg --pipe > tmp.svg
java -jar puml.jar --svg --pipe > tmp.svg <<EOF
@startuml
start
if (legel) then (T)
:process;
else
:report error;
endif
end
@enduml
EOF
之会在当前目录下生成一个tmp.svg(这里如果我把活干完了,这里应该能正常显示)。
因为不想要把图片额外存储,所以会使用svg。初步设想总体流程如下。
但是这个步骤可以放在不同的阶段去做,比如说md2html是处理markdown的主要流程,按照不同的处理时机节点,可以分成三种。
第二种的修改难度是最大的,需要理解md4c的原理,修改解析回调函数代码。md4c是一套很高效、优雅的解析框架,读它代码的成本不低,后面可以研究一下,这里有篇记录可以作为原理参考,todolisting。
第三种,后处理依赖于解析框架,我已经见过有些解析工具会把codeblock包装成li,这下依赖关系就开始变得恶心了。
所以我决定采用第一种修改方式。
由于sapin还没对外开源,这里就不写patch了。我额外生成了一个可以单独执行的二进制,可以套在md2html之前做,下面是库的代码。
// puml.h
#ifndef PUML_H
#define PUML_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef struct {
const char *input;
size_t len;
} puml_input;
typedef void (*puml_out_fn)(void*, const char *buf, size_t buflen);
int puml2svg(
const puml_input *,
void *,
puml_out_fn
);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* PUML_H */
// puml.c
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include "puml.h"
#define MIN(a, b) ((a) > (b)) ? (b) : (a)
#define PUML_START "```plantuml\n"
#define PUML_END "```"
enum puml_status {
NORMAL,
IN_PUML,
};
static int run_jar (
const char *jar,
const char *in,
size_t ilen,
void *out,
puml_out_fn out_fn)
{
int p2c[2];
int c2p[2];
if (pipe(p2c) < 0) return 1;
if (pipe(c2p) < 0) return 1;
pid_t pid = fork();
if (pid == 0) {
dup2(p2c[0], STDIN_FILENO);
dup2(c2p[1], STDOUT_FILENO);
close(p2c[0]);
close(p2c[1]);
close(c2p[0]);
close(c2p[1]);
execlp("java", "java", "-jar", jar, "--pipe", "--svg", NULL);
_exit(1);
} else if (pid < 0) {
return 1;
}
close(p2c[0]);
close(c2p[1]);
/* input */
write(p2c[1], in, ilen);
close(p2c[1]);
/* output */
char buf[4096];
ssize_t n;
while ((n = read(c2p[0], buf, sizeof(buf))) > 0) {
out_fn(out, buf, n);
}
char br[] = "\n<br>\n";
out_fn(out, br, strlen(br));
if (n < 0) {
close(c2p[0]);
printf("read jar stdout get n = %d\n", n);
waitpid(pid, NULL, 0);
return 1;
}
close(c2p[0]);
waitpid(pid, NULL, 0);
return 0;
}
static int judge_same (
const char *l,
size_t llen,
const char *r,
size_t rlen)
{
if (llen != rlen) return 0;
while (llen > 0) {
llen--;
if (l[llen] != r[llen]) return 0;
}
return 1;
}
int puml2svg(
const puml_input *input,
void *output,
puml_out_fn ouput_fn)
{
const char *ptr = input->input;
const char *optr = ptr;
const char *in;
const char *jar_path = getenv("PUML_JAR");
size_t ilen = 0;
size_t isize = 4096;
enum puml_status status = NORMAL;
int ret;
if (!jar_path) {
fprintf(stderr, "need env 'PUML_JAR'\n");
return 1;
}
while (ptr < (input->input + input->len)) {
if (*ptr == '\n' || (ptr + 1) == (input->input + input->len)) {
size_t len = (ptr - optr + 1);
if (status == NORMAL) {
if (judge_same(optr, len, PUML_START, strlen(PUML_START))) {
in = ptr + 1;
status = IN_PUML;
} else {
/* copy line to output */
ouput_fn(output, optr, len);
}
} else if (status == IN_PUML) {
/* maybe last char is not \n */
if (judge_same(optr, len - (*ptr == '\n'), PUML_END, strlen(PUML_END))) {
/* run jar with stdin & copy jar's stdout buf to output */
ilen = optr - in;
ret = run_jar(jar_path, in, ilen, output, ouput_fn);
if (ret != 0) {
return ret;
}
status = NORMAL;
}
}
optr = ptr + 1;
}
ptr++;
}
return 0;
}
// main.c
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "puml.h"
struct dync_out {
size_t osize;
size_t olen;
char *out;
};
void out_fn (void *output, const char *buf, size_t buflen)
{
struct dync_out *out = (struct dync_out *)output;
if (buflen + out->olen >= out->osize) {
out->osize *= 2;
char *tmp = realloc(out->out, out->osize);
if (!tmp) {
printf("vaild!!! realloc out failed\n");
return;
}
out->out = tmp;
}
memcpy(out->out + out->olen, buf, buflen);
out->olen += buflen;
}
int main (int argc, char *argv[])
{
if (argc == 1) {
return 1;
}
int fd = open(argv[1], O_RDONLY);
size_t ilen = 0;
size_t isize = 4096;
char *in = malloc(isize);
ssize_t n;
while((n = read(fd, in, isize - ilen)) > 0) {
if (ilen + n >= isize) {
isize *= 2;
char *tmp = realloc(in, isize);
if (!tmp) {
free(in);
close(fd);
printf("realloc failed!\n");
return 1;
}
in = tmp;
}
ilen += n;
}
if (n < 0) {
printf("read failed!\n");
close(fd);
free(in);
return 1;
}
puml_input input = { in, ilen };
struct dync_out out;
out.osize = 4096;
out.olen = 0;
out.out = malloc(out.osize);
int ret = puml2svg(&input, &out, out_fn);
for (int i = 0; i < out.olen; i++) {
putchar(out.out[i]);
}
close(fd);
free(in);
free(out.out);
return ret;
}
写了大概一个下午,用C写字符串处理真是痛苦,感觉写了一天的
realloc。缺少对C语言的经验总结,还是要多练多记。另外,java真的好慢啊,严重拖慢了sapin的解析速度。