text: // SPDX-License-Identifier: BSD-3-Clause
  // Copyright Contributors to the OpenColorIO Project.
  
  #include <algorithm>
  
  #include "fileformats/xmlutils/XMLReaderUtils.h"
  
  OCIO_NAMESPACE_ENTER
  {
  
  bool IsNotSpace(char c)
  {
      return !IsSpace(c);
  }
  
  // Trim from start.
  static inline void LTrim(std::string & s)
  {
      s.erase(s.begin(),
          std::find_if(s.begin(), s.end(), IsNotSpace));
  }
  
  // Trim from end.
  static inline void RTrim(std::string & s)
  {
      s.erase(std::find_if(s.rbegin(), s.rend(), IsNotSpace).base(),
          s.end());
  }
  
  // Trim from both ends.
  void Trim(std::string & s)
  {
      LTrim(s);
      RTrim(s);
  }
  
  // Find the position of the first non-whitespace character.
  // Whitespaces are defined as spaces, tabs or newlines here.
  // Returns the position of the first non-whitespace character or
  //         std::string::npos if the string only has whitespaces or is empty.
  inline size_t FindFirstNonWhiteSpace(const char * str, size_t len)
  {
      const char *ptr = str;
      size_t pos = 0;
  
      for (;;)
      {
          if (!IsSpace(*ptr))
          {
              return pos;
          }
          if (pos == len)
          {
              return len;
          }
          ptr++; pos++;
      }
  }
  
  // Find the position of the last the non-whitespace character.
  // Whitespaces are defined as spaces, tabs or newlines here.
  // Returns the position of the last non-whitespace character or
  //         std::string::npos if the string only has whitespaces or is empty.
  inline size_t FindLastNonWhiteSpace(const char * str, size_t len)
  {
      size_t pos = len - 1;
      const char *ptr = str + pos;
  
      for (;;)
      {
          if (!IsSpace(*ptr))
          {
              return pos;
          }
          if (pos == 0)
          {
              return 0;
          }
  
          ptr--; pos--;
      }
  }
  
  // Get start (first non space character) and
  // end (just after the last non space character).
  void FindSubString(const char * str, size_t length,
                     size_t & start,
                     size_t & end)
  {
      if (!str || !*str)
      {
          start = 0;
          end = 0;
          return; // nothing to Trim.
      }
  
      start = FindFirstNonWhiteSpace(str, length);
      if (start == length)
      {
          // str only contains spaces, tabs or newlines.
          // Return an empty string.
          start = 0;
          end = 0;
          return;
      }
  
      // It is guaranteed here that end will not be 'npos'.
      // At worst, it will equal start.
      end = FindLastNonWhiteSpace(str, length);
  
      // end-start should give the number of valid characters.
      if (!IsSpace(str[end])) ++end;
  }
  
  }
  OCIO_NAMESPACE_EXIT
  
  ///////////////////////////////////////////////////////////////////////////////
  
  #ifdef OCIO_UNIT_TEST
  
  namespace OCIO = OCIO_NAMESPACE;
  
  #include "MathUtils.h"
  #include "UnitTest.h"
  
  #define EXPECTED_PRECISION 1e-11f
  
  OCIO_ADD_TEST(XMLReaderHelper, string_to_float)
  {
      float value = 0.0f;
      const char str[] = "12345";
      const size_t len = strlen(str);
  
      OCIO_CHECK_NO_THROW(OCIO::ParseNumber(str, 0, len, value));
      OCIO_CHECK_EQUAL(value, 12345.0f);
  }
  
  OCIO_ADD_TEST(XMLReaderHelper, string_to_float_failure)
  {
      float value = 0.0f;
      const char str[] = "ABDSCSGFDS";
      const size_t len = strlen(str);
  
      OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(str, 0, len, value),
                            OCIO::Exception,
                            "can not be parsed");
  
  
      const char str1[] = "10 ";
      const size_t len1 = strlen(str1);
  
      OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(str1, 0, len1, value),
                            OCIO::Exception,
                            "followed by unexpected characters");
  
      // 2 characters are parsed and this is the length required.
      OCIO_CHECK_NO_THROW(OCIO::ParseNumber(str1, 0, 2, value));
  
      const char str2[] = "12345";
      const size_t len2 = strlen(str2);
      // All characters are parsed and this is more than the required length.
      // The string to double function strtod does not stop at a given length,
      // but we detect that strtod did read too many characters.
      OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(str2, 0, len2 - 2, value),
                            OCIO::Exception,
                            "followed by unexpected characters");
  
  
      const char str3[] = "123XX";
      const size_t len3 = strlen(str3);
      // Strtod will stop after parsing 123 and this happens to be the
      // excact length that is required to be parsed.
      OCIO_CHECK_NO_THROW(OCIO::ParseNumber(str3, 0, len3 - 2, value));
  }
  
  OCIO_ADD_TEST(XMLReaderHelper, get_numbers)
  {
      const char str[] = "  1.0 , 2.0     3.0,4";
      const size_t len = strlen(str);
  
      std::vector<float> values;
      OCIO_CHECK_NO_THROW(values = OCIO::GetNumbers<float>(str, len));
      OCIO_REQUIRE_EQUAL(values.size(), 4);
      OCIO_CHECK_EQUAL(values[0], 1.0f);
      OCIO_CHECK_EQUAL(values[1], 2.0f);
      OCIO_CHECK_EQUAL(values[2], 3.0f);
      OCIO_CHECK_EQUAL(values[3], 4.0f);
  
      // Same test without a null terminated string:
      // Copy the string into a buffer that will not be null terminated.
      // Add a delimiter at the end of the buffer.
      char * buffer = new char[len+1];
      memcpy(buffer, str, len * sizeof(char));
      buffer[len] = '\n';
      OCIO_CHECK_NO_THROW(values = OCIO::GetNumbers<float>(buffer, len));
      OCIO_REQUIRE_EQUAL(values.size(), 4);
      OCIO_CHECK_EQUAL(values[0], 1.0f);
      OCIO_CHECK_EQUAL(values[1], 2.0f);
      OCIO_CHECK_EQUAL(values[2], 3.0f);
      OCIO_CHECK_EQUAL(values[3], 4.0f);
  
      // Testing with more values.
      const char str1[] = "inf, -infinity 1.0, -2.0 0x42 nan  , -nan 5.0";
      const size_t len1 = strlen(str1);
  
      OCIO_CHECK_NO_THROW(values = OCIO::GetNumbers<float>(str1, len1));
      OCIO_REQUIRE_EQUAL(values.size(), 8);
      OCIO_CHECK_ASSERT(std::isinf(values[0]));
      OCIO_CHECK_ASSERT(std::isinf(values[1]));
      OCIO_CHECK_EQUAL(values[2], 1.0f);
      OCIO_CHECK_EQUAL(values[3], -2.0f);
      OCIO_CHECK_EQUAL(values[4], 66.0f);         // i.e. 0x42
      OCIO_CHECK_ASSERT(OCIO::IsNan(values[5]));
      OCIO_CHECK_ASSERT(OCIO::IsNan(values[6]));
      OCIO_CHECK_EQUAL(values[7], 5.0f);
  
      // It is valid to start with delimiters.
      const char str2[] = ",  ,, , 0 2.0 \n \t 3.0 0.1e+1";
      const size_t len2 = strlen(str2);
  
      OCIO_CHECK_NO_THROW(values = OCIO::GetNumbers<float>(str2, len2));
      OCIO_REQUIRE_EQUAL(values.size(), 4);
      OCIO_CHECK_EQUAL(values[0], 0.0f);
      OCIO_CHECK_EQUAL(values[1], 2.0f);
      OCIO_CHECK_EQUAL(values[2], 3.0f);
      OCIO_CHECK_EQUAL(values[3], 1.0f);
  
      // Error: text is not a number.
      const char str3[] = "  0   error 2.0 3.0";
      const size_t len3 = strlen(str3);
  
      OCIO_CHECK_THROW_WHAT(values = OCIO::GetNumbers<float>(str3, len3),
                            OCIO::Exception,
                            "can not be parsed");
  
      // Error: number is not separated from text.
      const char str4[] = "0   1.0error 2.0 3.0";
      const size_t len4 = strlen(str4);
  
      OCIO_CHECK_THROW_WHAT(values = OCIO::GetNumbers<float>(str4, len4),
                            OCIO::Exception,
                            "followed by unexpected characters");
  }
  
  OCIO_ADD_TEST(XMLReaderHelper, trim)
  {
      const std::string original1("    some text    ");
      const std::string original2(" \n \r some text  \t \v \f ");
      {
          std::string value(original1);
          OCIO::Trim(value);
          OCIO_CHECK_ASSERT(0 == strcmp("some text", value.c_str()));
          value = original2;
          OCIO::Trim(value);
          OCIO_CHECK_ASSERT(0 == strcmp("some text", value.c_str()));
      }
  
      {
          std::string value(original1);
          OCIO::RTrim(value);
          OCIO_CHECK_ASSERT(0 == strcmp("    some text", value.c_str()));
          value = original2;
          OCIO::RTrim(value);
          OCIO_CHECK_ASSERT(0 == strcmp(" \n \r some text", value.c_str()));
      }
  
      {
          std::string value(original1);
          OCIO::LTrim(value);
          OCIO_CHECK_ASSERT(0 == strcmp("some text    ", value.c_str()));
          value = original2;
          OCIO::LTrim(value);
          OCIO_CHECK_ASSERT(0 == strcmp("some text  \t \v \f ", value.c_str()));
      }
  }
  
  OCIO_ADD_TEST(XMLReaderHelper, parse_number)
  {
      float data = 0.0f;
      {
          std::string buffer("1 0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 1, data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer(" 1 0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 2, data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("1.0 0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 3, data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("1.0000 0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 6, data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("1.0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 3, data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 1, data);)
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("10.0e-1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("0.1e+1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("-1 0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 2, data));
          OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer("-1.0 0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 4, data));
          OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer("-1.0000 0");
          const size_t end = OCIO::FindDelim(buffer.c_str(), buffer.size(), 0);
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, end, data));
          OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer("  -1.0");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer("   -1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data);)
              OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer(" -10.0e-1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer("-0.1e+1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -1.0f);
      }
      {
          std::string buffer("INF");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, 3, data));
          OCIO_CHECK_EQUAL(data, std::numeric_limits<float>::infinity());
      }
      {
          std::string buffer("INF 1.0 2.0");
          size_t pos = 0;
          const size_t size = buffer.size();
          size_t nextPos = OCIO::FindDelim(buffer.c_str(), size, pos);
          OCIO_CHECK_EQUAL(nextPos, 3);
          OCIO_CHECK_NO_THROW(
              OCIO::ParseNumber(buffer.c_str(),
                                pos, nextPos, data));
          OCIO_CHECK_EQUAL(data, std::numeric_limits<float>::infinity());
          pos = OCIO::FindNextTokenStart(buffer.c_str(), size, nextPos);
          OCIO_CHECK_EQUAL(pos, 4);
          nextPos = OCIO::FindDelim(buffer.c_str(), size, pos);
          OCIO_CHECK_EQUAL(nextPos, 7);
          OCIO_CHECK_NO_THROW(
              OCIO::ParseNumber(buffer.c_str(),
                                pos, nextPos, data));
          OCIO_CHECK_EQUAL(data, 1.0f);
          pos = OCIO::FindNextTokenStart(buffer.c_str(), size, nextPos);
          OCIO_CHECK_EQUAL(pos, 8);
          nextPos = OCIO::FindDelim(buffer.c_str(), size, pos);
          OCIO_CHECK_EQUAL(nextPos, 11);
          OCIO_CHECK_NO_THROW(
              OCIO::ParseNumber(buffer.c_str(),
                                pos, nextPos, data));
          OCIO_CHECK_EQUAL(data, 2.0f);
      }
      {
          std::string buffer("INFINITY");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, std::numeric_limits<float>::infinity());
      }
      {
          std::string buffer("-INF");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -std::numeric_limits<float>::infinity());
      }
      {
          std::string buffer("-INFINITY");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -std::numeric_limits<float>::infinity());
      }
      {
          std::string buffer("NAN");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_ASSERT(OCIO::IsNan(data));
      }
      {
          std::string buffer("-NAN");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_ASSERT(OCIO::IsNan(data));
      }
      {
          std::string buffer("0.001");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 0.001f);
      }
      {
          std::string buffer("-0.001");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -0.001f);
      }
      {
          std::string buffer(".001");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 0.001f);
      }
      {
          std::string buffer("-.001");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -0.001f);
      }
      {
          std::string buffer(".01e-1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 0.001f);
      }
      {
          std::string buffer("-.01e-1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, -0.001f);
      }
      {
          std::string buffer("-.01e-1,");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size() - 1, data));
          OCIO_CHECK_EQUAL(data, -0.001f);
      }
      {
          std::string buffer("-.01e-1\n");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size() - 1, data));
          OCIO_CHECK_EQUAL(data, -0.001f);
      }
      {
          std::string buffer("-.01e-1\t");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size() - 1, data));
          OCIO_CHECK_EQUAL(data, -0.001f);
      }
      {
          std::string buffer("10E-1");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
      {
          std::string buffer("0.10E01");
          OCIO_CHECK_NO_THROW(OCIO::ParseNumber(buffer.c_str(),
                                                0, buffer.size(), data));
          OCIO_CHECK_EQUAL(data, 1.0f);
      }
  
      {
          std::string buffer(" 123 ");
          OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(buffer.c_str(),
                                                  0, 3, data),
                                OCIO::Exception,
                                "followed by unexpected characters");
      }
      {
          std::string buffer("XY");
          OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(buffer.c_str(),
                                                  0, 2, data),
                                OCIO::Exception,
                                "can not be parsed");
      }
  }
  
  OCIO_ADD_TEST(XMLReaderHelper, find_sub_string)
  {
      {
          //                        012345678901234
          const std::string buffer("   new order   ");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 3);
          OCIO_CHECK_EQUAL(end, 12);
      }
      {
          //                        012345678901234
          const std::string buffer("new order   ");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 0);
          OCIO_CHECK_EQUAL(end, 9);
      }
      {
          //                        012345678901234
          const std::string buffer("   new order");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 3);
          OCIO_CHECK_EQUAL(end, 12);
      }
      {
          //                        012345678901234
          const std::string buffer("new order");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 0);
          OCIO_CHECK_EQUAL(end, 9);
      }
      {
          const std::string buffer("");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 0);
          OCIO_CHECK_EQUAL(end, 0);
      }
      {
          const std::string buffer("      ");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 0);
          OCIO_CHECK_EQUAL(end, 0);
      }
      {
          const std::string buffer("   \t123    ");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 4);
          OCIO_CHECK_EQUAL(end, 7);
      }
      {
          const std::string buffer("1   \t \n \r");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 0);
          OCIO_CHECK_EQUAL(end, 1);
      }
      {
          const std::string buffer("\t");
          size_t start = 0, end = 0;
          OCIO::FindSubString(buffer.c_str(),
                              buffer.size(),
                              start,
                              end);
          OCIO_CHECK_EQUAL(start, 0);
          OCIO_CHECK_EQUAL(end, 0);
      }
  }
  
  #endif
  