Qtの基礎 - Markdown

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

Markdownは、軽量のマークアップ言語であり、テキストを簡単にフォーマットするために使用される。
主にWebで使用されており、HTMLに変換することができるが、Markdown自体は視覚的にわかりやすく、読みやすいテキストを提供する。
そのため、ブログ、ドキュメント、READMEファイル等、様々な場面で広く利用されている。

Markdownファイルは拡張子として、.md または .markdown が使用されることが一般的である。

Markdownの基本的な構文はシンプルであり、見出しを作成するためにはテキストの前にシャープ記号を使用する。
例えば、シャープ記号1つで大見出し、2つで中見出し、3つで小見出しといった具合に階層を指定できる。

強調する部分を表現するためには、テキストをアスタリスクやアンダースコアで囲むことにより、太字や斜体などを簡単に表現することができる。

リンクを作成する場合は、表示するテキストを角括弧で囲み、その直後にリンク先URLを丸括弧で記述する。
これにより、簡単にハイパーリンクを生成できる。

また、画像を挿入する場合も同様の構文を使用するが、リンクテキストの前に感嘆符を追加する。

コードスニペットを記述する場合、インラインコードはバッククォートで囲み、複数行のコードブロックは3つのバッククォートで囲む。
これにより、コードが視覚的に区別され、読みやすくなる。

Markdownはシンプルでありながら、基本的な文章構造を維持しつつ、HTMLのような複雑なタグを使用することなくテキストにフォーマットを適用できるため、
多くのユーザに使用されている。


Markdownファイルの読み込み

以下の例では、指定されたMarkdownファイルを読み込んでいる。

 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <QDebug>
 
 QString readMarkdownFile(const QString& filePath)
 {
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
       qDebug() << "ファイルを開けませんでした:" << filePath;
       return QString();
    }
    QTextStream in(&file);
 
    return in.readAll();
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    QString filePath = "input.md";
    QString content = readMarkdownFile(filePath);
 
    if (!content.isEmpty()) {
       qDebug() << "ファイルの内容:";
       qDebug().noquote() << content;
    }
    else {
       qDebug() << "ファイルの読み込みに失敗しました。";
    }
 
    return a.exec();
 }



Markdownファイルの書き込み

以下の例では、指定されたコンテンツをMarkdownファイルを書き込んでいる。

 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <QDebug>
 
 bool writeMarkdownFile(const QString& filePath, const QString& content)
 {
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
       qDebug() << "ファイルを書き込みエラー: " << filePath;
       return false;
    }
    QTextStream out(&file);
    out << content;
 
    return true;
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    QString filePath = "output.md";
    QString content = "# サンプルマークダウン\n\nこれはテストです。\n";
 
    if (writeMarkdownFile(filePath, content)) {
       qDebug() << "ファイルの書き込みに成功";
    }
    else {
       qDebug() << "ファイルの書き込みに失敗";
    }
 
    return a.exec();
 }



Markdownファイルの操作

以下の例では、指定されたコンテンツに対して様々な操作 (ヘッダの追加、ボールドテキストの追加、リンクの追加、ヘッダの抽出) を行っている。

 #include <QCoreApplication>
 #include <QDebug>
 
 QString addHeader(const QString& content, int level, const QString& headerText)
 {
    QString header = QString("#").repeated(level) + " " + headerText + "\n\n";
    return header + content;
 }
 
 QString addBoldText(const QString& content, const QString& text)
 {
    return content + "**" + text + "**\n";
 }
 
 QString addLink(const QString& content, const QString& text, const QString& url)
 {
    return content + "[" + text + "](" + url + ")\n";
 }
 
 QStringList extractHeaders(const QString& content)
 {
    QStringList headers;
    QStringList lines = content.split("\n");
 
    for (const QString& line : lines) {
       if (line.startsWith("#")) {
          headers << line.trimmed();
       }
    }
 
    return headers;
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    QString content = "# 元のヘッダ\n\n元のコンテンツ\n";
 
    // ヘッダの追加
    content = addHeader(content, 2, "新しいサブヘッダ");
    qDebug() << "ヘッダー追加後:";
    qDebug().noquote() << content;
 
    // ボールドテキストの追加
    content = addBoldText(content, "重要な情報");
    qDebug() << "ボールドテキスト追加後:";
    qDebug().noquote() << content;
 
    // リンクの追加
    content = addLink(content, "QtのWebサイト", "https://www.qt.io/");
    qDebug() << "リンク追加後:";
    qDebug().noquote() << content;
 
    // ヘッダの抽出
    QStringList headers = extractHeaders(content);
    qDebug() << "抽出されたヘッダー:";
    for (const QString& header : headers) {
       qDebug().noquote() << header;
    }
 
    return a.exec();
 }



cmarkライブラリ

cmarkライブラリとは

cmarkライブラリは、CommonMarkのC言語リファレンス実装で、Markdown構文の合理化バージョンである。

CommonMarkドキュメントを抽象構文木 (AST) にパースして、
ASTを操作、ドキュメントをHTML、groff man、LaTeX、CommonMark、ASTのXML表現にレンダリングする関数を備えたライブラリである。

また、CommonMark文書の解析とレンダリングのためのコマンドラインプログラム (cmark) も提供する。

ライブラリの機能と特徴を以下に示す。 下記の機能により、cmarkライブラリはMarkdownの解析や変換だけでなく、複雑なドキュメント処理システムの構築にも使用できる。

  • マークダウンの解析
    cmarkは、Markdownテキストを構文解析し、抽象構文木 (AST) を生成する。
    CommonMark標準に準拠しており、仕様に基づいた確実な解析が可能である。

  • MarkdownからHTMLへの変換
    解析されたMarkdownをHTMLに変換する。
    変換時に様々なオプションを指定することで出力をカスタマイズできる。

  • AST (抽象構文木) の操作
    解析されたMarkdownのASTを直接操作できる。
    これにより、Markdownの構造を動的に変更したり、カスタム処理を行ったりすることが可能である。

  • カスタム出力
    特定のニーズに合わせた出力フォーマットのカスタマイズが可能である。
    出力内容を制御するためのオプションやAPIが提供されている。

  • マルチスレッドセーフ
    並行処理を行うアプリケーションでも安全に使用できる。

  • 様々な出力形式
    HTML以外にも、LaTeX、XML、Manページ形式等、複数の出力形式をサポートしている。

  • UTF-8サポート
    Unicode文字を適切に処理する。

  • サニタイズ機能
    XSS攻撃を防ぐため、HTMLの安全な出力オプションを提供している。

  • コマンドラインツール
    ライブラリと共にコマンドラインツールも提供されており、ファイルの変換やデバッグに使用できる。


cmarkライブラリのライセンス

cmarkライブラリは、2条項BSDライセンスに準拠している。

cmarkライブラリのインストール

パッケージ管理システムからインストール
# RHEL
sudo dnf install cmark

# SUSE
sudo zypper install cmark


ソースコードからインストール

cmarkライブラリのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf cmark-<バージョン>.tar.gz
cd cmark-<バージョン>


cmarkライブラリをビルドおよびインストールする。

mkdir build && cd build

cmake -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=<cmakeライブラリのインストールディレクトリ> \
      ..
make -j $(nproc)
make install


Qtプロジェクトファイル / CMakeLists.txtファイル

 # Qtプロジェクトファイル (.pro)
 
 # Pkg-configを使用する場合
 CONFIG += link_pkgconfig
 PKGCONFIG += libcmark
 
 # Pkg-configを使用しない場合
 LIBS += -lcmark


 # CMakeLists.txtファイル
 
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(CMARK REQUIRED libcmark)
 
 target_link_libraries(<ターゲット名> PRIVATE
    # ...略
    ${CMARK_LIBRARIES}
 )
 
 target_include_directories(<ターゲット名> PRIVATE
    ${CMARK_INCLUDE_DIRS}
 )
 
 target_compile_options(<ターゲット名> PRIVATE
    ${CMARK_CFLAGS_OTHER}
 )


Markdownの解析および操作 1

以下の例では、cmarkライブラリを使用してMarkdownを解析および操作している。

  1. Markdownファイルを開いて、その内容を読み込む。
  2. cmarkライブラリを使用して、Markdownを解析する。
  3. Markdownの構造を表示する。
  4. 全ての段落を太字に変更する。
  5. 修正されたMarkdownを出力する。


 // MarkdownParser.hファイル
 
 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <cmark.h>
 #include <QDebug>
 
 class MarkdownParser
 {
 public:
    MarkdownParser() = default;
 
    bool parseFile(const QString &filePath)
    {
       QFile file(filePath);
       if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
          qDebug() << "ファイルのオープンに失敗: " << filePath;
          return false;
        }
 
       QTextStream in(&file);
       QString content = in.readAll();
       file.close();
 
       parseMarkdown(content);
 
       return true;
    }
 
    void parseMarkdown(const QString& markdown)
    {
       cmark_node *document = cmark_parse_document(markdown.toUtf8().constData(), markdown.length(), CMARK_OPT_DEFAULT);
 
       // Markdownの構造を表示
       printStructure(document);
 
       // 全ての段落を太字に変更
       modifyParagraphs(document);
 
       // 修正されたMarkdownを出力
       char *modified_markdown = cmark_render_commonmark(document, CMARK_OPT_DEFAULT, 0);
       qDebug() << "Modified Markdown:";
       qDebug() << QString::fromUtf8(modified_markdown);
 
       free(modified_markdown);
       cmark_node_free(document);
    }
 
 private:
    void printStructure(cmark_node *node, int depth = 0)
    {
       while (node) {
          QString nodeType = QString::fromUtf8(cmark_node_get_type_string(node));
          qDebug().nospace() << QString(depth * 2, ' ') << nodeType;
 
          if (cmark_node_first_child(node)) {
             printStructure(cmark_node_first_child(node), depth + 1);
          }
 
          node = cmark_node_next(node);
       }
    }
 
    void modifyParagraphs(cmark_node *node)
    {
       while (node) {
          if (cmark_node_get_type(node) == CMARK_NODE_PARAGRAPH) {
             cmark_node *strong = cmark_node_new(CMARK_NODE_STRONG);
             cmark_node *child = cmark_node_first_child(node);
             while (child) {
                cmark_node *next = cmark_node_next(child);
                cmark_node_append_child(strong, cmark_node_unlink(child));
                child = next;
             }
             cmark_node_append_child(node, strong);
          }
 
          if (cmark_node_first_child(node)) {
             modifyParagraphs(cmark_node_first_child(node));
          }
 
          node = cmark_node_next(node);
       }
    }
 };


 // main.cppファイル
 
 #include "MarkdownParser.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    MarkdownParser parser;
    parser.parseFile("<Markdownファイルのパス>");
 
    return a.exec();
 }



Markdownの解析および操作 2

以下の例では、cmarkライブラリを使用してMarkdownを解析および操作している。

  1. Markdownファイルを開いて、その内容を読み込む。
  2. cmarkライブラリを使用してMarkdownを解析する。
  3. ドキュメント内の全ての見出しを出力する。
  4. ドキュメント内のリンクを修正する
    (全てのリンクの前に"https://example.com/"を追加)
  5. 修正したMarkdownを出力する。


 // MarkdownParser.hファイル
 
 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <cmark.h>
 #include <QDebug>
 
 class MarkdownParser
 {
 public:
    MarkdownParser() = default;
 
    bool parseFile(const QString& filePath) {
       QFile file(filePath);
       if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
          qDebug() << "Could not open file:" << filePath;
          return false;
       }
 
       QTextStream in(&file);
       QString content = in.readAll();
       file.close();
 
       parseMarkdown(content);
 
       return true;
    }
 
    void parseMarkdown(const QString& markdown)
    {
       cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
       cmark_parser_feed(parser, markdown.toUtf8().constData(), markdown.length());
       cmark_node *document = cmark_parser_finish(parser);
 
       // ドキュメント内の全ての見出しを出力
       printHeadings(document);
 
       // ドキュメント内のリンクを修正
       modifyLinks(document);
 
       // 修正されたMarkdownを出力
       char *modified_markdown = cmark_render_commonmark(document, CMARK_OPT_DEFAULT, 0);
       qDebug() << "Modified Markdown:";
       qDebug() << QString::fromUtf8(modified_markdown);
 
       free(modified_markdown);
       cmark_node_free(document);
       cmark_parser_free(parser);
    }
 
 private:
    void printHeadings(cmark_node *node)
    {
       cmark_iter *iter = cmark_iter_new(node);
       cmark_event_type ev_type;
       cmark_node *cur;
 
       while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
          cur = cmark_iter_get_node(iter);
          if (cmark_node_get_type(cur) == CMARK_NODE_HEADING) {
             char *heading_text = cmark_render_commonmark(cur, CMARK_OPT_DEFAULT, 0);
             qDebug() << "Heading:" << QString::fromUtf8(heading_text);
             free(heading_text);
          }
       }
 
       cmark_iter_free(iter);
    }
 
    void modifyLinks(cmark_node *node)
    {
       cmark_iter *iter = cmark_iter_new(node);
       cmark_event_type ev_type;
       cmark_node *cur;
 
       while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
          cur = cmark_iter_get_node(iter);
          if (cmark_node_get_type(cur) == CMARK_NODE_LINK) {
             const char *url = cmark_node_get_url(cur);
 
             if (url) {
                QString newUrl = QString("https://example.com/") + QString::fromUtf8(url);
                cmark_node_set_url(cur, newUrl.toUtf8().constData());
             }
          }
       }
 
       cmark_iter_free(iter);
    }
 };


 // main.cppファイル
 
 #include "MarkdownParser.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    MarkdownParser parser;
    parser.parseFile("<Markdownファイルのパス>");
 
    return a.exec();
 }


HTMLファイルへ変換

以下の例では、cmarkライブラリを使用して、MarkdownファイルをHTMLファイルに変換している。

  1. Markdownファイルを開いて、その内容を読み込む。
  2. cmarkライブラリを使用して、MarkdownをHTMLに変換する。
  3. 変換されたHTMLを基本的なHTML構造でラッピングする。
  4. ラッピングしたHTMLを新しいファイルに書き込む。


 // MarkdownConverter.hファイル
 
 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <cmark.h>
 #include <QDebug>
 
 class MarkdownConverter
 {
 public:
    MarkdownConverter() = default;
 
    void convertFile(const QString &inputPath, const QString &outputPath)
    {
       // Markdownファイルの読み込み
       try {
          QString markdownContent = readMarkdownFile(inputPath);
          QString htmlContent     = convertMarkdownToHtml(markdownContent);
          writeHtmlFile(outputPath, htmlContent);
 
          qInfo() << "変換に成功: " << outputPath;
       }
       catch (const MarkdownConversionError& e) {
          qCritical() << "変換エラー: " << e.what();
          throw;
       }
       catch (const std::exception& e) {
          qCritical() << "予期せぬエラーが発生: " << e.what();
          throw;
       }
    }
 
 private:
    QString readMarkdownFile(const QString &inputPath)
    {
       QFileInfo fileInfo(inputPath);
       if (!fileInfo.exists()) {
          throw MarkdownConversionError(QString("Input file does not exist: %1").arg(inputPath));
       }
 
       if (!fileInfo.isReadable()) {
          throw MarkdownConversionError(QString("Input file is not readable: %1").arg(inputPath));
       }
 
       QFile inputFile(inputPath);
       if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
          throw MarkdownConversionError(QString("Could not open input file: %1").arg(inputPath));
       }
 
       QTextStream in(&inputFile);
       QString content = in.readAll();
       inputFile.close();
 
       if (content.isEmpty()) {
          throw MarkdownConversionError(QString("Input file is empty: %1").arg(inputPath));
       }
 
       return content;
    }
 
    QString convertMarkdownToHtml(const QString &markdown)
    {
       // Markdownファイルをパース
       cmark_node *document = cmark_parse_document(markdown.toUtf8().constData(), markdown.length(), CMARK_OPT_DEFAULT);
       if (!document) {
          throw MarkdownConversionError("Markdownのパースに失敗");
       }
 
       // Convert to HTML
       char *html = cmark_render_html(document, CMARK_OPT_DEFAULT);
       if (!html) {
          cmark_node_free(document);
          throw MarkdownConversionError("MarkdownからHTMLのレンダリングに失敗");
       }
 
       // HTMLの基本構文にラッピング
       QString fullHtml = QString(
          "<!DOCTYPE html>\n"
          "<html lang=\"en\">\n"
          "<head>\n"
          "    <meta charset=\"UTF-8\">\n"
          "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
          "    <title>Converted Markdown</title>\n"
          "</head>\n"
          "<body>\n"
          "%1\n"
          "</body>\n"
          "</html>"
       ).arg(QString::fromUtf8(html));
 
       // Free allocated memory
       free(html);
       cmark_node_free(document);
 
       return fullHtml;
    }
 
    void writeHtmlFile(const QString& outputPath, const QString& htmlContent)
    {
       QFileInfo fileInfo(outputPath);
       QDir dir = fileInfo.dir();
 
       if (!dir.exists()) {
          if (!dir.mkpath(".")) {
             throw MarkdownConversionError(QString("HTMLファイルを保存するディレクトリの作成に失敗: %1").arg(outputPath));
          }
       }
 
       QFile outputFile(outputPath);
       if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
          throw MarkdownConversionError(QString("HTMLファイルの作成に失敗: %1").arg(outputPath));
       }

       QTextStream out(&outputFile);
       out << htmlContent;
       outputFile.close();
 
       if (outputFile.error() != QFile::NoError) {
          throw MarkdownConversionError(QString("HTMLファイルの書き込みに失敗: %1").arg(outputPath));
       }
    }
 };


 // main.cppファイル
 
 #include "MarkdownConverter.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    QString inputFile  = "<入力 : Markdownファイルのパス>";
    QString outputFile = "<出力 : HTMLファイルのパス>";
 
    MarkdownConverter converter;
    try {
       converter.convertFile(inputFile, outputFile);
       return 0;
    }
    catch (const MarkdownConversionError& e) {
       qCritical() << "変換に失敗: " << e.what();
       return -1;
    }
    catch (const std::exception &e) {
       qCritical() << "予期せぬエラーが発生: " << e.what();
       return -2;
    }
 
    return 0;
 }



cmark-gfmライブラリ

cmark-gfmライブラリとは

cmark-gfmライブラリは、CommonMarkのC言語リファレンス実装の拡張版であり、GitHub Flavored Markdown (GFM) の解析と変換のためのライブラリである。
Webアプリケーション、デスクトップアプリケーション、コマンドラインツール等、様々な場面でMarkdownを扱う時に有用なライブラリである。

ライブラリの機能と特徴を以下に示す。 下記の機能により、cmark-gfmライブラリはMarkdownの解析や変換だけでなく、複雑なドキュメント処理システムの構築にも使用できる。

  • マークダウンの解析
    Markdownテキストを構文解析して、抽象構文木 (AST) を生成する。
    標準的なMarkdown構文に加えて、GFM固有の拡張をサポートしている。

  • MarkdownからHTMLへの変換
    解析されたMarkdownをHTMLに変換する。
    変換時に様々なオプションが指定できる。(例: 安全なHTML出力、ハードブレークの処理方法等)

  • HTMLからマークダウンへの変換
    HTMLをMarkdownに変換する機能も提供している。

  • AST (抽象構文木) の操作
    解析されたMarkdownのASTを直接操作できる。
    これにより、Markdownの構造を動的に変更したり、カスタム処理を行ったりすることが可能である。

  • GitHub Flavored Markdown (GFM) の拡張機能サポート
    • テーブル
    • タスクリスト
    • 取り消し線
    • 自動リンク
    • コードブロックの言語指定

  • カスタム拡張の実装
    ライブラリを拡張して、独自のMarkdown要素や処理を追加できる。

  • マルチスレッドセーフ
    並行処理を行うアプリケーションでも安全に使用できる。

  • 様々な出力形式
    HTML以外にも、XMLやManページ形式等、複数の出力形式をサポートしている。

  • UTF-8サポート
    Unicode文字を適切に処理する。

  • リンクの参照解決
    Markdown内のリンク参照を自動的に解決する。

  • 脚注のサポート
    脚注の解析と適切な出力をサポートしている。
  • サニタイズ機能
    XSS攻撃を防ぐため、HTMLの安全な出力オプションを提供している。

  • エスケープ処理
    特殊文字の適切なエスケープ処理を行う。

  • フェンスドコードブロックの処理
    コードブロックの言語指定や構文ハイライトの準備をする。

  • コマンドラインツール
    ライブラリと共にコマンドラインツールも提供されており、ファイルの変換やデバッグに使用できる。


cmark-gfmライブラリのライセンス

cmark-gfmライブラリは、2条項BSDライセンスに準拠している。

cmark-gfmライブラリのインストール

cmark-gfmライブラリのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf cmark-gfm-<バージョン>.tar.gz
cd cmark-gfm-<バージョン>


cmark-gfmライブラリをビルドおよびインストールする。

mkdir build && cd build

cmake -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=<cmake-gfmライブラリのインストールディレクトリ> \
      ..
make -j $(nproc)
make install


HTMLファイルへ変換

以下の例では、cmark-gfmライブラリを使用して、MarkdownファイルからHTMLファイルへ変換している。

 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <cmark-gfm.h>
 #include <QDebug>
 
 QString readMarkdownFile(const QString& fileName)
 {
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
       qDebug() << "ファイルのオープンに失敗";
       return QString();
    }
 
    QTextStream in(&file);
    QString content = in.readAll();
    file.close();
 
    return content;
 }
 
 QString markdownToHtml(const QString& markdown)
 {
    QByteArray markdownUtf8 = markdown.toUtf8();
    char *htmlOutput = cmark_markdown_to_html(markdownUtf8.constData(), markdownUtf8.size(), CMARK_OPT_DEFAULT);
    QString html = QString::fromUtf8(htmlOutput);
 
    free(htmlOutput);
 
    return html;
 }
 
 int main()
 {
    QString fileName = "<Markdownファイルのパス>";
    QString markdownContent = readMarkdownFile(fileName);
 
    if (markdownContent.isEmpty()) {
       qDebug() << "ファイルが存在しない または 空";
       return -1;
    }
 
    qDebug() << "Original Markdown content:";
    qDebug() << markdownContent;
 
    QString htmlContent = markdownToHtml(markdownContent);
    qDebug() << "変換されたHTMLの内容:";
    qDebug() << htmlContent;
 
    return 0;
 }


HTMLからMarkdownへ変換

以下の例では、cmark-gfmライブラリを使用して、HTMLファイルからMarkdownファイルへ変換している。

 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <cmark-gfm.h>
 #include <QDebug>
 
 bool writeMarkdownFile(const QString& fileName, const QString& content)
 {
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
       qDebug() << "ファイルのオープンに失敗";
       return false;
    }
 
    QTextStream out(&file);
    out << content;
    file.close();
 
    return true;
 }
 
 QString htmlToMarkdown(const QString& html)
 {
    QByteArray htmlUtf8 = html.toUtf8();
    cmark_node* doc = cmark_parse_document(htmlUtf8.constData(), htmlUtf8.size(), CMARK_OPT_DEFAULT);
    char* markdownOutput = cmark_render_commonmark(doc, CMARK_OPT_DEFAULT, 0);
    QString markdown = QString::fromUtf8(markdownOutput);
 
    free(markdownOutput);
    cmark_node_free(doc);
 
    return markdown;
 }
 
 int main()
 {
    QString fileName = "<HTMLファイルのパス>";
    QString htmlContent = "<h1>Hello, World!</h1><p>This is a <strong>bold</strong> text and <em>italic</em> text.</p>";
 
    QString markdownContent = htmlToMarkdown(htmlContent);
 
    qDebug() << "変換されたMarkdownの内容:";
    qDebug() << markdownContent;
 
    if (writeMarkdownFile(fileName, markdownContent)) {
       qDebug() << "Markdownファイルへの変換に成功";
    }
    else {
       qDebug() << "Markdownファイルへの変換に失敗";
       return -1;
    }
 
    return 0;
 }



Hoedownライブラリ

Hoedownライブラリとは

Hoedownライブラリは、高速で柔軟性のあるMarkdown処理のためのC言語ベースのライブラリである。
このライブラリは高速で柔軟性があり、多くのアプリケーションで使用されている。

Hoedownの特徴として、標準的なMarkdown構文のサポートに加えて、拡張機能も提供している。
これには表、コードブロック、自動リンク、脚注等が含まれる。
開発者は、これらの拡張機能を必要に応じて有効 / 無効にできるため、様々な要件に適応可能である。

パフォーマンス面では、Hoedownは高速な処理を実現している。
大量のMarkdownテキストを扱う場合でも効率的に動作して、リソース使用量も抑えられている。

また、Hoedownはカスタマイズ性も高く、独自のレンダラーを作成することが可能である。
これにより、Markdownを任意の出力形式に変換でき、HTML以外の他の形式への変換も可能である。

セキュリティ面では、XSS攻撃などのセキュリティリスクを軽減するための機能が組み込まれている。

Hoedownは様々なプログラミング言語から使用可能である。
C言語で記述されているため、多くの言語からバインディングを通じて使用できる。
Python、Ruby、Node.js等、多くの言語でHoedownを利用するためのラッパーが提供されている。

Hoedownの使用には、C言語の基本的な知識およびMarkdownの仕様についての理解が必要である。

Hoedownライブラリのライセンス

Hoedownライブラリは、ISCライセンスに準拠している。

Hoedownライブラリのインストール

HoedownライブラリのGithubにアクセスして、ソースコードをダウンロードする。

tar xf hoedown-<バージョン>.tar.gz
cd hoedown-<バージョン>


または、git cloneコマンドを実行して、ソースコードをダウンロードする。

git clone https://github.com/hoedown/hoedown.git
cd hoedown


Hoedownライブラリをビルドおよびインストールする。

make PREFIX=<hoedownのインストールディレクトリ> -j $(nproc)
make install


QtプロジェクトファイルおよびCMakeLists.txtファイル

 # Qtプロジェクトファイル (.pro)
 
 # Pkg-configを使用する場合
 CONFIG += link_pkgconfig
 PKGCONFIG += hoedown 
 
 # Pkg-configを使用しない場合
 LIBS += -lhoedown


 # CMakeLists.txtファイル
 
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(HOEDOWN REQUIRED hoedown)
 
 target_link_libraries(<ターゲット名> PRIVATE
    ${HOEDOWN_LIBRARIES}
 )
 
 target_include_directories(<ターゲット名> PRIVATE
    ${HOEDOWN_INCLUDE_DIRS}
 )
 
 target_compile_options(<ターゲット名> PRIVATE
    ${HOEDOWN_CFLAGS_OTHER}
 )


HTMLファイルへ変換

以下の例では、Hoedownライブラリを使用してMarkdownファイルを解析および操作している。

  1. Markdownファイルを読み込む。
  2. Hoedownライブラリを使用してMarkdownをHTMLに変換する。
  3. 変換結果を出力する。


 // main.cppファイル
 
 #include <QCoreApplication>
 #include <QFile>
 #include <QTextStream>
 #include <stdexcept>
 #include <hoedown/hoedown.h>
 #include <QDebug>
 
 QString parseMarkdown(const QString &markdown)
 {
    hoedown_buffer *ib = nullptr,
                   *ob = nullptr;
    hoedown_renderer *renderer = nullptr;
    hoedown_document *document = nullptr;
    QString result;
 
    try {
       ib = hoedown_buffer_new(markdown.toUtf8().length());
       if (!ib) throw std::runtime_error("Failed to create input buffer");
 
       hoedown_buffer_put(ib, markdown.toUtf8().constData(), markdown.toUtf8().length());
 
       ob = hoedown_buffer_new(64);
       if (!ob) throw std::runtime_error("出力バッファの生成に失敗");
 
       renderer = hoedown_html_renderer_new(HOEDOWN_HTML_SKIP_HTML, 0);
       if (!renderer) throw std::runtime_error("HTMLレンダラの生成に失敗");
 
       document = hoedown_document_new(renderer, HOEDOWN_EXT_TABLES | HOEDOWN_EXT_FENCED_CODE, 16);
       if (!document) throw std::runtime_error("ドキュメントの生成に失敗");
 
       hoedown_document_render(document, ob, ib->data, ib->size);
 
       result = QString::fromUtf8(hoedown_buffer_cstr(ob));
    }
    catch (const std::exception &e) {
        qCritical() << "Markdownのパースに失敗: " << e.what();
    }
 
    // リソースを削除
    if (ib) hoedown_buffer_free(ib);
    if (ob) hoedown_buffer_free(ob);
    if (renderer) hoedown_html_renderer_free(renderer);
    if (document) hoedown_document_free(document);
 
    return result;
 }
 
 bool readMarkdownFile(const QString &filePath, QString &content)
 {
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
       qCritical() << "Markdownファイルのオープンに失敗: " << file.errorString();
       return false;
    }
 
    QTextStream in(&file);
    content = in.readAll();
    file.close();
 
    if (content.isEmpty()) {
       qWarning() << "The file is empty";
       return false;
    }
 
    return true;
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    QString filePath = "<Markdownファイルのパス>";
    QFileInfo fileInfo(filePath);
 
    if (!fileInfo.exists()) {
       qCritical() << "Markdownファイルが見つからない: " << filePath;
       return -1;
    }
 
    if (!fileInfo.isFile() || !fileInfo.isReadable()) {
       qCritical() << "ファイルが読めない、または、正規のファイルではない: " << filePath;
       return -1;
    }
 
    QString content;
    if (!readMarkdownFile(filePath, content)) {
       return -1;
    }
 
    QString parsedContent = parseMarkdown(content);
    if (parsedContent.isEmpty()) {
       qCritical() << "Markdownのパースに失敗";
       return 1;
    }
 
    qDebug() << "パースされたMarkdownの内容:";
    qDebug().noquote() << parsedContent;
 
    return 0;
 }



その他のMarkdownパーサライブラリ

  • PEG Markdown Highlight
    PEGによるMarkdownパーサであり、Markdownの解析とシンタックスハイライトに特化している。
  • Discount
    C言語で記述されたMarkdownパーサであり、ANSIに準拠している。
  • MD4C
    C言語で記述されたMarkdownパーサであり、UTF-8に対応しており、HTMLレンダリングも可能である。