前回のHTMLのタグを分解するには? を拡張して、タグの種類や名前空間を扱えるようにしてみましょう。



【サンプル】

#include <cstdlib>
#include <iostream>
#include <iterator>

#include "boost/tokenizer.hpp"

using namespace std;
using namespace boost;


/**
タグ情報。コピーできるようにします。
*/

class tagInfo
{
public:

///タグ種類
enum type{
NONE,
START_TAG,
END_TAG,
EMPTY_TAG,
TEXT
};
///コンストラクタ
tagInfo(): m_type(NONE){};
///1文字を後ろに付け加える
void append(const char& c)
{ m_content.push_back(c); };
///内容を取得する
const string& getContent() const
{ return m_content; };
///タグ名を設定する
void setName(const string& name)
{ m_name = name; };
///タグ名を取得する
const string& getName() const
{ return m_name; };
///種類を設定する
void setType(const type& t)
{ m_type = t; };
///種類を取得する
const type& getType() const
{ return m_type; };
///出力ストリームに出力できるようにする
friend ostream& operator << (ostream& os, const tagInfo& info)
{
os << info.m_type << ":" << info.m_name << ":" << info.m_content;
};
private:
string m_content;
string m_name;
type m_type;
};


///開始タグcontentからタグ名を取り出す。nsは名前空間。セパレータ内部で使用する関数。
const string extractTagName(const string& content, const string& ns)
{
string::size_type size = ns.size();
const string space(" \n\r\t/>");
string::const_iterator i;
//タグの終端文字を検索する
i = find_first_of(content.begin()+size, content.end(),
space.begin(), space.end());
//見つからない場合、見つかった場合のそれぞれの処理
if(i == content.end()) return string("");
return string(content.begin()+size, i);
};


///iterの開始文字列が、strか?。セパレータ内部で使用する関数。
template<class Iterator>
bool cmpStr(Iterator iter, Iterator last, const string& str)
{
string::const_iterator i;
for(i=str.begin(); i!=str.end(); ++i){
if(iter == last) return false;
if(*iter != *i) return false;
++iter;
}
return true;
};


/**
タグとテキストを分けるセパレータ。
tagInfoのイテレータを返す。
*/

struct html_tag_separator
{
public:
///コンストラクタ。nsは名前空間(例:"nana:")。
html_tag_separator(const string& ns = string(""))
: m_start_literal(string("<")+ns), m_end_literal(string("</"+ns))
{};

void reset(){};


///タグ情報を取り出すメイン関数
template<class Iterator, class Token>
bool operator ()(Iterator& i, Iterator end, Token& tok)
{
tok = Token();

//既に全て読み終えていた場合、終了
if(i == end) return false;


//解析
tagInfo::type tag_type = tagInfo::NONE;
if(cmpStr(i, end, m_end_literal)){
//終了タグ
tag_type = tagInfo::END_TAG;
}else if(cmpStr(i, end, m_start_literal)){
//開始タグ
tag_type = tagInfo::START_TAG;
}else{
//テキストの場合の処理(次の開始か終了タグが見つかるまでループ)
for(; i!=end && !cmpStr(i, end, m_start_literal)
&& !cmpStr(i, end, m_end_literal); ++i)
{
tok.append(*i);
}
tok.setType(tagInfo::TEXT);
return true;
}


//タグの場合の処理
for(; i!=end && *i!='>'; ++i){
tok.append(*i);
}
if(i == end) return false;


//tokに文字をコピーする
tok.append(*i);
tok.setType(tag_type);


//タグ名取得
string name;
if(isEmptyTag(tok.getContent())){
//空タグの場合
name = getStartTagName(tok.getContent());
tok.setType(tagInfo::EMPTY_TAG);
}else if(tag_type == tagInfo::START_TAG){
//開始タグの場合
name = getStartTagName(tok.getContent());
}else if(tag_type == tagInfo::END_TAG){
//終了タグの場合
name = getEndTagName(tok.getContent());
}


//タグ名を設定
tok.setName(name);


//閉じ括弧の分を進める
++i;
return true;

};


///開始タグの名前を取得する
const string getStartTagName(const string& content)
{ return extractTagName(content, m_start_literal); };


///終了タグの名前を取得する
const string getEndTagName(const string& content)
{ return extractTagName(content, m_end_literal); };


///空タグの場合trueを返す
bool isEmptyTag(const string& content) const
{
string::size_type size = content.size();
if(content[size-2]=='/') return true;
return false;
};
private:
string m_start_literal;
string m_end_literal;
};


///タグを解析するtokenizerの型宣言。
typedef tokenizer<html_tag_separator, string::const_iterator, tagInfo> tagnizer;



//メイン
int main(int argc, char *argv[])
{
string str = "んん<nana:html>ああ<td>いう<nana:br/>え</td>か</nana:html>ささ";


//型宣言。ここで先ほどのセパレータを使います。
typedef tagnizer::iterator tag_iter;


//タグ解析の生成(名前空間をnanaにしています)
html_tag_separator sep("nana:");
tagnizer tokens(str, sep);
tag_iter i;


//++iするごとに次の情報が取得できます
for(i=tokens.begin(); i!=tokens.end(); ++i){
cout << *i << endl;
}


system("PAUSE");
return EXIT_SUCCESS;
}



【入力】

んん<nana:html>ああ<td>いう<nana:br/>え</td>か</nana:html>ささ



【出力】

「タグ種類(tagInfo::type):タグ名:内容」というフォーマットで出力されています。


4::んん
1:html:<nana:html>
4::ああ<td>い
3:br:<nana:br/>
4::うえ</td>か
2:html:</nana:html>
4::ささ



【説明】

長くてすみません。

力不足で短くできませんでした。。


ここでのポイントは、tokenizerのイテレータがstring以外の型を表していることです。

以前の「HTMLのタグを分解するには? 」では、メインのforで、stringを受け取ってました。


今回は、main関数内でループで使用しているイテレータがtagInfoを表しています


ですので、main関数内のforループで

cout << i->getName() << endl;

とすれば、タグ名を出力できます。


今回は、tokenizerで独自のクラスを返す方法のサンプルとも言えるかと思います。



参考:

boostとは?

・複数の文字のうち、いずれかの文字が最初に現れる位置を検索するには?
・HTMLタグの閉じ忘れをチェックするには?