//
// XMLWriter.h
//
// Library: XML
// Package: XML
// Module:  XMLWriter
//
// Definition of the XMLWriter class.
//
// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#ifndef XML_XMLWriter_INCLUDED
#define XML_XMLWriter_INCLUDED


#include "Poco/XML/XML.h"
#include "Poco/SAX/ContentHandler.h"
#include "Poco/SAX/LexicalHandler.h"
#include "Poco/SAX/DTDHandler.h"
#include "Poco/SAX/NamespaceSupport.h"
#include "Poco/XML/XMLString.h"
#include "Poco/XML/XMLStream.h"
#include "Poco/XML/Name.h"
#include "Poco/TextEncoding.h"
#include "Poco/StreamConverter.h"
#include <vector>
#include <map>


namespace Poco {
namespace XML {


class Locator;


class XML_API XMLWriter: public ContentHandler, public LexicalHandler, public DTDHandler
	/// This class serializes SAX2 ContentHandler, LexicalHandler and
	/// DTDHandler events back into a stream.
	///
	/// Various consistency checks are performed on the written data
	/// (i.e. there must be exactly one root element and every startElement()
	/// must have a matching endElement()).
	///
	/// The XMLWriter supports optional pretty-printing of the serialized XML.
	/// Note, however, that pretty-printing XML data alters the
	/// information set of the document being written, since in
	/// XML all whitespace is potentially relevant to an application.
	///
	/// The writer contains extensive support for XML Namespaces, so that a client
	/// application does not have to keep track of prefixes and supply xmlns attributes.
	///
	/// If the client does not provide namespace prefixes (either by specifying them
	/// as part of the qualified name given to startElement(), or by calling
	/// startPrefixMapping()), the XMLWriter automatically generates namespace
	/// prefixes in the form ns1, ns2, etc.
{
public:
	enum Options
	{
		CANONICAL               = 0x00,
			/// Do not write an XML declaration (default).

		CANONICAL_XML           = 0x01,
			/// Enables basic support for Canonical XML:
			///   - do not write an XML declaration
			///   - do not use special empty element syntax
			///   - set the New Line character to NEWLINE_LF
			///   - write namespace declarations and attributes
			///     in canonical order
			///   - use default namespace as much as possible

		WRITE_XML_DECLARATION   = 0x02,
			/// Write an XML declaration.

		PRETTY_PRINT            = 0x04,
			/// Pretty-print XML markup.

		PRETTY_PRINT_ATTRIBUTES = 0x08
			/// Write each attribute on a separate line.
			/// PRETTY_PRINT must be specified as well.
	};

	XMLWriter(XMLByteOutputStream& str, int options);
		/// Creates the XMLWriter and sets the specified options.
		///
		/// The resulting stream will be UTF-8 encoded.

	XMLWriter(XMLByteOutputStream& str, int options, const std::string& encodingName, Poco::TextEncoding& textEncoding);
		/// Creates the XMLWriter and sets the specified options.
		///
		/// The encoding is reflected in the XML declaration.
		/// The caller is responsible for that the given encodingName matches with
		/// the given textEncoding.

	XMLWriter(XMLByteOutputStream& str, int options, const std::string& encodingName, Poco::TextEncoding* pTextEncoding);
		/// Creates the XMLWriter and sets the specified options.
		///
		/// The encoding is reflected in the XML declaration.
		/// The caller is responsible for that the given encodingName matches with
		/// the given textEncoding.
		/// If pTextEncoding is null, the given encodingName is ignored and the
		/// default UTF-8 encoding is used.

	~XMLWriter();
		/// Destroys the XMLWriter.

	void setNewLine(const std::string& newLineCharacters);
		/// Sets the line ending for the resulting XML file.
		///
		/// Possible values are:
		///   * NEWLINE_DEFAULT (whatever is appropriate for the current platform)
		///   * NEWLINE_CRLF    (Windows),
		///   * NEWLINE_LF      (Unix),
		///   * NEWLINE_CR      (Macintosh)

	const std::string& getNewLine() const;
		/// Returns the line ending currently in use.

	void setIndent(const std::string& indent);
		/// Sets the string used for one indentation step.
		///
		/// The default is a single TAB character.
		/// The given string should only contain TAB or SPACE
		/// characters (e.g., a single TAB character, or
		/// two to four SPACE characters).

	const std::string& getIndent() const;
		/// Returns the string used for one indentation step.

	// ContentHandler
	void setDocumentLocator(const Locator* loc);
		/// Currently unused.

	void startDocument();
		/// Writes a generic XML declaration to the stream.
		/// If a document type has been set (see SetDocumentType),
		/// a DOCTYPE declaration is also written.

	void endDocument();
		/// Checks that all elements are closed and prints a final newline.

	void startFragment();
		/// Use this instead of StartDocument() if you want to write
		/// a fragment rather than a document (no XML declaration and
		/// more than one "root" element allowed).

	void endFragment();
		/// Checks that all elements are closed and prints a final newline.

	void startElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname, const Attributes& attributes);
		/// Writes an XML start element tag.
		///
		/// Namespaces are handled as follows.
		///   1. If a qname, but no namespaceURI and localName are given, the qname is taken as element name.
		///   2. If a namespaceURI and a localName, but no qname is given, and the given namespaceURI has been
		///      declared earlier, the namespace prefix for the given namespaceURI together with the localName
		///      is taken as element name. If the namespace has not been declared, a prefix in the form
		///      "ns1", "ns2", etc. is generated and the namespace is declared with the generated prefix.
		///   3. If all three are given, and the namespace given in namespaceURI has not been declared, it is declared now.
		///      Otherwise, see 2.

	void startElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname);
		/// Writes an XML start element tag with no attributes.
		/// See the other startElement() method for more information.

	void endElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname);
		/// Writes an XML end element tag.
		///
		/// Throws an exception if the name of doesn't match the
		/// one of the most recent startElement().

	void emptyElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname);
		/// Writes an empty XML element tag (<elem/>).

	void emptyElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname, const Attributes& attributes);
		/// Writes an empty XML element tag with the given attributes (<elem attr1="value1"... />).

	void characters(const XMLChar ch[], int start, int length);
		/// Writes XML character data. Quotes, ampersand's, less-than and
		/// greater-than signs are escaped, unless a CDATA section
		/// has been opened by calling startCDATA().
		///
		/// The characters must be encoded in UTF-8 (if XMLChar is char) or
		/// UTF-16 (if XMLChar is wchar_t).

	void characters(const XMLString& str);
		/// Writes XML character data. Quotes, ampersand's, less-than and
		/// greater-than signs are escaped, unless a CDATA section
		/// has been opened by calling startCDATA().
		///
		/// The characters must be encoded in UTF-8 (if XMLChar is char) or
		/// UTF-16 (if XMLChar is wchar_t).

	void rawCharacters(const XMLString& str);
		/// Writes the characters in the given string as they are.
		/// The caller is responsible for escaping characters as
		/// necessary to produce valid XML.
		///
		/// The characters must be encoded in UTF-8 (if XMLChar is char) or
		/// UTF-16 (if XMLChar is wchar_t).

	void ignorableWhitespace(const XMLChar ch[], int start, int length);
		/// Writes whitespace characters by simply passing them to
		/// characters().

	void processingInstruction(const XMLString& target, const XMLString& data);
		/// Writes a processing instruction.

	void startPrefixMapping(const XMLString& prefix, const XMLString& namespaceURI);
		/// Begin the scope of a prefix-URI Namespace mapping.
		/// A namespace declaration is written with the next element.

	void endPrefixMapping(const XMLString& prefix);
		/// End the scope of a prefix-URI mapping.

	void skippedEntity(const XMLString& name);
		/// Does nothing.

	void dataElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname, const XMLString& data,
	                         const XMLString& attr1 = XMLString(), const XMLString& value1 = XMLString(),
							 const XMLString& attr2 = XMLString(), const XMLString& value2 = XMLString(),
							 const XMLString& attr3 = XMLString(), const XMLString& value3 = XMLString());
		/// Writes a data element in the form <name attr1="value1"...>data</name>.

	// LexicalHandler
	void startCDATA();
		/// Writes the <![CDATA[ string that begins a CDATA section.
		/// Use characters() to write the actual character data.

	void endCDATA();
		/// Writes the ]]> string that ends a CDATA section.

	void comment(const XMLChar ch[], int start, int length);
		/// Writes a comment.

	void startDTD(const XMLString& name, const XMLString& publicId, const XMLString& systemId);
		/// Writes a DTD declaration.

	void endDTD();
		/// Writes the closing characters of a DTD declaration.

	void startEntity(const XMLString& name);
		/// Does nothing.

	void endEntity(const XMLString& name);
		/// Does nothing.

	// DTDHandler
	void notationDecl(const XMLString& name, const XMLString* publicId, const XMLString* systemId);
	void unparsedEntityDecl(const XMLString& name, const XMLString* publicId, const XMLString& systemId, const XMLString& notationName);

	static const std::string NEWLINE_DEFAULT;
	static const std::string NEWLINE_CR;
	static const std::string NEWLINE_CRLF;
	static const std::string NEWLINE_LF;

	// Namespace support.
	XMLString uniquePrefix();
		/// Creates and returns a unique namespace prefix that
		/// can be used with startPrefixMapping().

	bool isNamespaceMapped(const XMLString& namespc) const;
		/// Returns true if the given namespace has been mapped
		/// to a prefix in the current element or its ancestors.

	// Misc.
	int depth() const;
		/// Return the number of nested XML elements.
		///
		/// Will be -1 if no document or fragment has been started,
		/// 0 if the document or fragment has been started,
		/// 1 if the document element has been written and
		/// > 1 for every element nested within the document element.

protected:
	typedef std::map<XMLString, XMLString> AttributeMap;
	typedef std::map<XMLString, std::pair<XMLString, XMLString>> CanonicalAttributeMap;

	void writeStartElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname, const Attributes& attributes);
	void writeCanonicalStartElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname, const Attributes& attributes);
	void writeEndElement(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname);
	void writeMarkup(const std::string& str) const;
	void writeXML(const XMLString& str) const;
	void writeXML(XMLChar ch) const;
	void writeNewLine() const;
	void writeIndent() const;
	void writeIndent(int indent) const;
	void writeName(const XMLString& prefix, const XMLString& localName);
	void writeXMLDeclaration();
	void closeStartTag();
	void declareNamespaces(const XMLString& namespaceURI, const XMLString& localName, const XMLString& qname, const Attributes& attributes);
	void declareAttributeNamespaces(const Attributes& attributes);
	void addNamespaceAttributes(AttributeMap& attributeMap);
	void addNamespaceAttributes(CanonicalAttributeMap& attributeMap);
	void addAttributes(AttributeMap& attributeMap, const Attributes& attributes, const XMLString& elementNamespaceURI);
	void addAttributes(CanonicalAttributeMap& attributeMap, const Attributes& attributes, const XMLString& elementNamespaceURI);
	void writeAttributes(const AttributeMap& attributeMap);
	void writeAttributes(const CanonicalAttributeMap& attributeMap);
	void prettyPrint() const;
	static std::string nameToString(const XMLString& localName, const XMLString& qname);

private:
	struct Namespace
	{
		Namespace(const XMLString& thePrefix, const XMLString& theNamespaceURI):
			prefix(thePrefix),
			namespaceURI(theNamespaceURI)
		{
		}

		XMLString prefix;
		XMLString namespaceURI;
	};
	typedef std::vector<Name> ElementStack;

	Poco::OutputStreamConverter* _pTextConverter;
	Poco::TextEncoding*          _pInEncoding;
	Poco::TextEncoding*          _pOutEncoding;
	int              _options;
	std::string      _encoding;
	std::string      _newLine;
	int              _depth;
	int              _elementCount;
	bool             _inFragment;
	bool             _inCDATA;
	bool             _inDTD;
	bool             _inInternalDTD;
	bool             _contentWritten;
	bool             _unclosedStartTag;
	ElementStack     _elementStack;
	NamespaceSupport _namespaces;
	int              _prefix;
	bool             _nsContextPushed;
	std::string      _indent;

	static const std::string MARKUP_QUOTENC;
	static const std::string MARKUP_AMPENC;
	static const std::string MARKUP_LTENC;
	static const std::string MARKUP_GTENC;
	static const std::string MARKUP_TABENC;
	static const std::string MARKUP_CRENC;
	static const std::string MARKUP_LFENC;
	static const std::string MARKUP_LT;
	static const std::string MARKUP_GT;
	static const std::string MARKUP_SLASHGT;
	static const std::string MARKUP_LTSLASH;
	static const std::string MARKUP_COLON;
	static const std::string MARKUP_EQQUOT;
	static const std::string MARKUP_QUOT;
	static const std::string MARKUP_SPACE;
	static const std::string MARKUP_TAB;
	static const std::string MARKUP_BEGIN_CDATA;
	static const std::string MARKUP_END_CDATA;
};


//
// inlines
//
inline int XMLWriter::depth() const
{
	return _depth;
}


} } // namespace Poco::XML


#endif // XML_XMLWriter_INCLUDED
