TOP > プログラミング関連 > XML > XML「みたいなモノ」を読み込む簡易モジュール

[English]

◆◆ XML「みたいなモノ」を読み込む簡易モジュール ◆◆
タグつきの文書って応用が利いて便利なんですけど、 「XML」のブランド名がついてると、 面倒な しがらみがたくさんありますよね?

DOCTYPE宣言? なにそれ?
DTD? なにそれ?

おいらはタグつきの文書を読み込みたいだけなのですー! がおー!
タグつきの文書を読み込む処理のソース
XML? なにそれ?
タグつきの文書を読み込むだけだもんねー。

◆ ヘッダーファイル [NotXML.h]
				//=========================================================
				//
				// 簡易XMLパーサークラス(宣言)
				//
				//=========================================================
				
				#ifndef _NOTXML_
				#define _NOTXML_
				
				#include <vector>
				#include <map>
				#include <string>
				
				using namespace std;
				
				
				class NotXML {
				
				public:
				
					//=========================================================
					// タグ構造体
					//=========================================================
					struct Tag {
				
						string name;                   // タグ名
						string value;                  // 値
						map<string, string> attribute; // 属性
						vector<Tag> childList;         // 子要素
				
						Tag() {
							name  = "";
							value = "";
						}
					};
					
					//=========================================================
					// 読み込む
					//=========================================================
					vector<Tag> Read     (string fileName);
					Tag         ReadRoot (string fileName);
				
					//=========================================================
					// Utilities
					//=========================================================
					static Tag GetTagFromName( const Tag& parent, string name ) {
				
						Tag result;
				
						for (unsigned int i = 0 ; i < parent.childList.size(); ++i) {
				
							if ( parent.childList[i].name == name) {
								result = parent.childList[i];
								break;
							}
						}
				
						return result;
					}
					static vector<Tag> GetTagListFromName( const Tag& parent, string name ) {
				
						vector<Tag> result;
				
						for (unsigned int i = 0 ; i < parent.childList.size(); ++i) {
				
							if ( parent.childList[i].name == name) {
								result.push_back( parent.childList[i] );
							}
						}
				
						return result;
					}
					static string GetAttribute( const Tag& tag, string name, string defaultValue = "" ) {
				
						string result = defaultValue;
				
						if (tag.attribute.find(name) != tag.attribute.end()) {
							result = tag.attribute.find(name)->second;
						}
				
						return result;
					}
				
				private:
				
					///// タグの種類(読み取り中専用)
					enum PURE_TAG_TYPE {
				
						NOT_PURE_TAG_TYPE,     // 無効値
						OPEN,    // 開始タグ    <***>
						CLOSE,   // 閉じタグ    </***>
						EMPTY,   // 空要素タグ  <***/>
						COMMENT, // コメント    <!-- ***  -->
				
						PROCESS, // 処理命令    <?***?> Processing Instruction
						DOCTYPE, // DOCTYPE宣言 <!DOCTYPE ****/>
				
						VALUE    // 値          上記以外(タグではない)
					};
				
				
					//=========================================================
					// ファイルを読み込んで文字列にする
					//=========================================================
					string ReadFile_toString(string fileName);
				
					//=========================================================
					// 文字列からタグっぽい箇所を抽出
					//=========================================================
					vector<string> Parse_PureTagList(string str);
				
					//=========================================================
					// タグの種類を判別する
					//=========================================================
					PURE_TAG_TYPE JadgePureTag( string pureTag );
				
					//=========================================================
					// 属性を解釈
					//=========================================================
					map<string, string> GetAttribute(string str);
				
					//=========================================================
					// タグとしての情報を取得する
					//=========================================================
					Tag GetTagInfo(string str, PURE_TAG_TYPE type);
				
					//=========================================================
					// タグ間の構造を解釈
					//=========================================================
					vector<Tag> Parse(vector<string> pureTagList);
				
					//=========================================================
					// 前後Trim
					//【引数】
					// string
					//【戻り値】
					// 前後の半角スペース、\n、\r、\t を取り除いた文字列
					//=========================================================
					string Trim(const string &str);
				};
				#endif
				


◆ 実装ファイル [NotXML.cpp]
				//=========================================================
				//
				// 簡易XMLパーサークラス(実装)
				//
				//=========================================================
				
				#include "NotXML.h"
				
				#include <stdio.h>
				
				#include <fstream>
				
				#include <vector>
				#include <stack>
				
				using namespace std;
				
				
				//=========================================================
				// 読み込む
				//=========================================================
				vector<NotXML::Tag> NotXML::Read(string fileName) {
				
					// 戻り値となる変数を用意
					vector<Tag> result;
				
					// 指定されたファイルを読み込んで、中身を string型で持つ
					string fileContent = ReadFile_toString( fileName );
				
					// 「<」「>」で囲われた要素のリストを取り出す
					vector<string> pureTagList = Parse_PureTagList(fileContent);
				
					// タグの開始・閉じ、入れ子構造などを読み取って出来上がり
					result = Parse(pureTagList);
				
					// 召し上がれー
					return result;
				
				}
				//=========================================================
				// 読み込む
				// 最上位要素を一個だけ
				// ルート要素を持たせている場合はこっちを呼ぶと楽かも。
				//=========================================================
				NotXML::Tag NotXML::ReadRoot(string fileName) {
				
					/*
						Read() に丸投げして、結果から一番最初の要素を取り出しているだけです。
					*/
				
					Tag result;
				
					vector<Tag> rootList = Read(fileName);
				
					if (rootList.size() > 0) {
						result = rootList[0];
					}
				
					return result;
				}
				
				
				
				
				
				//=========================================================
				// ファイルを読み込んで文字列にする
				//=========================================================
				string NotXML::ReadFile_toString(string fileName) {
				
					string result = "";
					bool isFirstRow = true;
				
					std::ifstream ifs;
					
					ifs.open( fileName.c_str() );
					
					string row;
					while ( getline(ifs, row) ) {
				
						if (!isFirstRow) {
							result += '\n';
						}
				
						result += row;
				
						isFirstRow = false;
					}
				
					ifs.close();
				
					return result;
				}
				
				//=========================================================
				// 文字列からタグっぽい箇所を抽出
				//=========================================================
				vector<string> NotXML::Parse_PureTagList(string str) {
				
					vector<string> result;
				
					string temp = "";
				
					for (unsigned int i = 0; i < str.size(); ++i) {
				
						char c = str[i];
				
						// 別のタグが始まるならば
						if (c == '<') {
				
							// 現在の状態で保存
							if (temp != "") {
								result.push_back(temp);
								temp = "";
							}
						}
				
						temp += c;
				
						// 閉じましたか?
						if (c == '>') {
				
							// 現在の状態で保存
							result.push_back(temp);
							temp = "";
						}
				
						/*
							ファイル末尾に余った文字列は無視されるのですね。
							どのタグからも閉じられていない値ということで。
						*/
					}
				
				
				
					return result;
				}
				
				//=========================================================
				// タグの種類を判別する
				//=========================================================
				NotXML::PURE_TAG_TYPE NotXML::JadgePureTag( string pureTag ) {
				
					PURE_TAG_TYPE type = NOT_PURE_TAG_TYPE;
				
					/*
						・開始タグ    :開始が「<」   、末尾が「>」である。
						・閉じタグ    :開始が「</」  、末尾が「>」である。
						・自己完結タグ:開始が「<」   、末尾が「/>」である。
						・コメント    :開始が「<!--」、末尾が「-->」である。
						
						・処理命令    :開始が「<?」       、末尾が「?>」である。
						・DOCTYPE宣言 :開始が「<!DOCTYPE」、末尾が「/>」である。
				
						・値          :「<」~「>」ではない生の文字列
				
						・なんでもない:開始が「</」  、末尾が「/>」である
					*/
					do {
						///// コメント?
						string commentStart = pureTag.substr(0, 4);
						string commentEnd   = (pureTag.size() < 3) ? "" : pureTag.substr( pureTag.size() - 3);
						if (commentStart == "<!--" && commentEnd == "-->") {
							type = COMMENT;
							break;
						}
				
						string closeTagStart  = pureTag.substr(0, 2);
						string nonValueTagEnd = (pureTag.size() < 2) ? "" : pureTag.substr( pureTag.size() - 2);
				
						///// こんなのはおかしい!
						if (closeTagStart == "</" && nonValueTagEnd == "/>") {
							type = NOT_PURE_TAG_TYPE;
							break;
						}
				
						string processStart = pureTag.substr(0, 2);
						string processEnd   = (pureTag.size() < 2) ? "" : pureTag.substr( pureTag.size() - 2);
				
						///// 処理命令?
						if (processStart == "<?" && processEnd == "?>") {
							type = PROCESS;
							break;
						}
				
						string docTypeStart = pureTag.substr(0, 9);
				
						///// DOCTYPE宣言?
						if (docTypeStart == "<!DOCTYPE" && nonValueTagEnd == "/>") {
							type = DOCTYPE;
							break;
						}
				
				
						string nomalTagStart = pureTag.substr(0, 1);
						string nomalTagEnd   = (pureTag.size() < 1) ? "" : pureTag.substr( pureTag.size() - 1);
				
						///// 自己完結タグ?
						if (nomalTagStart == "<" && nonValueTagEnd == "/>") {
							type = EMPTY;
							break;
						}
				
						///// 閉じタグ?
						if (closeTagStart == "</" && nomalTagEnd == ">") {
							type = CLOSE;
							break;
						}
				
						///// 開始タグ?
						if (nomalTagStart == "<" && nomalTagEnd == ">") {
							type = OPEN;
							break;
						}
				
						///// 値?
						if (type == NOT_PURE_TAG_TYPE) {
							type = VALUE;
						}
				
					} while(false);
				
					//printf("%s:%d\n", pureTag.c_str(), type);
				
					return type;
				}
				
				
				//=========================================================
				// 属性を解釈
				//=========================================================
				map<string, string> NotXML::GetAttribute(string str) {
				
					map<string, string> result;
				
					// 現在、何を読み取っている最中なのか?
					string currentParseMode;
					string PARSE_MODE_KEY   = "key";
					string PARSE_MOSE_VALUE = "value";
					currentParseMode = PARSE_MODE_KEY;
				
					unsigned int index = 0;
					string tempKey   = "";
					string tempValue = "";
				
					bool isStartedAddValue = false;
				
					while ( index < str.size() ) {
				
						///// キーを読み取っているところです。
						if ( currentParseMode == PARSE_MODE_KEY ) {
						
							// 「=」が出たら、キー終了
							if (str[index] == '=') {
				
								currentParseMode = PARSE_MOSE_VALUE;
								++index;
								continue;
							}
				
							// キーに文字を追加していく
							tempKey += str[index];
				
						
						///// 値を読み取っているところです
						} else if ( currentParseMode == PARSE_MOSE_VALUE ) {
						
							// まだ値に文字を追加し始めていない
							if (!isStartedAddValue) {
				
								// 空白じゃないものが出た
								if ( !isspace( str[index])) {
				
									isStartedAddValue = true;
								}
							}
				
				
							// 値に文字を追加し始めているところ
							if (isStartedAddValue) {
						
								// 終了判定の微妙さ。現在の文字は追加していいの?
								bool isAddCurrentChar = true;
				
								// クォーテーションで囲んで終了した?
								bool isEndByQuort = false;
				
								// 終了判定
								if ( tempValue.size() > 0 ) {
				
									// 値の最初の文字が クォーテーションである場合
									if ( tempValue[0] == '"' || tempValue[0] == '\'' ) {
				
										// クォーテーションが現れたら、値終了
										if ( str[index] == '"' || str[index] == '\"' ) {
				
											// エスケープされているか?
											bool isEscape = false;
											if ( tempValue.size() > 1 && tempValue[ tempValue.size() - 1] == '\\' ) {
				
												isEscape = true;
				
												// しかし「\」記号がエスケープされていたら判定は覆る。
												if ( tempValue.size() > 2 && tempValue[ tempValue.size() - 2] == '\\' ) {
													isEscape = false;
												} 
											}
				
											// エスケープされてないなら値終了
											if (!isEscape) {
				
												currentParseMode  = PARSE_MODE_KEY;
												isStartedAddValue = false;
												isAddCurrentChar  = true;
				
												isEndByQuort = true;
											}
										}
				
									// 値の最初の文字が クォーテーションではない場合
									} else {
				
										// 空白が現れたら終了
										if ( isspace( str[index] )  ) {
											currentParseMode  = PARSE_MODE_KEY;
											isStartedAddValue = false;
											isAddCurrentChar  = false;
										}
				
										// 最後まで達していても終了
										if ( index == str.size() - 1 ) {
											currentParseMode  = PARSE_MODE_KEY;
											isStartedAddValue = false;
											isAddCurrentChar  = true;
										}
				
									}
				
								} // if 値読み込み 終了判定
				
								// 値に文字を追加していく
								if (isAddCurrentChar) {
									tempValue += str[index];
								}
				
								// 値読み込みが終了している場合
								if (currentParseMode != PARSE_MOSE_VALUE) {
				
									// 値が空白じゃないなら、加工する
									if (tempValue != "") {
				
										// 値の前後のクォーテーションを外す
										if ( isEndByQuort) {
											tempValue = tempValue.substr( 1, tempValue.size() - 2 );
				
				
											// エスケープ記号を外す
											/*
												\" → "
												\' → '
												\\ → \
				
												\\" → ???
												\\\ → ???
				
												あまり中途半端に気を利かさない方がいい?
												そんな気がしてきました。
												よし。処理しません。
											*/
										}
				
									}
									
									// キーの前後はトリムする
									tempKey = Trim( tempKey );
				
				
									// キーと値のペアを属性に追加する
									string key   = tempKey;
									string value = tempValue;
				
									result.insert( pair<string, string>( key, value ) );
				
									tempKey   = "";
									tempValue = "";
								}
				
							} // if 値に文字を追加し始めているところ
						}
				
				
						++index;
					};
				
				
				
					return result;
				}
				
				//=========================================================
				// タグとしての情報を取得する
				//=========================================================
				NotXML::Tag NotXML::GetTagInfo(string str, PURE_TAG_TYPE type) {
				
					Tag result;
				
					///// 「<」「>」を外した中身部分
					string content = "";
				
					// 開始タグです。<***>
					if        ( type == OPEN ) {
				
						content = str.substr(1, str.size() - 2);
				
					// 閉じタグです。</***>
					} else if ( type == CLOSE ) {
				
						content = str.substr(2, str.size() - 3);
				
					// 空要素タグです。<***/>
					} else if ( type == EMPTY ) {
				
						content = str.substr(1, str.size() - 3);
				
					// 処理命令タグです。<?***?>
					} else if ( type == PROCESS ) {
				
						content = str.substr(2, str.size() - 4);
				
					// DOCTYPE宣言です。<!DOCTYPE ***/>
					} else if ( type == DOCTYPE ) {
				
						content = str.substr(9, str.size() - 11);
				
						///// DOCTYPEの場合は特殊。
						result.name  = "";
						result.value = content;
						return result;
					}
				
					// タグ名終了時点のインデックス
					int tagNameEndIndex = -1;
				
					// タグ名を取り出す
					for (unsigned int i = 0; i < content.size(); ++i) {
				
						// 空白じゃないなら、タグ名として文字を追加していく
						if (!isspace( content[i] )) {
							result.name += content[i];
				
						// 空白が出た場合
						} else {
				
							// すでにタグ名を認識し始めていたなら、終了
							if (result.name != "") {
				
								// タグ名終了時点のインデックスを覚えておく。
								tagNameEndIndex = i;
				
								// 終了
								break;
				
							// まだタグ名を認識し始めていないなら、続行
							} else {
								// 続行
							}
						}
					}
				
					// タグ名以降の部分から属性を取り出す
					string attributeString = (tagNameEndIndex > 0) ?  content.substr( tagNameEndIndex ) : "";
				
					result.attribute = GetAttribute(attributeString);
				
				
					return result;
				}
				
				//=========================================================
				// タグ間の構造を解釈
				//=========================================================
				vector<NotXML::Tag> NotXML::Parse(vector<string> pureTagList) {
				
					///// 根元タグリスト
					vector<Tag> rootTagList;
				
					///// 開き中のタグ
					stack<Tag> tagStack;
				
					for (unsigned int i = 0; i < pureTagList.size(); ++i) {
				
						string pureTag = pureTagList[i];
				
				
						// 分類する
						PURE_TAG_TYPE type =  JadgePureTag( pureTag );
				
				
						// 開始タグでした
						if        ( type == OPEN ) {
				
							// タグの名前とかを取得して、
							Tag tag =  GetTagInfo( pureTag, type);
				
							// スタックに乗せる
							tagStack.push( tag );
				
				
						// 閉じタグでした
						} else if ( type == CLOSE ) {
				
							// タグの名前とかを取得
							Tag tag =  GetTagInfo( pureTag, type);
				
							// 開き中のタグがありますよね?
							if (tagStack.size() > 0 ) {
				
								// 開き中のタグと、名前が一致してますよね?
								if ( tagStack.top().name == tag.name ) {
				
									// タグを取り出して、閉じる。
									Tag closedTag = tagStack.top();
									tagStack.pop();
				
									// そして、開き中のタグがありますか?
									if ( tagStack.size() > 0 ) {
				
										// 開き中のタグの子要素として追加する
										tagStack.top().childList.push_back( closedTag );
				
									} else {
				
										// 最上位タグのリストに追加する
										rootTagList.push_back( closedTag );
									}
				
								// え、一致してないの? それは困りましたね。
								} else {
									printf("閉じタグに対して、開き中のタグがない:%s\n", pureTag.c_str());
								}
				
				
							// え、ないの? それは困りましたね。
							} else {
								printf("閉じタグに対して、開き中のタグがない:%s\n", pureTag.c_str());
							}
				
				
						// 空要素タグでした
						} else if ( type == EMPTY ) {
				
							// タグの名前とかを取得して、
							Tag tag =  GetTagInfo( pureTag, type);
				
							// 一応念を押して要素を空にしておく。
							tag.value = "";
				
							// 開き中のタグがありますか?
							if ( tagStack.size() > 0 ) {
				
								// 開いているタグの子要素として追加する
								tagStack.top().childList.push_back( tag );
				
							// 開き中のタグがないならば、
							} else {
				
								// 最上位タグのリストに追加する
								rootTagList.push_back( tag );
							}
				
						// 値でした
						} else if ( type == VALUE ) {
				
							// 開き中のタグがあるはずなのです。
							if ( tagStack.size() > 0 ) {
				
								// この次には閉じタグが来るはず…
								tagStack.top().value += pureTag;
				
							// え、開き中のタグがないの?
							} else {
								//printf("開き中のタグがない・値:%s\n", pureTag.c_str());
							}
				
				
						// コメントでした
						} else if ( type == COMMENT ) {
				
							// 無視
				
						// 処理命令でした
						} else if ( type == PROCESS ) {
				
							// 無視してしまいます。
				
						// DOCTYPE宣言でした
						} else if ( type == DOCTYPE ) {
				
							// 無視してしまいます。
				
				
						// 無効値でした
						} else if ( type == NOT_PURE_TAG_TYPE ) {
				
							// 無視
				
						}
					}
				
					///// 根元タグリスト
					rootTagList;
				
					///// 開き中のタグ
					tagStack;
				
				
					return rootTagList;
				}
				
				
				
				//=========================================================
				// Trim
				//【引数】
				// string
				//【戻り値】
				// 前後のホワイトスペースを取り除いた文字列
				//=========================================================
				string NotXML::Trim(const string &str){
				
					if (str == "") {
						return "";
					}
				
					string result = "";
				
					unsigned int firstIndex = 0;
					unsigned int lastIndex = str.length() - 1;
				
					// 開始位置を見つける
					while (true) {
				
						// 全部空白
						if ( firstIndex >= str.length() ) {
							return "";
						}
				
						// こんなんでいいの?
						if( str[firstIndex] == ' ' || str[firstIndex] == '\r' || str[firstIndex] == '\n' || str[firstIndex] == '\t'){
							++firstIndex;
						} else {
							break;
						}
					}
				
					// 終了位置を見つける
					while (true) {
				
						// 再びこんなんでいいの?
						if( lastIndex > 0 && str[lastIndex] == ' ' || str[lastIndex] == '\r' || str[lastIndex] == '\n' || str[lastIndex] == '\t'){
							--lastIndex;
						} else {
							break;
						}
					}
				
					// 開始位置から終了位置までを取り出す
					for ( unsigned int i = firstIndex; i <= lastIndex; ++i ) {
						result += str[i];
					}
				
					return result;
				}

				
ソースをコピーして、アナタのアプリケーションに混ぜてあげてね。
お望みなら 好きなように いじるとよいですよ。
ライセンスはパブリックドメインとします。

言い忘れてましたけど 言語は みんなの大好きな C++ です。
使い方
				#include "NotXML.h"
				
				int main () {
				
					///// ファイルを読み込む
					vector <NotXML::Tag> tagList = NotXML().Read( "yourStructuredDocumentFile.txt" );
				
					for ( unsigned int i = 0; i < tagList.size(); ++i ) {
				
						// タグ名="TAG_A" ?
						if ( tagList[i].name == "TAG_A") {
							
							printf ("値 : %s\n", tagList[i].value.c_str() );
							
						}
						// タグ名="TAG_B" ?
						else if (tagList[i].name == "TAG_B") {
						
							printf ("値 : %s\n", tagList[i].value.c_str() );
							
						}
					}
					
					return 0;
				}
				
ファイル名を指定して Read関数を実行すると、Tag構造体のリストが取得できます。
Tag構造体の中に、タグとしての情報があれこれと入ってます。 Tag構造体の中には、さらにTag構造体があるので、入れ子構造も再現できます。