Add "replace all" functionality to format.h.
authorTeemu Murtola <teemu.murtola@gmail.com>
Thu, 3 May 2012 08:44:18 +0000 (11:44 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Thu, 3 May 2012 15:57:58 +0000 (18:57 +0300)
Use the new function in File::readToString() to make the Windows line
end handling more robust.

Related to #666.

Change-Id: I24fc8e0dfd8af7ac08c94f2e9392fd418fe463c8

src/gromacs/utility/file.cpp
src/gromacs/utility/format.cpp
src/gromacs/utility/format.h
src/gromacs/utility/tests/format.cpp

index ba507704f7ca6eac905c767337d594fe8157ee95..1dcca51ca4531a1f36a776a1263c65964bbb0d23 100644 (file)
@@ -170,10 +170,8 @@ std::string File::readToString(const char *filename)
 
     std::string result(&data[0], len);
     // The below is necessary on Windows to make newlines stay as '\n' on a
-    // roundtrip.  Perhaps would be better to only replace '\r\n' with '\n',
-    // but in practice this probably makes little difference.
-    std::string::iterator end = std::remove(result.begin(), result.end(), '\r');
-    result.erase(end, result.end());
+    // roundtrip.
+    result = replaceAll(result, "\r\n", "\n");
 
     return result;
 }
index 37b71a13e1c57b07cc936c7a28b14c9d02dc427a..7702d8c5c8728b2ea156c892ce260c3d66fc6ea9 100644 (file)
@@ -104,6 +104,55 @@ std::string concatenateStrings(const char *const *sarray, size_t count)
     return result;
 }
 
+namespace
+{
+
+std::string
+replaceInternal(const std::string &input, const char *from, const char *to,
+                bool bWholeWords)
+{
+    GMX_RELEASE_ASSERT(from != NULL && to != NULL,
+                       "Replacement strings must not be NULL");
+    size_t matchLength = std::strlen(from);
+    std::string result;
+    size_t inputPos = 0;
+    size_t matchPos = input.find(from);
+    while (matchPos < input.length())
+    {
+        size_t matchEnd = matchPos + matchLength;
+        if (bWholeWords)
+        {
+            if (!((matchPos == 0 || !std::isalnum(input[matchPos-1]))
+                  && (matchEnd == input.length() || !std::isalnum(input[matchEnd]))))
+            {
+                matchPos = input.find(from, matchPos + 1);
+                continue;
+            }
+
+        }
+        result.append(input, inputPos, matchPos - inputPos);
+        result.append(to);
+        inputPos = matchEnd;
+        matchPos = input.find(from, inputPos);
+    }
+    result.append(input, inputPos, matchPos - inputPos);
+    return result;
+}
+
+} // namespace
+
+std::string
+replaceAll(const std::string &input, const char *from, const char *to)
+{
+    return replaceInternal(input, from, to, false);
+}
+
+std::string
+replaceAllWords(const std::string &input, const char *from, const char *to)
+{
+    return replaceInternal(input, from, to, true);
+}
+
 /********************************************************************
  * TextLineWrapper::Impl
  */
index cf74b8e3d6f61309da517a7dd72790dbb0d542a9..5036b88db67b8bb7691e6932dd6b465de0975f38 100644 (file)
@@ -95,6 +95,46 @@ std::string concatenateStrings(const char * const (&sarray)[count])
     return concatenateStrings(sarray, count);
 }
 
+/*! \brief
+ * Replace all occurrences of a string with another string.
+ *
+ * \param[in] input  Input string.
+ * \param[in] from   String to find.
+ * \param[in] to     String to use to replace \p from.
+ * \returns   \p input with all occurrences of \p from replaced with \p to.
+ * \throws    std::bad_alloc if out of memory.
+ *
+ * The replacement is greedy and not recursive: starting from the beginning of
+ * \p input, each match of \p from is replaced with \p to, and the search for
+ * the next match begins after the end of the previous match.
+ *
+ * Compexity is O(N), where N is length of output.
+ *
+ * \see replaceAllWords()
+ *
+ * \inpublicapi
+ */
+std::string replaceAll(const std::string &input,
+                       const char *from, const char *to);
+/*! \brief
+ * Replace whole words with others.
+ *
+ * \param[in] input  Input string.
+ * \param[in] from   String to find.
+ * \param[in] to     String to use to replace \p from.
+ * \returns   \p input with all \p from words replaced with \p to.
+ * \throws    std::bad_alloc if out of memory.
+ *
+ * Works as replaceAll(), but a match is only considered if it is delimited by
+ * non-alphanumeric characters.
+ *
+ * \see replaceAll()
+ *
+ * \inpublicapi
+ */
+std::string replaceAllWords(const std::string &input,
+                            const char *from, const char *to);
+
 /*! \brief
  * Wraps lines to a predefined length.
  *
index 424d215e3b8056257f339ba574ed0589ed49d048..39ef01207359755c712824fbb3504dffb84e2b12 100644 (file)
@@ -86,6 +86,54 @@ TEST_F(ConcatenateStringsTest, HandlesDifferentStringEndings)
     checkText(gmx::concatenateStrings(strings), "CombinedStrings");
 }
 
+/********************************************************************
+ * Tests for replaceAll() and replaceAllWords()
+ */
+
+TEST(ReplaceAllTest, HandlesEmptyStrings)
+{
+    EXPECT_EQ("", gmx::replaceAll("", "aaa", "bbbb"));
+    EXPECT_EQ("", gmx::replaceAllWords("", "aaa", "bbbb"));
+}
+
+TEST(ReplaceAllTest, HandlesNoMatches)
+{
+    const std::string text("Text with no matches");
+    EXPECT_EQ(text, gmx::replaceAll(text, "aaa", "bbbb"));
+    EXPECT_EQ(text, gmx::replaceAllWords(text, "aaa", "bbbb"));
+}
+
+TEST(ReplaceAllTest, HandlesMatchesAtEnds)
+{
+    EXPECT_EQ("bbbbtext", gmx::replaceAll("aaatext", "aaa", "bbbb"));
+    EXPECT_EQ("textbbbb", gmx::replaceAll("textaaa", "aaa", "bbbb"));
+    EXPECT_EQ("bbbb text", gmx::replaceAllWords("aaa text", "aaa", "bbbb"));
+    EXPECT_EQ("text bbbb", gmx::replaceAllWords("text aaa", "aaa", "bbbb"));
+}
+
+TEST(ReplaceAllTest, HandlesMultipleMatches)
+{
+    const std::string text("Text aaa with multiple aaa matches");
+    EXPECT_EQ("Text bbbb with multiple bbbb matches",
+              gmx::replaceAll(text, "aaa", "bbbb"));
+    EXPECT_EQ("Text bbbb with multiple bbbb matches",
+              gmx::replaceAllWords(text, "aaa", "bbbb"));
+}
+
+TEST(ReplaceAllTest, HandlesWordBoundaries)
+{
+    const std::string text("Text aaax with one word aaa match");
+    EXPECT_EQ("Text aaax with one word bbbb match",
+              gmx::replaceAllWords(text, "aaa", "bbbb"));
+}
+
+TEST(ReplaceAllTest, HandlesPossibleRecursiveMatches)
+{
+    const std::string text("Text with recursive aaabbbbbb matches");
+    EXPECT_EQ("Text with recursive aaaaaabbb matches",
+              gmx::replaceAll(text, "aaabbb", "aaaaaa"));
+}
+
 /********************************************************************
  * Tests for TextLineWrapper
  */