selectionOptionFormatter_.addColumnLine(1, context.substituteMarkup(option.description()));
file.writeString(selectionOptionFormatter_.formatRow());
+ TextLineWrapper wrapper;
+ wrapper.settings().setLineLength(77);
+ wrapper.settings().setFirstLineIndent(4);
+ wrapper.settings().setIndent(8);
+ wrapper.settings().setContinuationChar('\\');
// TODO: What to do with selection variables?
// They are not printed as values for any option.
for (int i = 0; i < option.valueCount(); ++i)
{
std::string value(option.formatValue(i));
- // TODO: Wrapping
- file.writeLine(formatString(" %s", value.c_str()));
+ file.writeLine(wrapper.wrapToString(value));
}
}
TextLineWrapper wrapper;
if (column.bWrap_)
{
- wrapper.setLineLength(column.width());
+ wrapper.settings().setLineLength(column.width());
}
std::vector<std::string> lines(wrapper.wrapToVector(text));
column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
#ifndef GMX_ONLINEHELP_HELPTOPIC_H
#define GMX_ONLINEHELP_HELPTOPIC_H
+#include "../utility/common.h"
#include "../utility/stringutil.h"
#include "../utility/uniqueptr.h"
void HelpWriterContext::writeTextBlock(const std::string &text) const
{
TextLineWrapper wrapper;
- wrapper.setLineLength(78);
+ wrapper.settings().setLineLength(78);
const char *program = ProgramInfo::getInstance().programName().c_str();
std::string newText = replaceAll(text, "[PROGRAM]", program);
outputFile().writeLine(wrapper.wrapToString(substituteMarkup(newText)));
return replaceInternal(input, from, to, true);
}
+
/********************************************************************
- * TextLineWrapper::Impl
+ * TextLineWrapperSettings
*/
-/*! \internal \brief
- * Private implementation class for TextLineWrapper.
- *
- * \ingroup module_utility
- */
-class TextLineWrapper::Impl
+TextLineWrapperSettings::TextLineWrapperSettings()
+ : maxLength_(0), indent_(0), firstLineIndent_(-1),
+ bStripLeadingWhitespace_(true), continuationChar_('\0')
{
- public:
- //! Initialize default values for the wrapper.
- Impl() : maxLength_(0) {}
+}
- /*! \brief
- * Helper method to find the next wrapped line.
- *
- * \param[in] input Full input string.
- * \param[in,out] lineStartPtr
- * Index of first character for the line to wrap.
- * On exit, index of the first character of the next line.
- * \returns Next output line.
- */
- std::string wrapNextLine(const std::string &input,
- size_t *lineStartPtr) const;
- //! Maximum length of output lines, or <= 0 if no limit.
- int maxLength_;
-};
+/********************************************************************
+ * TextLineWrapper
+ */
-std::string
-TextLineWrapper::Impl::wrapNextLine(const std::string &input,
- size_t *lineStartPtr) const
+size_t
+TextLineWrapper::findNextLine(const char *input, size_t lineStart) const
{
- // Strip leading whitespace.
- size_t lineStart = input.find_first_not_of(' ', *lineStartPtr);
- if (lineStart == std::string::npos)
+ size_t inputLength = std::strlen(input);
+ bool bFirstLine = (lineStart == 0 || input[lineStart - 1] == '\n');
+ // Ignore leading whitespace if necessary.
+ if (!bFirstLine || settings_.bStripLeadingWhitespace_)
{
- *lineStartPtr = lineStart;
- return std::string();
+ lineStart += std::strspn(input + lineStart, " ");
+ if (lineStart >= inputLength)
+ {
+ return inputLength;
+ }
}
- size_t lineEnd = std::string::npos;
- size_t nextNewline
- = std::min(input.find('\n', lineStart), input.length());
- if (maxLength_ <= 0 || nextNewline <= lineStart + maxLength_)
- {
- lineEnd = nextNewline;
- }
- else
+ int indent = (bFirstLine ? settings_.firstLineIndent() : settings_.indent());
+ size_t lastAllowedBreakPoint
+ = (settings_.lineLength() > 0
+ ? std::min(lineStart + settings_.lineLength() - indent, inputLength)
+ : inputLength);
+ // Ignore trailing whitespace.
+ lastAllowedBreakPoint += std::strspn(input + lastAllowedBreakPoint, " ");
+ size_t lineEnd = lineStart;
+ do
{
- size_t bestSpace = input.rfind(' ', lineStart + maxLength_);
- if (bestSpace < lineStart || bestSpace == std::string::npos)
+ const char *nextBreakPtr = std::strpbrk(input + lineEnd, " \n");
+ size_t nextBreak
+ = (nextBreakPtr != NULL ? nextBreakPtr - input : inputLength);
+ if (nextBreak > lastAllowedBreakPoint && lineEnd > lineStart)
{
- bestSpace = input.find(' ', lineStart);
+ break;
}
- lineEnd = std::min(bestSpace, nextNewline);
+ lineEnd = nextBreak + 1;
}
+ while (lineEnd < lastAllowedBreakPoint && input[lineEnd - 1] != '\n');
+ return (lineEnd < inputLength ? lineEnd : inputLength);
+}
+
+size_t
+TextLineWrapper::findNextLine(const std::string &input, size_t lineStart) const
+{
+ return findNextLine(input.c_str(), lineStart);
+}
- if (lineEnd == std::string::npos)
+std::string
+TextLineWrapper::formatLine(const std::string &input,
+ size_t lineStart, size_t lineEnd) const
+{
+ size_t inputLength = input.length();
+ bool bFirstLine = (lineStart == 0 || input[lineStart - 1] == '\n');
+ // Strip leading whitespace if necessary.
+ if (!bFirstLine || settings_.bStripLeadingWhitespace_)
{
- lineEnd = input.length();
+ lineStart = input.find_first_not_of(' ', lineStart);
+ if (lineStart >= inputLength)
+ {
+ return std::string();
+ }
}
- *lineStartPtr = lineEnd + 1;
+ int indent = (bFirstLine ? settings_.firstLineIndent() : settings_.indent());
+ bool bContinuation = (lineEnd < inputLength && input[lineEnd - 1] != '\n');
// Strip trailing whitespace.
while (lineEnd > lineStart && std::isspace(input[lineEnd - 1]))
{
}
size_t lineLength = lineEnd - lineStart;
- return input.substr(lineStart, lineLength);
-}
-
-/********************************************************************
- * TextLineWrapper
- */
-
-TextLineWrapper::TextLineWrapper()
- : impl_(new Impl)
-{
-}
-
-TextLineWrapper::~TextLineWrapper()
-{
-}
-
-TextLineWrapper &TextLineWrapper::setLineLength(int length)
-{
- impl_->maxLength_ = length;
- return *this;
+ std::string result(indent, ' ');
+ result.append(input, lineStart, lineLength);
+ if (bContinuation && settings_.continuationChar_ != '\0')
+ {
+ result.append(1, ' ');
+ result.append(1, settings_.continuationChar_);
+ }
+ return result;
}
std::string
size_t length = input.length();
while (lineStart < length)
{
- result.append(impl_->wrapNextLine(input, &lineStart));
- if (lineStart < length
- || (lineStart == length && input[length - 1] == '\n'))
+ size_t nextLineStart = findNextLine(input, lineStart);
+ result.append(formatLine(input, lineStart, nextLineStart));
+ if (nextLineStart < length
+ || (nextLineStart == length && input[length - 1] == '\n'))
{
result.append("\n");
}
+ lineStart = nextLineStart;
}
return result;
}
{
std::vector<std::string> result;
size_t lineStart = 0;
- while (lineStart < input.length())
+ size_t length = input.length();
+ while (lineStart < length)
{
- result.push_back(impl_->wrapNextLine(input, &lineStart));
+ size_t nextLineStart = findNextLine(input, lineStart);
+ result.push_back(formatLine(input, lineStart, nextLineStart));
+ lineStart = nextLineStart;
}
return result;
}
#include <string>
#include <vector>
-#include "common.h"
-
namespace gmx
{
std::string replaceAllWords(const std::string &input,
const char *from, const char *to);
+class TextLineWrapper;
+
+/*! \brief
+ * Stores settings for line wrapping.
+ *
+ * Methods in this class do not throw.
+ *
+ * \see TextLineWrapper
+ *
+ * \inpublicapi
+ * \ingroup module_utility
+ */
+class TextLineWrapperSettings
+{
+ public:
+ /*! \brief
+ * Initializes default wrapper settings.
+ *
+ * Default settings are:
+ * - No maximum line width (only explicit line breaks).
+ * - No indentation.
+ * - No continuation characters.
+ * - Ignore whitespace after an explicit newline.
+ */
+ TextLineWrapperSettings();
+
+ /*! \brief
+ * Sets the maximum length for output lines.
+ *
+ * \param[in] length Maximum length for the lines after wrapping.
+ *
+ * If this method is not called, or is called with zero \p length, the
+ * wrapper has no maximum length (only wraps at explicit line breaks).
+ */
+ void setLineLength(int length) { maxLength_ = length; }
+ /*! \brief
+ * Sets the indentation for output lines.
+ *
+ * \param[in] indent Number of spaces to add for indentation.
+ *
+ * If this method is not called, the wrapper does not add indentation.
+ */
+ void setIndent(int indent) { indent_ = indent; }
+ /*! \brief
+ * Sets the indentation for first output line after a line break.
+ *
+ * \param[in] indent Number of spaces to add for indentation.
+ *
+ * If this method is not called, or called with \p indent equal to -1,
+ * the value set with setIndent() is used.
+ */
+ void setFirstLineIndent(int indent) { firstLineIndent_ = indent; }
+ /*! \brief
+ * Sets whether to remove spaces after an explicit newline.
+ *
+ * \param[in] bStrip If true, spaces after newline are ignored.
+ *
+ * If not removed, the space is added to the indentation set with
+ * setIndent().
+ * The default is to strip such whitespace.
+ */
+ void setStripLeadingWhitespace(bool bStrip)
+ {
+ bStripLeadingWhitespace_ = bStrip;
+ }
+ /*! \brief
+ * Sets a continuation marker for wrapped lines.
+ *
+ * \param[in] continuationChar Character to use to mark continuation
+ * lines.
+ *
+ * If set to non-zero character code, this character is added at the
+ * end of each line where a line break is added by TextLineWrapper
+ * (but not after lines produced by explicit line breaks).
+ * The default (\c '\0') is to not add continuation markers.
+ *
+ * Note that currently, the continuation char may cause the output line
+ * length to exceed the value set with setLineLength() by at most two
+ * characters.
+ */
+ void setContinuationChar(char continuationChar)
+ {
+ continuationChar_ = continuationChar;
+ }
+
+ //! Returns the maximum length set with setLineLength().
+ int lineLength() const { return maxLength_; }
+ //! Returns the indentation set with setIndent().
+ int indent() const { return indent_; }
+ /*! \brief
+ * Returns the indentation set with setFirstLineIndent().
+ *
+ * If setFirstLineIndent() has not been called or has been called with
+ * -1, indent() is returned.
+ */
+ int firstLineIndent() const
+ {
+ return (firstLineIndent_ >= 0 ? firstLineIndent_ : indent_);
+ }
+
+ private:
+ //! Maximum length of output lines, or <= 0 if no limit.
+ int maxLength_;
+ //! Number of spaces to indent each output line with.
+ int indent_;
+ /*! \brief
+ * Number of spaces to indent the first line after a newline.
+ *
+ * If -1, \a indent_ is used.
+ */
+ int firstLineIndent_;
+ //! Whether to ignore or preserve space after a newline.
+ bool bStripLeadingWhitespace_;
+ //! If not \c '\0', mark each wrapping point with this character.
+ char continuationChar_;
+
+ //! Needed to access the members.
+ friend class TextLineWrapper;
+};
+
/*! \brief
* Wraps lines to a predefined length.
*
* longer than a predefined length. Explicit newlines ('\\n') are preserved.
* Only space is considered a word separator. If a single word exceeds the
* maximum line length, it is still printed on a single line.
- * Extra whitespace is stripped from the start and end of produced lines.
- * If maximum line length is not set using setLineLength(), only wraps at
- * explicit newlines.
- *
- * Two output formats are possible: wrapToString() produces a single string
- * with embedded newlines, and wrapToVector() produces a vector of strings,
- * where each element is one line.
+ * Extra whitespace is stripped from the end of produced lines.
+ * Other options on the wrapping, such as the line length or indentation,
+ * can be changed using a TextLineWrapperSettings object.
+ *
+ * Two interfaces to do the wrapping are provided:
+ * -# High-level interface using either wrapToString() (produces a single
+ * string with embedded newlines) or wrapToVector() (produces a vector of
+ * strings with each line as one element).
+ * These methods operate on std::string and wrap the entire input string.
+ * -# Low-level interface using findNextLine() and formatLine().
+ * findNextLine() operates either on a C string or an std::string, and does
+ * not do any memory allocation (so it does not throw). It finds the next
+ * line to be wrapped, considering the wrapping settings.
+ * formatLine() does whitespace operations on the line found by
+ * findNextLine() and returns an std::string.
+ * These methods allow custom wrapping implementation to either avoid
+ * exceptions or to wrap only a part of the input string.
*
* Typical usage:
* \code
gmx::TextLineWrapper wrapper;
-wrapper.setLineLength(78);
+wrapper.settings().setLineLength(78);
printf("%s\n", wrapper.wrapToString(textToWrap).c_str());
* \endcode
*
- * Methods in this class may throw std::bad_alloc if out of memory.
- * Other exceptions are not thrown.
- *
* \inpublicapi
* \ingroup module_utility
*/
class TextLineWrapper
{
public:
- //! Constructs a new line wrapper with no initial wrapping length.
- TextLineWrapper();
- ~TextLineWrapper();
+ /*! \brief
+ * Constructs a new line wrapper with default settings.
+ *
+ * Does not throw.
+ */
+ TextLineWrapper()
+ {
+ }
+ /*! \brief
+ * Constructs a new line wrapper with given settings.
+ *
+ * \param[in] settings Wrapping settings.
+ *
+ * Does not throw.
+ */
+ explicit TextLineWrapper(const TextLineWrapperSettings &settings)
+ : settings_(settings)
+ {
+ }
/*! \brief
- * Sets the maximum length for output lines.
+ * Provides access to settings of this wrapper.
*
- * \param[in] length Maximum length for the lines after wrapping.
- * \returns *this
+ * \returns The settings object for this wrapper.
*
- * If this method is not called, the wrapper has no maximum length
- * (only wraps at explicit line breaks).
+ * The returned object can be used to modify settings for the wrapper.
+ * All subsequent calls to wrapToString() and wrapToVector() use the
+ * modified settings.
*
* Does not throw.
*/
- TextLineWrapper &setLineLength(int length);
+ TextLineWrapperSettings &settings() { return settings_; }
+
+ /*! \brief
+ * Finds the next line to be wrapped.
+ *
+ * \param[in] input String to wrap.
+ * \param[in] lineStart Index of first character of the line to find.
+ * \returns Index of first character of the next line.
+ *
+ * If this is the last line, returns the length of \p input.
+ * In determining the length of the returned line, this function
+ * considers the maximum line length, leaving space for indentation,
+ * and also whitespace stripping behavior.
+ * Thus, the line returned may be longer than the maximum line length
+ * if it has leading and/or trailing space.
+ * When wrapping a line on a space (not on an explicit line break),
+ * the returned index is always on a non-whitespace character after the
+ * space.
+ *
+ * To iterate over lines in a string, use the following code:
+ * \code
+gmx::TextLineWrapper wrapper;
+// <set desired wrapping settings>
+size_t lineStart = 0;
+size_t length = input.length();
+while (lineStart < length)
+{
+ size_t nextLineStart = wrapper.findNextLine(input, lineStart);
+ std::string line = wrapper.formatLine(input, lineStart, nextLineStart));
+ // <do something with the line>
+ lineStart = nextLineStart;
+}
+return result;
+ * \endcode
+ *
+ * Does not throw.
+ */
+ size_t findNextLine(const char *input, size_t lineStart) const;
+ //! \copydoc findNextLine(const char *, size_t) const
+ size_t findNextLine(const std::string &input, size_t lineStart) const;
+ /*! \brief
+ * Formats a single line for output according to wrapping settings.
+ *
+ * \param[in] input Input string.
+ * \param[in] lineStart Index of first character of the line to format.
+ * \param[in] lineEnd Index of first character of the next line.
+ * \returns The line with leading and/or trailing whitespace removed
+ * and indentation applied.
+ * \throws std::bad_alloc if out of memory.
+ *
+ * Intended to be used on the lines found by findNextLine().
+ * When used with the lines returned from findNextLine(), the returned
+ * line conforms to the wrapper settings.
+ * Trailing whitespace is always stripped (including any newlines,
+ * i.e., the return value does not contain a newline).
+ */
+ std::string formatLine(const std::string &input,
+ size_t lineStart, size_t lineEnd) const;
/*! \brief
* Formats a string, producing a single string with all the lines.
* \param[in] input String to wrap.
* \returns \p input with added newlines such that maximum line
* length is not exceeded.
+ * \throws std::bad_alloc if out of memory.
*
* Newlines in the input are preserved, including terminal newlines.
* Note that if the input does not contain a terminal newline, the
* \param[in] input String to wrap.
* \returns \p input split into lines such that maximum line length
* is not exceeded.
+ * \throws std::bad_alloc if out of memory.
*
* The strings in the returned vector do not contain newlines at the
* end.
std::vector<std::string> wrapToVector(const std::string &input) const;
private:
- class Impl;
-
- PrivateImplPointer<Impl> impl_;
+ TextLineWrapperSettings settings_;
};
} // namespace gmx
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedAt14/12"><![CDATA[
+ A quick brown \
+ fox jumps
+ over the lazy \
+ dog]]></String>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedWithNoLimit"><![CDATA[
+ A quick brown fox jumps
+ over the lazy dog]]></String>
+ <String Name="WrappedAt14/12"><![CDATA[
+ A quick brown
+ fox jumps
+ over the lazy
+ dog]]></String>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedWithNoLimit"><![CDATA[
+ A quick brown fox jumps
+ over the lazy dog]]></String>
+ <String Name="WrappedAt14"><![CDATA[
+ A quick brown
+ fox jumps
+ over the lazy
+ dog]]></String>
+</ReferenceData>
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
<ReferenceData>
- <String Name="WrappedAt14"><![CDATA[
+ <String Name="WrappedAt14StripLeading"><![CDATA[
A quick brown
fox jumps
over the lazy
+dog]]></String>
+ <String Name="WrappedAt14PreserveLeading"><![CDATA[
+ A quick brown
+fox jumps
+ over the lazy
dog]]></String>
</ReferenceData>
* Tests for TextLineWrapper
*/
+//! Simple test string for wrapping.
const char g_wrapText[] = "A quick brown fox jumps over the lazy dog";
+//! Test string for wrapping with embedded line breaks.
const char g_wrapText2[] = "A quick brown fox jumps\nover the lazy dog";
+//! Test string for wrapping with a long word.
const char g_wrapTextLongWord[]
= "A quick brown fox jumps awordthatoverflowsaline over the lazy dog";
+//! Test string for wrapping with extra whitespace.
const char g_wrapTextWhitespace[] = " A quick brown fox jumps \n over the lazy dog";
typedef gmx::test::StringTestBase TextLineWrapperTest;
{
gmx::TextLineWrapper wrapper;
- wrapper.setLineLength(10);
+ wrapper.settings().setLineLength(10);
checkText(wrapper.wrapToString(g_wrapText), "WrappedAt10");
std::vector<std::string> wrapped(wrapper.wrapToVector(g_wrapText));
checker().checkSequence(wrapped.begin(), wrapped.end(), "WrappedToVector");
- wrapper.setLineLength(13);
+ wrapper.settings().setLineLength(13);
checkText(wrapper.wrapToString(g_wrapText), "WrappedAt13");
- wrapper.setLineLength(14);
+ wrapper.settings().setLineLength(14);
checkText(wrapper.wrapToString(g_wrapText), "WrappedAt14");
checkText(wrapper.wrapToString(g_wrapTextLongWord), "WrappedWithLongWord");
}
gmx::TextLineWrapper wrapper;
checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
- wrapper.setLineLength(10);
+ wrapper.settings().setLineLength(10);
checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt10");
- wrapper.setLineLength(14);
+ wrapper.settings().setLineLength(14);
checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14");
}
+TEST_F(TextLineWrapperTest, HandlesIndent)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setIndent(2);
+
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
+ wrapper.settings().setLineLength(16);
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14");
+}
+
+TEST_F(TextLineWrapperTest, HandlesHangingIndent)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setFirstLineIndent(2);
+ wrapper.settings().setIndent(4);
+
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
+ wrapper.settings().setLineLength(16);
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14/12");
+}
+
+TEST_F(TextLineWrapperTest, HandlesContinuationCharacter)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setFirstLineIndent(2);
+ wrapper.settings().setIndent(4);
+ wrapper.settings().setContinuationChar('\\');
+
+ wrapper.settings().setLineLength(16);
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14/12");
+}
+
TEST_F(TextLineWrapperTest, WrapsCorrectlyWithExtraWhitespace)
{
gmx::TextLineWrapper wrapper;
- wrapper.setLineLength(14);
- checkText(wrapper.wrapToString(g_wrapTextWhitespace), "WrappedAt14");
+ wrapper.settings().setLineLength(14);
+ wrapper.settings().setStripLeadingWhitespace(true);
+ checkText(wrapper.wrapToString(g_wrapTextWhitespace),
+ "WrappedAt14StripLeading");
+ wrapper.settings().setStripLeadingWhitespace(false);
+ checkText(wrapper.wrapToString(g_wrapTextWhitespace),
+ "WrappedAt14PreserveLeading");
}
} // namespace