Please enable Javascript to view the contents

HTML的img元素的src转换成datauri

 ·  ☕ 3 分钟  ·  🐵 chenyanggao

背景

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

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

代码实现

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

  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
#!/usr/bin/env python3
# coding: utf-8

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

if __name__ == "__main__":
    from argparse import ArgumentParser

    parser = ArgumentParser(description="把 html 文件中 <img> 元素的 src 属性转换成 datauri。注意:所有 HTML 文件会被原地替换,并不生成新文件")
    parser.add_argument("pathlist", nargs="+", metavar="path_to_file_or_dir", help="html 所在的文件或文件夹")
    parser.add_argument("-e", "--encoding", help="html 文件的编码")

    args = parser.parse_args()


from base64 import b64encode
from contextlib import contextmanager
from mimetypes import guess_type
from os import chdir, getcwd, path
from re import compile as re_compile
from urllib.parse import urlsplit
from urllib.request import urlopen


# 正则表达式,匹配有 src 属性的 <img> 元素
cre_src = re_compile(r'(<img\b[^>]+?\bsrc=")(?P<uri>[^"]+)')


@contextmanager
def tempcwd(dir_):
    "上下文管理器,切换当前工作目录"
    if dir_ in ("", "."):
        yield
        return
    cwdold = getcwd()
    chdir(dir_)
    try:
        yield
    finally:
        chdir(cwdold)


# TODO: 以后将会支持 ftp(s)://, dav(s)://, sftp:// 等协议
def _read(path):
    "读取文件的二进制数据"
    if path.startswith(("http://", "https://")):
        return urlopen(path).read()
    return open(path, "rb").read()


def _repl(m):
    "正则表达式替换时用到的函数,用于把搜索到的 uri 替换成 datauri"
    uri = m["uri"]
    # 如果本身就是 datauri,则原样返回
    if uri.startswith("data:"):
        return uri[0]
    mimetype = guess_type(urlsplit(uri).path)[0]
    # 如果判断不了 mimetype,则原样返回
    if mimetype is None:
        return uri[0]
    # 如果读取失败,则原样返回
    try:
        content = _read(uri)
    except:
        return uri[0]
    # datauri 使用 base64 编码二进制数据
    return "%sdata:%s;base64,%s" % (m[1], mimetype, b64encode(content).decode('ascii'))


def process(path_file, path_save=None, encoding=None):
    """读取 HTML 文件,把其中可读取的图片路径转化并替换为 datauri,并保存修改后的 HTML

    :param path_file: 原始 HTML 文件的路径
    :param path_save: 保存更改后的 HTML 文件的路径,如果为 None,那么等于参数 `path_file`
    :param encoding: 文件的编码
    """
    if path_save is None:
        path_save = path_file
    dir_, name = path.split(path_file)
    with tempcwd(dir_):
        text = open(name, encoding=encoding).read()
        text_new = cre_src.sub(_repl, text)
    if path_file != path_save or text != text_new:
        open(path_save, "w", encoding=encoding).write(text_new)


if __name__ == "__main__":
    from glob import glob

    encoding = args.encoding

    def process_one(path_):
        print("-" * 20)
        print("[PROCESSING]", path_)
        try:
            process(path_, encoding=encoding)
        except Exception as exc:
            print("😭 FAILED:", exc)
        else:
            print("😄 SUCCESS")

    for path_ in args.pathlist:
        if path.isdir(path_):
            for p in glob(path.join(path_, "**", "*.htm"), recursive=True):
                process_one(p)
            for p in glob(path.join(path_, "**", "*.html"), recursive=True):
                process_one(p)
        else:
            process_one(path_)

使用说明

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

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 中,只是作为尝试的第一步,所以,敬请期待我的后续更新。

参考资料

Is it possible to directly embed an image into a Markdown document? - Super User

分享

码中人
作者
Web Developer