Python把HTML中的img元素的src转换成datauri

Python把HTML中的img元素的src转换成datauri

背景

有时候,我们编写完 markdown 文档,为了便于分享阅读,会把它转换成 HTML 文档。但是,大多数的 markdownHTML 工具,只会保留外部资源链接,而不是把资源整合到 HTML 文档之中。如果是网络资源,只要联网就能下载,如果是本地资源,那么必须把对应文件也一起打包,分享给别人。

我个人觉得,分享一个单独的 HTML ,而不是一个 HTML 以及一堆文件的打包,可以更方便用户的阅读。这里,我分享一个 Python 脚本,可以把一个 HTML 文档中所有 <img> 元素中的 src 属性的值,转换成 datauri (更现代的称呼是 data URL,详阅:Data URLs | MDN

代码实现

TIPS 代码的最新版本在 GitHub Gist 中维护
https://gist.github.com/ChenyangGao/5088ae89bc7359b5c931343f5e690e87

文件名称是 html_img_src2datauri.py,是一个命令行工具,Python 实现代码如下:

html_img_src2datauri.py
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
# coding: utf-8

__author__ = "ChenyangGao <https://chenyanggao.github.io/>"
__version__ = (0, 1, 1)
__all__ = ["func_cmdline"]

# Inspired by:
# - https://pypi.org/project/typer/

import inspect

from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter
from functools import partial
from textwrap import dedent, indent
from typing import Callable, Optional


def func_cmdline(
fn: Optional[Callable] = None,
/,
parser: Optional[ArgumentParser] = None,
parse_map: dict[type, Callable] = {},
helpinfo_map: dict[str, str] = {},
) -> Callable:
"""装饰器让一个函数,可以作为命令行使用

:param fn: 被装饰的函数
:param parser: 命令行解析器对象
:param parse_map: 不同类型的解析函数的映射,
字符串通过相应的解析函数来转换成对应对象,
如果没有相应的解析函数,则尝试用对象本身
(它的构造器)
:param helpinfo_map: 不同参数的帮助说明,
用于命令行的帮助信息
:param: 返函数`fn`本身,只是它身上绑定了几个属性
.parser 字段,使用的命令行解析器对象
.parse() 方法,解析命令行参数
.run() 方法,解析命令行参数并运行函数`fn`,
返回函数`fn`的执行结果

:return: 被装饰函数本身,添加了几个属性
"""
if fn is None:
return partial(
func_cmdline,
parser=parser,
parse_map=parse_map,
helpinfo_map=helpinfo_map,
)
params = inspect.signature(fn).parameters
if parser is None:
parser = ArgumentParser(
formatter_class=RawDescriptionHelpFormatter)
if parser.epilog is None:
parser.epilog = ""
if fn.__doc__ is not None:
parser.epilog += "documentation:\n" + \
indent(fn.__doc__, " ")
parser.epilog += "\n\nsource code:\n" + \
indent(dedent(inspect.getsource(fn)), " ")
abbrs: dict[str, int] = {}
for name, param in params.items():
kargs: dict = {"dest": name, "help": helpinfo_map.get(name)}
kind = param.kind
if kind is inspect._ParameterKind.VAR_POSITIONAL:
kargs["required"] = False
kargs["nargs"] = "*"
kargs["default"] = []
elif kind is inspect._ParameterKind.VAR_KEYWORD:
continue
default = param.default
if default is inspect._empty:
kargs.setdefault("required", True)
else:
kargs["default"] = default
anno = param.annotation
if anno is not inspect._empty:
if anno is bool:
kargs["required"] = False
if "default" in kargs:
if kargs["default"]:
kargs["action"] = "store_false"
else:
kargs["action"] = "store_true"
else:
kargs["action"] = "store_true"
elif anno is list:
kargs["nargs"] = "*"
elif getattr(anno, "__origin__", None) is list and len(anno.__args__) == 1:
kargs["nargs"] = "*"
kargs["type"] = parse_map.get(anno.__args__[0], anno.__args__[0])
else:
kargs["type"] = parse_map.get(anno, anno)
names = []
first_letter = name[0]
n = abbrs.setdefault(first_letter, 0)
if n == 0:
abbr = first_letter
else:
abbr = f"{first_letter}{n}"
abbrs[first_letter] += 1
names.append(f"-{abbr}")
names.append(f"--{name}")
if "_" in name:
names.append(f"--{name.replace('_', '-')}")
parser.add_argument(*names, **kargs)
def parse(argv: Optional[list[str]] = None) -> Namespace:
return parser.parse_args(argv)
def run(argv: Optional[list[str]] = None):
"解析命令行参数,默认使用"
args = parser.parse_args(argv).__dict__
pargs = []
kargs = {}
for name, param in params.items():
kind = param.kind
if name in args:
if kind.value < 2:
pargs.append(args[name])
elif kind.value == 2:
pargs.extend(args[name])
else:
kargs[name] = args[name]
return fn(*pargs, **kargs)
setattr(fn, "parser", parser)
setattr(fn, "parse", parse)
setattr(fn, "run", run)
return fn


if __name__ == "__main__":
@func_cmdline(helpinfo_map=dict(
a="参数a", b="参数b", c="参数c",
d="参数d", e="参数e", f="参数f",
))
def foo(a: int, /, b: bool, c: bool = True, *d: int, e: list[int], f: int = 4):
"Example for function as command line."
print(a, b, c, d, e, f)

foo.run()

使用说明

在命令行中输入类似如下命令,可以获取帮助信息:

1
python html_img_src2datauri.py -h

返回的帮助信息,大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
usage: html_img_src2datauri.py [-h]
[-e ENCODING]
path_to_file_or_dir
[path_to_file_or_dir ...]

把 HTML 文件中 <img> 元素的 src 属性转换成 datauri。注意:所有
HTML 文件会被原地替换,并不生成新文件

positional arguments:
path_to_file_or_dir html 所在的文件或文件 夹

options:
-h, --help show this help
message and exit
-e ENCODING, --encoding ENCODING
html 文件的编码

现在假设,在当前的工作目录下,有 1 个 HTML 文件 3.html ,另外还有 1 个文件夹 src,里面有 2 个 HTML 文件,1.html2.html,文档都采用 utf-8 编码,可以在命令行执行如下代码:

1
2
# 4.html 并不存在,只是为了演示发生错误时的效果
python html_img_src2datauri.py -e utf-8 src/ 3.html 4.html

可以得到如下打印信息:

1
2
3
4
5
6
7
8
9
10
11
12
--------------------
[PROCESSING] src/1.html
😄 SUCCESS
--------------------
[PROCESSING] src/2.html
😄 SUCCESS
--------------------
[PROCESSING] 3.html
😄 SUCCESS
--------------------
[PROCESSING] 4.html
😭 FAILED: [Errno 2] No such file or directory: '4.html'

未来规划

正如我在前言中所提到的,要把外部资源整合到一个 HTML 中,那便不仅仅是图片了。我计划在之后的更新中,实现这一想法,而把图片整合进 HTML 中,只是作为尝试的第一步,所以,敬请期待我的后续更新。

评论

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×