// balxml_formatter_prettyimpl.h                                      -*-C++-*-
#ifndef INCLUDED_BALXML_FORMATTER_PRETTYIMPL
#define INCLUDED_BALXML_FORMATTER_PRETTYIMPL

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide pretty-printing implementation for `balxml_formatter`.
//
//@CLASSES:
//  balxml::Formatter_PrettyImplState: state of formatter state machine
//  balxml::Formatter_PrettyImplStateId: labels for formatter state
//  balxml::Formatter_PrettyImplUtil: actions of formatter state machine
//
//@DESCRIPTION: This private, subordinate component to `balxml_formatter`
// provides an in-core value semantic attribute class,
// `balxml::Formatter_PrettyImplState`, and a utility `struct`,
// `balxml::Formatter_PrettyImplUtil`, that implements XML pretty-printing
// operations using the state value type.  These two classes work in
// conjunction to implement a state machine for pretty-printing an XML document
// given a sequence of tokens to emit.  The class
// `balxml::Formatter_PrettyImplStateId` enumerates the set of labels for
// distinct states of `balxml::Formatter_PrettyImplState`, upon which most
// control-flow decisions of `balxml::Formatter_PrettyImplUtil` are based.

#include <balscm_version.h>

#include <balxml_encoderoptions.h>
#include <balxml_formatterwhitespacetype.h>
#include <balxml_typesprintutil.h>

#include <bdlma_localsequentialallocator.h>

#include <bdlsb_memoutstreambuf.h>

#include <bslma_bslallocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_ios.h>
#include <bsl_ostream.h>
#include <bsl_string_view.h>
#include <bsl_vector.h>

namespace BloombergLP {
namespace balxml {

                     // ==================================
                     // struct Formatter_PrettyImplStateId
                     // ==================================

/// This `struct` provides a namespace for enumerating the set of labels for
/// distinct states of `Formatter_PrettyImplState`.
struct Formatter_PrettyImplStateId {

    // TYPES
    enum Enum {
        /// This state indicates that the current write position of the
        /// formatter is at the start of the document.  The formatter is
        /// only allowed to add an XML header when in this state.
        e_AT_START,

        /// This state indicates that the current write position of the
        /// formatter is after some tokens have been emitted, such as an XML
        /// header and/or some comments, but before any XML tags have been
        /// emitted.  The formatter is not allowed to emit an XML header
        /// when in this state.
        e_AFTER_START_NO_TAG,

        /// This state indicates that the current write position of the
        /// formatter is immediately after the name of an opening tag, or
        /// otherwise immediately after the value of an attribute of an
        /// opening tag.  In this state, most token printing operations,
        /// other than adding attributes, need to emit a ">" character to
        /// close the currently open tag before emitting their content.  For
        /// example:
        /// ```
        /// 1| <someTag
        ///  `---------^
        ///
        ///  * Note that there is no '>' character yet
        /// ```
        /// or:
        /// ```
        /// 1| <someTag attr="value" otherAttr="42"
        ///  `-------------------------------------^
        /// ```
        e_IN_TAG,

        /// This state indicates that the current write position of the
        /// formatter is after a complete opening tag, before any data for
        /// the tag, and that there are tokens already emitted on the
        /// current line (i.e., that the current write position is not at
        /// the start of the line).  In this state, whether or not token
        /// printing operations need to emit a new line and indentation
        /// generally depends on the whitespace mode set for the
        /// currently-open tag.  Note that comments are not considered data.
        /// For example:
        /// ```
        /// 1| <someTag>
        ///  `----------^
        /// ```
        /// or:
        /// ```
        /// 1| <someTag> <!-- comment -->
        ///  `---------------------------^
        /// ```
        /// or:
        /// ```
        /// 1| <someTag attr="value">
        /// 2|   <!-- comment -->
        ///  `-------------------^
        /// ```
        e_FIRST_DATA_BETWEEN_TAGS,

        /// This state indicates that the current write position of the
        /// formatter is at column 0 (i.e., that it is at the start of a new
        /// line) and either 1) after a closing tag, or 2) after a complete
        /// opening tag and optional data.  In this state, most token
        /// printing operations need to emit indentation before their
        /// content.  For example:
        /// ```
        /// 1| <someTag>
        /// 2|
        ///  `^
        /// ```
        /// or:
        /// ```
        /// 1| <someTag>
        /// 2| </someTag>
        /// 3|
        ///  `^
        ///
        ///  * Note that the 'closeElement' operation, which is used to
        ///    print closing tags, e.g., "</example>", *always* writes a
        ///    newline character after the closing tag.
        /// ```
        /// or:
        /// ```
        /// 1| <someTag>
        /// 2|   some list data
        /// 3|   some more list data
        /// 4|
        ///  `^
        /// ```
        e_FIRST_DATA_AT_LINE_BETWEEN_TAGS,

        /// This state indicates that the current write position of the
        /// formatter is after one or more data tokens for the
        /// currently-open tag, and that there are tokens already emitted on
        /// the current line (i.e., that the current write position is not
        /// at the start of a new line).  In this state, data printing
        /// operations must put delimiting whitespace before their data.
        /// What whitespace they emit may depend on the whitespace mode of
        /// the currently-open tag.  For example:
        /// ```
        /// 1| <someTag> someData
        ///  `-------------------^
        /// ```
        /// or
        /// ```
        /// 1| <someTag>
        /// 2|   some list data
        /// 3|   some more list data
        ///  `----------------------^
        /// ```
        e_TRAILING_DATA_BETWEEN_TAGS,

        /// This state indicates that the current write position of the
        /// formatter is immediately after the top-level closing tag of the
        /// document.
        e_AT_END
    };
};

                      // ===============================
                      // class Formatter_PrettyImplState
                      // ===============================

/// This class provides an in-core, value-semantic attribute type that
/// maintains all of the state information needed to pretty-print an XML
/// document using the operations provided by `Formatter_PrettyImplUtil`.
class Formatter_PrettyImplState {

  public:
    // TYPES
    typedef bsl::allocator<char>        allocator_type;
    typedef Formatter_PrettyImplStateId Id;
    typedef FormatterWhitespaceType     WhitespaceType;

  private:
    // DATA

    // the canonical "state" in the state machine, upon which most
    // control-flow decisions are based when printing
    Id::Enum                          d_id;

    // number of indentations to perform when printing an element on a new
    // line
    int                               d_indentLevel;

    // number of spaces to print per level of indentation
    int                               d_spacesPerLevel;

    // the current column number
    int                               d_column;

    // the column number at which an element will be printed on the next
    // line and optionally indented depending on the requested whitespace
    // mode
    int                               d_wrapColumn;

    // a stack of names of currently nested elements with the whitespace
    // handling constraint for each element in the stack
    bsl::vector<WhitespaceType::Enum> d_elementNesting;

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(Formatter_PrettyImplState,
                                   bslma::UsesBslmaAllocator);

    // CREATORS

    /// Create a `Formatter_PrettyImplState` having an `id` attribute of
    /// `Id::e_AT_START`, `indentLevel`, `spacesPerLevel`, `column`, and
    /// `wrapColumn` attributes of 0, and an empty `elementNesting`
    /// attribute.  Optionally specify an `allocator` (e.g., the address of
    /// a `bslma::Allocator` object) to supply memory; otherwise, the
    /// default allocator is used.
    Formatter_PrettyImplState();
    explicit Formatter_PrettyImplState(const allocator_type& allocator);

    /// Create a `Formatter_PrettyImplState` having an `id` attribute of
    /// `Id::e_AT_START`, the specified `indentLevel`, the specified
    /// `spacesPerLevel`, and the specified `wrapColumn`, a `column`
    /// attribute of 0, and an empty `elementNesting` attribute.  Optionally
    /// specify an `allocator` (e.g., the address of a `bslma::Allocator`
    /// object) to supply memory; otherwise, the default allocator is used.
    Formatter_PrettyImplState(
                           int                   indentLevel,
                           int                   spacesPerLevel,
                           int                   wrapColumn,
                           const allocator_type& allocator = allocator_type());

    /// Create a `Formatter_PrettyImplState` having the specified `id`,
    /// `indentLevel`, `spacesPerLevel`, `column`, `wrapColumn`, and
    /// `elementNesting` attributes.  Optionally specify an `allocator`
    /// (e.g., the address of a `bslma::Allocator` object) to supply memory;
    /// otherwise, the default allocator is used.
    Formatter_PrettyImplState(
        Id::Enum                                 id,
        int                                      indentLevel,
        int                                      spacesPerLevel,
        int                                      column,
        int                                      wrapColumn,
        const bsl::vector<WhitespaceType::Enum>& elementNesting,
        const allocator_type&                    allocator = allocator_type());

    /// Create a `Formatter_PrettyImplState` object having the same value as
    /// the specified `original` object.  Optionally specify an `allocator`
    /// (e.g., the address of a `bslma::Allocator` object) to supply memory;
    /// otherwise, the default allocator is used.
    Formatter_PrettyImplState(
                const Formatter_PrettyImplState& original,
                const allocator_type&            allocator = allocator_type());

    // MANIPULATORS

    /// Return a reference providing modifiable access to the `column`
    /// attribute of this object.
    int& column();

    /// Return a reference providing modifiable access to the
    /// `elementNesting` attribute of this object.
    bsl::vector<WhitespaceType::Enum>& elementNesting();

    /// Return a reference providing modifiable access to the `id` attribute
    /// of this object.
    Id::Enum& id();

    /// Return a reference providing modifiable access to the `indentLevel`
    /// attribute of this object.
    int& indentLevel();

    /// Return a reference providing modifiable access to the
    /// `spacesPerLevel` attribute of this object.
    int& spacesPerLevel();

    /// Return a reference providing modifiable access to the `wrapColumn`
    /// attribute of this object.
    int& wrapColumn();

    // ACCESSORS

    /// Return a reference providing non-modifiable access to the `column`
    /// attribute of this object.
    const int& column() const;

    /// Return a reference providing non-modifiable access to the
    /// `elementNesting` attribute of this object.
    const bsl::vector<WhitespaceType::Enum>& elementNesting() const ;

    /// Return the allocator associated with this object.
    allocator_type get_allocator() const;

    /// Return a reference providing non-modifiable access to the `id`
    /// attribute of this object.
    const Id::Enum& id() const;

    /// Return a reference providing non-modifiable access to the
    /// `indentLevel` attribute of this object.
    const int& indentLevel() const;

    /// Return a reference providing non-modifiable access to the
    /// `spacesPerLevel` attribute of this object.
    const int& spacesPerLevel() const;

    /// Return a reference providing non-modifiable access to the
    /// `wrapColumn` attribute of this object.
    const int& wrapColumn() const;
};

                       // ==============================
                       // class Formatter_PrettyImplUtil
                       // ==============================

/// This utility `struct` provides a namespace for a suite of operations
/// used to pretty-print XML documents given a sequence of tokens to emit.
/// Together with `Formatter_PrettyImplState`, this `struct` provides an
/// implementation of a state machine for such pretty-printing.
struct Formatter_PrettyImplUtil {

    // TYPES
    typedef Formatter_PrettyImplState   State;
    typedef Formatter_PrettyImplStateId StateId;
    typedef FormatterWhitespaceType     WhitespaceType;

  private:
    // PRIVATE TYPES
    enum {
        k_VALUE_STRING_BUFFER_SIZE = 256,

        k_INFINITE_WRAP_COLUMN = 0
    };

    typedef bdlma::LocalSequentialAllocator<k_VALUE_STRING_BUFFER_SIZE>
        BufferedAllocator;

    // PRIVATE CLASS METHODS

    /// Add an attribute of the specified `name` and `value` to the
    /// currently open element in the specified `stream`, with formatting
    /// depending on the specified `state`, and update the `state`
    /// accordingly.  Precede this name="value" pair with a single space.
    /// Wrap line (write the attribute on next line with proper
    /// indentation), if the length of name="value" is too long.  `value` is
    /// truncated at any invalid UTF-8 byte-sequence or any control
    /// character.  The list of invalid control characters includes
    /// characters in the range `[0x00, 0x20)` and `0x7F` (DEL) but does not
    /// include `0x9`, `0xA`, and `0x0D`.  The five special characters:
    /// apostrophe, double quote, ampersand, less than, and greater than are
    /// escaped in the output XML.  The behavior is undefined unless the
    /// last manipulator was `openElement` or `addAttribute`.
    static void addAttributeImpl(bsl::ostream&            stream,
                                 State                   *state,
                                 const bsl::string_view&  name,
                                 const bsl::string_view&  value);

    /// Write the specified `openMarker`, `comment`, and then `closeMarker`
    /// into the specified `stream`, with formatting depending on the
    /// specified `state`, and update the `state` accordingly.  If an
    /// element-opening tag is not completed with a `>`, `addCommentImpl`
    /// will add `>`.
    static void addCommentImpl(bsl::ostream&            stream,
                               State                   *state,
                               const bsl::string_view&  comment,
                               const bsl::string_view&  openMarker,
                               const bsl::string_view&  closeMarker);

    /// Write the specified `openMarker`, `comment`, and then `closeMarker`
    /// into the specified `stream` on their own line, with formatting
    /// depending on the specified `state`, and update the `state`
    /// accordingly.  If an element-opening tag is not completed with a `>`,
    /// `addCommentImpl` will add `>`.
    static void addCommentOnNewLineImpl(bsl::ostream&            stream,
                                        State                   *state,
                                        const bsl::string_view&  comment,
                                        const bsl::string_view&  openMarker,
                                        const bsl::string_view&  closeMarker);

    static void addDataImpl(bsl::ostream&            stream,
                            State                   *state,
                            const bsl::string_view&  value);

    /// Add the specified `value` as the data content to the specified
    /// `stream`, with formatting depending on the specified `state`, and
    /// update `state` accordingly.  `addListData` prefixes the `value` with
    /// a space(`0x20`) unless the data being added is the first data on a
    /// line.  In the case of `addData`, perform no line-wrapping or
    /// indentation as if the whitespace constraint were always
    /// `BAEXML_PRESERVE_WHITESPACE` in `openElement`, with the only
    /// exception that an initial newline and an initial indent is added
    /// when `openElement` specifies `BAEXML_NEWLINE_INDENT` option.  In the
    /// case of `addListData`, when adding the data makes the line too long,
    /// perform line-wrapping and indentation as determined by the
    /// whitespace constraint used when the current element is opened with
    /// `openElement`.  `value` is truncated at any invalid UTF-8
    /// byte-sequence or any control character.  The list of invalid control
    /// characters includes characters in the range `[0x00, 0x20)` and
    /// `0x7F` (DEL) but does not include `0x9`, `0xA`, and `0x0D`.  The
    /// five special characters: apostrophe, double quote, ampersand, less
    /// than, and greater than are escaped in the output XML.  The behavior
    /// is undefined if the call is made when there are no opened elements.
    static void addListDataImpl(bsl::ostream&            stream,
                                State                   *state,
                                const bsl::string_view&  value);

  public:
    // CLASS METHODS

    /// Add an attribute of the specified `name` and specified `value` to
    /// the currently open element in the specified `stream`, with
    /// formatting depending on the specified `state`, and update the
    /// `state` accordingly.  Return the `stream`.  `value` can be of the
    /// following types: `char`, `short`, `int`, `bsls::Types::Int64`,
    /// `float`, `double`, `bsl::string`, `bdlt::Datetime`, `bdlt::Date`,
    /// and `bdlt::Time`.  Precede this name="value" pair with a single
    /// space.  Wrap line (write the attribute on next line with proper
    /// indentation), if the length of name="value" is too long.  Optionally
    /// specify `formattingMode` and `encoderOptions` to control the
    /// formatting of `value`.  If `value` is of type `bsl::string`, it is
    /// truncated at any invalid UTF-8 byte-sequence or any control
    /// character.  The list of invalid control characters includes
    /// characters in the range `[0x00, 0x20)` and `0x7F` (DEL) but does not
    /// include `0x9`, `0xA`, and `0x0D`.  The five special characters:
    /// apostrophe, double quote, ampersand, less than, and greater than are
    /// escaped in the output XML.  If `value` is of type `char`, it is cast
    /// to a signed byte value with a range `[ -128 ..  127 ]`.  The
    /// behavior is undefined unless the last manipulator was `openElement`
    /// or `addAttribute`.
    template <class VALUE_TYPE>
    static bsl::ostream& addAttribute(
                   bsl::ostream&            stream,
                   State                   *state,
                   const bsl::string_view&  name,
                   const VALUE_TYPE&        value,
                   int                      formattingMode = 0,
                   const EncoderOptions&    encoderOptions = EncoderOptions());

    /// Insert one or two newline characters into the specified `stream`
    /// stream such that a blank line results, depending on the specified
    /// `state`, and update the `state` accordingly.  Return the `stream`.
    /// If the last output was a newline, then only one newline is added,
    /// otherwise two newlines are added.  If following a call to
    /// `openElement`, or `addAttribute`, add a closing `>` to the opened
    /// tag.
    static bsl::ostream& addBlankLine(bsl::ostream& stream, State *state);

    /// Write the specified `comment` into the specified `stream`, with
    /// formatting depending on the specified `state`, and update the
    /// `state` accordingly.  Return the `stream`.  The optionally specified
    /// `forceNewline`, if true, forces to start a new line solely for the
    /// comment if it's not on a new line already.  Otherwise, comments
    /// continue on current line.  If an element-opening tag is not
    /// completed with a `>`, `addComment` will add `>`.
    ///
    /// @DEPRECATED: Use `addValidComment` instead.
    static bsl::ostream& addComment(
                                 bsl::ostream&            stream,
                                 State                   *state,
                                 const bsl::string_view&  comment,
                                 bool                     forceNewline = true);

    /// Add the specified `value` as the data content to the specified
    /// `stream`, with formatting depending on the specified `state`, and
    /// update `state` accordingly.  Return the `stream`.  Return the
    /// `stream`.  `value` can be of the following types: `char`, `short`,
    /// `int`, `bsls::Types::Int64`, `float`, `double`, `bsl::string`,
    /// `bdlt::Datetime`, `bdlt::Date`, and `bdlt::Time`.  Perform no
    /// line-wrapping or indentation as if the whitespace constraint were
    /// always `BAEXML_PRESERVE_WHITESPACE` in `openElement`, with the only
    /// exception that an initial newline and an initial indent is added
    /// when `openElement` specifies `BAEXML_NEWLINE_INDENT` option.  If
    /// `value` is of type `bsl::string`, it is truncated at any invalid
    /// UTF-8 byte-sequence or any control character.  The list of invalid
    /// control characters includes characters in the range `[0x00, 0x20)`
    /// and `0x7F` (DEL) but does not include `0x9`, `0xA`, and `0x0D`.  The
    /// five special characters: apostrophe, double quote, ampersand, less
    /// than, and greater than are escaped in the output XML.  If `value` is
    /// of type `char`, it is cast to a signed byte value with a range of '[
    /// -128 ..  127 ]`.  Optionally specify the `formattingMode' and
    /// `encoderOptions` to specify the format used to encode `value`.  The
    /// behavior is undefined if the call is made when there are no opened
    /// elements.
    template <class VALUE_TYPE>
    static bsl::ostream& addData(
                     bsl::ostream&          stream,
                     State                 *state,
                     const VALUE_TYPE&      value,
                     int                    formattingMode = 0,
                     const EncoderOptions&  encoderOptions = EncoderOptions());

    /// Add element of the specified `name` and the specified `value` as the
    /// data content to the specified `stream`, with formatting depending on
    /// the specified `state` and the optionally specified `encoderOptions`,
    /// and update `state` accordingly.  Return the `stream`.  This has the
    /// same effect as calling the following sequence: 'openElement(name);
    /// addData(value), closeElement(name);'.  Optionally specify the
    /// `formattingMode`.
    template <class TYPE>
    static bsl::ostream& addElementAndData(
                   bsl::ostream&            stream,
                   State                   *state,
                   const bsl::string_view&  name,
                   const TYPE&              value,
                   int                      formattingMode = 0,
                   const EncoderOptions&    encoderOptions = EncoderOptions());

    /// Add XML header with optionally specified `encoding` to the specified
    /// `stream`, with formatting depending on the specified `state`, and
    /// update `state` accordingly.  Return the `stream`.  Version is always
    /// "1.0".  The behavior is undefined unless `addHeader` is the first
    /// manipulator (with the exception of `rawOutputStream`) after
    /// construction or `reset`.
    static bsl::ostream& addHeader(bsl::ostream&            stream,
                                   State                   *state,
                                   const bsl::string_view&  encoding);

    /// Add the specified `value` as the data content to the specified
    /// `stream`, with formatting depending on the specified `state`, and
    /// update `state` accordingly.  Return the `stream`.  `value` can be of
    /// the following types: `char`, `short`, `int`, `bsls::Types::Int64`,
    /// `float`, `double`, `bsl::string`, `bdlt::Datetime`, `bdlt::Date`,
    /// and `bdlt::Time`.  Prefix the `value` with a space(`0x20`) unless
    /// the data being added is the first data on a line.  When adding the
    /// data makes the line too long, perform line-wrapping and indentation
    /// as determined by the whitespace constraint used when the current
    /// element is opened with `openElement`.  If `value` is of type
    /// `bsl::string`, it is truncated at any invalid UTF-8 byte-sequence or
    /// any control character.  The list of invalid control characters
    /// includes characters in the range `[0x00, 0x20)` and `0x7F` (DEL) but
    /// does not include `0x9`, `0xA`, and `0x0D`.  The five special
    /// characters: apostrophe, double quote, ampersand, less than, and
    /// greater than are escaped in the output XML.  If `value` is of type
    /// `char`, it is cast to a signed byte value with a range of '[ -128 ..
    /// 127 ]`.  Optionally specify the `formattingMode' and
    /// `encoderOptions` to specify the format used to encode `value`.  The
    /// behavior is undefined if the call is made when there are no opened
    /// elements.
    template <class VALUE_TYPE>
    static bsl::ostream& addListData(
                     bsl::ostream&          stream,
                     State                 *state,
                     const VALUE_TYPE&      value,
                     int                    formattingMode = 0,
                     const EncoderOptions&  encoderOptions = EncoderOptions());

    /// Insert a literal newline into the XML output of the specified
    /// `stream`, with formatting depending on the specified `state`, and
    /// update `state` accordingly.  Return the `stream`.  If following a
    /// call to `openElement`, or `addAttribute`, add a closing `>` to the
    /// opened tag.
    static bsl::ostream& addNewline(bsl::ostream& stream, State *state);

    /// Write the specified `comment` into the specified `stream`, with
    /// formatting depending on the specified `state`, and update the
    /// `state` accordingly.  If the optionally specified `forceNewline` is
    /// `true` then a new line is inserted for comments not already on a new
    /// line.  Also optionally specify an `omitEnclosingWhitespace` that
    /// specifies if a space character should be omitted before and after
    /// `comment`.  If `omitEnclosingWhitespace` is not specified then a
    /// space character is inserted before and after `comment`.  Return 0 on
    /// success, and non-zero value otherwise.  Note that a non-zero return
    /// value is returned if either `comment` contains `--` or if
    /// `omitEnclosingWhitespace` is `true` and `comment` ends with `-`.
    /// Also note that if an element-opening tag is not completed with a
    /// `>`, `addValidComment` will add `>`.
    static int addValidComment(
                     bsl::ostream&            stream,
                     State                   *state,
                     const bsl::string_view&  comment,
                     bool                     forceNewline            = true,
                     bool                     omitEnclosingWhitespace = false);

    /// Decrement the indent level and add the closing tag for the element
    /// of the specified `name` to the specified `stream`, with formatting
    /// depending on the specified `state`, and update `state` accordingly.
    /// Return the `stream`.  If the element does not have content, write
    /// `/>` and a newline into stream.  Otherwise, write `</name>` and a
    /// newline.  If this `</name>` does not share the same line with data,
    /// or it follows another element's closing tag, indent properly before
    /// writing `</name>` and the newline.  If `name` is root element, flush
    /// the output stream.  The behavior is undefined if `name` is not the
    /// most recently opened element that's yet to be closed.
    static bsl::ostream& closeElement(bsl::ostream&            stream,
                                      State                   *state,
                                      const bsl::string_view&  name);

    /// Insert the closing `>` if there is an incomplete tag, and flush the
    /// specified output `stream`, with formatting depending on the
    /// specified `state`, and update `state` accordingly.  Return the
    /// `stream`.
    static bsl::ostream& flush(bsl::ostream& stream, State *state);

    /// Open an element of the specified `name` at current indent level with
    /// the optionally specified whitespace constraint `whitespaceMode` for
    /// its textual data to the specified `stream`, with formatting
    /// depending on the specified `state`, and update `state` accordingly,
    /// incrementing the indent level.  Return the `stream`.
    /// `whitespaceMode` constrains how textual data is written with
    /// `addListData` for the current element, but not its nested elements.
    /// The behavior is undefined if `openElement` is called after the root
    /// element is closed and there is no subsequent call to `reset`.
    static bsl::ostream&
    openElement(bsl::ostream&            stream,
                State                   *state,
                const bsl::string_view&  name,
                WhitespaceType::Enum     whitespaceMode =
                    WhitespaceType::e_PRESERVE_WHITESPACE);

    /// Reset the specified formatter `state` such that it can be used to
    /// format a new XML document as if the formatter were just constructed
    static void reset(State *state);
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                      // -------------------------------
                      // class Formatter_PrettyImplState
                      // -------------------------------

// CREATORS
inline
Formatter_PrettyImplState::Formatter_PrettyImplState()
: d_id(Id::e_AT_START)
, d_indentLevel(0)
, d_spacesPerLevel(0)
, d_column(0)
, d_wrapColumn(0)
, d_elementNesting()
{
}

inline
Formatter_PrettyImplState::Formatter_PrettyImplState(
                                               const allocator_type& allocator)
: d_id(Id::e_AT_START)
, d_indentLevel(0)
, d_spacesPerLevel(0)
, d_column(0)
, d_wrapColumn(0)
, d_elementNesting(allocator)
{
}

inline
Formatter_PrettyImplState::Formatter_PrettyImplState(
                                    const Formatter_PrettyImplState& original,
                                    const allocator_type&            allocator)
: d_id(original.d_id)
, d_indentLevel(original.d_indentLevel)
, d_spacesPerLevel(original.d_spacesPerLevel)
, d_column(original.d_column)
, d_wrapColumn(original.d_wrapColumn)
, d_elementNesting(original.d_elementNesting, allocator)
{
}

inline
Formatter_PrettyImplState::Formatter_PrettyImplState(
                                          int                   indentLevel,
                                          int                   spacesPerLevel,
                                          int                   wrapColumn,
                                          const allocator_type& allocator)
: d_id(Id::e_AT_START)
, d_indentLevel(indentLevel)
, d_spacesPerLevel(spacesPerLevel)
, d_column()
, d_wrapColumn(wrapColumn)
, d_elementNesting(allocator)
{
}

inline
Formatter_PrettyImplState::Formatter_PrettyImplState(
                       Id::Enum                                 id,
                       int                                      indentLevel,
                       int                                      spacesPerLevel,
                       int                                      column,
                       int                                      wrapColumn,
                       const bsl::vector<WhitespaceType::Enum>& elementNesting,
                       const allocator_type&                    allocator)
: d_id(id)
, d_indentLevel(indentLevel)
, d_spacesPerLevel(spacesPerLevel)
, d_column(column)
, d_wrapColumn(wrapColumn)
, d_elementNesting(elementNesting, allocator)
{
}

// MANIPULATORS
inline
int& Formatter_PrettyImplState::column()
{
    return d_column;
}

inline
bsl::vector<FormatterWhitespaceType::Enum>&
Formatter_PrettyImplState::elementNesting()
{
    return d_elementNesting;
}

inline
Formatter_PrettyImplStateId::Enum& Formatter_PrettyImplState::id()
{
    return d_id;
}

inline
int& Formatter_PrettyImplState::indentLevel()
{
    return d_indentLevel;
}

inline
int& Formatter_PrettyImplState::spacesPerLevel()
{
    return d_spacesPerLevel;
}

inline
int& Formatter_PrettyImplState::wrapColumn()
{
    return d_wrapColumn;
}

// ACCESSORS
inline
const int& Formatter_PrettyImplState::column() const
{
    return d_column;
}

inline
const bsl::vector<FormatterWhitespaceType::Enum>&
Formatter_PrettyImplState::elementNesting() const
{
    return d_elementNesting;
}

inline
Formatter_PrettyImplState::allocator_type
Formatter_PrettyImplState::get_allocator() const
{
    return d_elementNesting.get_allocator();
}

inline
const Formatter_PrettyImplStateId::Enum& Formatter_PrettyImplState::id() const
{
    return d_id;
}

inline
const int& Formatter_PrettyImplState::indentLevel() const
{
    return d_indentLevel;
}

inline
const int& Formatter_PrettyImplState::spacesPerLevel() const
{
    return d_spacesPerLevel;
}

inline
const int& Formatter_PrettyImplState::wrapColumn() const
{
    return d_wrapColumn;
}

                       // ------------------------------
                       // class Formatter_PrettyImplUtil
                       // ------------------------------

// CLASS METHODS
template <class VALUE_TYPE>
bsl::ostream& Formatter_PrettyImplUtil::addAttribute(
                                       bsl::ostream&            stream,
                                       State                   *state,
                                       const bsl::string_view&  name,
                                       const VALUE_TYPE&        value,
                                       int                      formattingMode,
                                       const EncoderOptions&    encoderOptions)
{
    BufferedAllocator      allocator;
    bdlsb::MemOutStreamBuf sb(&allocator);
    bsl::ostream           ss(&sb);

    TypesPrintUtil::print(ss, value, formattingMode, &encoderOptions);
    if (!ss.good()) {
        stream.setstate(bsl::ios_base::failbit);
        return stream;                                                // RETURN
    }

    const bsl::string_view valueString(sb.data(), sb.length());

    addAttributeImpl(stream, state, name, valueString);

    return stream;
}


template <class VALUE_TYPE>
bsl::ostream& Formatter_PrettyImplUtil::addData(
                                         bsl::ostream&          stream,
                                         State                 *state,
                                         const VALUE_TYPE&      valueData,
                                         int                    formattingMode,
                                         const EncoderOptions&  encoderOptions)
{
    BufferedAllocator      allocator;
    bdlsb::MemOutStreamBuf sb(&allocator);
    bsl::ostream           ss(&sb);

    TypesPrintUtil::print(ss, valueData, formattingMode, &encoderOptions);
    if (!ss.good()) {
        stream.setstate(bsl::ios_base::failbit);
        return stream;                                                // RETURN
    }

    const bsl::string_view valueString(sb.data(), sb.length());
    addDataImpl(stream, state, valueString);
    return stream;
}

template <class TYPE>
bsl::ostream& Formatter_PrettyImplUtil::addElementAndData(
                                       bsl::ostream&            stream,
                                       State                   *state,
                                       const bsl::string_view&  name,
                                       const TYPE&              value,
                                       int                      formattingMode,
                                       const EncoderOptions&    encoderOptions)
{
    openElement(stream, state, name, WhitespaceType::e_PRESERVE_WHITESPACE);
    addData(stream, state, value, formattingMode, encoderOptions);
    closeElement(stream, state, name);
    return stream;
}

template <class VALUE_TYPE>
bsl::ostream& Formatter_PrettyImplUtil::addListData(
                                         bsl::ostream&          stream,
                                         State                 *state,
                                         const VALUE_TYPE&      value,
                                         int                    formattingMode,
                                         const EncoderOptions&  encoderOptions)
{
    BufferedAllocator      allocator;
    bdlsb::MemOutStreamBuf sb(&allocator);
    bsl::ostream           ss(&sb);

    TypesPrintUtil::print(ss, value, formattingMode, &encoderOptions);
    if (!ss.good()) {
        stream.setstate(bsl::ios_base::failbit);
        return stream;                                                // RETURN
    }

    const bsl::string_view valueString(sb.data(), sb.length());
    addListDataImpl(stream, state, valueString);
    return stream;
}

inline
void Formatter_PrettyImplUtil::reset(State *state)
{
    state->column() = 0;
    state->id()     = StateId::e_AT_START;
    state->indentLevel() -= static_cast<int>(state->elementNesting().size());
    state->elementNesting().clear();
}

}  // close package namespace
}  // close enterprise namespace

#endif // INCLUDED_BALXML_FORMATTER_PRETTYIMPL

// ----------------------------------------------------------------------------
// Copyright 2021 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
