Add fxcrt::Split() templated function.

All the FX string functions implement Find(), Right(), etc. so
it is easy to build a generic split based on these.

-- Use it in CXFA_Node, CJX_Object, and RegenerateFormFile_Changed()

Change-Id: Ie280bb0a3b6ee7165d9b942245a59dc31ea9bfb2
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/57935
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fxcrt/fx_string.h b/core/fxcrt/fx_string.h
index ce033fe..8152fbc 100644
--- a/core/fxcrt/fx_string.h
+++ b/core/fxcrt/fx_string.h
@@ -9,6 +9,8 @@
 
 #include <stdint.h>
 
+#include <vector>
+
 #include "core/fxcrt/bytestring.h"
 #include "core/fxcrt/widestring.h"
 
@@ -23,4 +25,23 @@
 float StringToFloat(WideStringView wsStr);
 size_t FloatToString(float f, char* buf);
 
+namespace fxcrt {
+
+template <typename StrType>
+std::vector<StrType> Split(const StrType& that, typename StrType::CharType ch) {
+  std::vector<StrType> result;
+  StrType remaining(that);
+  while (1) {
+    Optional<size_t> index = remaining.Find(ch);
+    if (!index.has_value())
+      break;
+    result.push_back(remaining.Left(index.value()));
+    remaining = remaining.Right(remaining.GetLength() - index.value() - 1);
+  }
+  result.push_back(remaining);
+  return result;
+}
+
+}  // namespace fxcrt
+
 #endif  // CORE_FXCRT_FX_STRING_H_
diff --git a/core/fxcrt/fx_string_unittest.cpp b/core/fxcrt/fx_string_unittest.cpp
index 578bda5..ad813ab 100644
--- a/core/fxcrt/fx_string_unittest.cpp
+++ b/core/fxcrt/fx_string_unittest.cpp
@@ -76,3 +76,199 @@
   EXPECT_FLOAT_EQ(1.000000119f, StringToFloat("1.000000119"));
   EXPECT_FLOAT_EQ(1.999999881f, StringToFloat("1.999999881"));
 }
+
+TEST(fxstring, SplitByteString) {
+  std::vector<ByteString> result;
+  result = fxcrt::Split(ByteString(""), ',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ("", result[0]);
+
+  result = fxcrt::Split(ByteString("a"), ',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ("a", result[0]);
+
+  result = fxcrt::Split(ByteString(","), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("", result[0]);
+  EXPECT_EQ("", result[1]);
+
+  result = fxcrt::Split(ByteString("a,"), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("", result[1]);
+
+  result = fxcrt::Split(ByteString(",b"), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("", result[0]);
+  EXPECT_EQ("b", result[1]);
+
+  result = fxcrt::Split(ByteString("a,b"), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("b", result[1]);
+
+  result = fxcrt::Split(ByteString("a,b,"), ',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("b", result[1]);
+  EXPECT_EQ("", result[2]);
+
+  result = fxcrt::Split(ByteString("a,,"), ',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("", result[1]);
+  EXPECT_EQ("", result[2]);
+
+  result = fxcrt::Split(ByteString(",,a"), ',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ("", result[0]);
+  EXPECT_EQ("", result[1]);
+  EXPECT_EQ("a", result[2]);
+}
+
+TEST(fxstring, SplitByteStringView) {
+  std::vector<ByteStringView> result;
+  result = fxcrt::Split(ByteStringView(""), ',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ("", result[0]);
+
+  result = fxcrt::Split(ByteStringView("a"), ',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ("a", result[0]);
+
+  result = fxcrt::Split(ByteStringView(","), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("", result[0]);
+  EXPECT_EQ("", result[1]);
+
+  result = fxcrt::Split(ByteStringView("a,"), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("", result[1]);
+
+  result = fxcrt::Split(ByteStringView(",b"), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("", result[0]);
+  EXPECT_EQ("b", result[1]);
+
+  result = fxcrt::Split(ByteStringView("a,b"), ',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("b", result[1]);
+
+  result = fxcrt::Split(ByteStringView("a,b,"), ',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("b", result[1]);
+  EXPECT_EQ("", result[2]);
+
+  result = fxcrt::Split(ByteStringView("a,,"), ',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ("a", result[0]);
+  EXPECT_EQ("", result[1]);
+  EXPECT_EQ("", result[2]);
+
+  result = fxcrt::Split(ByteStringView(",,a"), ',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ("", result[0]);
+  EXPECT_EQ("", result[1]);
+  EXPECT_EQ("a", result[2]);
+}
+
+TEST(fxstring, SplitWideString) {
+  std::vector<WideString> result;
+  result = fxcrt::Split(WideString(L""), L',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(L"", result[0]);
+
+  result = fxcrt::Split(WideString(L"a"), L',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+
+  result = fxcrt::Split(WideString(L","), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"", result[0]);
+  EXPECT_EQ(L"", result[1]);
+
+  result = fxcrt::Split(WideString(L"a,"), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"", result[1]);
+
+  result = fxcrt::Split(WideString(L",b"), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"", result[0]);
+  EXPECT_EQ(L"b", result[1]);
+
+  result = fxcrt::Split(WideString(L"a,b"), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"b", result[1]);
+
+  result = fxcrt::Split(WideString(L"a,b,"), L',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"b", result[1]);
+  EXPECT_EQ(L"", result[2]);
+
+  result = fxcrt::Split(WideString(L"a,,"), L',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"", result[1]);
+  EXPECT_EQ(L"", result[2]);
+
+  result = fxcrt::Split(WideString(L",,a"), L',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ(L"", result[0]);
+  EXPECT_EQ(L"", result[1]);
+  EXPECT_EQ(L"a", result[2]);
+}
+
+TEST(fxstring, SplitWideStringView) {
+  std::vector<WideStringView> result;
+  result = fxcrt::Split(WideStringView(L""), L',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(L"", result[0]);
+
+  result = fxcrt::Split(WideStringView(L"a"), L',');
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+
+  result = fxcrt::Split(WideStringView(L","), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"", result[0]);
+  EXPECT_EQ(L"", result[1]);
+
+  result = fxcrt::Split(WideStringView(L"a,"), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"", result[1]);
+
+  result = fxcrt::Split(WideStringView(L",b"), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"", result[0]);
+  EXPECT_EQ(L"b", result[1]);
+
+  result = fxcrt::Split(WideStringView(L"a,b"), L',');
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"b", result[1]);
+
+  result = fxcrt::Split(WideStringView(L"a,b,"), L',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"b", result[1]);
+  EXPECT_EQ(L"", result[2]);
+
+  result = fxcrt::Split(WideStringView(L"a,,"), L',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ(L"a", result[0]);
+  EXPECT_EQ(L"", result[1]);
+  EXPECT_EQ(L"", result[2]);
+
+  result = fxcrt::Split(WideStringView(L",,a"), L',');
+  ASSERT_EQ(3u, result.size());
+  EXPECT_EQ(L"", result[0]);
+  EXPECT_EQ(L"", result[1]);
+  EXPECT_EQ(L"a", result[2]);
+}
diff --git a/fxjs/xfa/cjx_object.cpp b/fxjs/xfa/cjx_object.cpp
index 29d6d17..6b42802 100644
--- a/fxjs/xfa/cjx_object.cpp
+++ b/fxjs/xfa/cjx_object.cpp
@@ -557,26 +557,8 @@
 
         CXFA_Node* pBind = ToNode(GetXFAObject())->GetBindData();
         if (bSyncData && pBind) {
-          std::vector<WideString> wsSaveTextArray;
-          if (!wsContent.IsEmpty()) {
-            size_t iStart = 0;
-            size_t iLength = wsContent.GetLength();
-            auto iEnd = wsContent.Find(L'\n', iStart);
-            iEnd = !iEnd.has_value() ? iLength : iEnd;
-            while (iEnd.value() >= iStart) {
-              wsSaveTextArray.push_back(
-                  wsContent.Mid(iStart, iEnd.value() - iStart));
-              iStart = iEnd.value() + 1;
-              if (iStart >= iLength)
-                break;
-
-              iEnd = wsContent.Find(L'\n', iStart);
-              if (!iEnd.has_value()) {
-                wsSaveTextArray.push_back(
-                    wsContent.Mid(iStart, iLength - iStart));
-              }
-            }
-          }
+          std::vector<WideString> wsSaveTextArray =
+              fxcrt::Split(wsContent, L'\n');
           std::vector<CXFA_Node*> valueNodes =
               pBind->GetNodeListForType(XFA_Element::DataValue);
 
diff --git a/xfa/fxfa/parser/cxfa_node.cpp b/xfa/fxfa/parser/cxfa_node.cpp
index 1de4a9e..e66bd80 100644
--- a/xfa/fxfa/parser/cxfa_node.cpp
+++ b/xfa/fxfa/parser/cxfa_node.cpp
@@ -4258,27 +4258,12 @@
 }
 
 std::vector<WideString> CXFA_Node::GetSelectedItemsValue() {
-  std::vector<WideString> wsSelTextArray;
   WideString wsValue = GetRawValue();
-  if (IsChoiceListMultiSelect()) {
-    if (!wsValue.IsEmpty()) {
-      size_t iStart = 0;
-      size_t iLength = wsValue.GetLength();
-      auto iEnd = wsValue.Find(L'\n', iStart);
-      iEnd = (!iEnd.has_value()) ? iLength : iEnd;
-      while (iEnd >= iStart) {
-        wsSelTextArray.push_back(wsValue.Mid(iStart, iEnd.value() - iStart));
-        iStart = iEnd.value() + 1;
-        if (iStart >= iLength)
-          break;
-        iEnd = wsValue.Find(L'\n', iStart);
-        if (!iEnd.has_value())
-          wsSelTextArray.push_back(wsValue.Mid(iStart, iLength - iStart));
-      }
-    }
-  } else {
-    wsSelTextArray.push_back(wsValue);
-  }
+  if (IsChoiceListMultiSelect())
+    return fxcrt::Split(wsValue, L'\n');
+
+  std::vector<WideString> wsSelTextArray;
+  wsSelTextArray.push_back(wsValue);
   return wsSelTextArray;
 }
 
diff --git a/xfa/fxfa/parser/xfa_utils.cpp b/xfa/fxfa/parser/xfa_utils.cpp
index 3dddbd9..e9a01ba 100644
--- a/xfa/fxfa/parser/xfa_utils.cpp
+++ b/xfa/fxfa/parser/xfa_utils.cpp
@@ -212,25 +212,12 @@
         if (!rawValue || rawValue->IsEmpty())
           break;
 
-        std::vector<WideString> wsSelTextArray;
-        size_t iStart = 0;
-        auto iEnd = rawValue->Find(L'\n', iStart);
-        iEnd = !iEnd.has_value() ? rawValue->GetLength() : iEnd;
-        while (iEnd.has_value() && iEnd >= iStart) {
-          wsSelTextArray.push_back(
-              rawValue->Mid(iStart, iEnd.value() - iStart));
-          iStart = iEnd.value() + 1;
-          if (iStart >= rawValue->GetLength())
-            break;
-          iEnd = rawValue->Find(L'\n', iStart);
-        }
+        std::vector<WideString> wsSelTextArray =
+            fxcrt::Split(rawValue.value(), L'\n');
 
         CXFA_Node* pParentNode = pNode->GetParent();
-        ASSERT(pParentNode);
         CXFA_Node* pGrandparentNode = pParentNode->GetParent();
-        ASSERT(pGrandparentNode);
-        WideString bodyTagName;
-        bodyTagName =
+        WideString bodyTagName =
             pGrandparentNode->JSObject()->GetCData(XFA_Attribute::Name);
         if (bodyTagName.IsEmpty())
           bodyTagName = L"ListBox1";