libxml2でHTMLを保存した時にmeta charsetの前にmeta http-equivがつかないようにするパッチを作った

# はじめに
libxml2でHTMLを保存した時にmeta charsetの前にmeta http-equivがつかないようにするパッチを作ったのでメモ。

gnome.orgのBugzillaに[Bug 728436 – \[PATCH\] Write meta charset tag instead of meta http-equiv content-type](https://bugzilla.gnome.org/show_bug.cgi?id=728436)として報告済みです。

元々はNokogiriを使っていてこの現象に遭遇しました。[additional meta Content-Type is added to HTML5 · Issue #1008 · sparklemotion/nokogiri](https://github.com/sparklemotion/nokogiri/issues/1008)にイシューが上がっています。

# 問題

パッチを当てていないlibxml2-2.9.1だと、以下のサンプルプログラム

```c:read_write_html.c
#include <libxml/HTMLparser.h>
#include <libxml/tree.h>
#include <libxml/xmlsave.h>

/*
 * cc -Iinclude -L.libs -lxml2.2 -g read_write_html.c
 */

static xmlDocPtr readHtmlFile(const char *filename) {
    htmlParserCtxtPtr parserCtxt;
    xmlDocPtr doc;

    parserCtxt = htmlNewParserCtxt();
    if (parserCtxt == NULL) {
        fprintf(stderr, "htmlNewParserCtxt failed\n");
        return NULL;
    }

    doc = htmlCtxtReadFile(parserCtxt, "example1.html", NULL, 0);

    htmlFreeParserCtxt(parserCtxt);
    return doc;
}

static int saveHtmlFile(xmlDocPtr doc, const char *filename) {
    int ret;
    xmlSaveCtxtPtr saveCtxt;

    saveCtxt = xmlSaveToFilename(filename, "UTF-8",
        XML_SAVE_FORMAT | XML_SAVE_AS_HTML);
    if (saveCtxt == NULL) {
        fprintf(stderr, "xmlSaveToFilename failed\n");
        return -1;
    }

    ret = xmlSaveDoc(saveCtxt, doc);
    if (ret == -1) {
        fprintf(stderr, "xmlSaveDoc failed\n");
        return -1;
    }

    ret = xmlSaveClose(saveCtxt);
    if (ret == -1) {
        fprintf(stderr, "xmlSaveClose failed\n");
        return -1;
    }

    return 0;
}

int main(int argc, char **argv) {
    xmlDocPtr doc;
    int ret;

    doc = readHtmlFile("example1.html");
    if (doc == NULL) {
        fprintf(stderr, "readHtmlFile failed\n");
        return 1;
    }

    ret = saveHtmlFile(doc, "example1-out.html");
    if (ret == -1) {
        fprintf(stderr, "saveHtmlFile failed\n");
        xmlFreeDoc(doc);
        return 1;
    }

    xmlFreeDoc(doc);
    return 0;
}
```

で、以下のようにmeta charsetのみを持つhtmlファイルを読み込んで保存すると

```html:example1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>HTML5 example</title>
</head>
<body>
<h1>HTML5 save example</h1>
<p>I would like to meta charset tag is used instead of meta http-equiv.</p>
</body>
</html>
```

以下のようにmeta charsetタグの前にmeta http-equivタグが付けられてしまいます。

```html:example1-out.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>HTML5 example</title>
</head>
<body>
<h1>HTML5 save example</h1>
<p>I would like to meta charset tag is used instead of meta http-equiv.</p>
</body>
</html>
```

# パッチ

```diff:libxml2-2.9.1-HTMLtree-meta-charset.patch
--- libxml2-2.9.1/HTMLtree.c.orig	2013-02-27 14:08:48.000000000 +0900
+++ libxml2-2.9.1/HTMLtree.c	2014-04-18 01:05:26.000000000 +0900
@@ -242,6 +242,12 @@
 		        (attr->children->type == XML_TEXT_NODE) &&
 		        (attr->children->next == NULL)) {
 			value = attr->children->content;
+			if (!xmlStrcasecmp(attr->name, BAD_CAST"charset"))
+			{
+			    content = value;
+			    http = 1;
+			}
+			else
 			if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
 			 && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
 			    http = 1;
```

このパッチを当てたlibxml2では

```html:example1-out.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>HTML5 example</title>
</head>
<body>
<h1>HTML5 save example</h1>
<p>I would like to meta charset tag is used instead of meta http-equiv.</p>
</body>
</html>
```

と余分なmeta http-equivタグが追加されずにmeta charsetタグだけが出力されます。