// balxml_elementattribute.h                                          -*-C++-*-
#ifndef INCLUDED_BALXML_ELEMENTATTRIBUTE
#define INCLUDED_BALXML_ELEMENTATTRIBUTE

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

//@PURPOSE: Provide the properties of an attribute in an XML element tag.
//
//@CLASSES:
// balxml::ElementAttribute: Properties of an attribute in an XML element tag.
//
//@SEE_ALSO: balxml_reader, balxml_prefixstack
//
//@DESCRIPTION: This component provides a class, `balxml::ElementAttribute`,
// that encapsulates the name, namespace, and value of an attribute within an
// element in an XML document, along with a flag indicating whether the value
// comes from the default value in the document's schema.  There are accessors
// to return the attribute name as a qualified name, namespace prefix,
// namespace URI, namespace ID (in a namespace registry), and local name
// (without the prefix).
//
// If a namespace prefix stack and a qualified name are provided at
// construction (or via the `reset` method), then all of the other facets of
// the name can be computed automatically.  For efficiency reasons, these
// various decompositions of the attribute name are computed only when needed
// and cached within the `balxml::ElementAttribute` structure.  The caller can
// supply any or all of these computed facets in the constructor or `reset`
// method.  Explicitly supplying a facet prevents it from being computed, thus
// saving processing time.  If the object is constructed with explicit values
// for all facets of the name, then the prefix stack is never used and may be
// null.  If an explicitly-supplied facet differs from the value that would
// have been computed otherwise, then the facets returned by the accessors
// will be inconsistent with one-another.  This inconsistency is deliberately
// permitted so that an program may construct a `balxml::ElementAttribute`
// even when some parts of the name are not yet known, e.g., if the prefix URI
// is known but the qualified name is not.  If the qualified name facet is
// zero, then no other facets will be computed -- their original values will be
// returned from the accessors.
//
///Not Value Semantic
///------------------
// `balxml::ElementAttribute` is not a value-semantic class.  It provides copy
// construction and assignment, but does not provide equality or persistence
// operators.  Copying a `balxml::ElementAttribute` object copies all of its
// pointer facets but does not make copies of the pointed-to strings or
// objects.  The presence of copy operations makes it possible to use
// `balxml::ElementAttribute` with container class templates.
//
// A `balxml::ElementAttribute` object does not own any of its pointer facets
// and it performs no memory management.  This means that a
// `balxml::ElementAttribute` object can be invalidated by modifying or
// deleting any of the pointed-to entities.  Calling an accessor on an invalid
// `balxml::ElementAttribute` is unsafe because the accessor may return an
// invalid pointer.  However an invalid object may be safely destroyed or
// reset.  Components that set or return a `balxml::ElementAttribute` object
// should provide clear documentation describing the events that will cause
// the resulting `balxml::ElementAttribute` object to become invalid (see
// `balxml::Reader`).
//
///Thread Safety
///-------------
// It is safe to access or modify two `balxml::ElementAttribute` objects
// simultaneously, each from a separate thread.  It is safe to access a single
// `balxml::ElementAttribute` object simultaneously from two or more separate
// threads, provided no other thread is simultaneously modifying the object.
// It is not safe to access or modify a `balxml::ElementAttribute` object in
// one thread while another thread modifies the same object.
//
// A `balxml::ElementAttribute` object holds pointers to objects that it does
// not own and which can be modified independently of the
// `balxml::ElementAttribute` object.  It is not safe to modify or delete any
// of these object in one thread while accessing or modifying the
// `balxml::ElementAttribute` object in another thread.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// The following function parses an XML-style attribute assignment in the form
// "qname=`value`", where `qname` can be a qualified name in the form,
// "prefix:localName".  The prefix (if any) must be registered in the specified
// `prefixStack` object.  Either single or double quotes may be used to
// enclose the attribute value.  The parsed attribute is stored in the
// specified `attribute` object of type `balxml::ElementAttribute`.  Note that
// this function modifies the input string by inserting null characters,
// rather than copying the component parts into allocated memory.  This is a
// realistic interface for a function used within an XML parser that has
// already copied the XML stream into allocated memory.  Also note that this
// function does not interpret character escapes such as "&amp;".
// ```
// int parseAttribute(balxml::ElementAttribute  *attribute,
//                    char                     *attributeString,
//                    const balxml::PrefixStack *prefixStack)
// {
// ```
// First, find the end of the qualified name, i.e., the `=` character:
// ```
//     char *qname    = attributeString;
//     char *equalPtr = bsl::strchr(qname, '=');
//     if (0 == equalPtr) {
//         return -1;
//     }
// ```
// Then find out which quote character is used to start the value string
// ```
//     char quote = *(equalPtr + 1);
//     if (quote != '\'' && quote != '"') {
//         return -1;
//     }
// ```
// The value string starts after the opening quote and extends until a
// matching quote:
// ```
//     char *value    = equalPtr + 2;
//     char *endValue = bsl::strchr(value, quote);
//     if (0 == endValue) {
//         return -1;
//     }
// ```
// Once we have successfully parsed the string, chop it into pieces by putting
// a null terminator at the end of the qualified name and at the end of the
// value string:
// ```
//     *equalPtr = '\0';    // Terminate qualified name
//     *endValue = '\0';    // Terminate value string
// ```
// Use the prefix stack, qname, and value to set the attribute object.  All
// other arguments are defaulted and will be computed as needed.
// ```
//     attribute->reset(prefixStack, qname, value);
//     return 0;
// }
// ```
// Before calling the `parseAttribute` function, it is necessary to create a
// namespace registry and prefix stack, as well as to register one or more
// prefixes:
// ```
// int main()
// {
//     balxml::NamespaceRegistry registry;
//     balxml::PrefixStack prefixes(&registry);
//     int cal = prefixes.pushPrefix("cal",
//                               "http://www.bloomberg.com/schemas/calendar");
//     prefixes.pushPrefix("math", "http://www.bloomberg.com/schemas/math");
//     prefixes.pushPrefix("",  // Default namespace
//                         "http://www.bloomberg.com/schemas/app");
// ```
// Now we can parse an attribute string and the `balxml::ElementAttribute`
// object will provide detailed information about it.
// ```
//     char attrStr1[] = "cal:date='12-07-2006'";
//     balxml::ElementAttribute attribute1;
//     assert(attribute1.isNull());
//     parseAttribute(&attribute1, attrStr1, &prefixes);
//
//     assert(0 == bsl::strcmp("cal:date", attribute1.qualifiedName()));
//     assert(0 == bsl::strcmp("12-07-2006", attribute1.value()));
//     assert(0 == bsl::strcmp("date", attribute1.localName()));
//     assert(0 == bsl::strcmp("cal", attribute1.prefix()));
//     assert(cal == attribute1.namespaceId());
//     assert(0 == bsl::strcmp("http://www.bloomberg.com/schemas/calendar",
//                             attribute1.namespaceUri()));
//     assert(0 == attribute1.flags());
// ```
// Results are slightly different when the attribute name has no prefix:
// ```
//     char attrStr2[] = "name=\"Bloomberg, L.P.\"";
//     balxml::ElementAttribute attribute2;
//     parseAttribute(&attribute2, attrStr2, &prefixes);
//
//     assert(0 == bsl::strcmp("name", attribute2.qualifiedName()));
//     assert(0 == bsl::strcmp("Bloomberg, L.P.", attribute2.value()));
//     assert(0 == bsl::strcmp("name", attribute2.localName()));
// ```
// As per the XML namespace standard, an attribute with no namespace prefix
// does NOT inherit the default namespace but rather has NO namespace:
// ```
//     assert(0 == bsl::strcmp("", attribute2.prefix()));
//     assert(-1 == attribute2.namespaceId());
//     assert(0 == bsl::strcmp("", attribute2.namespaceUri()));
//     assert(0 == attribute2.flags());
// ```
// A `balxml::ElementAttribute` does not need to be generated by parsing XML
// code.  If a specific facet of the object is set to a non-null value, then
// that value will be returned by the corresponding accessor even if it is
// inconsistent with the other values in the object.  For example, the
// following constructs a valid `balxml::ElementAttribute` object, even though
// the prefix value does not agree with the qualified name:
// ```
//     balxml::ElementAttribute attribute3(&prefixes,
//                                        "math:product", "4.5", "cal");
// ```
// There is no consistency checking, and the mismatched prefix is simply
// returned by the accessor:
// ```
//     assert(0 == bsl::strcmp("math:product", attribute3.qualifiedName()));
//     assert(0 == bsl::strcmp("product", attribute3.localName()));
//     assert(0 == bsl::strcmp("cal", attribute3.prefix()));
// ```
// Note that the ability to create inconsistent objects is a deliberate
// feature.  It allows parsers to construct `balxml::Attribute` objects before
// all information is known, e.g., before the namespace is registered with the
// prefix stack.  Consistency checking also reduces performance.
// ```
//     return 0;
// }
// ```

#include <balscm_version.h>

#include <bslmf_istriviallycopyable.h>
#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_climits.h>
#include <bsl_ostream.h>

#ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES
#include <bslalg_typetraits.h>
#endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES

namespace BloombergLP {
namespace balxml {

class PrefixStack;

                           // ======================
                           // class ElementAttribute
                           // ======================

/// Class to represent the properties of an attribute in an XML element tag.
/// Note that this class is not value semantic and does not own any of its
/// pointer values.  The owner of the arguments used to set the value of a
/// `ElementAttribute` is responsible for ensuring that the values remain
/// valid or else must document the conditions that will make the values
/// invalid.  Some facets are computed the first time that they are needed.
/// To avoid extra processing, the caller may supply otherwise-computed
/// facets at construction or by calling `reset`.  Facets provided by the
/// caller are not checked to ensure that they are consistent with one
/// another.
class ElementAttribute {

    // MEMBER VARIABLES

    // Mutable members may be used to cache computed results.
    const PrefixStack  *d_prefixStack;   // Held, not owned
    const char         *d_qualifiedName; // Held, not owned
    const char         *d_value;         // Held, not owned
    mutable const char *d_prefix;        // Held, not owned
    mutable const char *d_localName;     // Held, not owned
    mutable int         d_namespaceId;
    mutable const char *d_namespaceUri;  // Held, not owned
    unsigned            d_flags;

  public:
    // FLAGS

    // The flags property should be set to the bitwise-OR of one or more of
    // the following values:
    enum {
        /// No flags set.
        k_ATTR_NO_FLAGS   = 0x0000,

        /// Set this bit if the value for this attribute object was
        /// generated from the default attribute value in the DTD or
        /// schema.
        k_ATTR_IS_DEFAULT = 0x0001,

        /// Set this bit if this attribute represents a namespace
        /// declaration.
        k_ATTR_IS_NSDECL  = 0x0002,

        /// Set this bit if this attribute represents a XML schema instance
        /// declaration.
        k_ATTR_IS_XSIDECL = 0x0004

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
      , BAEXML_ATTR_NO_FLAGS = k_ATTR_NO_FLAGS
      , BAEXML_ATTR_IS_DEFAULT = k_ATTR_IS_DEFAULT
      , BAEXML_ATTR_IS_NSDECL = k_ATTR_IS_NSDECL
      , BAEXML_ATTR_IS_XSIDECL = k_ATTR_IS_XSIDECL
      , ATTR_NO_FLAGS   = k_ATTR_NO_FLAGS
      , ATTR_IS_DEFAULT = k_ATTR_IS_DEFAULT
      , ATTR_IS_NSDECL  = k_ATTR_IS_NSDECL
#endif // BDE_OMIT_INTERNAL_DEPRECATED
    };

    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(ElementAttribute,
                                   bsl::is_trivially_copyable);

    // PUBLIC CREATORS

    /// Construct a null attribute object: Set all string facets to zero,
    /// the namespace ID to `INT_MIN`, and flags to zero.
    ElementAttribute();

    /// Construct an attribute object with the specified, `prefixStack`,
    /// `qualifiedName`, and `value`, with optionally specified `prefix`,
    /// `localName`, `namespaceId`, `namespaceUri`, and `flags`.  Except
    /// for `flags`, if any of the optional arguments are null (for
    /// pointers) or `INT_MIN` (for integer arguments), then the
    /// corresponding facet will computed from the other arguments on an
    /// as-needed basis.  If all of the optional arguments are given
    /// non-null, non-`INT_MIN` values, then `prefixStack` is unused and
    /// may be null.  Arguments are permitted to be inconsistent with one
    /// another (e.g., `prefix` may not match the beginning of
    /// `qualifiedName`) and will produce an object for with facets that
    /// are inconsistent with one another.  The constructed object will
    /// become invalid if any of the supplied pointers is invalidated
    /// during its lifetime (or before it is reset).  An invalid object may
    /// be destroyed or reset, but any other access yields undefined
    /// behavior.
    ElementAttribute(const PrefixStack *prefixStack,
                     const char        *qualifiedName,
                     const char        *value,
                     const char        *prefix = 0,
                     const char        *localName = 0,
                     int                namespaceId = INT_MIN,
                     const char        *namespaceUri = 0,
                     unsigned           flags = 0);

#ifdef DOXYGEN
    // For efficiency, use compiler-generated copy constructor, destructor,
    // and assignment, but document them as though they were explicitly
    // declared:

    ElementAttribute(const ElementAttribute& other);
        // Construct a copy of the specified 'other' attribute object.  The
        // copy will have pointers to the same strings and prefix table as
        // 'other'.

    ~ElementAttribute();
        // Destroy this attribute object.  The targets of the pointers used to
        // construct or reset this object are not owned by this object and are
        // not deallocated.  It is safe to destroy a 'ElementAttribute'
        // object that is in an invalid state.

    ElementAttribute operator=(const ElementAttribute& rhs);
        // Assign this attribute object the value of 'rhs'.  This object will
        // have pointers to the same strings and prefix table as 'other'.
#endif

    /// Reset this object to the default-constructed state.
    void reset();

    /// Reset this attribute object with the specified, `prefixStack`,
    /// `qualifiedName`, and `value`, with optionally specified `prefix`,
    /// `localName`, `namespaceId`, `namespaceUri`, and `flags`.  Except
    /// for `flags`, if any of the optional arguments are null (for
    /// pointers) or `INT_MIN` (for integer arguments), then the
    /// corresponding facet will computed from the other arguments on an
    /// as-needed basis.  If all of the optional arguments are given
    /// non-null, non-`INT_MIN` values, then `prefixStack` is unused and
    /// may be null.  Arguments are permitted to be inconsistent with one
    /// another (e.g., `prefix` may not match the beginning of
    /// `qualifiedName`) and will produce an object for with facets that
    /// are inconsistent with one another.  This object will become invalid
    /// if any of the supplied pointers is invalidated during its lifetime
    /// (or before it is reset).  An invalid object may be destroyed or
    /// reset, but any other access yields undefined behavior.
    void reset(const PrefixStack *prefixStack,
               const char        *qualifiedName,
               const char        *value,
               const char        *prefix = 0,
               const char        *localName = 0,
               int                namespaceId = INT_MIN,
               const char        *namespaceUri = 0,
               unsigned           flags = 0);

    // ACCESSORS

    /// Return the value of `prefixStack` specified at the last call to
    /// `reset` or the constructor or 0 if no prefix stack was specified.
    const PrefixStack *prefixStack() const;

    /// Return the value of `qualifiedName` specified at the last call to
    /// `reset` or the constructor or 0 if no qualified name was specified.
    const char *qualifiedName() const;

    /// Return the value of `value` specified at the last call to `reset`
    /// or the constructor or 0 if no value was specified.
    const char *value() const;

    /// Return the value of `prefix` specified at the last call to `reset`
    /// or the constructor, if non-zero, else return a copy of the prefix
    /// portion of the qualified name.  More precisely, return a copy of
    /// the portion of `qualifiedName()` up to, but not including, the
    /// colon.  Return an empty string if the qualified name has no colon
    /// or if `prefixStack()` returns zero.
    const char *prefix() const;

    /// Return the value of `localName` specified at the last call to
    /// `reset` or the constructor, if non-zero, else return the local part
    /// of the qualified name.  More precisely, return the portion of
    /// `qualifiedName()` after the colon, if any, or the entire
    /// `qualifiedName()` if there is no colon.
    const char *localName() const;

    /// Return the value of `localName` specified at the last call to
    /// `reset` or the constructor, if not `INT_MIN`, else return the ID
    /// returned by the prefix stack for the current value of `prefix()`.
    /// Return -1 if `prefix()` is an empty string, `prefixStack()` is
    /// zero, or `prefix()` is not active in the prefix stack.  Note that,
    /// as per the XML namespace standard, an empty prefix does *NOT* refer
    /// to the default namespace, but rather refers to *NO* namespace.
    int namespaceId() const;

    /// Return the value of `namespaceUri` specified at the last call to
    /// `reset` or the constructor, if non-zero, else return the URI
    /// returned by the prefix stack for the current value of `prefix()`.
    /// Return an empty string if `prefix()` is an empty string,
    /// `prefixStack()` is zero, or `prefix()` is not active in the prefix
    /// stack.  Note that, as per the XML namespace standard, an empty
    /// prefix does *NOT* refer to the default namespace, but rather refers
    /// to *NO* namespace.
    const char *namespaceUri() const;

    /// Return the value of `flags` specified at the last call to `reset`
    /// or the constructor, if non-zero, or zero otherwise.  Pre-defined
    /// flags are enumerated in the class definition.
    unsigned flags() const;

    /// Return true if this object is null.  More precisely, return true if
    /// `qualifiedName()` returns 0.  A default-constructed object will be
    /// null, as will an object that was reset with no arguments.
    bool isNull() const;

    /// Format this object to the specified output `stream` at the
    /// (absolute value of) the optionally specified indentation `level`
    /// and return a reference to `stream`.  If `level` is specified,
    /// optionally specify `spacesPerLevel`, the number of spaces per
    /// indentation level for this and all of its nested objects.  If
    /// `level` is negative, suppress indentation of the first line.  If
    /// `spacesPerLevel` is negative, format the entire output on one line.
    /// If `stream` is not valid on entry, this operation has no effect.
    /// Facet values that have not yet been computed are represented by
    /// "<null>" in the resulting stream.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
};

// FREE OPERATORS

/// Write the contents of the specified `attribute` object to the specified
/// `os` in human-readable form.  Attributes that have not yet been
/// computed are not computed by this function.
inline
bsl::ostream& operator<<(bsl::ostream&           os,
                         const ElementAttribute& attribute);

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

                           // ----------------------
                           // class ElementAttribute
                           // ----------------------

// ACCESSORS
inline
const PrefixStack *ElementAttribute::prefixStack() const
{
    return d_prefixStack;
}

inline
const char *ElementAttribute::qualifiedName() const
{
    return d_qualifiedName;
}

inline
const char *ElementAttribute::value() const
{
    return d_value;
}

inline
unsigned ElementAttribute::flags() const
{
    return d_flags;
}

inline
bool ElementAttribute::isNull() const
{
    return 0 == d_qualifiedName;
}

}  // close package namespace

// FREE OPERATORS
inline
bsl::ostream& balxml::operator<<(bsl::ostream&           os,
                                 const ElementAttribute& attribute)
{
    return attribute.print(os);
}

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 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 ----------------------------------
