蛇ノ目の記

技術のことも。そうでないことも。

PythonでQRコードを生成してXMLに変換する

PythonQRコードを生成してXMLに変換する話。

qrcodeというパッケージを使う。

pypi.org

8月半ばに作ったWebアプリでXMLに変換したQRコードを使っている。

nao-y.hatenablog.com

QRコードSVGを生成する話は以下のブログが詳しい。

tokibito.hatenablog.com

今回はさらに、SVGXMLにタグに変換してHTMLに埋め込むところまでをやっていく。

コードは以下のGistで公開している。

PythonでQRコードを生成してSVGタグに変換する · GitHub

全体の流れ

def main():
    # QRコードに埋め込むテキストの読み込み
    with open('zen.txt', encoding='utf-8') as f:
        text = f.read()
    # QRコードのSVG作成
    qr = make_qrcode_svg(text)
    # DOMオブジェクトを作成
    dom = create_dom(qr)
    # DOMをXMLに整形
    xml = dom.toprettyxml()

    HTML = f'''
<!DOCTYPE html>
<head>
<title>sample QR</title>
</head>
<body>
{xml}
</body>
</html>
'''
    # HTMLに埋め込み
    with open('sample.html', 'w', encoding='utf-8') as f:
        f.write(HTML)

QRコードSVGを作る

import xml.dom.minidom

import qrcode
import qrcode.image.svg as svg


def make_qrcode_svg(text):
    """
    QRコードのSVGを作る関数
    :param text: QRコードに埋め込みたい文字列
    :return: QRコードのSVG
    """
    factory = svg.SvgPathImage
    img = qrcode.make(
        text,
        image_factory=factory,
        version=1,
        box_size=6,
        error_correction=qrcode.constants.ERROR_CORRECT_L)
    return img

XMLの構造

具体的にXMLにする方法を説明する前に、XMLの構造を簡単にまとめる。

以下が今回、生成するXML

<?xml version="1.0" ?>
<svg height="21mm" viewBox="0 0 21 21" width="21mm" xmlns="http://www.w3.org/2000/svg">
    <path d=". . .">
</svg>

初めにXML宣言がある。<?xml version="1.0" ?> の部分。バージョンや文字コードを定義する。

XMLにおいてタグは要素と呼ぶ(要素ノードとも呼ばれる)。

要素には属性を設定できる(属性もまた、属性ノードとも呼ばれる)。

svg要素のheight属性、というような感じ。

また要素には子要素を設定できる。path要素がsvg要素の子要素にあたる。

XMLを生成する

標準ライブラリのxml.dom.minidomを使う。以下のQiita記事を参考にした。

qiita.com

def create_dom(img):
    """
    QRコードのsvgをXMLとして出力する関数
    :param img: QRコードのsvg
    :return dom: xml.dom.minidom.Document Object
    """
    width = img.width
    items = img.get_image()
    path = img.make_path()

    # DOM
    dom = xml.dom.minidom.Document()
    # svg要素を作成
    elem = dom.createElement('svg')
    # DOMに子要素elemを追加
    dom.appendChild(elem)

    # svgからxmlns属性を取得
    xmlns = items.get('xmlns')

    # svg要素の属性を指定
    values = {
        'width': f'{width}mm',
        'height': f'{width}mm',
        'viewBox': f'0 0 {width} {width}',
        'xmlns': xmlns
    }

    # DOMにsvg要素を設定
    set_dom_attr(dom, elem, values)

    # path要素を作成
    path_node = dom.createElement('path')
    # elem要素に子要素(path_node)を追加
    elem.appendChild(path_node)

    # pathからd属性を取得
    d = path.get('d')
    # pathからid属性を取得
    id_ = path.get('id')
    # pathからstyle属性を取得
    style = path.get('style')

    # path要素の属性を指定
    values = {
        'd': d,
        'id': id_,
        'style': style,
    }

    # DOMにpath要素を設定
    set_dom_attr(dom, path_node, values)
    return dom
def set_dom_attr(dom, elem, values):
    """
    属性ノードを生成して要素を設定する関数
    :param dom: xml.dom.minidom.Document Object
    :param elem: 要素
    :param values: 属性
    :return:
    """
    for attr, value in values.items():
        # 属性ノードを生成
        subnode_attrs = dom.createAttribute(attr)
        # 属性ノードの値を設定
        subnode_attrs.value = value
        # 属性ノードを要素ノードにセットする
        elem.setAttributeNode(subnode_attrs)

以下のようにHTMLが出力される(path要素の中身は長すぎるので省略。

<!DOCTYPE html>
<head>
<title>sample QR</title>
</head>
<body>
<?xml version="1.0" ?>
<svg height="21mm" viewBox="0 0 21 21" width="21mm" xmlns="http://www.w3.org/2000/svg">
    <path d=". . .">
</svg>
</body>
</html>

出力されたHTMLは以下のようになる。 f:id:Nao_Y:20180923203817p:plain

Webアプリで使う場合はこのQRコードの部分(今回はxml変数)にテンプレートエンジンのフィルター(Jinja2ではsafeフィルター)を掛ければHTML埋め込むことができる。