

//
//  BnfParser おためし
//
//  BNF と XML は 而さん が作成されたものをほぼそのままパクっています．
//  http://tamachan.club.kyutech.ac.jp/~gridbug/spirit/spirit04.html
//
//  この test04.cpp では，t04_xml.xml を解析しています．
//

#include <fstream>
#include <vector>
#include <string>
#include <cstdio>

#include "../bnf.hpp"

using namespace std;

//=========================================================
struct Attr
{
  string name;
  string value;

  // ctor
  Attr(string a_name, string a_value): name(a_name), value(a_value)
  {
  }
};

typedef vector<Attr> Attrs;

//=========================================================

typedef vector<class Element *> Elems;

struct Element
{
  string name;  // 要素名
  Attrs attrs;  // 属性
  
  Element * parent;  // 親
  Elems   children; // 子のリスト

  // コンストラクタ
  Element(string const & a_name): name(a_name)
  {
  }
  
  // デストラクタ
  ~Element()
  {
    for (vector<Element*>::iterator itor = children.begin();
        itor != children.end();
        ++itor)
    {
      delete *itor;
    }
  }
};

//=========================================================
struct Resource
{
  string data; // 解析対象データ

  Element * root; // ツリーの根になるもの

  Element * current_element;  // 解析のためのワーク

  // コンストラクタ
  Resource(string const & a_data)
      : data(a_data), root(0), current_element(0)
  {
  }
  
  // デストラクタ
  ~Resource()
  {
    delete root;
  }

  // 開始タグが解析できたので要素を追加する
  void add_element(string const & name, Attrs const & attrs)
  {
    Element * e = new Element(name);

    for (Attrs::const_iterator itor = attrs.begin();
        itor != attrs.end();
        ++itor)
    {
      e->attrs.push_back(*itor);
    }
    
    if (! root)
    {
      root = current_element = e;
    }
    else
    {
      current_element->children.push_back(e);
      e->parent = current_element;
      current_element = e;
    }
  }
  
  // 要素を閉じる
  void close_element()
  {
    current_element = current_element->parent;
  }
};




//=========================================================

//
//   ルールがマッチする毎に呼ばれるコールバック関数
//

static string tagname_buf = "";

static Attrs attributes_buf;
static string attrname_buf = "";
static string string_buf = "";


bool matched_callback(
    void * option,
    char const * _rn,
    int start,
    int length)
{
  Resource * resource = static_cast<Resource*>(option);
  string rulename = _rn;

  string  part = resource->data.substr(start, length);
  
  if (length < 0)
  {
    // このサンプルではまだメタ情報を有効に使う必要はないので
    // 無視しています．
  }
  else if (rulename == "emptyelem_name_r"
      || rulename == "etag_name_r"
      || rulename == "stag_name_r")
  {
    // まだ何を表す名前なのか決定できないので
    // とりあえず，バッファだけしておきます
    tagname_buf = part;
    
    // 属性を解析していくためのワークをリセット
    string_buf = "";
    attributes_buf.clear();
  }
  else if (rulename == "attr_name_r")
  {
    attrname_buf = part;
  }
  else if (rulename == "string_inner_r")
  {
    string_buf += part; // 追加していきます
  }
  else if (rulename == "attribute")
  {
    attributes_buf.push_back( Attr(attrname_buf, string_buf) );
    string_buf = "";
  }

  // 空要素のタグの解析が完了
  else if (rulename == "emptyelem")
  {
    resource->add_element(tagname_buf, attributes_buf);
    resource->close_element();
    attributes_buf.clear();
  }
  // 終了タグ
  else if (rulename == "etag")
  {
    resource->close_element();
  }
  // 開始タグ
  else if (rulename == "stag")
  {
    resource->add_element(tagname_buf, attributes_buf);
    attributes_buf.clear();
  }

  return true;  // 今のところ，戻り値は使われません．何を返してもよいです．
}

// ファイルから読み込み
string fromfile(char const * fname)
{
  ifstream file (fname);
  string buf;
  buf.assign(istreambuf_iterator<char>(file),
      istreambuf_iterator<char>());
  file.close();
  
  return buf;
}

// (前方宣言) Elementの内容を出力
void dump_element(Element const * );

// おためし
int main()
{
  try
  {
    string rule = fromfile("t04_xml.bnf");
    Resource rsc(fromfile("t04_xml.xml"));

    bnf::BnfParser c = rule.c_str();

    c.parse(
        rsc.data.c_str(),
        "xml",
        &rsc,
        matched_callback);
    
    dump_element(rsc.root);

  }
  catch(...)
  {
    puts("something wrong");
  }
}

static int indent = 0;

void print_indent()
{
  for (int i = 0; i < indent; i++)
  {
    printf(" ");
  }
}

// Elementの内容を出力
void dump_element(Element const * e)
{
  // 要素名
  print_indent();
  printf("tag name  : %s\n", e->name.c_str());
  
  
  // 属性
  print_indent();
  printf("attributes:\n");
  for (Attrs::const_iterator itor = e->attrs.begin();
      itor != e->attrs.end();
      ++itor)
  {
    print_indent();
    printf("          - %s = %s\n",
        itor->name.c_str(), itor->value.c_str());
  }
  
  
  // 内容
  indent += 2;
  for (Elems::const_iterator itor = e->children.begin();
      itor != e->children.end();
      ++itor)
  {
    dump_element(*itor);
  }
  indent -= 2;
}

/* 出力結果

tag name  : MResourceFile
attributes:
          - version = 1.00
  tag name  : Description
  attributes:
  tag name  : Window
  attributes:
            - name = testwin1
            - width = 320
            - height = 240
    tag name  : Editbox
    attributes:
              - name = edit1
              - left = 16
              - top = 16
              - width = 160
              - height = 16
    tag name  : StaticText
    attributes:
              - name = stext1
              - left = 32
              - top = 64
              - text = StaticText1
    tag name  : Listbox
    attributes:
              - name = list
              - left = 16
              - top = 96
              - width = 240
              - height = 120
      tag name  : String
      attributes:
                - text = test
      tag name  : Icon
      attributes:
                - icon = hoge
  tag name  : Window
  attributes:
            - name = testwin2
            - width = 60%
            - height = 30%

*/

