dxml.writer

This module provides functionality for creating XML 1.0 documents.

Primary Symbols

Symbol Description
XMLWriter Type used for writing XML documents.
xmlWriter Function used to create an XMLWriter.

Helper Types

XMLWritingException Thrown by XMLWriter when it's given data that would result in invalid XML.

Helper Functions

Symbol Description
writeTaggedText Shortcut for writing text enclosed by tags. e.g. <tag>text</tag>.
writeXMLDecl Writes the optional XML declaration that goes at the top of an XML document to an ouput range.
class XMLWritingException: object.Exception;
Exception thrown when the writer is given data that would result in invalid XML.
alias EmptyTag = std.typecons.Flag!"EmptyTag".Flag;
std.typecons.Flag indicating whether closeStartTag or writeStartTag or will write an empty element tag (which then does not require a corresponding end tag).
alias Newline = std.typecons.Flag!"Newline".Flag;
std.typecons.Flag indicating whether a write function of XMLWriter will write a newline followed by an indent before the entity being written.
alias InsertIndent = std.typecons.Flag!"InsertIndent".Flag;
std.typecons.Flag indicating whether a write function of XMLWriter which accepts text which may include newlines will write an indent after each newline is written.
struct XMLWriter(OR) if (isOutputRange!(OR, char));
auto xmlWriter(OR)(OR output, string baseIndent = " ");
Writes XML to an output range of characters.
Note that default initialization, copying, and assignment are disabled for XMLWriter. This is because XMLWriter is essentially a reference type, but in many cases, it doesn't need to be passed around, and forcing it to be allocated on the heap in order to be a reference type seemed like an unnecessary heap allocation. So, it's a struct with default initialization, copying, and assignment disabled so that like a reference type, it will not be copied or overwritten. Code that needs to pass it around can pass it by ref or use the constructor to explicitly allocate it on the heap and then pass around the resulting pointer.
The optional Newline and InsertIndent parameters to the various write functions are used to control the formatting of the XML, and writeIndent and output can be used for additional control over the formatting.
The indent provided to the XMLWriter is the base indent that will be used whenever writeIndent and any write functions using Newline.yes or InsertIndent.yes are called - e.g. if the base indent is 4 spaces, tagDepth == 3, and Newline.yes is passed to writeComment, then a newline followed by 12 spaces will be written to the output range after the comment.
writeXMLDecl can be used to write the <?xml...?> declaration to the output range before constructing an XML writer, but if an application wishes to do anything with a DTD section, it will have to write that to the output range on its own before constructing the XMLWriter. XMLWriter expects to start writing XML after any <?xml...?> or <!DOCTYPE...> declarations.
The write functions check the arguments prior to writing anything to the output range, so the XMLWriter is not in an invalid state after an XMLWritingException is thrown, but it is in an invalid state if any other exception is thrown (which will only occur if an input range that is passed to a write function throws or if the ouput range throws when XMLWriter calls put on it).
Parameters:
OR output The output range that the XML will be written to.
string baseIndent Optional argument indicating the base indent to be used when an indent is inserted after a newline in the XML (with the actual indent being the base indent inserted once for each level of the tagDepth). The default is four spaces. baseIndent may only contain spaces and/or tabs.
Examples:
import std.array : appender;
{
    auto writer = xmlWriter(appender!string());
    writer.writeStartTag("root");

    writer.openStartTag("foo");
    writer.writeAttr("a", "42");
    writer.closeStartTag();

    writer.writeText("bar");

    writer.writeEndTag("foo");

    writer.writeEndTag("root");

    assert(writer.output.data ==
           "\n" ~
           "<root>\n" ~
           `    <foo a="42">` ~ "\n" ~
           "        bar\n" ~
           "    </foo>\n" ~
           "</root>");
}

// Newline.no can be used to avoid inserting newlines.
{
    auto writer = xmlWriter(appender!string());

    // Unless writeXMLDecl was used, Newline.no is needed on the first
    // entity to avoid having the document start with a newline.
    writer.writeStartTag("root", Newline.no);

    writer.openStartTag("foo");
    writer.writeAttr("a", "42");
    writer.closeStartTag();

    writer.writeText("bar", Newline.no);

    writer.writeEndTag("foo", Newline.no);

    writer.writeEndTag("root");

    assert(writer.output.data ==
           "<root>\n" ~
           `    <foo a="42">bar</foo>` ~ "\n" ~
           "</root>");
}
void openStartTag(string name, Newline newline = Newline.yes);
Writes the first portion of a start tag to the given output range.
Once openStartTag has been called, writeAttr can be called to add attributes to the start tag. closeStartTag writes the closing portion of the start tag.
Once openStartTag has been called, it is an error to call any function on XMLWriter other than closeStartTag, writeAttr, writeIndent, tagDepth, baseIndent, or output until closeStartTag has been called (basically, any function that involves writing XML that is not legal in a start tag can't be called until the start tag has been properly closed).
It is also an error to call openStartTag after the end tag for the root element has been written.
Parameters:
string name The name of the start tag.
Newline newline Whether a newline followed by an indent will be written to the output range before the start tag.
Throws: XMLWritingException if the given name is not a valid XML tag name.
void writeAttr(char quote = '"', R)(string name, R value, Newline newline = Newline.no)
if((quote == '"' || quote == '\'') && isForwardRange!R && isSomeChar!(ElementType!R));
Writes an attribute for a start tag to the output range.
It is an error to call writeAttr except between calls to openStartTag and closeStartTag.
Parameters:
quote The quote character to use for the attribute value's delimiter.
string name The name of the attribute.
R value The value of the attribute.
Newline newline Whether a newline followed by an indent will be written to the output range before the attribute. Note that unlike most write functions, the default is Newline.no (since it's more common to not want newlines between attributes).
Throws: XMLWritingException if the given name is not a valid XML attribute name, if the given value is not a valid XML attribute value, or if the given name has already been written to the current start tag. dxml.util.encodeAttr can be used to encode any characters that are not legal in their literal form in an attribute value but are legal as entity references.
Examples:
import std.array : appender;
import std.exception : assertThrown;
import dxml.util : encodeAttr;

auto writer = xmlWriter(appender!string());

writer.openStartTag("root", Newline.no);
assert(writer.output.data == "<root");

writer.writeAttr("a", "one");
assert(writer.output.data == `<root a="one"`);

writer.writeAttr("b", "two");
assert(writer.output.data == `<root a="one" b="two"`);

// It's illegal for two attributes on the same start tag
// to have the same name.
assertThrown!XMLWritingException(writer.writeAttr("a", "three"));

// Invalid name.
assertThrown!XMLWritingException(writer.writeAttr("=", "value"));

// Can't have a quote that matches the enclosing quote.
assertThrown!XMLWritingException(writer.writeAttr("c", `foo"bar`));
assertThrown!XMLWritingException(writer.writeAttr!'\''("c", "foo'bar"));

// Unchanged after an XMLWritingException is thrown.
assert(writer.output.data == `<root a="one" b="two"`);

writer.closeStartTag();
assert(writer.output.data == `<root a="one" b="two">`);

writer.openStartTag("foobar");
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar");

// " is the default for the quote character, but ' can be specified.
writer.writeAttr!'\''("answer", "42");
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'");

writer.writeAttr("base", "13", Newline.yes);
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13"`);

writer.closeStartTag();
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13">`);

writer.openStartTag("tag");
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13">` ~ "\n" ~
       "        <tag");

// &, <, and > are not legal in an attribute value.
assertThrown!XMLWritingException(writer.writeAttr("foo", "&"));

// Unchanged after an XMLWritingException is thrown.
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13">` ~ "\n" ~
       "        <tag");

// Use dxml.util.encodeAttr to encode characters that aren't
// legal in an attribute value but can legally be encoded.
writer.writeAttr("foo", encodeAttr("&"));
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13">` ~ "\n" ~
       `        <tag foo="&amp;"`);

writer.closeStartTag(EmptyTag.yes);
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13">` ~ "\n" ~
       `        <tag foo="&amp;"/>`);

writer.writeEndTag();
writer.writeEndTag();
assert(writer.output.data ==
       `<root a="one" b="two">` ~ "\n" ~
       "    <foobar answer='42'\n" ~
       `        base="13">` ~ "\n" ~
       `        <tag foo="&amp;"/>` ~ "\n" ~
       "    </foobar>\n" ~
       "</root>");
void closeStartTag(EmptyTag emptyTag = EmptyTag.no);
Writes the end of a start tag to the ouput range.
It is an error to call closeStartTag unless a start tag has been opened and not yet closed.
Parameters:
EmptyTag emptyTag Whether the start tag will be empty (i.e. terminated with "/>" so that there is no corresponding end tag).
Examples:
import std.array : appender;

auto writer = xmlWriter(appender!string());

writer.openStartTag("root", Newline.no);
assert(writer.output.data == "<root");

writer.closeStartTag();
assert(writer.output.data == "<root>");

writer.openStartTag("foo");
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo");

writer.closeStartTag(EmptyTag.yes);
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo/>");

writer.writeEndTag();
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo/>\n" ~
       "</root>");
void writeStartTag(string name, EmptyTag emptyTag = EmptyTag.no, Newline newline = Newline.yes);
void writeStartTag(string name, Newline newline, EmptyTag emptyTag = EmptyTag.no);
Writes a start tag with no attributes.
This is equivalent to calling openStartTag immediately followed by closeStartTag.
It is an error to call writeStartTag after the end tag for the root element has been written.
Parameters:
string name The name of the start tag.
EmptyTag emptyTag Whether the start tag will be empty (i.e. terminated with "/>" so that there is no corresponding end tag).
Newline newline Whether a newline followed by an indent will be written to the output range before the start tag.
Throws: XMLWritingException if the given name is not a valid XML tag name.
void writeEndTag(string name, Newline newline = Newline.yes);
void writeEndTag(Newline newline = Newline.yes);
Writes an end tag to the output range with the name of the start tag that was most recently written and does not yet have a matching end tag.
If a name is provided, then it will be validated against the matching start tag.
Parameters:
string name Name to check against the matching start tag.
Newline newline Whether a newline followed by an indent will be written to the output range before the end tag.
Throws: XMLWritingException if no start tag is waiting for a matching end tag or if the given name does not match the name of the start tag that needs to be matched next.
Examples:
import std.array : appender;
import std.exception : assertThrown;

auto writer = xmlWriter(appender!string());
writer.writeStartTag("root", Newline.no);
assert(writer.output.data == "<root>");

writer.writeStartTag("foo");
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo>");

// Name doesn't match start tag, which is <foo>.
assertThrown!XMLWritingException(writer.writeEndTag("bar"));

// Unchanged after an XMLWritingException is thrown.
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo>");

writer.writeEndTag("foo", Newline.no);
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo></foo>");

writer.writeStartTag("bar");
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo></foo>\n" ~
       "    <bar>");

writer.writeEndTag("bar");
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo></foo>\n" ~
       "    <bar>\n" ~
       "    </bar>");

// No name is required, but if it is not provided, then the code cannot
// validate that it's writing the end tag that it thinks it's writing.
writer.writeEndTag();
assert(writer.output.data ==
       "<root>\n" ~
       "    <foo></foo>\n" ~
       "    <bar>\n" ~
       "    </bar>\n" ~
       "</root>");
void writeText(R)(R text, Newline newline = Newline.yes, InsertIndent insertIndent = InsertIndent.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
void writeText(R)(R text, InsertIndent insertIndent, Newline newline = Newline.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
This writes the text that goes between start tags and end tags.
It can be called multiple times in a row, and the given text will just end up being appended to the current text field.
It is an error to call writeText after the end tag for the root element has been written.
Parameters:
R text The text to write.
Newline newline Whether a newline followed by an indent will be written to the output range before the text. It will not include an indent if insertIndent == InsertIndent.no.
InsertIndent insertIndent Whether an indent will be inserted after each newline within the text.
Throws: XMLWritingException if any characters or sequence of characters in the given text are not legal in the text portion of an XML document. dxml.util.encodeText can be used to encode any characters that are not legal in their literal form but are legal as entity references.
void writeComment(R)(R text, Newline newline = Newline.yes, InsertIndent insertIndent = InsertIndent.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
void writeComment(R)(R text, InsertIndent insertIndent, Newline newline = Newline.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
Writes a comment to the output range.
Parameters:
R text The text of the comment.
Newline newline Whether a newline followed by an indent will be written to the output range before the comment tag.
InsertIndent insertIndent Whether an indent will be inserted after each newline within the text.
Throws: XMLWritingException if the given text contains "--" or ends with "-".
Examples:
import std.array : appender;
import std.exception : assertThrown;

auto writer = xmlWriter(appender!string());

writer.writeComment(" And so it begins... ", Newline.no);
writer.writeStartTag("root");
writer.writeComment("A comment");
writer.writeComment("Another comment");
writer.writeComment("No preceding newline", Newline.no);
writer.writeComment("A comment\nwith a newline");
writer.writeComment("Another newline\nbut no indent",
                    InsertIndent.no);
writer.writeStartTag("tag");
writer.writeComment("Deeper comment");
writer.writeEndTag("tag");
writer.writeEndTag("root");
writer.writeComment(" And so it ends... ");

assert(writer.output.data ==
       "<!-- And so it begins... -->\n" ~
       "<root>\n" ~
       "    <!--A comment-->\n" ~
       "    <!--Another comment--><!--No preceding newline-->\n" ~
       "    <!--A comment\n" ~
       "        with a newline-->\n" ~
       "    <!--Another newline\n" ~
       "but no indent-->\n" ~
       "    <tag>\n" ~
       "        <!--Deeper comment-->\n" ~
       "    </tag>\n" ~
       "</root>\n" ~
       "<!-- And so it ends... -->");

// -- is not legal in an XML comment.
assertThrown!XMLWritingException(writer.writeComment("foo--bar"));

// - is not legal at the end of an XML comment.
assertThrown!XMLWritingException(writer.writeComment("foo-"));

// Unchanged after an XMLWritingException is thrown.
assert(writer.output.data ==
       "<!-- And so it begins... -->\n" ~
       "<root>\n" ~
       "    <!--A comment-->\n" ~
       "    <!--Another comment--><!--No preceding newline-->\n" ~
       "    <!--A comment\n" ~
       "        with a newline-->\n" ~
       "    <!--Another newline\n" ~
       "but no indent-->\n" ~
       "    <tag>\n" ~
       "        <!--Deeper comment-->\n" ~
       "    </tag>\n" ~
       "</root>\n" ~
       "<!-- And so it ends... -->");
void writeCDATA(R)(R text, Newline newline = Newline.yes, InsertIndent insertIndent = InsertIndent.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
void writeCDATA(R)(R text, InsertIndent insertIndent, Newline newline = Newline.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
Writes a <![CDATA[...]]> section with the given text between the brackets.
Parameters:
R text The text of the CDATA section.
Newline newline Whether a newline followed by an indent will be written to the output range before the cdata section.
InsertIndent insertIndent Whether an indent will be inserted after each newline within the text.
Throws: XMLWritingException if the given text contains "]]>".
Examples:
import std.array : appender;
import std.exception : assertThrown;

auto writer = xmlWriter(appender!string());

writer.writeStartTag("root", Newline.no);
writer.writeCDATA("see data run");
writer.writeCDATA("More data");
writer.writeCDATA("No preceding newline", Newline.no);
writer.writeCDATA("some data\nwith a newline");
writer.writeCDATA("Another newline\nbut no indent", InsertIndent.no);
writer.writeStartTag("tag");
writer.writeCDATA(" Deeper data <><> ");
writer.writeEndTag("tag");
writer.writeEndTag("root");

assert(writer.output.data ==
       "<root>\n" ~
       "    <![CDATA[see data run]]>\n" ~
       "    <![CDATA[More data]]><![CDATA[No preceding newline]]>\n" ~
       "    <![CDATA[some data\n" ~
       "        with a newline]]>\n" ~
       "    <![CDATA[Another newline\n" ~
       "but no indent]]>\n" ~
       "    <tag>\n" ~
       "        <![CDATA[ Deeper data <><> ]]>\n" ~
       "    </tag>\n" ~
       "</root>");

// ]]> is not legal in a CDATA section.
assertThrown!XMLWritingException(writer.writeCDATA("]]>"));

// Unchanged after an XMLWritingException is thrown.
assert(writer.output.data ==
       "<root>\n" ~
       "    <![CDATA[see data run]]>\n" ~
       "    <![CDATA[More data]]><![CDATA[No preceding newline]]>\n" ~
       "    <![CDATA[some data\n" ~
       "        with a newline]]>\n" ~
       "    <![CDATA[Another newline\n" ~
       "but no indent]]>\n" ~
       "    <tag>\n" ~
       "        <![CDATA[ Deeper data <><> ]]>\n" ~
       "    </tag>\n" ~
       "</root>");
void writePI(R)(R name, Newline newline = Newline.yes)
if(isForwardRange!R && isSomeChar!(ElementType!R));
void writePI(R1, R2)(R1 name, R2 text, Newline newline = Newline.yes, InsertIndent insertIndent = InsertIndent.yes)
if(isForwardRange!R1 && isSomeChar!(ElementType!R1) && isForwardRange!R2 && isSomeChar!(ElementType!R2));
void writePI(R1, R2)(R1 name, R2 text, InsertIndent insertIndent, Newline newline = Newline.yes)
if(isForwardRange!R1 && isSomeChar!(ElementType!R1) && isForwardRange!R2 && isSomeChar!(ElementType!R2));
Writes a parsing instruction to the output range.
Parameters:
R name The name of the parsing instruction.
R2 text The text of the parsing instruction.
Newline newline Whether a newline followed by an indent will be written to the output range before the processing instruction.
InsertIndent insertIndent Whether an indent will be inserted after each newline within the text.
Throws: XMLWritingException if the given name or text is not legal in an XML processing instruction.
Examples:
import std.array : appender;
import std.exception : assertThrown;

auto writer = xmlWriter(appender!string());

writer.writePI("pi", Newline.no);
writer.writeStartTag("root");
writer.writePI("Poirot", "has a cane");
writer.writePI("Sherlock");
writer.writePI("No", "preceding newline", Newline.no);
writer.writePI("Ditto", Newline.no);
writer.writePI("target", "some data\nwith a newline");
writer.writePI("name", "Another newline\nbut no indent",
               InsertIndent.no);
writer.writeStartTag("tag");
writer.writePI("Deep", "Thought");
writer.writeEndTag("tag");
writer.writeEndTag("root");

assert(writer.output.data ==
       "<?pi?>\n" ~
       "<root>\n" ~
       "    <?Poirot has a cane?>\n" ~
       "    <?Sherlock?><?No preceding newline?><?Ditto?>\n" ~
       "    <?target some data\n" ~
       "        with a newline?>\n" ~
       "    <?name Another newline\n" ~
       "but no indent?>\n" ~
       "    <tag>\n" ~
       "        <?Deep Thought?>\n" ~
       "    </tag>\n" ~
       "</root>");

// The name xml (no matter the casing) is illegal as a name for
// processing instructions (so that it can't be confused for the
// optional <?xml...> declaration at the top of an XML document).
assertThrown!XMLWritingException(writer.writePI("xml", "bar"));

// ! is not legal in a processing instruction's name.
assertThrown!XMLWritingException(writer.writePI("!", "bar"));

// ?> is not legal in a processing instruction.
assertThrown!XMLWritingException(writer.writePI("foo", "?>"));

// Unchanged after an XMLWritingException is thrown.
assert(writer.output.data ==
       "<?pi?>\n" ~
       "<root>\n" ~
       "    <?Poirot has a cane?>\n" ~
       "    <?Sherlock?><?No preceding newline?><?Ditto?>\n" ~
       "    <?target some data\n" ~
       "        with a newline?>\n" ~
       "    <?name Another newline\n" ~
       "but no indent?>\n" ~
       "    <tag>\n" ~
       "        <?Deep Thought?>\n" ~
       "    </tag>\n" ~
       "</root>");
const pure nothrow @nogc @property @safe int tagDepth();
The current depth of the tag stack.
Examples:
import std.array : appender;

auto writer = xmlWriter(appender!string());
assert(writer.tagDepth == 0);

writer.writeStartTag("root", Newline.no);
assert(writer.tagDepth == 1);
assert(writer.output.data == "<root>");

writer.writeStartTag("a");
assert(writer.tagDepth == 2);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>");

// The tag depth is increased as soon as a start tag is opened, so
// any calls to writeIndent or writeAttr while a start tag is open
// will use the same tag depth as the children of the start tag.
writer.openStartTag("b");
assert(writer.tagDepth == 3);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>\n" ~
       "        <b");

writer.closeStartTag();
assert(writer.tagDepth == 3);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>\n" ~
       "        <b>");

writer.writeEndTag("b");
assert(writer.tagDepth == 2);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>\n" ~
       "        <b>\n" ~
       "        </b>");

// Only start tags and end tags affect the tag depth.
writer.writeComment("comment");
assert(writer.tagDepth == 2);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>\n" ~
       "        <b>\n" ~
       "        </b>\n" ~
       "        <!--comment-->");

writer.writeEndTag("a");
assert(writer.tagDepth == 1);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>\n" ~
       "        <b>\n" ~
       "        </b>\n" ~
       "        <!--comment-->\n" ~
       "    </a>");

writer.writeEndTag("root");
assert(writer.tagDepth == 0);
assert(writer.output.data ==
       "<root>\n" ~
       "    <a>\n" ~
       "        <b>\n" ~
       "        </b>\n" ~
       "        <!--comment-->\n" ~
       "    </a>\n" ~
       "</root>");
const pure nothrow @nogc @property @safe string baseIndent();
The text that will be written for each level of the tag depth when an indent is written.
Examples:
import std.array : appender;
{
    auto writer = xmlWriter(appender!string());
    assert(writer.baseIndent == "    ");
}
{
    auto writer = xmlWriter(appender!string(), "  ");
    assert(writer.baseIndent == "  ");
}
{
    auto writer = xmlWriter(appender!string(), "\t");
    assert(writer.baseIndent == "\t");
}
void writeIndent();
Writes a newline followed by an indent to the output range.
In general, the various write functions already provide this functionality via their Newline parameter, but there may be cases where it is desirable to insert a newline independently of calling a write function.
If arbitrary whitespace needs to be inserted, then output can be used to get at the output range so that it can be written to directly.
Examples:
import std.array : appender;

auto writer = xmlWriter(appender!string());
writer.writeStartTag("root", Newline.no);
assert(writer.output.data == "<root>");

writer.writeIndent();
assert(writer.output.data ==
       "<root>\n" ~
       "    ");

writer.writeStartTag("foo");
assert(writer.output.data ==
       "<root>\n" ~
       "    \n" ~
       "    <foo>");

writer.writeIndent();
assert(writer.output.data ==
       "<root>\n" ~
       "    \n" ~
       "    <foo>\n" ~
       "        ");

writer.writeText("some text");
assert(writer.output.data ==
       "<root>\n" ~
       "    \n" ~
       "    <foo>\n" ~
       "        \n" ~
       "        some text");

writer.writeIndent();
assert(writer.output.data ==
       "<root>\n" ~
       "    \n" ~
       "    <foo>\n" ~
       "        \n" ~
       "        some text\n" ~
       "        ");

writer.writeEndTag();
writer.writeEndTag();
assert(writer.output.data ==
       "<root>\n" ~
       "    \n" ~
       "    <foo>\n" ~
       "        \n" ~
       "        some text\n" ~
       "        \n" ~
       "    </foo>\n" ~
       "</root>");
pure nothrow @nogc @property ref @safe auto output();
Provides access to the output range that's used by XMLWriter.
Note that any is data written to the output range without using XMLWriter could result in invalid XML.
This property is here primarily to provide easy access to the output range when XMLWriter is done writing (e.g. to get at its data member if it's a std.array.Appender), but programs can use it to write other data (such as whitespace other than the indent) to the output range while XMLWriter is still writing so long as it's understood that unlike when the XMLWriter's write functions are called, calling put on the output range directly is unchecked and therefore does risk making the XML invalid.
Also, depending on the type of the output range, copying it will cause problems (e.g. if it's not a reference type, writing to a copy may not write to the output range inside of XMLWriter), So in general, if the output range is going to be written to, it should be written to by using output directly rather than assigning it to a variable.
this(OR output, string baseIndent = " ");
In general, it's more user-friendly to use xmlWriter rather than calling the constructor directly, because then the type of the output range can be inferred. However, in the case where a pointer is desirable, then the constructor needs to be called instead of xmlWriter.
Parameters:
OR output The output range that the XML will be written to.
string baseIndent Optional argument indicating the base indent to be used when an indent is inserted after a newline in the XML (with the actual indent being the base indent inserted once for each level of the tagDepth). The default is four spaces.
See Also: xmlWriter
Examples:
import std.array : Appender, appender;

auto writer = new XMLWriter!(Appender!string)(appender!string());
writer.writeStartTag("root", Newline.no, EmptyTag.yes);
assert(writer.output.data == "<root/>");
void writeXMLDecl(S, OR)(ref OR output)
if(isOutputRange!(OR, char) && isSomeString!S);
Writes the <?xml...?> declaration to the given output range. If it's going to be used in conjunction with XMLWriter, then either writeXMLDecl will need to be called before constructing the XMLWriter, or XMLWriter.output will need to be used to write to the output range before writing anything else using the XMLWriter. XMLWriter expects to be writing XML after the <?xml...?> and <!DOCTYPE...> declarations (assuming they're present at all), and it is invalid to put a <?xml...?> declaration anywhere but at the very beginning of an XML document.
Parameters:
S The string type used to infer the encoding type. Ideally, it would be inferred from the type of the output range, but unfortunately, the output range API does not provide that functionality. If S does not match the encoding of the output range, then the result will be invalid XML.
OR output The output range to write to.
Examples:
import std.array : appender;

{
    auto app = appender!string();
    app.writeXMLDecl!string();
    assert(app.data == `<?xml version="1.0" encoding="UTF-8"?>`);
}
{
    auto app = appender!wstring();
    app.writeXMLDecl!wstring();
    assert(app.data == `<?xml version="1.0" encoding="UTF-16"?>`w);
}
{
    auto app = appender!dstring();
    app.writeXMLDecl!dstring();
    assert(app.data == `<?xml version="1.0" encoding="UTF-32"?>`d);
}

// This would be invalid XML, because the output range contains UTF-8, but
// writeXMLDecl is told to write that the encoding is UTF-32.
{
    auto app = appender!string();
    app.writeXMLDecl!dstring();
    assert(app.data == `<?xml version="1.0" encoding="UTF-32"?>`);
}
void writeTaggedText(XW, R)(ref XW writer, string name, R text, Newline newline = Newline.yes, InsertIndent insertIndent = InsertIndent.yes)
if(isInstanceOf!(XMLWriter, XW) && isForwardRange!R && isSomeChar!(ElementType!R));
void writeTaggedText(XW, R)(ref XW writer, string name, R text, InsertIndent insertIndent, Newline newline = Newline.yes)
if(isInstanceOf!(XMLWriter, XW) && isForwardRange!R && isSomeChar!(ElementType!R));
Helper function for writing text which has a start tag and end tag on each side and no attributes so that it can be done with one function call instead of three.
writeTaggedText is essentially equivalent to calling
writer.writeStartTag(name, newline);
writer.writeText(text, insertIndent, Newline.no);
writer.writeEndTag(Newline.no);
with the difference being that both the name and text are validated before any data is written. So, if the text is invalid XML, then nothing will have been written to the output range when the exception is thrown (whereas if each function were called individually, then the start tag would have been written before the exception was thrown from writeText).
If more control is needed over the formatting, or if attributes are needed on the start tag, then the functions will have to be called separately instead of calling writeTaggedText.
Parameters:
XW writer The XMLWriter to write to.
string name The name of the start tag.
R text The text to write between the start and end tags.
Newline newline Whether a newline followed by an indent will be written to the output range before the start tag.
InsertIndent insertIndent Whether an indent will be inserted after each newline within the text.
Throws: XMLWritingException if the given name is an invalid XML tag name or if the given text contains any characters or sequence of characters which are not legal in the text portion of an XML document. dxml.util.encodeText can be used to encode any characters that are not legal in their literal form in the text but are legal as entity references.
Examples:
import std.array : appender;

{
    auto writer = xmlWriter(appender!string());
    writer.writeStartTag("root", Newline.no);
    writer.writeTaggedText("foo", "Some text between foos");
    writer.writeEndTag("root");

    assert(writer.output.data ==
           "<root>\n" ~
           "    <foo>Some text between foos</foo>\n" ~
           "</root>");
}

// With Newline.no
{
    auto writer = xmlWriter(appender!string());
    writer.writeStartTag("root", Newline.no);
    writer.writeTaggedText("foo", "Some text between foos", Newline.no);
    writer.writeEndTag("root");

    assert(writer.output.data ==
           "<root><foo>Some text between foos</foo>\n" ~
           "</root>");
}

// With InsertIndent.yes
{
    auto writer = xmlWriter(appender!string());
    writer.writeStartTag("root", Newline.no);
    writer.writeTaggedText("foo", "Some text\nNext line");
    writer.writeEndTag("root");

    assert(writer.output.data ==
           "<root>\n" ~
           "    <foo>Some text\n" ~
           "        Next line</foo>\n" ~
           "</root>");
}

// With InsertIndent.no
{
    auto writer = xmlWriter(appender!string());
    writer.writeStartTag("root", Newline.no);
    writer.writeTaggedText("foo", "Some text\nNext line", InsertIndent.no);
    writer.writeEndTag("root");

    assert(writer.output.data ==
           "<root>\n" ~
           "    <foo>Some text\n" ~
           "Next line</foo>\n" ~
           "</root>");
}