text: /*!The Treasure Box Library
   *
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   * 
   * Copyright (C) 2009 - 2018, TBOOX Open Source Group.
   *
   * @author      ruki
   * @file        xplist.c
   * @ingroup     object
   *
   */
   
  /* //////////////////////////////////////////////////////////////////////////////////////
   * trace
   */
  #define TB_TRACE_MODULE_NAME        "oc_reader_xplist"
  #define TB_TRACE_MODULE_DEBUG       (0)
  
  /* //////////////////////////////////////////////////////////////////////////////////////
   * includes
   */
  #include "xplist.h"
  #include "reader.h"
  
  /* //////////////////////////////////////////////////////////////////////////////////////
   * macros
   */
  
  // the array grow
  #ifdef __tb_small__
  #   define TB_OC_XPLIST_READER_ARRAY_GROW           (64)
  #else
  #   define TB_OC_XPLIST_READER_ARRAY_GROW           (256)
  #endif
  
  /* //////////////////////////////////////////////////////////////////////////////////////
   * implementation
   */
  static tb_object_ref_t tb_oc_xplist_reader_func_date(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // empty?
      if (event == TB_XML_READER_EVENT_ELEMENT_EMPTY) 
          return tb_oc_date_init_from_time(0);
  
      // done
      tb_bool_t       leave = tb_false;
      tb_object_ref_t date = tb_null;
      while (!leave && (event = tb_xml_reader_next(reader->reader)))
      {
          switch (event)
          {
          case TB_XML_READER_EVENT_ELEMENT_END: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, leave, tb_true);
                  
                  // is end?
                  if (!tb_stricmp(name, "date"))
                  {
                      // empty?
                      if (!date) date = tb_oc_date_init_from_time(0);
  
                      // leave it
                      leave = tb_true;
                  }
              }
              break;
          case TB_XML_READER_EVENT_TEXT: 
              {
                  // text
                  tb_char_t const* text = tb_xml_reader_text(reader->reader);
                  tb_assert_and_check_break_state(text, leave, tb_true);
                  tb_trace_d("date: %s", text);
  
                  // done date: %04ld-%02ld-%02ld %02ld:%02ld:%02ld
                  tb_tm_t tm = {0};
                  tb_char_t const* p = text;
                  tb_char_t const* e = text + tb_strlen(text);
  
                  // init year
                  while (p < e && *p && !tb_isdigit(*p)) p++;
                  tb_assert_and_check_break_state(p < e, leave, tb_true);
                  tm.year = tb_atoi(p);
  
                  // init month
                  while (p < e && *p && tb_isdigit(*p)) p++;
                  while (p < e && *p && !tb_isdigit(*p)) p++;
                  tb_assert_and_check_break_state(p < e, leave, tb_true);
                  tm.month = tb_atoi(p);
                  
                  // init day
                  while (p < e && *p && tb_isdigit(*p)) p++;
                  while (p < e && *p && !tb_isdigit(*p)) p++;
                  tb_assert_and_check_break_state(p < e, leave, tb_true);
                  tm.mday = tb_atoi(p);
                  
                  // init hour
                  while (p < e && *p && tb_isdigit(*p)) p++;
                  while (p < e && *p && !tb_isdigit(*p)) p++;
                  tb_assert_and_check_break_state(p < e, leave, tb_true);
                  tm.hour = tb_atoi(p);
                          
                  // init minute
                  while (p < e && *p && tb_isdigit(*p)) p++;
                  while (p < e && *p && !tb_isdigit(*p)) p++;
                  tb_assert_and_check_break_state(p < e, leave, tb_true);
                  tm.minute = tb_atoi(p);
                  
                  // init second
                  while (p < e && *p && tb_isdigit(*p)) p++;
                  while (p < e && *p && !tb_isdigit(*p)) p++;
                  tb_assert_and_check_break_state(p < e, leave, tb_true);
                  tm.second = tb_atoi(p);
              
                  // time
                  tb_time_t time = tb_mktime(&tm);
                  tb_assert_and_check_break_state(time >= 0, leave, tb_true);
  
                  // date
                  date = tb_oc_date_init_from_time(time);
              }
              break;
          default:
              break;
          }
      }
  
      // ok?
      return date;
  }
  static tb_object_ref_t tb_oc_xplist_reader_func_data(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // empty?
      if (event == TB_XML_READER_EVENT_ELEMENT_EMPTY) 
          return tb_oc_data_init_from_data(tb_null, 0);
  
      // done
      tb_bool_t           leave = tb_false;
      tb_char_t*          base64 = tb_null;
      tb_object_ref_t     data = tb_null;
      while (!leave && (event = tb_xml_reader_next(reader->reader)))
      {
          switch (event)
          {
          case TB_XML_READER_EVENT_ELEMENT_END: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, leave, tb_true);
                  
                  // is end?
                  if (!tb_stricmp(name, "data"))
                  {
                      // empty?
                      if (!data) data = tb_oc_data_init_from_data(tb_null, 0);
                      
                      // leave it
                      leave = tb_true;
                  }
              }
              break;
          case TB_XML_READER_EVENT_TEXT: 
              {
                  // text
                  tb_char_t const* text = tb_xml_reader_text(reader->reader);
                  tb_assert_and_check_break_state(text, leave, tb_true);
                  tb_trace_d("data: %s", text);
  
                  // base64
                  base64 = tb_strdup(text);
                  tb_char_t* p = base64;
                  tb_char_t* q = p;
                  for (; *p; p++) if (!tb_isspace(*p)) *q++ = *p;
                  *q = '\0';
  
                  // decode base64 data
                  tb_char_t const*    ib = base64;
                  tb_size_t           in = tb_strlen(base64); 
                  if (in)
                  {
                      tb_size_t           on = in;
                      tb_byte_t*          ob = tb_malloc0_bytes(on);
                      tb_assert_and_check_break_state(ob && on, leave, tb_true);
                      on = tb_base64_decode(ib, in, ob, on);
                      tb_trace_d("base64: %u => %u", in, on);
  
                      // init data
                      data = tb_oc_data_init_from_data(ob, on); tb_free(ob);
                  }
                  else data = tb_oc_data_init_from_data(tb_null, 0);
                  tb_assert_and_check_break_state(data, leave, tb_true);
              }
              break;
          default:
              break;
          }
      }
  
      // free
      if (base64) tb_free(base64);
  
      // ok?
      return data;
  }
  static tb_object_ref_t tb_oc_xplist_reader_func_array(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // empty?
      if (event == TB_XML_READER_EVENT_ELEMENT_EMPTY) 
          return tb_oc_array_init(TB_OC_XPLIST_READER_ARRAY_GROW, tb_false);
  
      // init array
      tb_object_ref_t array = tb_oc_array_init(TB_OC_XPLIST_READER_ARRAY_GROW, tb_false);
      tb_assert_and_check_return_val(array, tb_null);
  
      // done
      tb_long_t ok = 0;
      while (!ok && (event = tb_xml_reader_next(reader->reader)))
      {
          switch (event)
          {
          case TB_XML_READER_EVENT_ELEMENT_BEG: 
          case TB_XML_READER_EVENT_ELEMENT_EMPTY: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, ok, -1);
                  tb_trace_d("item: %s", name);
  
                  // func
                  tb_oc_xplist_reader_func_t func = tb_oc_xplist_reader_func(name);
                  tb_assert_and_check_break_state(func, ok, -1);
  
                  // read
                  tb_object_ref_t object = func(reader, event);
  
                  // append object
                  if (object) tb_oc_array_append(array, object);
              }
              break;
          case TB_XML_READER_EVENT_ELEMENT_END: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, ok, -1);
                  
                  // is end?
                  if (!tb_stricmp(name, "array")) ok = 1;
              }
              break;
          default:
              break;
          }
      }
  
      // failed?
      if (ok < 0)
      {
          // exit it
          if (array) tb_object_exit(array);
          array = tb_null;
      }
  
      // ok?
      return array;
  }
  static tb_object_ref_t tb_oc_xplist_reader_func_string(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // empty?
      if (event == TB_XML_READER_EVENT_ELEMENT_EMPTY) 
          return tb_oc_string_init_from_cstr(tb_null);
  
      // done
      tb_bool_t       leave = tb_false;
      tb_object_ref_t string = tb_null;
      while (!leave && (event = tb_xml_reader_next(reader->reader)))
      {
          switch (event)
          {
          case TB_XML_READER_EVENT_ELEMENT_END: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, leave, tb_true);
                  
                  // is end?
                  if (!tb_stricmp(name, "string"))
                  {
                      // empty?
                      if (!string) string = tb_oc_string_init_from_cstr(tb_null);
                      
                      // leave it
                      leave = tb_true;
                  }
              }
              break;
          case TB_XML_READER_EVENT_TEXT: 
              {
                  // text
                  tb_char_t const* text = tb_xml_reader_text(reader->reader);
                  tb_assert_and_check_break_state(text, leave, tb_true);
                  tb_trace_d("string: %s", text);
                  
                  // string
                  string = tb_oc_string_init_from_cstr(text);
                  tb_assert_and_check_break_state(string, leave, tb_true);
              }
              break;
          default:
              break;
          }
      }
  
      // ok?
      return string;
  }
  static tb_object_ref_t tb_oc_xplist_reader_func_number(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // empty?
      if (event == TB_XML_READER_EVENT_ELEMENT_EMPTY) 
          return tb_oc_number_init_from_uint32(0);
  
      // done
      tb_bool_t           leave = tb_false;
      tb_object_ref_t  number = tb_null;
      while (!leave && (event = tb_xml_reader_next(reader->reader)))
      {
          switch (event)
          {
          case TB_XML_READER_EVENT_ELEMENT_END: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, leave, tb_true);
                  
                  // is end?
                  if (!tb_stricmp(name, "integer") || !tb_stricmp(name, "real")) leave = tb_true;
              }
              break;
          case TB_XML_READER_EVENT_TEXT: 
              {
                  // text
                  tb_char_t const* text = tb_xml_reader_text(reader->reader);
                  tb_assert_and_check_break_state(text, leave, tb_true);
                  tb_trace_d("number: %s", text);
  
                  // has sign? is float?
                  tb_size_t s = 0;
                  tb_size_t f = 0;
                  tb_char_t const* p = text;
                  for (; *p; p++)
                  {
                      if (!s && *p == '-') s = 1;
                      if (!f && *p == '.') f = 1;
                      if (s && f) break;
                  }
                  
                  // number
  #ifdef TB_CONFIG_TYPE_HAVE_FLOAT
                  if (f) number = tb_oc_number_init_from_double(tb_atof(text));
  #else
                  if (f) tb_trace_noimpl();
  #endif
                  else number = s? tb_oc_number_init_from_sint64(tb_stoi64(text)) : tb_oc_number_init_from_uint64(tb_stou64(text));
                  tb_assert_and_check_break_state(number, leave, tb_true);
              }
              break;
          default:
              break;
          }
      }
  
      // ok?
      return number;
  }
  static tb_object_ref_t tb_oc_xplist_reader_func_boolean(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // name
      tb_char_t const* name = tb_xml_reader_element(reader->reader);
      tb_assert_and_check_return_val(name, tb_null);
      tb_trace_d("boolean: %s", name);
  
      // the boolean value
      tb_bool_t val = tb_false;
      if (!tb_stricmp(name, "true")) val = tb_true;
      else if (!tb_stricmp(name, "false")) val = tb_false;
      else return tb_null;
  
      // ok?
      return (tb_object_ref_t)tb_oc_boolean_init(val);
  }
  static tb_object_ref_t tb_oc_xplist_reader_func_dictionary(tb_oc_xplist_reader_t* reader, tb_size_t event)
  {
      // check
      tb_assert_and_check_return_val(reader && reader->reader && event, tb_null);
  
      // empty?
      if (event == TB_XML_READER_EVENT_ELEMENT_EMPTY) 
          return tb_oc_dictionary_init(TB_OC_DICTIONARY_SIZE_MICRO, tb_false);
  
      // init key name
      tb_static_string_t  kname;
      tb_char_t       kdata[8192];
      if (!tb_static_string_init(&kname, kdata, 8192)) return tb_null;
  
      // init dictionary
      tb_object_ref_t dictionary = tb_oc_dictionary_init(0, tb_false);
      tb_assert_and_check_return_val(dictionary, tb_null);
  
      // done
      tb_long_t   ok = 0;
      tb_bool_t   key = tb_false;
      while (!ok && (event = tb_xml_reader_next(reader->reader)))
      {
          switch (event)
          {
          case TB_XML_READER_EVENT_ELEMENT_BEG: 
          case TB_XML_READER_EVENT_ELEMENT_EMPTY: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, ok, -1);
                  tb_trace_d("%s", name);
  
                  // is key
                  if (!tb_stricmp(name, "key")) key = tb_true;
                  else if (!key)
                  {
                      // func
                      tb_oc_xplist_reader_func_t func = tb_oc_xplist_reader_func(name);
                      tb_assert_and_check_break_state(func, ok, -1);
  
                      // read
                      tb_object_ref_t object = func(reader, event);
                      tb_trace_d("%s => %p", tb_static_string_cstr(&kname), object);
                      tb_assert_and_check_break_state(object, ok, -1);
  
                      // set key & value
                      if (tb_static_string_size(&kname) && dictionary) 
                          tb_oc_dictionary_insert(dictionary, tb_static_string_cstr(&kname), object);
  
                      // clear key name
                      tb_static_string_clear(&kname);
                  }
              }
              break;
          case TB_XML_READER_EVENT_ELEMENT_END: 
              {
                  // name
                  tb_char_t const* name = tb_xml_reader_element(reader->reader);
                  tb_assert_and_check_break_state(name, ok, -1);
                  
                  // is end?
                  if (!tb_stricmp(name, "dict")) ok = 1;
                  else if (!tb_stricmp(name, "key")) key = tb_false;
              }
              break;
          case TB_XML_READER_EVENT_TEXT: 
              {
                  if (key)
                  {
                      // text
                      tb_char_t const* text = tb_xml_reader_text(reader->reader);
                      tb_assert_and_check_break_state(text, ok, -1);
  
                      // writ key name
                      tb_static_string_cstrcpy(&kname, text);
                  }
              }
              break;
          default:
              break;
          }
      }
  
      // failed
      if (ok < 0) 
      {
          // exit it
          if (dictionary) tb_object_exit(dictionary);
          dictionary = tb_null;
      }
  
      // exit key name
      tb_static_string_exit(&kname);
  
      // ok?
      return dictionary;
  }
  static tb_object_ref_t tb_oc_xplist_reader_done(tb_stream_ref_t stream)
  {
      // init reader 
      tb_oc_xplist_reader_t reader = {0};
      reader.reader = tb_xml_reader_init();
      tb_assert_and_check_return_val(reader.reader, tb_null);
  
      // open reader
      tb_object_ref_t object = tb_null;
      if (tb_xml_reader_open(reader.reader, stream, tb_false))
      {
          // done
          tb_bool_t       leave = tb_false;
          tb_size_t       event = TB_XML_READER_EVENT_NONE;
          while (!leave && !object && (event = tb_xml_reader_next(reader.reader)))
          {
              switch (event)
              {
              case TB_XML_READER_EVENT_ELEMENT_EMPTY: 
              case TB_XML_READER_EVENT_ELEMENT_BEG: 
                  {
                      // name
                      tb_char_t const* name = tb_xml_reader_element(reader.reader);
                      tb_assert_and_check_break_state(name, leave, tb_true);
  
                      // <plist/> ?
                      if (tb_stricmp(name, "plist"))
                      {
                          // func
                          tb_oc_xplist_reader_func_t func = tb_oc_xplist_reader_func(name);
                          tb_assert_and_check_break_state(func, leave, tb_true);
  
                          // read
                          object = func(&reader, event);
                      }
                  }
                  break;
              default:
                  break;
              }
          }
      }
  
      // exit reader
      tb_xml_reader_exit(reader.reader);
  
      // ok?
      return object;
  }
  static tb_size_t tb_oc_xplist_reader_probe(tb_stream_ref_t stream)
  {
      // check
      tb_assert_and_check_return_val(stream, 0);
  
      // need it
      tb_byte_t* p = tb_null;
      if (!tb_stream_need(stream, &p, 5)) return 0;
      tb_assert_and_check_return_val(p, 0);
  
      // is xml data?
      if (!tb_strnicmp((tb_char_t const*)p, "<?xml", 5)) 
      {
          // need more data
          if (!tb_stream_need(stream, &p, 256)) return 5;
          tb_assert_and_check_return_val(p, 5);
  
          // is xplist?
          return tb_strnistr((tb_char_t const*)p, 256, "DOCTYPE plist")? 80 : 10;
      }
  
      // ok?
      return 0;
  }
  
  /* //////////////////////////////////////////////////////////////////////////////////////
   * interfaces
   */
  tb_oc_reader_t* tb_oc_xplist_reader()
  {
      // the reader
      static tb_oc_reader_t s_reader = {0};
  
      // init reader
      s_reader.read   = tb_oc_xplist_reader_done;
      s_reader.probe  = tb_oc_xplist_reader_probe;
  
      // init hooker
      s_reader.hooker = tb_hash_map_init(TB_HASH_MAP_BUCKET_SIZE_MICRO, tb_element_str(tb_false), tb_element_ptr(tb_null, tb_null));
      tb_assert_and_check_return_val(s_reader.hooker, tb_null);
  
      // hook reader 
      tb_hash_map_insert(s_reader.hooker, "date", tb_oc_xplist_reader_func_date);
      tb_hash_map_insert(s_reader.hooker, "data", tb_oc_xplist_reader_func_data);
      tb_hash_map_insert(s_reader.hooker, "array", tb_oc_xplist_reader_func_array);
      tb_hash_map_insert(s_reader.hooker, "string", tb_oc_xplist_reader_func_string);
      tb_hash_map_insert(s_reader.hooker, "integer", tb_oc_xplist_reader_func_number);
      tb_hash_map_insert(s_reader.hooker, "real", tb_oc_xplist_reader_func_number);
      tb_hash_map_insert(s_reader.hooker, "true", tb_oc_xplist_reader_func_boolean);
      tb_hash_map_insert(s_reader.hooker, "false", tb_oc_xplist_reader_func_boolean);
      tb_hash_map_insert(s_reader.hooker, "dict", tb_oc_xplist_reader_func_dictionary);
  
      // ok
      return &s_reader;
  }
  tb_bool_t tb_oc_xplist_reader_hook(tb_char_t const* type, tb_oc_xplist_reader_func_t func)
  {
      // check
      tb_assert_and_check_return_val(type && func, tb_false);
  
      // the reader
      tb_oc_reader_t* reader = tb_oc_reader_get(TB_OBJECT_FORMAT_XPLIST);
      tb_assert_and_check_return_val(reader && reader->hooker, tb_false);
  
      // hook it
      tb_hash_map_insert(reader->hooker, type, func);
  
      // ok
      return tb_true;
  }
  tb_oc_xplist_reader_func_t tb_oc_xplist_reader_func(tb_char_t const* type)
  {
      // check
      tb_assert_and_check_return_val(type, tb_null);
  
      // the reader
      tb_oc_reader_t* reader = tb_oc_reader_get(TB_OBJECT_FORMAT_XPLIST);
      tb_assert_and_check_return_val(reader && reader->hooker, tb_null);
  
      // the func
      return (tb_oc_xplist_reader_func_t)tb_hash_map_get(reader->hooker, type);
  }
  
  