C++でHTMLエンティティパーサーを実装する方法
文字列が与えられたとき、HTML構文の特殊文字を通常の文字へ置き換えるHTMLパーサーを設計することを考えます。HTMLエンティティパーサーとは、HTMLコードを入力として受け取り、特殊文字のエンティティをすべて対応する実際の文字に置き換えるパーサーのことです。
まず、対象となる特殊文字とそのエンティティの一覧を確認しましょう。
- 二重引用符:エンティティは
"、記号は" - 単一引用符:エンティティは
'、記号は' - アンパサンド:エンティティは
&、記号は& - 大なり記号:エンティティは
>、記号は> - 小なり記号:エンティティは
<、記号は< - スラッシュ:エンティティは
⁄、記号は/
たとえば、入力が「& is changed but &ambassador; is not.」である場合、出力は「& is changed but &ambassador; is not.」となります。& は有効なエンティティなので変換されますが、&ambassador; は定義されていないため元のまま残る点に注目してください。
アルゴリズムの考え方
この問題は、次の手順で解決できます。
- 入力文字列をスペースで分割し、トークンの配列
vを作成します。 - 結果を格納する空文字列
retを用意します。 - マップ
mを定義し、キーにHTMLエンティティ名、値に対応する特殊文字を登録します。 - 各トークンについて先頭から走査します。
- 現在の文字が
'&'であれば、セミコロン';'まで読み進めて1つの候補文字列tempを組み立てます。 tempがマップmに存在すれば対応する記号に置き換えてretに追加し、存在しなければ元の文字列のまま追加します。'&'以外の文字はそのままretに追加します。
- 現在の文字が
- トークン間にはスペースを挿入し、最後に
retを返します。
この方法により、& のような正当なエンティティだけが変換され、&ambassador; のような未定義の文字列は壊されることなく保持されます。
C++による実装例
それでは、実際の実装を見ていきましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
vector<string> split(string& s, char delimiter){
vector<string> tokens;
string token;
istringstream tokenStream(s);
while(getline(tokenStream, token, delimiter)){
tokens.push_back(token);
}
return tokens;
}
void out(vector<string> v){
for(string s : v) cout << s << endl;
}
string entityParser(string text) {
vector<string> v = split(text, ' ');
string ret = "";
map<string, string> m;
m["""] = "\"";
m["'"] = "\'";
m["&"] = "&";
m[">"] = ">";
m["<"] = "<";
m["⁄"] = "/";
for (int i = 0; i < v.size(); i++) {
string s = v[i];
string temp = "";
int n = v[i].size();
int k = 0;
while (k < n) {
if (v[i][k] == '&') {
temp += v[i][k];
k++;
while (k < n && v[i][k] != ';') {
temp += v[i][k];
k++;
}
temp += v[i][k];
k++;
if (m.count(temp))
ret += m[temp];
else
ret += temp;
temp = "";
}
else {
ret += v[i][k];
k++;
}
}
if (temp.size() && m.count(temp)) {
ret += m[temp];
}
else if (temp.size())
ret += temp;
if (i != v.size() - 1)
ret += " ";
}
return ret;
}
};
main(){
Solution ob;
cout << (ob.entityParser("& is changed but &ambassador; is not."));
}実装のポイント
split関数ではistringstreamとgetlineを使って、区切り文字ごとに文字列を分割しています。- マップへの登録により、エンティティの判定処理がシンプルな
m.count(temp)の1行で済みます。 - セミコロンまで読み込んだ文字列がマップに存在しない場合は、そのまま連結することで元のテキストを保護しています。
入力例
"& is changed but &ambassador; is not."
出力例
& is changed but &ambassador; is not.
まとめ
この記事では、C++を使ってHTMLエンティティパーサーを実装する方法を紹介しました。文字列をトークンに分割し、& から ; までの範囲を抽出してマップで照合するというシンプルなアプローチで、正当なエンティティのみを正確に変換できる点がポイントです。同様の手法は、他のプログラミング言語でも応用できますので、ぜひ参考にしてみてください。
-
C++で文字列をトークン化する方法:stringstreamとgetline()による分割テクニック
この記事では、C++における文字列のトークン化(分割)の方法について解説します。C言語では、文字配列に対してstrtok()関数を使用することで文字列を分割できましたが、C++ではstd::stringクラスを扱うため、少し異なるアプローチが必要です。C++の機能を活用して文字列を分割するには、まずstd::stringをstringstream(文字列ストリーム)に変換します。その後、getline()関数を使うことで、指定した区切り文字(デリミタ)ごとに文字列を切り出すことができます。getline()関数は、以下の3つの引数を受け取ります。入力元となる文字列ストリーム出力結果を格納する文
-
C++で文字列をトークン化(分割)する2つの方法を解説
文字列のトークン化(分割)とは、1つの文字列を区切り文字(スペースやカンマなど)を基準に、複数の部分文字列へ分割する処理のことです。C++では、標準ライブラリだけでもいくつかの方法で実現できます。本記事では、代表的な2つの方法をサンプルコード付きで紹介します。方法1:stringstreamを使って空白で分割する1つ目の方法は、stringstreamを使ってスペースで区切られた単語を順に読み取る方法です。この方法はやや制限がありますが、適切なチェックを加えれば十分に目的を果たすことができます。サンプルコード#include <vector> #include <string