瀏覽代碼

完成了失败时的卡片显示

MoYuWang 9 月之前
父節點
當前提交
b28d03da65
共有 28 個文件被更改,包括 2266 次插入151 次删除
  1. 35 12
      Classes/IAP/IAPCtlShop.cpp
  2. 2 3
      Classes/IAP/IAPCtlShop.hpp
  3. 4 2
      Classes/IAP/IAPDelegate.hpp
  4. 1 1
      Classes/IAP/Shop/IAPCtlShopUI.hpp
  5. 2 2
      Classes/IAP/purchaseBanner/PBConfigData.cpp
  6. 2 1
      Classes/IAP/purchaseBanner/PBConfigData.hpp
  7. 1 1
      Classes/IAP/purchaseBanner/PurchaseBannerCell.hpp
  8. 1 91
      Classes/IAP/purchaseBanner/PurchaseBannerDelegate.h
  9. 1 1
      Classes/IAP/purchaseBanner/RedreamLoader.cpp
  10. 0 0
      Classes/IAP/purchaseBanner/RedreamLoader.hpp
  11. 2132 0
      Classes/IAP/purchaseBanner/rapidcsv.h
  12. 2 1
      Classes/TestScene.cpp
  13. 4 4
      Resources/resources/ccb/purchaseBanner/内购版位_活动模版.red
  14. 0 20
      Resources/resources/ccb/purchaseBanner/内购版位_浮层_提示文本.rebolt
  15. 16 2
      Resources/resources/ccb/purchaseBanner/内购版位_礼包模版.rebolt
  16. 2 2
      Resources/resources/ccb/purchaseBanner/内购版位_计时模版.rebolt
  17. 1 1
      Resources/resources/ccb/purchaseBanner/内购版位_计时模版.red
  18. 1 1
      Resources/resources/ccbi/Rebolt_BT/H5d5TSmrDQQF.xml
  19. 二進制
      Resources/resources/ccbi/内购版位_活动模版.redream
  20. 2 0
      Resources/resources/config/bannerViewConfig.csv
  21. 5 0
      Resources/resources/config/purchaseBannerConfig.csv
  22. 二進制
      Resources/resources/font/内购版位_字体_半透明黑色描边.GlyphProject
  23. 二進制
      Resources/resources/font/内购版位_字体_灰色描边.GlyphProject
  24. 二進制
      Resources/resources/font/内购版位_字体_紫色描边.GlyphProject
  25. 二進制
      Resources/resources/font/内购版位_字体_红色描边.GlyphProject
  26. 二進制
      Resources/resources/font/内购版位_字体_纯白字体.GlyphProject
  27. 二進制
      Resources/resources/font/内购版位_字体_绿色描边.GlyphProject
  28. 52 6
      proj.ios_mac/demo.xcodeproj/project.pbxproj

+ 35 - 12
Classes/IAP/IAPCtlShop.cpp

@@ -17,6 +17,9 @@
 #include "IAPUserData.hpp"
 #include "IAPRunTimeData.hpp"
 
+#include "PBConfigData.hpp"
+#include "PurchaseBannerView.hpp"
+
 NS_IAP_BEGIN
 
 IAPCtlShop* IAPCtlShop::_instance = nullptr;
@@ -30,8 +33,6 @@ IAPCtlShop* IAPCtlShop::createWith(){
 }
 
 void IAPCtlShop::init(std::string &cfgFN){
-    _normalCfgFN = cfgFN;
-    
     redutils::BaseConf* baseConf = new redutils::BaseConf(cfgFN.c_str());
     
     _conf = IAPConf::getInstance();
@@ -43,18 +44,41 @@ void IAPCtlShop::setDelegate(IAPDelegate *delegate){
 }
 
 void IAPCtlShop::setCfg4Failure(std::string &cfgFN){
-    _failCfgFN = cfgFN;
-    
-    redutils::BaseConf* baseConf = new redutils::BaseConf(cfgFN.c_str());
-    
-    _conf = IAPConf::getInstance();
-    _conf->initWith(baseConf);
+    _pbConf = PBConfigData::getInstance();
+    _pbConf->initData(cfgFN);
 }
 
 void IAPCtlShop::displayFailureCardIn(cocos2d::Node *node){
-    int stillNeedCoins = 0;
-    if(_delegate)stillNeedCoins = _delegate->getStillNeedCoinsWhenFailed();
-    IAPCtlShopUI::getInstance()->displayFailureCardIn(node, stillNeedCoins);
+    auto sDot = _pbConf->getViewDot();
+    PurchaseBannerView::sDotCfg dot;
+    dot.highSp = sDot.highSp;
+    dot.normalSp = sDot.normalSp;
+    dot.sepWidth = sDot.sepWidth;
+    dot.yStart = sDot.yStart;
+    
+    auto visibleSize = Director::getInstance()->getVisibleSize();
+    float width = visibleSize.width;
+    cocos2d::Size sz = cocos2d::Size(width, 300);
+    
+    PurchaseBannerView* view = PurchaseBannerView::create(sz, dot, true);
+    std::vector<std::string> names;
+    if(_delegate)names = _delegate->getCardNameWhenFailed();
+    for(const auto& name : names){
+        PurchaseBannerCell* tmpNode = PurchaseBannerCell::create(name);
+        view->addBanner(tmpNode);
+    }
+    
+//    // 测试暂用
+//    PurchaseBannerCell* tmpNode1 = PurchaseBannerCell::create("礼包1");
+//    view->addBanner(tmpNode1);
+//    PurchaseBannerCell* tmpNode2 = PurchaseBannerCell::create("tilefavor");
+//    view->addBanner(tmpNode2);
+//    PurchaseBannerCell* tmpNode3 = PurchaseBannerCell::create("tilepack");
+//    view->addBanner(tmpNode3);
+//    PurchaseBannerCell* tmpNode4 = PurchaseBannerCell::create("tilepass");
+//    view->addBanner(tmpNode4);
+    
+    node->addChild(view);
 }
 
 void IAPCtlShop::setDeviceLevel(int level){
@@ -87,7 +111,6 @@ size_t IAPCtlShop::getPlacementCount(){
 }
 
 void IAPCtlShop::showInNode(cocos2d::Node *pParent, ShopRequirement &requirement){
-    init(_normalCfgFN);
     IAPCtlShopUI::getInstance()->create(pParent, _conf, requirement);
 }
 

+ 2 - 3
Classes/IAP/IAPCtlShop.hpp

@@ -13,6 +13,7 @@
 #include "IAPDefine.hpp"
 #include "IAPDelegate.hpp"
 
+#include "PBConfigData.hpp"
 
 class IAPConf;
 class IAPCtlShopUI;
@@ -95,9 +96,7 @@ private:
     IAPDelegate* _delegate;
 
     IAPConf* _conf;
-    
-    std::string _normalCfgFN;
-    std::string _failCfgFN;
+    PBConfigData* _pbConf;
 };
 
 NS_IAP_END

+ 4 - 2
Classes/IAP/IAPDelegate.hpp

@@ -18,9 +18,11 @@ public:
     // 用户成功购买通知
     virtual void onUserBuySuccess(std::map<std::string, string> buyInfo) = 0;
     
-    // 获取还需要的金币数量
-    virtual int getStillNeedCoinsWhenFailed() = 0;
+//     //获取还需要的金币数量
+//    virtual int getStillNeedCoinsWhenFailed() = 0;
     
+    // 获取失败时需要显示的卡片名称
+    virtual std::vector<std::string> getCardNameWhenFailed() = 0;
 };
 
 

+ 1 - 1
Classes/IAP/Shop/IAPCtlShopUI.hpp

@@ -28,7 +28,7 @@ public:
     
     // 以滑动卡片的形式在某个节点中显示某些版位(适用于失败时的场景)
     // pParent 父节点
-    // plIds 需要的版位id
+    // stillNeedCoins 距离所需要的金币数还差多少金币
     void displayFailureCardIn(cocos2d::Node* pNode, int stillNeedCoins);
     
     // 添加一个商店版位

+ 2 - 2
Classes/IAP/purchaseBanner/PBConfigData.cpp

@@ -23,8 +23,8 @@ PBConfigData* PBConfigData::getInstance() {
     return _gPBConfigData;
 }
 
-void PBConfigData::initData() {
-    string fileName = "purchaseBannerConfig.csv";
+void PBConfigData::initData(std::string cfgFN) {
+    string fileName = cfgFN;
     _bannerInfoList.clear();
     if (!FileUtils::getInstance()->isFileExist(fileName)) {
         return;

+ 2 - 1
Classes/IAP/purchaseBanner/PBConfigData.hpp

@@ -9,6 +9,7 @@
 #define PBConfigData_hpp
 
 #include "cocos2d.h"
+#include <string.h>
 
 class PBConfigData {
 public:
@@ -51,7 +52,7 @@ public:
     static PBConfigData* getInstance();
     
     ///初始化数据配置
-    void initData();
+    void initData(std::string cfgFN = "");
     
     bannerInfo getBannerInfo(string bName);
     sPBDotCfg getViewDot();

+ 1 - 1
Classes/IAP/purchaseBanner/PurchaseBannerCell.hpp

@@ -10,7 +10,7 @@
 
 #include "cocos2d.h"
 #include "extensions/cocos-ext.h"
-#include "RedreamLoader.h"
+#include "RedreamLoader.hpp"
 #include "PBConfigData.hpp"
 
 class PurchaseBannerCell :

+ 1 - 91
Classes/IAP/purchaseBanner/PurchaseBannerDelegate.h

@@ -9,6 +9,7 @@
 #define PurchaseBannerDelegate_h
 
 #include "cocos2d.h"
+#include "IAPDefine.hpp"
 
 class PurchaseBannerDelegate {
 public:
@@ -17,94 +18,3 @@ public:
 };
 
 #endif /* PurchaseBannerDelegate_h */
-
-
-void PurchaseBannerDelegateImpl::onActBtnClick(string name) {
-    if (name == "tilepass") {
-        // 显示tilepass的购买页面
-        auto tpSys = ActivityManager::getInstance()->getTilePassSystem();
-        tpSys->showActivatePopup();
-    }
-}
-
-
-void PurchaseBannerDelegateImpl::onPurchaseClick(string ppid,std::function<void(bool)> overCallBack) {
-    GoodsInfo gi;
-    if (ppid == "tilefavor") {
-        gi.id = ppid;
-        gi.idxGT2 = 0;
-        AreaInGooods aig;
-        {
-            ItemInGoods iig;
-            iig.name = "coin";
-            iig.count = "1000";
-            aig.push_back(iig);
-        }
-        {
-            ItemInGoods iig;
-            iig.name = "back1";
-            iig.count = "1";
-            aig.push_back(iig);
-        }
-        {
-            ItemInGoods iig;
-            iig.name = "life";
-            iig.count = "5";
-            aig.push_back(iig);
-        }
-        gi.areas.push_back(aig);
-    } else if (ppid == "tilepack") {
-        gi.id = ppid;
-        gi.idxGT2 = 0;
-        AreaInGooods aig;
-        {
-            ItemInGoods iig;
-            iig.name = "coin";
-            iig.count = "4000";
-            aig.push_back(iig);
-        }
-        {
-            ItemInGoods iig;
-            iig.name = "back1";
-            iig.count = "3";
-            aig.push_back(iig);
-        }
-        gi.areas.push_back(aig);
-    }
-    IAPProcess::getInstance()->buy(gi, 104, true, [=](){
-        if (overCallBack) {
-            overCallBack(true);
-        }
-    }, [=](){
-        if (overCallBack) {
-            overCallBack(false);
-        }
-    });
-}
-
-void MetaGameDelegateImpl::showIAPCardWhenFailed(cocos2d::Node* nd, int coinsNeeded, int livedNeeded) {
-    vector<string> banners;
-    int coinsNow = UserDataManager::getInstance()->getCoinNum();
-    int gap = coinsNeeded - coinsNow;
-    if (gap > 0) {
-        if (gap <= 1000 && !UserDataManager::getInstance()->everPurchased()) {
-            banners.push_back("tilefavor");
-        } else if (gap <= 4000) {
-            banners.push_back("tilepack");
-        }
-    }
-    if (livedNeeded > UserDataManager::getInstance()->getLifeLeft()) {
-        if (!UserDataManager::getInstance()->everPurchased()) {
-            if (std::find(banners.begin(), banners.end(), "tilefavor") == banners.end()) {
-                banners.push_back("tilefavor");
-            }
-        }
-        auto tp = ActivityManager::getInstance()->getTilePassSystem();
-        if (tp && tp->isActive() && !tp->passActivated()) {
-            banners.push_back("tilepass");
-        }
-    }
-    if (banners.size() > 0) {
-        PurchaseBannerCtrl::getInstance()->showBanner(nd, banners);
-    }
-}

+ 1 - 1
Classes/IAP/purchaseBanner/RedreamLoader.cpp

@@ -1,4 +1,4 @@
-#include "RedreamLoader.h"
+#include "RedreamLoader.hpp"
 
 RedreamLoader::RedreamLoader() {
 }

+ 0 - 0
Classes/IAP/purchaseBanner/RedreamLoader.h → Classes/IAP/purchaseBanner/RedreamLoader.hpp


+ 2132 - 0
Classes/IAP/purchaseBanner/rapidcsv.h

@@ -0,0 +1,2132 @@
+/*
+ * rapidcsv.h
+ *
+ * URL:      https://github.com/d99kris/rapidcsv
+ * Version:  8.84
+ *
+ * Copyright (C) 2017-2024 Kristofer Berggren
+ * All rights reserved.
+ *
+ * rapidcsv is distributed under the BSD 3-Clause license, see LICENSE for details.
+ *
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#ifdef HAS_CODECVT
+#include <codecvt>
+#include <locale>
+#endif
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <sstream>
+#include <string>
+#include <typeinfo>
+#include <vector>
+
+namespace rapidcsv
+{
+#if defined(_MSC_VER)
+  static const bool sPlatformHasCR = true;
+#else
+  static const bool sPlatformHasCR = false;
+#endif
+  static const std::vector<char> s_Utf8BOM = { '\xef', '\xbb', '\xbf' };
+
+  /**
+   * @brief     Datastructure holding parameters controlling how invalid numbers (including
+   *            empty strings) should be handled.
+   * @brief     保存控制如何处理无效数字(包括空字符串)的参数的数据结构
+   */
+  struct ConverterParams
+  {
+    /**
+     * @brief   Constructor
+     * @param   pHasDefaultConverter  specifies if conversion of non-numerical strings shall be
+     *                               converted to a default numerical value, instead of causing
+     *                               an exception to be thrown (default).
+     *                               指定是否将非数值字符串转换为默认的数值,
+     *                               而不是抛出异常(默认情况下抛出异常)
+     * @param   pDefaultFloat         floating-point default value to represent invalid numbers.
+     *                           表示无效数字的浮点数默认值
+     * @param   pDefaultInteger      integer default value to represent invalid numbers.
+     *                            表示无效数字的整数默认值
+     * @param   pNumericLocale        specifies whether to honor LC_NUMERIC locale (default
+     *                                true).
+     *                            指定是否遵循 `LC_NUMERIC` 区域设置
+     */
+    explicit ConverterParams(const bool pHasDefaultConverter = false,
+                             const long double pDefaultFloat = std::numeric_limits<long double>::signaling_NaN(),
+                             const long long pDefaultInteger = 0,
+                             const bool pNumericLocale = true)
+      : mHasDefaultConverter(pHasDefaultConverter)
+      , mDefaultFloat(pDefaultFloat)
+      , mDefaultInteger(pDefaultInteger)
+      , mNumericLocale(pNumericLocale)
+    {
+    }
+
+    /**
+     * @brief   specifies if conversion of non-numerical strings shall be converted to a default
+     *          numerical value, instead of causing an exception to be thrown (default).
+     *          指定是否将非数值字符串转换为默认的数值,而不是抛出异常(默认情况下抛出异常)
+     */
+    bool mHasDefaultConverter;
+
+    /**
+     * @brief   floating-point default value to represent invalid numbers.
+     *          表示无效数字的浮点数默认值
+     */
+    long double mDefaultFloat;
+
+    /**
+     * @brief   integer default value to represent invalid numbers.
+     *          表示无效数字的整数默认值
+     */
+    long long mDefaultInteger;
+
+    /**
+     * @brief   specifies whether to honor `LC_NUMERIC` locale.
+     *          指定是否遵循 `LC_NUMERIC` 区域设置
+     * @property    不同的区域设置中,小数点符号可能是不同的
+     */
+    bool mNumericLocale;
+  };
+
+  /**
+   * @brief     Exception thrown when attempting to access Document data in a datatype which
+   *            is not supported by the Converter class.
+   *            当尝试以`Converter`类不支持的数据类型访问`Document`数据时抛出的异常
+   */
+  class no_converter : public std::exception
+  {
+  public:
+    /**
+     * @brief   Provides details about the exception
+     *          提供有关异常的详细信息
+     * @returns an explanatory string
+     *          描述性字符串
+     */
+    const char* what() const throw() override
+    {
+      return "unsupported conversion datatype";
+    }
+  };
+
+  /**
+   * @brief     Class providing conversion to/from numerical datatypes and strings. Only
+   *            intended for rapidcsv internal usage, but exposed externally to allow
+   *            specialization for custom datatype conversions.
+   *            提供数值数据类型和字符串之间转换的类
+   *            仅供`rapidcsv`内部使用,但公开以允许对自定义数据类型进行特化。
+   */
+  template<typename T>
+  class Converter
+  {
+  public:
+    /**
+     * @brief   Constructor
+     * @param   pConverterParams        specifies how conversion of non-numerical values to
+     *                              numerical datatype shall be handled.
+     *                              指定如何处理非数值值转换为数值数据类型的参数。
+     */
+    Converter(const ConverterParams& pConverterParams)
+      : mConverterParams(pConverterParams)
+    {
+    }
+
+    /**
+     * @brief   Converts numerical value to string representation.
+     *          将数值转换为字符串表示
+     * @param   pVal    numerical value
+     * @param   pStr    output string
+     */
+    void ToStr(const T& pVal, std::string& pStr) const
+    {
+      if (typeid(T) == typeid(int) ||
+          typeid(T) == typeid(long) ||
+          typeid(T) == typeid(long long) ||
+          typeid(T) == typeid(unsigned) ||
+          typeid(T) == typeid(unsigned long) ||
+          typeid(T) == typeid(unsigned long long) ||
+          typeid(T) == typeid(long double) ||
+          typeid(T) == typeid(char))
+      {
+        std::ostringstream out;
+        out << pVal;
+        pStr = out.str();
+      }
+      else if (typeid(T) == typeid(float))
+      {
+        std::ostringstream out;
+        out << std::setprecision(9) << pVal;
+        pStr = out.str();
+      }
+      else if (typeid(T) == typeid(double))
+      {
+        std::ostringstream out;
+        out << std::setprecision(17) << pVal;
+        pStr = out.str();
+      }
+      else
+      {
+        throw no_converter();
+      }
+    }
+
+    /**
+     * @brief   Converts string holding a numerical value to numerical datatype representation.
+     *          将包含数值的字符串转换为数值数据类型表示
+     * @param   pVal    numerical value
+     * @param   pStr    output string
+     */
+    void ToVal(const std::string& pStr, T& pVal) const
+    {
+      try
+      {
+        if (typeid(T) == typeid(int))
+        {
+          pVal = static_cast<T>(std::stoi(pStr));
+          return;
+        }
+        else if (typeid(T) == typeid(long))
+        {
+          pVal = static_cast<T>(std::stol(pStr));
+          return;
+        }
+        else if (typeid(T) == typeid(long long))
+        {
+          pVal = static_cast<T>(std::stoll(pStr));
+          return;
+        }
+        else if (typeid(T) == typeid(unsigned))
+        {
+          pVal = static_cast<T>(std::stoul(pStr));
+          return;
+        }
+        else if (typeid(T) == typeid(unsigned long))
+        {
+          pVal = static_cast<T>(std::stoul(pStr));
+          return;
+        }
+        else if (typeid(T) == typeid(unsigned long long))
+        {
+          pVal = static_cast<T>(std::stoull(pStr));
+          return;
+        }
+      }
+      catch (...)
+      {
+        if (!mConverterParams.mHasDefaultConverter)
+        {
+          throw;
+        }
+        else
+        {
+          pVal = static_cast<T>(mConverterParams.mDefaultInteger);
+          return;
+        }
+      }
+
+      try
+      {
+        if (mConverterParams.mNumericLocale)
+        {
+          if (typeid(T) == typeid(float))
+          {
+            pVal = static_cast<T>(std::stof(pStr));
+            return;
+          }
+          else if (typeid(T) == typeid(double))
+          {
+            pVal = static_cast<T>(std::stod(pStr));
+            return;
+          }
+          else if (typeid(T) == typeid(long double))
+          {
+            pVal = static_cast<T>(std::stold(pStr));
+            return;
+          }
+        }
+        else
+        {
+          if ((typeid(T) == typeid(float)) ||
+              (typeid(T) == typeid(double)) ||
+              (typeid(T) == typeid(long double)))
+          {
+            std::istringstream iss(pStr);
+            iss.imbue(std::locale::classic());
+            iss >> pVal;
+            if (iss.fail() || iss.bad() || !iss.eof())
+            {
+              throw std::invalid_argument("istringstream: no conversion");
+            }
+            return;
+          }
+        }
+      }
+      catch (...)
+      {
+        if (!mConverterParams.mHasDefaultConverter)
+        {
+          throw;
+        }
+        else
+        {
+          pVal = static_cast<T>(mConverterParams.mDefaultFloat);
+          return;
+        }
+      }
+
+      if (typeid(T) == typeid(char))
+      {
+        pVal = static_cast<T>(pStr[0]);
+        return;
+      }
+      else
+      {
+        throw no_converter();
+      }
+    }
+
+  private:
+    const ConverterParams& mConverterParams;
+  };
+
+  /**
+   * @brief     Specialized implementation handling string to string conversion.
+   *            专门用于处理字符串到字符串转换的特化实现
+   * @param     pVal                  string
+   * @param     pStr                  string
+   */
+  template<>
+  inline void Converter<std::string>::ToStr(const std::string& pVal, std::string& pStr) const
+  {
+    pStr = pVal;
+  }
+
+  /**
+   * @brief     Specialized implementation handling string to string conversion.
+   *            专门用于处理字符串到字符串转换的特化实现
+   * @param     pVal                  string
+   * @param     pStr                  string
+   */
+  template<>
+  inline void Converter<std::string>::ToVal(const std::string& pStr, std::string& pVal) const
+  {
+    pVal = pStr;
+  }
+
+  template<typename T>
+  using ConvFunc = std::function<void (const std::string & pStr, T & pVal)>;
+
+  /**
+   * @brief     Datastructure holding parameters controlling which row and column should be
+   *            treated as labels.
+   *            保存控制哪些行和列应被视为标签的参数的数据结构
+   */
+  struct LabelParams
+  {
+    /**
+     * @brief   Constructor
+     * @param   pColumnNameIdx    specifies the zero-based row index of the column labels, setting
+     *                          it to -1 prevents column lookup by label name, and gives access
+     *                          to all rows as document data. Default: 0
+     *                          指定列标签的基于零的行索引,将其设置为 -1
+     *                          将阻止通过标签名进行列查找,并允许访问所有行作为文档数据。
+     *                          默认值:0
+     * @param   pRowNameIdx           specifies the zero-based column index of the row labels, setting
+     *                          it to -1 prevents row lookup by label name, and gives access
+     *                          to all columns as document data. Default: -1
+     *                          指定行标签的基于零的列索引,将其设置为 -1
+     *                          将阻止通过标签名进行行查找,并允许访问所有列作为文档数据。
+     *                          默认值:-1
+     */
+    explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1)
+      : mColumnNameIdx(pColumnNameIdx)
+      , mRowNameIdx(pRowNameIdx)
+    {
+      if (mColumnNameIdx < -1)
+      {
+        const std::string errStr = "invalid column name index " +
+          std::to_string(mColumnNameIdx) + " < -1";
+        throw std::out_of_range(errStr);
+      }
+
+      if (mRowNameIdx < -1)
+      {
+        const std::string errStr = "invalid row name index " +
+          std::to_string(mRowNameIdx) + " < -1";
+        throw std::out_of_range(errStr);
+      }
+    }
+
+    /**
+     * @brief   specifies the zero-based row index of the column labels.
+     *          指定列标签的基于零的行索引
+     */
+    int mColumnNameIdx;
+
+    /**
+     * @brief   specifies the zero-based column index of the row labels.
+     *          指定行标签的基于零的列索引
+     */
+    int mRowNameIdx;
+  };
+
+  /**
+   * @brief     Datastructure holding parameters controlling how the CSV data fields are separated.
+   *            保存控制如何分隔`CSV`数据字段的参数的数据结构
+   */
+  struct SeparatorParams
+  {
+    /**
+     * @brief   Constructor
+     * @param   pSeparator       specifies the column separator (default ',').
+     *                       指定列分隔符(默认是',')
+     * @param   pTrim                  specifies whether to trim leading and trailing spaces from
+     *                       cells read (default false).
+     *                       指定是否修剪读取到的单元格中的前导和尾随空格(默认是 false)
+     * @param   pHasCR               specifies whether a new document (i.e. not an existing document read)
+     *                       should use CR/LF instead of only LF (default is to use standard
+     *                       behavior of underlying platforms - CR/LF for Win, and LF for others).
+     *                       指定新文档(即,不是读取的现有文档)是否应使用 CR/LF 而不是仅使用 LF
+     * @param   pQuotedLinebreaks     specifies whether to allow line breaks in quoted text (default false)
+     *                             指定是否允许带引号的文本中包含换行符(默认是 false)
+     * @param   pAutoQuote             specifies whether to automatically dequote data during read, and add
+     *                          quotes during write (default true).
+     *                          指定在读取时是否自动去除引号,并在写入时添加引号(默认是 true)
+     * @param   pQuoteChar             specifies the quote character (default '\"').
+     *                          指定引号字符(默认是 '\"')
+     */
+    explicit SeparatorParams(const char pSeparator = ',', const bool pTrim = false,
+                             const bool pHasCR = sPlatformHasCR, const bool pQuotedLinebreaks = false,
+                             const bool pAutoQuote = true, const char pQuoteChar = '"')
+      : mSeparator(pSeparator)
+      , mTrim(pTrim)
+      , mHasCR(pHasCR)
+      , mQuotedLinebreaks(pQuotedLinebreaks)
+      , mAutoQuote(pAutoQuote)
+      , mQuoteChar(pQuoteChar)
+    {
+    }
+
+    /**
+     * @brief   specifies the column separator.
+     *          指定列分隔符
+     */
+    char mSeparator;
+
+    /**
+     * @brief   specifies whether to trim leading and trailing spaces from cells read.
+     *          指定是否修剪读取到的单元格中的前导和尾随空格
+     */
+    bool mTrim;
+
+    /**
+     * @brief   specifies whether new documents should use CR/LF instead of LF.
+     *          指定新文档是否应使用`CR/LF`而不是`LF`
+     */
+    bool mHasCR;
+
+    /**
+     * @brief   specifies whether to allow line breaks in quoted text.
+     *          指定是否允许带引号的文本中包含换行符
+     */
+    bool mQuotedLinebreaks;
+
+    /**
+     * @brief   specifies whether to automatically dequote cell data.
+     *          指定是否自动去除单元格数据的引号
+     */
+    bool mAutoQuote;
+
+    /**
+     * @brief   specifies the quote character.
+     *          指定引号字符
+     */
+    char mQuoteChar;
+  };
+
+  /**
+   * @brief     Datastructure holding parameters controlling how special line formats should be
+   *            treated.
+   *            保存控制如何处理特殊行格式的参数的数据结构
+   */
+  struct LineReaderParams
+  {
+    /**
+     * @brief   Constructor
+     * @param   pSkipCommentLines       specifies whether to skip lines prefixed with
+     *                              mCommentPrefix. Default: true
+     *                              指定是否跳过以 mCommentPrefix 为前缀的行。默认值:true
+     * @param   pCommentPrefix             specifies which prefix character to indicate a comment
+     *                              line. Default: #
+     *                              指定用于表示注释行的前缀字符。默认值:#
+     * @param   pSkipEmptyLines           specifies whether to skip empty lines. Default: false
+     *                              指定是否跳过空行。默认值:false
+     */
+    explicit LineReaderParams(const bool pSkipCommentLines = true,
+                              const char pCommentPrefix = '#',
+                              const bool pSkipEmptyLines = false)
+      : mSkipCommentLines(pSkipCommentLines)
+      , mCommentPrefix(pCommentPrefix)
+      , mSkipEmptyLines(pSkipEmptyLines)
+    {
+    }
+
+    /**
+     * @brief   specifies whether to skip lines prefixed with mCommentPrefix.
+     *          指定是否跳过以`mCommentPrefix`为前缀的行
+     */
+    bool mSkipCommentLines;
+
+    /**
+     * @brief   specifies which prefix character to indicate a comment line.
+     *          指定用于表示注释行的前缀字符
+     */
+    char mCommentPrefix;
+
+    /**
+     * @brief   specifies whether to skip empty lines.
+     *          指定是否跳过空行
+     */
+    bool mSkipEmptyLines;
+  };
+
+  /**
+   * @brief     Class representing a CSV document.
+   *            表示 CSV 文档的类
+   */
+  class Document
+  {
+  public:
+    /**
+     * @brief   Constructor
+     * @param   pPath                specifies the path of an existing CSV-file to populate the Document
+     *                      data with.
+     *                      指定现有 CSV 文件的路径,以填充 Document 的数据
+     * @param   pLabelParams         specifies which row and column should be treated as labels.
+     *                          指定应作为标签的行和列
+     * @param   pSeparatorParams     specifies which field and row separators should be used.
+     *                            指定应使用的字段和行分隔符
+     * @param   pConverterParams     specifies how invalid numbers (including empty strings) should be
+     *                            handled.
+     *                            指定应如何处理无效的数字(包括空字符串)
+     * @param   pLineReaderParams     specifies how special line formats should be treated.
+     *                             指定应如何处理特殊的行格式
+     */
+    explicit Document(const std::string& pPath = std::string(),
+                      const LabelParams& pLabelParams = LabelParams(),
+                      const SeparatorParams& pSeparatorParams = SeparatorParams(),
+                      const ConverterParams& pConverterParams = ConverterParams(),
+                      const LineReaderParams& pLineReaderParams = LineReaderParams())
+      : mPath(pPath)
+      , mLabelParams(pLabelParams)
+      , mSeparatorParams(pSeparatorParams)
+      , mConverterParams(pConverterParams)
+      , mLineReaderParams(pLineReaderParams)
+      , mData()
+      , mColumnNames()
+      , mRowNames()
+    {
+      if (!mPath.empty())
+      {
+        ReadCsv();
+      }
+    }
+
+    /**
+     * @brief   Constructor
+     * @param   pStream         specifies a binary input stream to read CSV data from.
+     *                     指定用于读取 CSV 数据的二进制输入流
+     * @param   pLabelParams         specifies which row and column should be treated as labels.
+     *                          指定应作为标签的行和列
+     * @param   pSeparatorParams     specifies which field and row separators should be used.
+     *                            指定应使用的字段和行分隔符
+     * @param   pConverterParams     specifies how invalid numbers (including empty strings) should be
+     *                            handled.
+     *                            指定应如何处理无效的数字(包括空字符串)
+     * @param   pLineReaderParams     specifies how special line formats should be treated.
+     *                             指定应如何处理特殊的行格式
+     */
+    explicit Document(std::istream& pStream,
+                      const LabelParams& pLabelParams = LabelParams(),
+                      const SeparatorParams& pSeparatorParams = SeparatorParams(),
+                      const ConverterParams& pConverterParams = ConverterParams(),
+                      const LineReaderParams& pLineReaderParams = LineReaderParams())
+      : mPath()
+      , mLabelParams(pLabelParams)
+      , mSeparatorParams(pSeparatorParams)
+      , mConverterParams(pConverterParams)
+      , mLineReaderParams(pLineReaderParams)
+      , mData()
+      , mColumnNames()
+      , mRowNames()
+    {
+      ReadCsv(pStream);
+    }
+
+    /**
+     * @brief   Read Document data from file.
+     *          从文件中读取`Document`数据
+     * @param   pPath           specifies the path of an existing CSV-file to populate the Document
+     *                    data with.
+     *                    指定现有 CSV 文件的路径,以填充 Document 的数据
+     * @param   pLabelParams         specifies which row and column should be treated as labels.
+     *                          指定应作为标签的行和列
+     * @param   pSeparatorParams         specifies which field and row separators should be used.
+     *                              指定应使用的字段和行分隔符
+     * @param   pConverterParams         specifies how invalid numbers (including empty strings) should be
+     *                              handled.
+     *                              指定应如何处理无效的数字(包括空字符串)
+     * @param   pLineReaderParams       specifies how special line formats should be treated.
+     *                              指定应如何处理特殊的行格式
+     */
+    void Load(const std::string& pPath,
+              const LabelParams& pLabelParams = LabelParams(),
+              const SeparatorParams& pSeparatorParams = SeparatorParams(),
+              const ConverterParams& pConverterParams = ConverterParams(),
+              const LineReaderParams& pLineReaderParams = LineReaderParams())
+    {
+      mPath = pPath;
+      mLabelParams = pLabelParams;
+      mSeparatorParams = pSeparatorParams;
+      mConverterParams = pConverterParams;
+      mLineReaderParams = pLineReaderParams;
+      ReadCsv();
+    }
+
+    /**
+     * @brief   Read Document data from stream.
+     *          从流中读取`Document`数据
+     * @param   pStream           specifies a binary input stream to read CSV data from.
+     *                      指定用于读取 CSV 数据的二进制输入流
+     * @param   pLabelParams         specifies which row and column should be treated as labels.
+     *                          指定应作为标签的行和列
+     * @param   pSeparatorParams         specifies which field and row separators should be used.
+     *                              指定应使用的字段和行分隔符
+     * @param   pConverterParams         specifies how invalid numbers (including empty strings) should be
+     *                              handled.
+     *                              指定应如何处理无效的数字(包括空字符串)
+     * @param   pLineReaderParams       specifies how special line formats should be treated.
+     *                              指定应如何处理特殊的行格式
+     */
+    void Load(std::istream& pStream,
+              const LabelParams& pLabelParams = LabelParams(),
+              const SeparatorParams& pSeparatorParams = SeparatorParams(),
+              const ConverterParams& pConverterParams = ConverterParams(),
+              const LineReaderParams& pLineReaderParams = LineReaderParams())
+    {
+      mPath = "";
+      mLabelParams = pLabelParams;
+      mSeparatorParams = pSeparatorParams;
+      mConverterParams = pConverterParams;
+      mLineReaderParams = pLineReaderParams;
+      ReadCsv(pStream);
+    }
+
+    /**
+     * @brief   Write Document data to file.
+     *          将`Document`数据写入文件
+     * @param   pPath       optionally specifies the path where the CSV-file will be created
+     *                  (if not specified, the original path provided when creating or
+     *                  loading the Document data will be used).
+     *                  可选参数,指定 CSV 文件将创建的路径
+     *                  (如果未指定,则使用创建或加载 Document 数据时提供的原始路径)
+     */
+    void Save(const std::string& pPath = std::string())
+    {
+      if (!pPath.empty())
+      {
+        mPath = pPath;
+      }
+      WriteCsv();
+    }
+
+    /**
+     * @brief   Write Document data to stream.
+     *          将`Document`数据写入流
+     * @param   pStream           specifies a binary output stream to write the data to.
+     *                      指定用于写入数据的二进制输出流
+     */
+    void Save(std::ostream& pStream) const
+    {
+      WriteCsv(pStream);
+    }
+
+    /**
+     * @brief   Clears loaded Document data.
+     *          清除加载的`Document`数据
+     *
+     */
+    void Clear()
+    {
+      mData.clear();
+      mColumnNames.clear();
+      mRowNames.clear();
+#ifdef HAS_CODECVT
+      mIsUtf16 = false;
+      mIsLE = false;
+#endif
+      mHasUtf8BOM = false;
+    }
+
+    /**
+     * @brief   Get column index by name.
+     *          获取列索引通过列名称
+     * @param   pColumnName       column label name.
+     *                        列标签名称
+     * @returns zero-based column index.
+     *          返回零基列索引
+     */
+    int GetColumnIdx(const std::string& pColumnName) const
+    {
+      if (mLabelParams.mColumnNameIdx >= 0)
+      {
+        if (mColumnNames.find(pColumnName) != mColumnNames.end())
+        {
+          return static_cast<int>(mColumnNames.at(pColumnName)) - (mLabelParams.mRowNameIdx + 1);
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * @brief   Get column by index.
+     *          通过索引获取列数据
+     * @param   pColumnIdx     zero-based column index.
+     *                      零基列索引
+     * @returns vector of column data.
+     *          返回列数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetColumn(const size_t pColumnIdx) const
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      std::vector<T> column;
+      Converter<T> converter(mConverterParams);
+      for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+      {
+        if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx)
+        {
+          if (dataColumnIdx < itRow->size())
+          {
+            T val;
+            converter.ToVal(itRow->at(dataColumnIdx), val);
+            column.push_back(val);
+          }
+          else
+          {
+            const std::string errStr = "requested column index " +
+              std::to_string(pColumnIdx) + " >= " +
+              std::to_string(itRow->size() - GetDataColumnIndex(0)) +
+              " (number of columns on row index " +
+              std::to_string(std::distance(mData.begin(), itRow) -
+                             (mLabelParams.mColumnNameIdx + 1)) + ")";
+            throw std::out_of_range(errStr);
+          }
+        }
+      }
+      return column;
+    }
+
+    /**
+     * @brief   Get column by index.
+     *          通过索引获取列数据
+     * @param   pColumnIdx     zero-based column index.
+     *                      零基列索引
+     * @param   pToVal    conversion function.
+     *                  转换函数
+     * @returns vector of column data.
+     *          返回列数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetColumn(const size_t pColumnIdx, ConvFunc<T> pToVal) const
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      std::vector<T> column;
+      for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+      {
+        if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx)
+        {
+          T val;
+          pToVal(itRow->at(dataColumnIdx), val);
+          column.push_back(val);
+        }
+      }
+      return column;
+    }
+
+    /**
+     * @brief   Get column by name.
+     *          通过名称获取列数据
+     * @param   pColumnName     column label name.
+     *                       列标签名称
+     * @returns vector of column data.
+     *          返回列数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetColumn(const std::string& pColumnName) const
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+      return GetColumn<T>(static_cast<size_t>(columnIdx));
+    }
+
+    /**
+     * @brief   Get column by name.
+     *          通过名称获取列数据
+     * @param   pColumnName     column label name.
+     *                       列标签名称
+     * @param   pToVal                conversion function.
+     *                       转换函数
+     * @returns vector of column data.
+     *          返回列数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetColumn(const std::string& pColumnName, ConvFunc<T> pToVal) const
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+      return GetColumn<T>(static_cast<size_t>(columnIdx), pToVal);
+    }
+
+    /**
+     * @brief   Set column by index.
+     *          设置列数据通过索引
+     * @param   pColumnIdx       zero-based column index.
+     *                       零基列索引
+     * @param   pColumn             vector of column data.
+     *                       列数据向量
+     */
+    template<typename T>
+    void SetColumn(const size_t pColumnIdx, const std::vector<T>& pColumn)
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+
+      while (GetDataRowIndex(pColumn.size()) > GetDataRowCount())
+      {
+        std::vector<std::string> row;
+        row.resize(GetDataColumnCount());
+        mData.push_back(row);
+      }
+
+      if ((dataColumnIdx + 1) > GetDataColumnCount())
+      {
+        for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+        {
+          if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+          {
+            itRow->resize(GetDataColumnIndex(dataColumnIdx + 1));
+          }
+        }
+      }
+
+      Converter<T> converter(mConverterParams);
+      for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow)
+      {
+        std::string str;
+        converter.ToStr(*itRow, str);
+        mData.at(static_cast<size_t>(std::distance(pColumn.begin(), itRow) + mLabelParams.mColumnNameIdx + 1)).at(
+          dataColumnIdx) = str;
+      }
+    }
+
+    /**
+     * @brief   Set column by name.
+     *          通过名称设置列数据
+     * @param   pColumnName           column label name.
+     *                          列标签名称
+     * @param   pColumn                    vector of column data.
+     *                          列数据向量
+     */
+    template<typename T>
+    void SetColumn(const std::string& pColumnName, const std::vector<T>& pColumn)
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+      SetColumn<T>(static_cast<size_t>(columnIdx), pColumn);
+    }
+
+    /**
+     * @brief   Remove column by index.
+     *          通过索引移除列
+     * @param   pColumnIdx              zero-based column index.
+     *                          零基列索引
+     */
+    void RemoveColumn(const size_t pColumnIdx)
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+      {
+        if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+        {
+          itRow->erase(itRow->begin() + static_cast<int>(dataColumnIdx));
+        }
+      }
+
+      UpdateColumnNames();
+    }
+
+    /**
+     * @brief   Remove column by name.
+     *          通过名称移除列
+     * @param   pColumnName           column label name.
+     *                          列标签名称
+     */
+    void RemoveColumn(const std::string& pColumnName)
+    {
+      int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      RemoveColumn(static_cast<size_t>(columnIdx));
+    }
+
+    /**
+     * @brief   Insert column at specified index.
+     *          在指定索引处插入列
+     * @param   pColumnIdx              zero-based column index.
+     *                          零基列索引
+     * @param   pColumn                    vector of column data (optional argument).
+     *                          列数据向量(可选参数)
+     * @param   pColumnName           column label name (optional argument).
+     *                          列标签名称(可选参数)
+     */
+    template<typename T>
+    void InsertColumn(const size_t pColumnIdx, const std::vector<T>& pColumn = std::vector<T>(),
+                      const std::string& pColumnName = std::string())
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+
+      std::vector<std::string> column;
+      if (pColumn.empty())
+      {
+        column.resize(GetDataRowCount());
+      }
+      else
+      {
+        column.resize(GetDataRowIndex(pColumn.size()));
+        Converter<T> converter(mConverterParams);
+        for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow)
+        {
+          std::string str;
+          converter.ToStr(*itRow, str);
+          const size_t rowIdx =
+            static_cast<size_t>(std::distance(pColumn.begin(), itRow) + (mLabelParams.mColumnNameIdx + 1));
+          column.at(rowIdx) = str;
+        }
+      }
+
+      while (column.size() > GetDataRowCount())
+      {
+        std::vector<std::string> row;
+        const size_t columnCount = std::max<size_t>(static_cast<size_t>(mLabelParams.mColumnNameIdx + 1),
+                                                    GetDataColumnCount());
+        row.resize(columnCount);
+        mData.push_back(row);
+      }
+
+      for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+      {
+        if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+        {
+          const size_t rowIdx = static_cast<size_t>(std::distance(mData.begin(), itRow));
+          itRow->insert(itRow->begin() + static_cast<int>(dataColumnIdx), column.at(rowIdx));
+        }
+      }
+
+      if (!pColumnName.empty())
+      {
+        SetColumnName(pColumnIdx, pColumnName);
+      }
+
+      UpdateColumnNames();
+    }
+
+    /**
+     * @brief   Get number of data columns (excluding label columns).
+     *          获取数据列的数量(不包括标签列)
+     * @returns column count.
+     *          返回列数
+     */
+    size_t GetColumnCount() const
+    {
+      const int count = static_cast<int>((mData.size() > 0) ? mData.at(0).size() : 0) -
+        (mLabelParams.mRowNameIdx + 1);
+      return (count >= 0) ? static_cast<size_t>(count) : 0;
+    }
+
+    /**
+     * @brief   Get row index by name.
+     *          通过名称获取行索引
+     * @param   pRowName         row label name.
+     *                      行标签名称
+     * @returns zero-based row index.
+     *          返回零基行索引
+     */
+    int GetRowIdx(const std::string& pRowName) const
+    {
+      if (mLabelParams.mRowNameIdx >= 0)
+      {
+        if (mRowNames.find(pRowName) != mRowNames.end())
+        {
+          return static_cast<int>(mRowNames.at(pRowName)) - (mLabelParams.mColumnNameIdx + 1);
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * @brief   Get row by index.
+     *          通过索引获取行数据
+     * @param   pRowIdx           zero-based row index.
+     *                      零基行索引
+     * @returns vector of row data.
+     *          返回行数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetRow(const size_t pRowIdx) const
+    {
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+      std::vector<T> row;
+      Converter<T> converter(mConverterParams);
+      for (auto itCol = mData.at(dataRowIdx).begin(); itCol != mData.at(dataRowIdx).end(); ++itCol)
+      {
+        if (std::distance(mData.at(dataRowIdx).begin(), itCol) > mLabelParams.mRowNameIdx)
+        {
+          T val;
+          converter.ToVal(*itCol, val);
+          row.push_back(val);
+        }
+      }
+      return row;
+    }
+
+    /**
+     * @brief   Get row by index.
+     *          通过索引获取行数据
+     * @param   pRowIdx           zero-based row index.
+     *                      零基行索引
+     * @param   pToVal             conversion function.
+     *                      转换函数
+     * @returns vector of row data.
+     *          返回行数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetRow(const size_t pRowIdx, ConvFunc<T> pToVal) const
+    {
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+      std::vector<T> row;
+      Converter<T> converter(mConverterParams);
+      for (auto itCol = mData.at(dataRowIdx).begin(); itCol != mData.at(dataRowIdx).end(); ++itCol)
+      {
+        if (std::distance(mData.at(dataRowIdx).begin(), itCol) > mLabelParams.mRowNameIdx)
+        {
+          T val;
+          pToVal(*itCol, val);
+          row.push_back(val);
+        }
+      }
+      return row;
+    }
+
+    /**
+     * @brief   Get row by name.
+     *          通过名称获取行数据
+     * @param   pRowName         row label name.
+     *                      行标签名称
+     * @returns vector of row data.
+     *          返回行数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetRow(const std::string& pRowName) const
+    {
+      int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+      return GetRow<T>(static_cast<size_t>(rowIdx));
+    }
+
+    /**
+     * @brief   Get row by name.
+     *          通过名称获取行数据
+     * @param   pRowName         row label name.
+     *                      行标签名称
+     * @param   pToVal             conversion function.
+     *                      转换函数
+     * @returns vector of row data.
+     *          返回行数据的向量
+     */
+    template<typename T>
+    std::vector<T> GetRow(const std::string& pRowName, ConvFunc<T> pToVal) const
+    {
+      int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+      return GetRow<T>(static_cast<size_t>(rowIdx), pToVal);
+    }
+
+    /**
+     * @brief   Set row by index.
+     *          通过索引设置行数据
+     * @param   pRowIdx             zero-based row index.
+     *                       零基行索引
+     * @param   pRow                    vector of row data.
+     *                       行数据的向量
+     */
+    template<typename T>
+    void SetRow(const size_t pRowIdx, const std::vector<T>& pRow)
+    {
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+      while ((dataRowIdx + 1) > GetDataRowCount())
+      {
+        std::vector<std::string> row;
+        row.resize(GetDataColumnCount());
+        mData.push_back(row);
+      }
+
+      if (pRow.size() > GetDataColumnCount())
+      {
+        for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+        {
+          if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+          {
+            itRow->resize(GetDataColumnIndex(pRow.size()));
+          }
+        }
+      }
+
+      Converter<T> converter(mConverterParams);
+      for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol)
+      {
+        std::string str;
+        converter.ToStr(*itCol, str);
+        mData.at(dataRowIdx).at(static_cast<size_t>(std::distance(pRow.begin(),
+                                                                  itCol) + mLabelParams.mRowNameIdx + 1)) = str;
+      }
+    }
+
+    /**
+     * @brief   Set row by name.
+     *          通过名称设置行数据
+     * @param   pRowName         row label name.
+     *                      行标签名称
+     * @param   pRow                  vector of row data.
+     *                      行数据的向量
+     */
+    template<typename T>
+    void SetRow(const std::string& pRowName, const std::vector<T>& pRow)
+    {
+      int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+      return SetRow<T>(static_cast<size_t>(rowIdx), pRow);
+    }
+
+    /**
+     * @brief   Remove row by index.
+     *          通过索引移除行
+     * @param   pRowIdx           zero-based row index.
+     *                      零基行索引
+     */
+    void RemoveRow(const size_t pRowIdx)
+    {
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+      mData.erase(mData.begin() + static_cast<int>(dataRowIdx));
+      UpdateRowNames();
+    }
+
+    /**
+     * @brief   Remove row by name.
+     *          通过名称移除行
+     * @param   pRowName         row label name.
+     *                      行标签名称
+     */
+    void RemoveRow(const std::string& pRowName)
+    {
+      int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      RemoveRow(static_cast<size_t>(rowIdx));
+    }
+
+    /**
+     * @brief   Insert row at specified index.
+     *          在指定索引处插入行
+     * @param   pRowIdx           zero-based row index.
+     *                      零基行索引
+     * @param   pRow                  vector of row data (optional argument).
+     *                      行数据的向量(可选参数)
+     * @param   pRowName         row label name (optional argument).
+     *                      行标签名称(可选参数)
+     */
+    template<typename T>
+    void InsertRow(const size_t pRowIdx, const std::vector<T>& pRow = std::vector<T>(),
+                   const std::string& pRowName = std::string())
+    {
+      const size_t rowIdx = GetDataRowIndex(pRowIdx);
+
+      std::vector<std::string> row;
+      if (pRow.empty())
+      {
+        row.resize(GetDataColumnCount());
+      }
+      else
+      {
+        row.resize(GetDataColumnIndex(pRow.size()));
+        Converter<T> converter(mConverterParams);
+        for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol)
+        {
+          std::string str;
+          converter.ToStr(*itCol, str);
+          row.at(static_cast<size_t>(std::distance(pRow.begin(), itCol) + mLabelParams.mRowNameIdx + 1)) = str;
+        }
+      }
+
+      while (rowIdx > GetDataRowCount())
+      {
+        std::vector<std::string> tempRow;
+        tempRow.resize(GetDataColumnCount());
+        mData.push_back(tempRow);
+      }
+
+      mData.insert(mData.begin() + static_cast<int>(rowIdx), row);
+
+      if (!pRowName.empty())
+      {
+        SetRowName(pRowIdx, pRowName);
+      }
+
+      UpdateRowNames();
+    }
+
+    /**
+     * @brief   Get number of data rows (excluding label rows).
+     *          获取数据行的数量(不包括标签行)
+     * @returns row count.
+     *          返回行数
+     */
+    size_t GetRowCount() const
+    {
+      const int count = static_cast<int>(mData.size()) - (mLabelParams.mColumnNameIdx + 1);
+      return (count >= 0) ? static_cast<size_t>(count) : 0;
+    }
+
+    /**
+     * @brief   Get cell by index.
+     *          通过索引获取单元格数据
+     * @param   pColumnIdx       zero-based column index.
+     *                       零基列索引
+     * @param   pRowIdx             zero-based row index.
+     *                       零基行索引
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+      T val;
+      Converter<T> converter(mConverterParams);
+      converter.ToVal(mData.at(dataRowIdx).at(dataColumnIdx), val);
+      return val;
+    }
+
+    /**
+     * @brief   Get cell by index.
+     *          通过索引获取单元格数据
+     * @param   pColumnIdx       zero-based column index.
+     *                       零基列索引
+     * @param   pRowIdx              zero-based row index.
+     *                       零基行索引
+     * @param   pToVal                conversion function.
+     *                       转换函数
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const size_t pColumnIdx, const size_t pRowIdx, ConvFunc<T> pToVal) const
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+      T val;
+      pToVal(mData.at(dataRowIdx).at(dataColumnIdx), val);
+      return val;
+    }
+
+    /**
+     * @brief   Get cell by name.
+     *          通过名称获取单元格数据
+     * @param   pColumnName       column label name.
+     *                        列标签名称
+     * @param   pRowName              row label name.
+     *                        行标签名称
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const std::string& pColumnName, const std::string& pRowName) const
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      const int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      return GetCell<T>(static_cast<size_t>(columnIdx), static_cast<size_t>(rowIdx));
+    }
+
+    /**
+     * @brief   Get cell by name.
+     *          通过名称获取单元格数据
+     * @param   pColumnName         column label name.
+     *                         列标签名称
+     * @param   pRowName               row label name.
+     *                         行标签名称
+     * @param   pToVal                    conversion function.
+     *                         转换函数
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const std::string& pColumnName, const std::string& pRowName, ConvFunc<T> pToVal) const
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      const int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      return GetCell<T>(static_cast<size_t>(columnIdx), static_cast<size_t>(rowIdx), pToVal);
+    }
+
+    /**
+     * @brief   Get cell by column name and row index.
+     *          通过列名称和行索引获取单元格数据
+     * @param   pColumnName           column label name.
+     *                          列标签名称
+     * @param   pRowIdx                    zero-based row index.
+     *                          零基行索引
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const std::string& pColumnName, const size_t pRowIdx) const
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      return GetCell<T>(static_cast<size_t>(columnIdx), pRowIdx);
+    }
+
+    /**
+     * @brief   Get cell by column name and row index.
+     *          通过列名称和行索引获取单元格数据
+     * @param   pColumnName         column label name.
+     *                         列标签名称
+     * @param   pRowIdx                  zero-based row index.
+     *                         零基行索引
+     * @param   pToVal                    conversion function.
+     *                         转换函数
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const std::string& pColumnName, const size_t pRowIdx, ConvFunc<T> pToVal) const
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      return GetCell<T>(static_cast<size_t>(columnIdx), pRowIdx, pToVal);
+    }
+
+    /**
+     * @brief   Get cell by column index and row name.
+     *          通过列索引和行名称获取单元格数据
+     * @param   pColumnIdx          zero-based column index.
+     *                        零基列索引
+     * @param   pRowName              row label name.
+     *                        行标签名称
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const size_t pColumnIdx, const std::string& pRowName) const
+    {
+      const int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      return GetCell<T>(pColumnIdx, static_cast<size_t>(rowIdx));
+    }
+
+    /**
+     * @brief   Get cell by column index and row name.
+     *          通过列索引和行名称获取单元格数据
+     * @param   pColumnIdx       zero-based column index.
+     *                       零基列索引
+     * @param   pRowName           row label name.
+     *                       行标签名称
+     * @param   pToVal                conversion function.
+     *                       转换函数
+     * @returns cell data.
+     *          单元格数据
+     */
+    template<typename T>
+    T GetCell(const size_t pColumnIdx, const std::string& pRowName, ConvFunc<T> pToVal) const
+    {
+      const int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      return GetCell<T>(pColumnIdx, static_cast<size_t>(rowIdx), pToVal);
+    }
+
+    /**
+     * @brief   Set cell by index.
+     *          通过索引设置单元格数据
+     * @param   pRowIdx           zero-based row index.
+     *                      零基行索引
+     * @param   pColumnIdx    zero-based column index.
+     *                      零基列索引
+     * @param   pCell               cell data.
+     *                      单元格数据
+     */
+    template<typename T>
+    void SetCell(const size_t pColumnIdx, const size_t pRowIdx, const T& pCell)
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+      while ((dataRowIdx + 1) > GetDataRowCount())
+      {
+        std::vector<std::string> row;
+        row.resize(GetDataColumnCount());
+        mData.push_back(row);
+      }
+
+      if ((dataColumnIdx + 1) > GetDataColumnCount())
+      {
+        for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+        {
+          if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+          {
+            itRow->resize(dataColumnIdx + 1);
+          }
+        }
+      }
+
+      std::string str;
+      Converter<T> converter(mConverterParams);
+      converter.ToStr(pCell, str);
+      mData.at(dataRowIdx).at(dataColumnIdx) = str;
+    }
+
+    /**
+     * @brief   Set cell by name.
+     *          通过列名称和行名称设置单元格数据
+     * @param   pColumnName           column label name.
+     *                          列标签名称
+     * @param   pRowName                  row label name.
+     *                          行标签名称
+     * @param   pCell                         cell data.
+     *                          单元格数据
+     */
+    template<typename T>
+    void SetCell(const std::string& pColumnName, const std::string& pRowName, const T& pCell)
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      const int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      SetCell<T>(static_cast<size_t>(columnIdx), static_cast<size_t>(rowIdx), pCell);
+    }
+
+    /**
+     * @brief   Set cell by column index and row name.
+     *          通过列索引和行名称设置单元格数据
+     * @param   pColumnIdx             zero-based column index.
+     *                          零基列索引
+     * @param   pRowName                 row label name.
+     *                          行标签名称
+     * @param   pCell                        cell data.
+     *                          单元格数据
+     */
+    template<typename T>
+    void SetCell(const size_t pColumnIdx, const std::string& pRowName, const T& pCell)
+    {
+      const int rowIdx = GetRowIdx(pRowName);
+      if (rowIdx < 0)
+      {
+        throw std::out_of_range("row not found: " + pRowName);
+      }
+
+      SetCell<T>(pColumnIdx, static_cast<size_t>(rowIdx), pCell);
+    }
+
+    /**
+     * @brief   Set cell by column name and row index.
+     *          通过列名称和行索引设置单元格数据
+     * @param   pColumnName           column label name.
+     *                          列标签名称
+     * @param   pRowIdx                    zero-based row index.
+     *                          零基行索引
+     * @param   pCell                        cell data.
+     *                          单元格数据
+     */
+    template<typename T>
+    void SetCell(const std::string& pColumnName, const size_t pRowIdx, const T& pCell)
+    {
+      const int columnIdx = GetColumnIdx(pColumnName);
+      if (columnIdx < 0)
+      {
+        throw std::out_of_range("column not found: " + pColumnName);
+      }
+
+      SetCell<T>(static_cast<size_t>(columnIdx), pRowIdx, pCell);
+    }
+
+    /**
+     * @brief   Get column name
+     *          获取列名称
+     * @param   pColumnIdx       zero-based column index.
+     *                       零基列索引
+     * @returns column name.
+     *          返回列名称
+     */
+    std::string GetColumnName(const size_t pColumnIdx) const
+    {
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      if (mLabelParams.mColumnNameIdx < 0)
+      {
+        throw std::out_of_range("column name row index < 0: " + std::to_string(mLabelParams.mColumnNameIdx));
+      }
+
+      return mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).at(dataColumnIdx);
+    }
+
+    /**
+     * @brief   Set column name
+     *          设置列名称
+     * @param   pColumnIdx              zero-based column index.
+     *                          零基列索引
+     * @param   pColumnName           column name.
+     *                          列名称
+     */
+    void SetColumnName(size_t pColumnIdx, const std::string& pColumnName)
+    {
+      if (mLabelParams.mColumnNameIdx < 0)
+      {
+        throw std::out_of_range("column name row index < 0: " + std::to_string(mLabelParams.mColumnNameIdx));
+      }
+
+      const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+      mColumnNames[pColumnName] = dataColumnIdx;
+
+      // increase table size if necessary:
+      const size_t rowIdx = static_cast<size_t>(mLabelParams.mColumnNameIdx);
+      if (rowIdx >= mData.size())
+      {
+        mData.resize(rowIdx + 1);
+      }
+      auto& row = mData[rowIdx];
+      if (dataColumnIdx >= row.size())
+      {
+        row.resize(dataColumnIdx + 1);
+      }
+
+      mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).at(dataColumnIdx) = pColumnName;
+    }
+
+    /**
+     * @brief   Get column names
+     *          获取列名称
+     * @returns vector of column names.
+     *          返回列名称的向量
+     */
+    std::vector<std::string> GetColumnNames() const
+    {
+      if (mLabelParams.mColumnNameIdx >= 0)
+      {
+        return std::vector<std::string>(mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).begin() +
+                                        (mLabelParams.mRowNameIdx + 1),
+                                        mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).end());
+      }
+
+      return std::vector<std::string>();
+    }
+
+    /**
+     * @brief   Get row name
+     *          获取行名称
+     * @param   pRowIdx           zero-based column index.
+     *                      零基列索引
+     * @returns row name.
+     *          返回行名称
+     */
+    std::string GetRowName(const size_t pRowIdx) const
+    {
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+      if (mLabelParams.mRowNameIdx < 0)
+      {
+        throw std::out_of_range("row name column index < 0: " + std::to_string(mLabelParams.mRowNameIdx));
+      }
+
+      return mData.at(dataRowIdx).at(static_cast<size_t>(mLabelParams.mRowNameIdx));
+    }
+
+    /**
+     * @brief   Set row name
+     *          设置行名称
+     * @param   pRowIdx           zero-based row index.
+     *                      零基行索引
+     * @param   pRowName         row name.
+     *                      行名称
+     */
+    void SetRowName(size_t pRowIdx, const std::string& pRowName)
+    {
+      const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+      mRowNames[pRowName] = dataRowIdx;
+      if (mLabelParams.mRowNameIdx < 0)
+      {
+        throw std::out_of_range("row name column index < 0: " + std::to_string(mLabelParams.mRowNameIdx));
+      }
+
+      // increase table size if necessary:
+      if (dataRowIdx >= mData.size())
+      {
+        mData.resize(dataRowIdx + 1);
+      }
+      auto& row = mData[dataRowIdx];
+      if (mLabelParams.mRowNameIdx >= static_cast<int>(row.size()))
+      {
+        row.resize(static_cast<size_t>(mLabelParams.mRowNameIdx) + 1);
+      }
+
+      mData.at(dataRowIdx).at(static_cast<size_t>(mLabelParams.mRowNameIdx)) = pRowName;
+    }
+
+    /**
+     * @brief   Get row names
+     *          获取行名称
+     * @returns vector of row names.
+     *          返回行名称的向量
+     */
+    std::vector<std::string> GetRowNames() const
+    {
+      std::vector<std::string> rownames;
+      if (mLabelParams.mRowNameIdx >= 0)
+      {
+        for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+        {
+          if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx)
+          {
+            rownames.push_back(itRow->at(static_cast<size_t>(mLabelParams.mRowNameIdx)));
+          }
+        }
+      }
+      return rownames;
+    }
+
+  private:
+    void ReadCsv()
+    {
+      std::ifstream stream;
+      stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+      stream.open(mPath, std::ios::binary);
+      ReadCsv(stream);
+    }
+
+    void ReadCsv(std::istream& pStream)
+    {
+      Clear();
+      pStream.seekg(0, std::ios::end);
+      std::streamsize length = pStream.tellg();
+      pStream.seekg(0, std::ios::beg);
+
+#ifdef HAS_CODECVT
+      std::vector<char> bom2b(2, '\0');
+      if (length >= 2)
+      {
+        pStream.read(bom2b.data(), 2);
+        pStream.seekg(0, std::ios::beg);
+      }
+
+      static const std::vector<char> bomU16le = { '\xff', '\xfe' };
+      static const std::vector<char> bomU16be = { '\xfe', '\xff' };
+      if ((bom2b == bomU16le) || (bom2b == bomU16be))
+      {
+        mIsUtf16 = true;
+        mIsLE = (bom2b == bomU16le);
+
+        std::wifstream wstream;
+        wstream.exceptions(std::wifstream::failbit | std::wifstream::badbit);
+        wstream.open(mPath, std::ios::binary);
+        if (mIsLE)
+        {
+          wstream.imbue(std::locale(wstream.getloc(),
+                                    new std::codecvt_utf16<wchar_t, 0x10ffff,
+                                                           static_cast<std::codecvt_mode>(std::consume_header |
+                                                                                          std::little_endian)>));
+        }
+        else
+        {
+          wstream.imbue(std::locale(wstream.getloc(),
+                                    new std::codecvt_utf16<wchar_t, 0x10ffff,
+                                                           std::consume_header>));
+        }
+        std::wstringstream wss;
+        wss << wstream.rdbuf();
+        std::string utf8 = ToString(wss.str());
+        std::stringstream ss(utf8);
+        ParseCsv(ss, static_cast<std::streamsize>(utf8.size()));
+      }
+      else
+#endif
+      {
+        // check for UTF-8 Byte order mark and skip it when found
+        if (length >= 3)
+        {
+          std::vector<char> bom3b(3, '\0');
+          pStream.read(bom3b.data(), 3);
+
+          if (bom3b != s_Utf8BOM)
+          {
+            // file does not start with a UTF-8 Byte order mark
+            pStream.seekg(0, std::ios::beg);
+          }
+          else
+          {
+            // file did start with a UTF-8 Byte order mark, simply skip it
+            length -= 3;
+            mHasUtf8BOM = true;
+          }
+        }
+
+        ParseCsv(pStream, length);
+      }
+    }
+
+    void ParseCsv(std::istream& pStream, std::streamsize p_FileLength)
+    {
+      const std::streamsize bufLength = 64 * 1024;
+      char* buffer = new char[bufLength];
+      std::vector<std::string> row;
+      std::string cell;
+      bool quoted = false;
+      int cr = 0;
+      int lf = 0;
+
+      while (p_FileLength > 0)
+      {
+        const std::streamsize toReadLength = std::min<std::streamsize>(p_FileLength, bufLength);
+        pStream.read(buffer, toReadLength);
+
+        // With user-specified istream opened in non-binary mode on windows, we may have a
+        // data length mismatch, so ensure we don't parse outside actual data length read.
+        const std::streamsize readLength = pStream.gcount();
+        if (readLength <= 0)
+        {
+          break;
+        }
+
+        for (size_t i = 0; i < static_cast<size_t>(readLength); ++i)
+        {
+          if (buffer[i] == mSeparatorParams.mQuoteChar)
+          {
+            if (cell.empty() || (cell[0] == mSeparatorParams.mQuoteChar))
+            {
+              quoted = !quoted;
+            }
+            else if (mSeparatorParams.mTrim)
+            {
+              // allow whitespace before first mQuoteChar
+              const auto firstQuote = std::find(cell.begin(), cell.end(), mSeparatorParams.mQuoteChar);
+              if (std::all_of(cell.begin(), firstQuote, [](int ch) { return isspace(ch); }))
+              {
+                quoted = !quoted;
+              }
+            }
+            cell += buffer[i];
+          }
+          else if (buffer[i] == mSeparatorParams.mSeparator)
+          {
+            if (!quoted)
+            {
+              row.push_back(Unquote(Trim(cell)));
+              cell.clear();
+            }
+            else
+            {
+              cell += buffer[i];
+            }
+          }
+          else if (buffer[i] == '\r')
+          {
+            if (mSeparatorParams.mQuotedLinebreaks && quoted)
+            {
+              cell += buffer[i];
+            }
+            else
+            {
+              ++cr;
+            }
+          }
+          else if (buffer[i] == '\n')
+          {
+            if (mSeparatorParams.mQuotedLinebreaks && quoted)
+            {
+              cell += buffer[i];
+            }
+            else
+            {
+              ++lf;
+              if (mLineReaderParams.mSkipEmptyLines && row.empty() && cell.empty())
+              {
+                // skip empty line
+              }
+              else
+              {
+                row.push_back(Unquote(Trim(cell)));
+
+                if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() &&
+                    (row.at(0)[0] == mLineReaderParams.mCommentPrefix))
+                {
+                  // skip comment line
+                }
+                else
+                {
+                  mData.push_back(row);
+                }
+
+                cell.clear();
+                row.clear();
+                quoted = false;
+              }
+            }
+          }
+          else
+          {
+            cell += buffer[i];
+          }
+        }
+        p_FileLength -= readLength;
+      }
+
+      // Handle last row / cell without linebreak
+      if (row.empty() && cell.empty())
+      {
+        // skip empty trailing line
+      }
+      else
+      {
+        row.push_back(Unquote(Trim(cell)));
+
+        if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() &&
+            (row.at(0)[0] == mLineReaderParams.mCommentPrefix))
+        {
+          // skip comment line
+        }
+        else
+        {
+          mData.push_back(row);
+        }
+
+        cell.clear();
+        row.clear();
+        quoted = false;
+      }
+        
+      delete [] buffer;
+
+      // Assume CR/LF if at least half the linebreaks have CR
+      mSeparatorParams.mHasCR = (cr > (lf / 2));
+
+      // Set up column labels
+      UpdateColumnNames();
+
+      // Set up row labels
+      UpdateRowNames();
+    }
+
+    void WriteCsv() const
+    {
+#ifdef HAS_CODECVT
+      if (mIsUtf16)
+      {
+        std::stringstream ss;
+        WriteCsv(ss);
+        std::string utf8 = ss.str();
+        std::wstring wstr = ToWString(utf8);
+
+        std::wofstream wstream;
+        wstream.exceptions(std::wofstream::failbit | std::wofstream::badbit);
+        wstream.open(mPath, std::ios::binary | std::ios::trunc);
+
+        if (mIsLE)
+        {
+          wstream.imbue(std::locale(wstream.getloc(),
+                                    new std::codecvt_utf16<wchar_t, 0x10ffff,
+                                                           static_cast<std::codecvt_mode>(std::little_endian)>));
+        }
+        else
+        {
+          wstream.imbue(std::locale(wstream.getloc(),
+                                    new std::codecvt_utf16<wchar_t, 0x10ffff>));
+        }
+
+        wstream << static_cast<wchar_t>(0xfeff);
+        wstream << wstr;
+      }
+      else
+#endif
+      {
+        std::ofstream stream;
+        stream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
+        stream.open(mPath, std::ios::binary | std::ios::trunc);
+        if (mHasUtf8BOM)
+        {
+          stream.write(s_Utf8BOM.data(), 3);
+        }
+
+        WriteCsv(stream);
+      }
+    }
+
+    void WriteCsv(std::ostream& pStream) const
+    {
+      for (auto itr = mData.begin(); itr != mData.end(); ++itr)
+      {
+        for (auto itc = itr->begin(); itc != itr->end(); ++itc)
+        {
+          if (mSeparatorParams.mAutoQuote &&
+              ((itc->find(mSeparatorParams.mSeparator) != std::string::npos) ||
+               (itc->find(' ') != std::string::npos) ||
+               (itc->find('\n') != std::string::npos)))
+          {
+            // escape quotes in string
+            std::string str = *itc;
+            const std::string quoteCharStr = std::string(1, mSeparatorParams.mQuoteChar);
+            ReplaceString(str, quoteCharStr, quoteCharStr + quoteCharStr);
+
+            pStream << quoteCharStr << str << quoteCharStr;
+          }
+          else
+          {
+            pStream << *itc;
+          }
+
+          if (std::distance(itc, itr->end()) > 1)
+          {
+            pStream << mSeparatorParams.mSeparator;
+          }
+        }
+        pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n");
+      }
+    }
+
+    size_t GetDataRowCount() const
+    {
+      return mData.size();
+    }
+
+    size_t GetDataColumnCount() const
+    {
+      const size_t firstDataRow = static_cast<size_t>((mLabelParams.mColumnNameIdx >= 0) ? mLabelParams.mColumnNameIdx : 0);
+      return (mData.size() > firstDataRow) ? mData.at(firstDataRow).size() : 0;
+    }
+
+    inline size_t GetDataRowIndex(const size_t pRowIdx) const
+    {
+      const size_t firstDataRow = static_cast<size_t>((mLabelParams.mColumnNameIdx + 1 >= 0) ? mLabelParams.mColumnNameIdx + 1 : 0);
+      return pRowIdx + firstDataRow;
+    }
+
+    inline size_t GetDataColumnIndex(const size_t pColumnIdx) const
+    {
+      const size_t firstDataColumn = static_cast<size_t>((mLabelParams.mRowNameIdx + 1 >= 0) ? mLabelParams.mRowNameIdx + 1 : 0);
+      return pColumnIdx + firstDataColumn;
+    }
+
+    std::string Trim(const std::string& pStr) const
+    {
+      if (mSeparatorParams.mTrim)
+      {
+        std::string str = pStr;
+
+        // ltrim
+        str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int ch) { return !isspace(ch); }));
+
+        // rtrim
+        str.erase(std::find_if(str.rbegin(), str.rend(), [](int ch) { return !isspace(ch); }).base(), str.end());
+
+        return str;
+      }
+      else
+      {
+        return pStr;
+      }
+    }
+
+    std::string Unquote(const std::string& pStr) const
+    {
+      if (mSeparatorParams.mAutoQuote && (pStr.size() >= 2) &&
+          (pStr.front() == mSeparatorParams.mQuoteChar) &&
+          (pStr.back() == mSeparatorParams.mQuoteChar))
+      {
+        // remove start/end quotes
+        std::string str = pStr.substr(1, pStr.size() - 2);
+
+        // unescape quotes in string
+        const std::string quoteCharStr = std::string(1, mSeparatorParams.mQuoteChar);
+        ReplaceString(str, quoteCharStr + quoteCharStr, quoteCharStr);
+
+        return str;
+      }
+      else
+      {
+        return pStr;
+      }
+    }
+
+    void UpdateColumnNames()
+    {
+      mColumnNames.clear();
+      if ((mLabelParams.mColumnNameIdx >= 0) &&
+          (static_cast<int>(mData.size()) > mLabelParams.mColumnNameIdx))
+      {
+        size_t i = 0;
+        for (auto& columnName : mData[static_cast<size_t>(mLabelParams.mColumnNameIdx)])
+        {
+          mColumnNames[columnName] = i++;
+        }
+      }
+    }
+
+    void UpdateRowNames()
+    {
+      mRowNames.clear();
+      if ((mLabelParams.mRowNameIdx >= 0) &&
+          (static_cast<int>(mData.size()) >
+           (mLabelParams.mColumnNameIdx + 1)))
+      {
+        size_t i = 0;
+        for (auto& dataRow : mData)
+        {
+          if (static_cast<int>(dataRow.size()) > mLabelParams.mRowNameIdx)
+          {
+            mRowNames[dataRow[static_cast<size_t>(mLabelParams.mRowNameIdx)]] = i++;
+          }
+        }
+      }
+    }
+
+#ifdef HAS_CODECVT
+#if defined(_MSC_VER)
+#pragma warning (push)
+#pragma warning (disable: 4996)
+#endif
+    static std::string ToString(const std::wstring& pWStr)
+    {
+      return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>{ }.to_bytes(pWStr);
+    }
+
+    static std::wstring ToWString(const std::string& pStr)
+    {
+      return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>{ }.from_bytes(pStr);
+    }
+#if defined(_MSC_VER)
+#pragma warning (pop)
+#endif
+#endif
+
+    static void ReplaceString(std::string& pStr, const std::string& pSearch, const std::string& pReplace)
+    {
+      size_t pos = 0;
+
+      while ((pos = pStr.find(pSearch, pos)) != std::string::npos)
+      {
+        pStr.replace(pos, pSearch.size(), pReplace);
+        pos += pReplace.size();
+      }
+    }
+
+  private:
+    std::string mPath;
+    LabelParams mLabelParams;
+    SeparatorParams mSeparatorParams;
+    ConverterParams mConverterParams;
+    LineReaderParams mLineReaderParams;
+    std::vector<std::vector<std::string>> mData;
+    std::map<std::string, size_t> mColumnNames;
+    std::map<std::string, size_t> mRowNames;
+#ifdef HAS_CODECVT
+    bool mIsUtf16 = false;
+    bool mIsLE = false;
+#endif
+    bool mHasUtf8BOM = false;
+  };
+}

+ 2 - 1
Classes/TestScene.cpp

@@ -32,10 +32,11 @@ bool TestScene::init(){
     }
     
     std::string cfgFN = "shop_5.json";
+    std::string failFN = "purchaseBannerConfig.csv";
     
     _iapShop = iap::IAPCtlShop::createWith();
     _iapShop->init(cfgFN);
-    _iapShop->setCfg4Failure(cfgFN);
+    _iapShop->setCfg4Failure(failFN);
     
     iap::IAPPlacement plInfo;
     plInfo.id = "1";

+ 4 - 4
Resources/resources/ccb/purchaseBanner/内购版位_活动模版.red

@@ -1052,8 +1052,8 @@
                                     <string>Size</string>
                                     <key>value</key>
                                     <array>
-                                        <real>0.0000000</real>
-                                        <real>0.0000000</real>
+                                        <real>350.0000000</real>
+                                        <real>80.0000000</real>
                                         <integer>0</integer>
                                         <integer>0</integer>
                                         <false/>
@@ -1610,8 +1610,8 @@
                                                     <string>Size</string>
                                                     <key>value</key>
                                                     <array>
-                                                        <real>0.0000000</real>
-                                                        <real>0.0000000</real>
+                                                        <real>180.0000000</real>
+                                                        <real>54.0000000</real>
                                                         <integer>0</integer>
                                                         <integer>0</integer>
                                                         <false/>

+ 0 - 20
Resources/resources/ccb/purchaseBanner/内购版位_浮层_提示文本.rebolt

@@ -1,20 +0,0 @@
-{
-    "CustomFunc": {
-    },
-    "CustomList": {
-    },
-    "CustomMessage": {
-        "Hello Red": 1
-    },
-    "CustomTestFunc": {
-    },
-    "CustomVar": {
-    },
-    "DisPlayName": "内购版位_浮层_提示文本",
-    "RedFileList": {
-    },
-    "RedNoteInfo": {
-    },
-    "TreeList": {
-    }
-}

+ 16 - 2
Resources/resources/ccb/purchaseBanner/内购版位_礼包模版.rebolt

@@ -193,10 +193,10 @@
                                             "baseSelect": {
                                                 "DisplayName": "折扣数字",
                                                 "Type": "Action",
-                                                "Value": "fdCsMtP886mU"
+                                                "Value": "aTo1UVOsNfiy"
                                             },
                                             "isIllegal": true,
-                                            "randomID": "MkDaWoLrcYuw",
+                                            "randomID": "OL13VmM4nCPS",
                                             "stepSlot": {
                                                 "ContainerValue": {
                                                     "TitleInput": {
@@ -1028,6 +1028,20 @@
             },
             "x": 65,
             "y": 672
+        },
+        "Tree4": {
+            "ExportTree": false,
+            "Type": "BTCoderVariableAction",
+            "VarScope": "Scene",
+            "isIllegal": true,
+            "randomID": "ZUKykjYRaGQR",
+            "titleLabel": {
+                "Type": "STRING",
+                "Value": "P-价格数字",
+                "displayName": ""
+            },
+            "x": 543,
+            "y": 154.5
         }
     }
 }

+ 2 - 2
Resources/resources/ccb/purchaseBanner/内购版位_计时模版.rebolt

@@ -98,7 +98,7 @@
                         "ContainerValue": {
                             "Type": "BTPlaySceneTimeLineAction",
                             "baseSelect": {
-                                "DisplayName": "计时开启",
+                                "DisplayName": "normal",
                                 "Type": "Action",
                                 "Value": "0"
                             },
@@ -887,7 +887,7 @@
                                                 "ContainerValue": {
                                                     "Type": "BTPlaySceneTimeLineAction",
                                                     "baseSelect": {
-                                                        "DisplayName": "计时结束",
+                                                        "DisplayName": "finished",
                                                         "Type": "Action",
                                                         "Value": "1"
                                                     },

+ 1 - 1
Resources/resources/ccb/purchaseBanner/内购版位_计时模版.red

@@ -4762,7 +4762,7 @@
                     <key>alias</key>
                     <string>时钟动画</string>
                     <key>isPublic</key>
-                    <false/>
+                    <true/>
                 </dict>
             </dict>
         </dict>

+ 1 - 1
Resources/resources/ccbi/Rebolt_BT/H5d5TSmrDQQF.xml

@@ -69,7 +69,7 @@
             <property Opr="Self.red::RedBehaviacTree::getLocalTreeString(&quot;__ttbt__localVariableIndex_4&quot;)"/>
         </node>
         <node class="Action" id="16">
-            <property Method="Self.red::RedBehaviacTree::setLabelTitle(&quot;MkDaWoLrcYuw&quot;,&quot;fdCsMtP886mU&quot;,string Self.red::RedBehaviacTree::temporaryVariablesString)"/>
+            <property Method="Self.red::RedBehaviacTree::setLabelTitle(&quot;OL13VmM4nCPS&quot;,&quot;aTo1UVOsNfiy&quot;,string Self.red::RedBehaviacTree::temporaryVariablesString)"/>
             <property ResultOption="BT_SUCCESS"/>
         </node>
         <node class="Assignment" id="17">

二進制
Resources/resources/ccbi/内购版位_活动模版.redream


+ 2 - 0
Resources/resources/config/bannerViewConfig.csv

@@ -0,0 +1,2 @@
+资源名称,圆点宽度,圆点高度,圆点亮图,圆点暗图
+内购版位_UI.plist,2,18,内购版位_通用_图标_滑动点_已选中.png,内购版位_通用_图标_滑动点_未选中.png

+ 5 - 0
Resources/resources/config/purchaseBannerConfig.csv

@@ -0,0 +1,5 @@
+名称,描述,类型,文件名称,标题,图集名称,背景图片,商品ID,折扣数字,价格数字,按钮文字,有效时间秒,物品1ID,物品1类型,物品1数量,物品1图片,物品2ID,物品2类型,物品2数量,物品2图片,物品3ID,物品3类型,物品3数量,物品3图片,物品3描述
+礼包1,礼包示例,礼包,内购版位_礼包模版.redream,Goods Favor,内购版位_UI.plist,内购版位_通用_底板_纹理底板.png,com.id.1,50,6.99,0,0,4,金币奖励,300,内购版位_通用_道具_金币堆.png,cn,限时奖励,3600,gmui_prop_1.png,生命,0,5,内购版位_通用_道具_阴影爱心.png,Free life refill!
+tilefavor,计时未内购,计时,内购版位_计时模版.redream,Goods Favor,内购版位_UI.plist,内购版位_通用_底板_纹理底板.png,tilefavor,50,$0.99,0,60,金币,金币奖励,1000,内购版位_通用_道具_金币堆.png,魔棒,普通奖励,1,gmui_prop_1.png,0,0,0,0,0
+tilepack,计时已内购,计时,内购版位_计时模版.redream,Goods Favor,内购版位_UI.plist,内购版位_通用_底板_纹理底板.png,tilepack,50,$6.99,0,60,金币,金币奖励,4000,内购版位_通用_道具_金币堆.png,魔棒,普通奖励,3,gmui_prop_1.png,0,0,0,0,0
+tilepass,活动示例,活动,内购版位_活动模版.redream,Summer Pass,内购版位_UI.plist,内购版位_活动模版_底板_插图底板.png,tilepass,0,0,Activate,0,0,0,0,0,0,0,0,0,0,0,0,0,0

二進制
Resources/resources/font/内购版位_字体_半透明黑色描边.GlyphProject


二進制
Resources/resources/font/内购版位_字体_灰色描边.GlyphProject


二進制
Resources/resources/font/内购版位_字体_紫色描边.GlyphProject


二進制
Resources/resources/font/内购版位_字体_红色描边.GlyphProject


二進制
Resources/resources/font/内购版位_字体_纯白字体.GlyphProject


二進制
Resources/resources/font/内购版位_字体_绿色描边.GlyphProject


+ 52 - 6
proj.ios_mac/demo.xcodeproj/project.pbxproj

@@ -59,8 +59,16 @@
 		C18082332CCB71D3009DE140 /* IAPTestCardViewDelegate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C18082312CCB71D3009DE140 /* IAPTestCardViewDelegate.cpp */; };
 		C18082342CCB71D3009DE140 /* IAPTestCardViewDelegate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C18082312CCB71D3009DE140 /* IAPTestCardViewDelegate.cpp */; };
 		C180823B2CCB82BD009DE140 /* IAPRunTimeData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C18082382CCB82BD009DE140 /* IAPRunTimeData.cpp */; };
-		C19B68922CD1D30B00536ED4 /* purchaseBanner in Resources */ = {isa = PBXBuildFile; fileRef = C19B688A2CD1D30B00536ED4 /* purchaseBanner */; };
-		C19B68932CD1D30B00536ED4 /* purchaseBanner in Resources */ = {isa = PBXBuildFile; fileRef = C19B688A2CD1D30B00536ED4 /* purchaseBanner */; };
+		C19B68A42CD1E4C800536ED4 /* PBConfigData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68A22CD1E4C800536ED4 /* PBConfigData.cpp */; };
+		C19B68A52CD1E4C800536ED4 /* PBConfigData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68A22CD1E4C800536ED4 /* PBConfigData.cpp */; };
+		C19B68A82CD1E4DA00536ED4 /* PurchaseBannerCell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68A62CD1E4DA00536ED4 /* PurchaseBannerCell.cpp */; };
+		C19B68A92CD1E4DA00536ED4 /* PurchaseBannerCell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68A62CD1E4DA00536ED4 /* PurchaseBannerCell.cpp */; };
+		C19B68AC2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68AA2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp */; };
+		C19B68AD2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68AA2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp */; };
+		C19B68B12CD1E50600536ED4 /* PurchaseBannerView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68AF2CD1E50600536ED4 /* PurchaseBannerView.cpp */; };
+		C19B68B22CD1E50600536ED4 /* PurchaseBannerView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68AF2CD1E50600536ED4 /* PurchaseBannerView.cpp */; };
+		C19B68B52CD1E51300536ED4 /* RedreamLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68B32CD1E51300536ED4 /* RedreamLoader.cpp */; };
+		C19B68B62CD1E51300536ED4 /* RedreamLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C19B68B32CD1E51300536ED4 /* RedreamLoader.cpp */; };
 		D44C620C132DFF330009C878 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D44C620B132DFF330009C878 /* OpenAL.framework */; };
 		D44C620E132DFF430009C878 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D44C620D132DFF430009C878 /* AVFoundation.framework */; };
 		D44C6210132DFF4E0009C878 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D44C620F132DFF4E0009C878 /* AudioToolbox.framework */; };
@@ -207,7 +215,18 @@
 		C18082322CCB71D3009DE140 /* IAPTestCardViewDelegate.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IAPTestCardViewDelegate.hpp; sourceTree = "<group>"; };
 		C18082382CCB82BD009DE140 /* IAPRunTimeData.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IAPRunTimeData.cpp; sourceTree = "<group>"; };
 		C18082392CCB82BD009DE140 /* IAPRunTimeData.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IAPRunTimeData.hpp; sourceTree = "<group>"; };
-		C19B688A2CD1D30B00536ED4 /* purchaseBanner */ = {isa = PBXFileReference; lastKnownFileType = folder; path = purchaseBanner; sourceTree = "<group>"; };
+		C19B68A22CD1E4C800536ED4 /* PBConfigData.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PBConfigData.cpp; sourceTree = "<group>"; };
+		C19B68A32CD1E4C800536ED4 /* PBConfigData.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PBConfigData.hpp; sourceTree = "<group>"; };
+		C19B68A62CD1E4DA00536ED4 /* PurchaseBannerCell.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PurchaseBannerCell.cpp; sourceTree = "<group>"; };
+		C19B68A72CD1E4DA00536ED4 /* PurchaseBannerCell.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PurchaseBannerCell.hpp; sourceTree = "<group>"; };
+		C19B68AA2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PurchaseBannerCtrl.cpp; sourceTree = "<group>"; };
+		C19B68AB2CD1E4E800536ED4 /* PurchaseBannerCtrl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PurchaseBannerCtrl.hpp; sourceTree = "<group>"; };
+		C19B68AE2CD1E4F800536ED4 /* PurchaseBannerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PurchaseBannerDelegate.h; sourceTree = "<group>"; };
+		C19B68AF2CD1E50600536ED4 /* PurchaseBannerView.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PurchaseBannerView.cpp; sourceTree = "<group>"; };
+		C19B68B02CD1E50600536ED4 /* PurchaseBannerView.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PurchaseBannerView.hpp; sourceTree = "<group>"; };
+		C19B68B32CD1E51300536ED4 /* RedreamLoader.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RedreamLoader.cpp; sourceTree = "<group>"; };
+		C19B68B42CD1E51300536ED4 /* RedreamLoader.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RedreamLoader.hpp; sourceTree = "<group>"; };
+		C19B68B72CD2007B00536ED4 /* rapidcsv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rapidcsv.h; sourceTree = "<group>"; };
 		D44C620B132DFF330009C878 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; };
 		D44C620D132DFF430009C878 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
 		D44C620F132DFF4E0009C878 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
@@ -378,7 +397,7 @@
 				2F15C5E62CB8F65A0057855D /* IAPDelegate.hpp */,
 				2F15C5EA2CB8F65A0057855D /* IAPCtlShop.hpp */,
 				2F15C5DE2CB8F65A0057855D /* IAPCtlShop.cpp */,
-				C19B688A2CD1D30B00536ED4 /* purchaseBanner */,
+				C19B68A12CD1E46D00536ED4 /* purchaseBanner */,
 				C1CA495B2CD08C37002745E2 /* Test */,
 				2F15C5DF2CB8F65A0057855D /* Shop */,
 				2F15C5E72CB8F65A0057855D /* User */,
@@ -490,6 +509,25 @@
 			name = mac;
 			sourceTree = "<group>";
 		};
+		C19B68A12CD1E46D00536ED4 /* purchaseBanner */ = {
+			isa = PBXGroup;
+			children = (
+				C19B68B72CD2007B00536ED4 /* rapidcsv.h */,
+				C19B68A32CD1E4C800536ED4 /* PBConfigData.hpp */,
+				C19B68A22CD1E4C800536ED4 /* PBConfigData.cpp */,
+				C19B68A72CD1E4DA00536ED4 /* PurchaseBannerCell.hpp */,
+				C19B68A62CD1E4DA00536ED4 /* PurchaseBannerCell.cpp */,
+				C19B68AB2CD1E4E800536ED4 /* PurchaseBannerCtrl.hpp */,
+				C19B68AA2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp */,
+				C19B68AE2CD1E4F800536ED4 /* PurchaseBannerDelegate.h */,
+				C19B68B02CD1E50600536ED4 /* PurchaseBannerView.hpp */,
+				C19B68AF2CD1E50600536ED4 /* PurchaseBannerView.cpp */,
+				C19B68B42CD1E51300536ED4 /* RedreamLoader.hpp */,
+				C19B68B32CD1E51300536ED4 /* RedreamLoader.cpp */,
+			);
+			path = purchaseBanner;
+			sourceTree = "<group>";
+		};
 		C1CA495B2CD08C37002745E2 /* Test */ = {
 			isa = PBXGroup;
 			children = (
@@ -692,7 +730,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				18BC68102106EAB400288C27 /* Images.xcassets in Resources */,
-				C19B68922CD1D30B00536ED4 /* purchaseBanner in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -702,7 +739,6 @@
 			files = (
 				23F924B52ADCE4A500563AFD /* Icon.icns in Resources */,
 				23F924B42ADCE49D00563AFD /* Images.xcassets in Resources */,
-				C19B68932CD1D30B00536ED4 /* purchaseBanner in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -746,12 +782,17 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				C19B68A82CD1E4DA00536ED4 /* PurchaseBannerCell.cpp in Sources */,
 				2F15E2422CB52B0F00082BF8 /* IAPUtils.cpp in Sources */,
 				46880B8819C43A87006E1F66 /* AppDelegate.cpp in Sources */,
+				C19B68B12CD1E50600536ED4 /* PurchaseBannerView.cpp in Sources */,
+				C19B68A42CD1E4C800536ED4 /* PBConfigData.cpp in Sources */,
 				C17946172CC7502600779B63 /* IAPTestCard.cpp in Sources */,
 				E4D223422BD667D4006F1F8D /* AdUtilsVideo.cpp in Sources */,
 				2F15E2402CB52B0F00082BF8 /* IAPProductInfo.cpp in Sources */,
 				E4D223482BD667E8006F1F8D /* AdUtils.cpp in Sources */,
+				C19B68B52CD1E51300536ED4 /* RedreamLoader.cpp in Sources */,
+				C19B68AC2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp in Sources */,
 				E4E7A1222BD75C3F00107AED /* RPRedAudio.cpp in Sources */,
 				C17ACCBC2CB903BA0072A711 /* TestScene.cpp in Sources */,
 				E4D223402BD667D4006F1F8D /* AdUtilsInterstitial.cpp in Sources */,
@@ -765,7 +806,9 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				C19B68A92CD1E4DA00536ED4 /* PurchaseBannerCell.cpp in Sources */,
 				2F15C5F42CB8F65A0057855D /* IAPCtlShop.cpp in Sources */,
+				C19B68A52CD1E4C800536ED4 /* PBConfigData.cpp in Sources */,
 				E4D223412BD667D4006F1F8D /* AdUtilsInterstitial.cpp in Sources */,
 				C18082292CCB3338009DE140 /* IAPCardView.cpp in Sources */,
 				2F15C5F62CB8F65A0057855D /* IAPCtlShopUI.cpp in Sources */,
@@ -778,9 +821,12 @@
 				2F15C5FC2CB8F65A0057855D /* IAPConf.cpp in Sources */,
 				E4D223492BD667E8006F1F8D /* AdUtils.cpp in Sources */,
 				2F15C5F72CB8F65A0057855D /* IAPCtlShopItem.cpp in Sources */,
+				C19B68B22CD1E50600536ED4 /* PurchaseBannerView.cpp in Sources */,
 				C17946182CC7502600779B63 /* IAPTestCard.cpp in Sources */,
 				E4D223502BD66CDE006F1F8D /* RedWise.cpp in Sources */,
 				184505681E2E5BD700EF4807 /* main.cpp in Sources */,
+				C19B68AD2CD1E4E800536ED4 /* PurchaseBannerCtrl.cpp in Sources */,
+				C19B68B62CD1E51300536ED4 /* RedreamLoader.cpp in Sources */,
 				2F15E2432CB52B0F00082BF8 /* IAPUtils.cpp in Sources */,
 				2F15C5F82CB8F65A0057855D /* IAPUserData.cpp in Sources */,
 				C17ACCBD2CB903BA0072A711 /* TestScene.cpp in Sources */,