Introduce o3tl::string_view.hxx approximation of C++17 <string_view>

...and use it in configmgr/source/writemodfile.hxx

Change-Id: Ie683dc21010ed45cc454ff89bea0376994b351f2
Reviewed-on: https://gerrit.libreoffice.org/36270
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
diff --git a/configmgr/source/winreg.cxx b/configmgr/source/winreg.cxx
index c48a85e..76d7abe 100644
--- a/configmgr/source/winreg.cxx
+++ b/configmgr/source/winreg.cxx
@@ -149,7 +149,7 @@ void dumpWindowsRegistryKey(HKEY hKey, OUString const & aKeyName, TempFile &aFil
            bool bHasNode = false;
            sal_Int32 nCloseNode = 0;

            writeData(aFileHandle, "<item oor:path=\"");
            aFileHandle.writeString("<item oor:path=\"");
            for(sal_Int32 nIndex = 0;; ++nIndex)
            {
                OUString aNextPathPart = aPathAndNodes.getToken(nIndex, '\\');
@@ -160,13 +160,13 @@ void dumpWindowsRegistryKey(HKEY hKey, OUString const & aKeyName, TempFile &aFil
                    {
                        bHasNode = true;
                        nCloseNode++;
                        writeData(aFileHandle, "\"><node oor:name=\"");
                        aFileHandle.writeString("\"><node oor:name=\"");
                        sal_Int32 nCommandSeparator = aNextPathPart.lastIndexOf('#');
                        if(nCommandSeparator != -1)
                        {
                            OUString aNodeOp = aNextPathPart.copy(nCommandSeparator + 1);
                            writeAttributeValue(aFileHandle, aNextPathPart.copy(0, nCommandSeparator - 1));
                            writeData(aFileHandle, "\" oor:op=\"");
                            aFileHandle.writeString("\" oor:op=\"");
                            writeAttributeValue(aFileHandle, aNodeOp);
                        }
                        else
@@ -176,33 +176,34 @@ void dumpWindowsRegistryKey(HKEY hKey, OUString const & aKeyName, TempFile &aFil
                    }
                    else
                    {
                        writeAttributeValue(aFileHandle, "/" + aNextPathPart);
                        writeAttributeValue(
                            aFileHandle, OUString("/" + aNextPathPart));
                    }
                }
                else
                {
                    writeData(aFileHandle, "\">");
                    aFileHandle.writeString("\">");
                    break;
                }
            }

            writeData(aFileHandle, "<prop oor:name=\"");
            aFileHandle.writeString("<prop oor:name=\"");
            writeAttributeValue(aFileHandle, aProp);
            writeData(aFileHandle, "\"");
            aFileHandle.writeString("\"");
            if(!aType.isEmpty())
            {
                writeData(aFileHandle, " oor:type=\"");
                aFileHandle.writeString(" oor:type=\"");
                writeAttributeValue(aFileHandle, aType);
                writeData(aFileHandle, "\"");
                aFileHandle.writeString("\"");
            }
            if(bFinal)
                writeData(aFileHandle, " oor:finalized=\"true\"");
            writeData(aFileHandle, "><value>");
                aFileHandle.writeString(" oor:finalized=\"true\"");
            aFileHandle.writeString("><value>");
            writeValueContent(aFileHandle, aValue);
            writeData(aFileHandle, "</value></prop>");
            aFileHandle.writeString("</value></prop>");
            for(; nCloseNode > 0; nCloseNode--)
                writeData(aFileHandle, "</node>");
            writeData(aFileHandle, "</item>\n");
                aFileHandle.writeString("</node>");
            aFileHandle.writeString("</item>\n");
        }
        RegCloseKey(hCurKey);
    }
@@ -235,14 +236,13 @@ bool dumpWindowsRegistry(OUString* pFileURL, WinRegType eType)
            "cannot create temporary file");
    }
    aFileHandle.url = *pFileURL;
    writeData(
        aFileHandle,
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
            " xmlns:oor=\"http://openoffice.org/2001/registry\""
            " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
            " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
    aFileHandle.writeString(
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
        " xmlns:oor=\"http://openoffice.org/2001/registry\""
        " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
        " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
    dumpWindowsRegistryKey(hKey, "", aFileHandle);
    writeData(aFileHandle, "</oor:items>");
    aFileHandle.writeString("</oor:items>");
    oslFileError e = aFileHandle.closeWithoutUnlink();
    if (e != osl_File_E_None)
        SAL_WARN("configmgr", "osl_closeFile failed with " << +e);
diff --git a/configmgr/source/writemodfile.cxx b/configmgr/source/writemodfile.cxx
index a1e545e..e34b604 100644
--- a/configmgr/source/writemodfile.cxx
+++ b/configmgr/source/writemodfile.cxx
@@ -20,12 +20,15 @@
#include <sal/config.h>

#include <cassert>
#include <cstddef>
#include <limits>

#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/uno/RuntimeException.hpp>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/uno/XInterface.hpp>
#include <o3tl/string_view.hxx>
#include <osl/file.h>
#include <osl/file.hxx>
#include <rtl/string.h>
@@ -57,13 +60,11 @@ class Components;

namespace {

OString convertToUtf8(
    OUString const & text, sal_Int32 offset, sal_Int32 length)
{
    assert(offset <= text.getLength() && text.getLength() - offset >= length);
OString convertToUtf8(o3tl::u16string_view text) {
    OString s;
    assert(text.size() <= sal_uInt32(std::numeric_limits<sal_Int32>::max()));
    if (!rtl_convertUStringToString(
            &s.pData, text.pData->buffer + offset, length,
            &s.pData, text.data(), text.size(),
            RTL_TEXTENCODING_UTF8,
            (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
             RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
@@ -139,43 +140,38 @@ oslFileError TempFile::flush() {
    return e;
}

void TempFile::writeString(char const *begin, sal_Int32 length) {
    buffer.append(begin, length);
void TempFile::writeString(o3tl::string_view text) {
    buffer.append(text.data(), text.size());
    if (buffer.getLength() > 0x10000)
        flush();
}

namespace {

void writeData_(TempFile &handle, char const * begin, sal_Int32 length) {
    assert(length >= 0);
    handle.writeString(begin, length);
}

void writeValueContent_(TempFile &, bool) = delete;
    // silence loplugin:salbool
void writeValueContent_(TempFile &handle, sal_Bool value) {
    if (value) {
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("true"));
        handle.writeString("true");
    } else {
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("false"));
        handle.writeString("false");
    }
}

void writeValueContent_(TempFile &handle, sal_Int16 value) {
    writeData(handle, OString::number(value));
    handle.writeString(OString::number(value));
}

void writeValueContent_(TempFile &handle, sal_Int32 value) {
    writeData(handle, OString::number(value));
    handle.writeString(OString::number(value));
}

void writeValueContent_(TempFile &handle, sal_Int64 value) {
    writeData(handle, OString::number(value));
    handle.writeString(OString::number(value));
}

void writeValueContent_(TempFile &handle, double value) {
    writeData(handle, OString::number(value));
    handle.writeString(OString::number(value));
}

void writeValueContent_(TempFile &handle, const OUString& value) {
@@ -189,48 +185,49 @@ void writeValueContent_(
        static char const hexDigit[16] = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
            'D', 'E', 'F' };
        writeData_(handle, hexDigit + ((value[i] >> 4) & 0xF), 1);
        writeData_(handle, hexDigit + (value[i] & 0xF), 1);
        handle.writeString(
            o3tl::string_view(hexDigit + ((value[i] >> 4) & 0xF), 1));
        handle.writeString(o3tl::string_view(hexDigit + (value[i] & 0xF), 1));
    }
}

template< typename T > void writeSingleValue(
    TempFile &handle, css::uno::Any const & value)
{
    writeData_(handle, RTL_CONSTASCII_STRINGPARAM(">"));
    handle.writeString(">");
    T val = T();
    value >>= val;
    writeValueContent_(handle, val);
    writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</value>"));
    handle.writeString("</value>");
}

template< typename T > void writeListValue(
    TempFile &handle, css::uno::Any const & value)
{
    writeData_(handle, RTL_CONSTASCII_STRINGPARAM(">"));
    handle.writeString(">");
    css::uno::Sequence< T > val;
    value >>= val;
    for (sal_Int32 i = 0; i < val.getLength(); ++i) {
        if (i != 0) {
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM(" "));
            handle.writeString(" ");
        }
        writeValueContent_(handle, val[i]);
    }
    writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</value>"));
    handle.writeString("</value>");
}

template< typename T > void writeItemListValue(
    TempFile &handle, css::uno::Any const & value)
{
    writeData_(handle, RTL_CONSTASCII_STRINGPARAM(">"));
    handle.writeString(">");
    css::uno::Sequence< T > val;
    value >>= val;
    for (sal_Int32 i = 0; i < val.getLength(); ++i) {
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<it>"));
        handle.writeString("<it>");
        writeValueContent_(handle, val[i]);
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</it>"));
        handle.writeString("</it>");
    }
    writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</value>"));
    handle.writeString("</value>");
}

void writeValue(TempFile &handle, Type type, css::uno::Any const & value) {
@@ -284,7 +281,7 @@ void writeValue(TempFile &handle, Type type, css::uno::Any const & value) {

void writeNode(
    Components & components, TempFile &handle,
    rtl::Reference< Node > const & parent, OUString const & name,
    rtl::Reference< Node > const & parent, o3tl::u16string_view name,
    rtl::Reference< Node > const & node)
{
    static xmlreader::Span const typeNames[] = {
@@ -308,51 +305,49 @@ void writeNode(
    case Node::KIND_PROPERTY:
        {
            PropertyNode * prop = static_cast< PropertyNode * >(node.get());
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<prop oor:name=\""));
            handle.writeString("<prop oor:name=\"");
            writeAttributeValue(handle, name);
            writeData_(
                handle, RTL_CONSTASCII_STRINGPARAM("\" oor:op=\"fuse\""));
            handle.writeString("\" oor:op=\"fuse\"");
            Type type = prop->getStaticType();
            Type dynType = getDynamicType(prop->getValue(components));
            assert(dynType != TYPE_ERROR);
            if (type == TYPE_ANY) {
                type = dynType;
                if (type != TYPE_NIL) {
                    writeData_(
                        handle, RTL_CONSTASCII_STRINGPARAM(" oor:type=\""));
                    writeData_(
                        handle, typeNames[type].begin, typeNames[type].length);
                    writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\""));
                    handle.writeString(" oor:type=\"");
                    handle.writeString(
                        o3tl::string_view(
                            typeNames[type].begin, typeNames[type].length));
                    handle.writeString("\"");
                }
            }
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("><value"));
            handle.writeString("><value");
            if (dynType == TYPE_NIL) {
                writeData_(
                    handle, RTL_CONSTASCII_STRINGPARAM(" xsi:nil=\"true\"/>"));
                handle.writeString(" xsi:nil=\"true\"/>");
            } else {
                writeValue(handle, type, prop->getValue(components));
            }
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</prop>"));
            handle.writeString("</prop>");
        }
        break;
    case Node::KIND_LOCALIZED_PROPERTY:
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<prop oor:name=\""));
        handle.writeString("<prop oor:name=\"");
        writeAttributeValue(handle, name);
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\" oor:op=\"fuse\">"));
        handle.writeString("\" oor:op=\"fuse\">");
        for (NodeMap::const_iterator i(node->getMembers().begin());
             i != node->getMembers().end(); ++i)
        {
            writeNode(components, handle, node, i->first, i->second);
        }
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</prop>"));
        handle.writeString("</prop>");
        break;
    case Node::KIND_LOCALIZED_VALUE:
        {
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<value"));
            if (!name.isEmpty()) {
                writeData_(handle, RTL_CONSTASCII_STRINGPARAM(" xml:lang=\""));
            handle.writeString("<value");
            if (!name.empty()) {
                handle.writeString(" xml:lang=\"");
                writeAttributeValue(handle, name);
                writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\""));
                handle.writeString("\"");
            }
            Type type = static_cast< LocalizedPropertyNode * >(parent.get())->
                getStaticType();
@@ -363,16 +358,15 @@ void writeNode(
            if (type == TYPE_ANY) {
                type = dynType;
                if (type != TYPE_NIL) {
                    writeData_(
                        handle, RTL_CONSTASCII_STRINGPARAM(" oor:type=\""));
                    writeData_(
                        handle, typeNames[type].begin, typeNames[type].length);
                    writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\""));
                    handle.writeString(" oor:type=\"");
                    handle.writeString(
                        o3tl::string_view(
                            typeNames[type].begin, typeNames[type].length));
                    handle.writeString("\"");
                }
            }
            if (dynType == TYPE_NIL) {
                writeData_(
                    handle, RTL_CONSTASCII_STRINGPARAM(" xsi:nil=\"true\"/>"));
                handle.writeString(" xsi:nil=\"true\"/>");
            } else {
                writeValue(handle, type, value);
            }
@@ -380,19 +374,18 @@ void writeNode(
        break;
    case Node::KIND_GROUP:
    case Node::KIND_SET:
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<node oor:name=\""));
        handle.writeString("<node oor:name=\"");
        writeAttributeValue(handle, name);
        if (!node->getTemplateName().isEmpty()) { // set member
            writeData_(
                handle, RTL_CONSTASCII_STRINGPARAM("\" oor:op=\"replace"));
            handle.writeString("\" oor:op=\"replace");
        }
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\">"));
        handle.writeString("\">");
        for (NodeMap::const_iterator i(node->getMembers().begin());
             i != node->getMembers().end(); ++i)
        {
            writeNode(components, handle, node, i->first, i->second);
        }
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</node>"));
        handle.writeString("</node>");
        break;
    case Node::KIND_ROOT:
        assert(false); // this cannot happen
@@ -422,48 +415,40 @@ void writeModifications(
    if (modifications.children.empty()) {
        assert(parent.is());
            // components themselves have no parent but must have children
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<item oor:path=\""));
        handle.writeString("<item oor:path=\"");
        writeAttributeValue(handle, parentPathRepresentation);
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\">"));
        handle.writeString("\">");
        if (node.is()) {
            writeNode(components, handle, parent, nodeName, node);
        } else {
            switch (parent->kind()) {
            case Node::KIND_LOCALIZED_PROPERTY:
                writeData_(handle, RTL_CONSTASCII_STRINGPARAM("<value"));
                handle.writeString("<value");
                if (!nodeName.isEmpty()) {
                    writeData_(
                        handle, RTL_CONSTASCII_STRINGPARAM(" xml:lang=\""));
                    handle.writeString(" xml:lang=\"");
                    writeAttributeValue(handle, nodeName);
                    writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\""));
                    handle.writeString("\"");
                }
                writeData_(
                    handle, RTL_CONSTASCII_STRINGPARAM(" oor:op=\"remove\"/>"));
                handle.writeString(" oor:op=\"remove\"/>");
                break;
            case Node::KIND_GROUP:
                assert(
                    static_cast< GroupNode * >(parent.get())->isExtensible());
                writeData_(
                    handle, RTL_CONSTASCII_STRINGPARAM("<prop oor:name=\""));
                handle.writeString("<prop oor:name=\"");
                writeAttributeValue(handle, nodeName);
                writeData_(
                    handle,
                    RTL_CONSTASCII_STRINGPARAM("\" oor:op=\"remove\"/>"));
                handle.writeString("\" oor:op=\"remove\"/>");
                break;
            case Node::KIND_SET:
                writeData_(
                    handle, RTL_CONSTASCII_STRINGPARAM("<node oor:name=\""));
                handle.writeString("<node oor:name=\"");
                writeAttributeValue(handle, nodeName);
                writeData_(
                    handle,
                    RTL_CONSTASCII_STRINGPARAM("\" oor:op=\"remove\"/>"));
                handle.writeString("\" oor:op=\"remove\"/>");
                break;
            default:
                assert(false); // this cannot happen
                break;
            }
        }
        writeData_(handle, RTL_CONSTASCII_STRINGPARAM("</item>\n"));
        handle.writeString("</item>\n");
    } else {
        assert(node.is());
        OUString pathRep(
@@ -496,91 +481,85 @@ void writeModifications(

}

void writeData(TempFile &handle, OString const & text) {
    writeData_(handle, text.getStr(), text.getLength());
}

void writeAttributeValue(TempFile &handle, OUString const & value) {
    sal_Int32 i = 0;
    sal_Int32 j = i;
    for (; j < value.getLength(); ++j) {
void writeAttributeValue(TempFile &handle, o3tl::u16string_view value) {
    std::size_t i = 0;
    std::size_t j = i;
    for (; j != value.size(); ++j) {
        assert(
            value[j] == 0x0009 || value[j] == 0x000A || value[j] == 0x000D ||
            (value[j] >= 0x0020 && value[j] != 0xFFFE && value[j] != 0xFFFF));
        switch(value[j]) {
        case '\x09':
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&#9;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&#9;");
            i = j + 1;
            break;
        case '\x0A':
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&#xA;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&#xA;");
            i = j + 1;
            break;
        case '\x0D':
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&#xD;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&#xD;");
            i = j + 1;
            break;
        case '"':
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&quot;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&quot;");
            i = j + 1;
            break;
        case '&':
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&amp;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&amp;");
            i = j + 1;
            break;
        case '<':
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&lt;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&lt;");
            i = j + 1;
            break;
        default:
            break;
        }
    }
    writeData(handle, convertToUtf8(value, i, j - i));
    handle.writeString(convertToUtf8(value.substr(i, j - i)));
}

void writeValueContent(TempFile &handle, OUString const & value) {
    sal_Int32 i = 0;
    sal_Int32 j = i;
    for (; j < value.getLength(); ++j) {
        sal_Unicode c = value[j];
void writeValueContent(TempFile &handle, o3tl::u16string_view value) {
    std::size_t i = 0;
    std::size_t j = i;
    for (; j != value.size(); ++j) {
        char16_t c = value[j];
        if ((c < 0x0020 && c != 0x0009 && c != 0x000A && c != 0x000D) ||
            c == 0xFFFE || c == 0xFFFF)
        {
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(
                handle, RTL_CONSTASCII_STRINGPARAM("<unicode oor:scalar=\""));
            writeData(
                handle, OString::number(c));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("\"/>"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("<unicode oor:scalar=\"");
            handle.writeString(OString::number(c));
            handle.writeString("\"/>");
            i = j + 1;
        } else if (c == '\x0D') {
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&#xD;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&#xD;");
            i = j + 1;
        } else if (c == '&') {
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&amp;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&amp;");
            i = j + 1;
        } else if (c == '<') {
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&lt;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&lt;");
            i = j + 1;
        } else if (c == '>') {
            // "MUST, for compatibility, be escaped [...] when it appears in the
            // string ']]>'":
            writeData(handle, convertToUtf8(value, i, j - i));
            writeData_(handle, RTL_CONSTASCII_STRINGPARAM("&gt;"));
            handle.writeString(convertToUtf8(value.substr(i, j - i)));
            handle.writeString("&gt;");
            i = j + 1;
        }
    }
    writeData(handle, convertToUtf8(value, i, j - i));
    handle.writeString(convertToUtf8(value.substr(i, j - i)));
}

void writeModFile(
@@ -617,13 +596,11 @@ void writeModFile(
        throw css::uno::RuntimeException(
            "cannot create temporary file in " + dir);
    }
    writeData_(
        tmp,
        RTL_CONSTASCII_STRINGPARAM(
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
            " xmlns:oor=\"http://openoffice.org/2001/registry\""
            " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
            " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"));
    tmp.writeString(
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
        " xmlns:oor=\"http://openoffice.org/2001/registry\""
        " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
        " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
    //TODO: Do not write back information about those removed items that did not
    // come from the .xcs/.xcu files, anyway (but had been added dynamically
    // instead):
@@ -658,7 +635,7 @@ void writeModFile(
            data.getComponents().findNode(Data::NO_LAYER, j->first),
            j->second);
    }
    writeData_(tmp, RTL_CONSTASCII_STRINGPARAM("</oor:items>\n"));
    tmp.writeString("</oor:items>\n");
    tmp.closeAndRename(url);
}

diff --git a/configmgr/source/writemodfile.hxx b/configmgr/source/writemodfile.hxx
index d1a9348..a8925ab 100644
--- a/configmgr/source/writemodfile.hxx
+++ b/configmgr/source/writemodfile.hxx
@@ -21,6 +21,8 @@
#define INCLUDED_CONFIGMGR_SOURCE_WRITEMODFILE_HXX

#include <sal/config.h>

#include <o3tl/string_view.hxx>
#include <rtl/strbuf.hxx>

namespace configmgr {
@@ -41,16 +43,15 @@ struct TempFile {
#ifdef _WIN32
    oslFileError closeWithoutUnlink();
#endif
    void writeString(char const *begin, sal_Int32 length);
    void writeString(o3tl::string_view text);

private:
    TempFile(const TempFile&) = delete;
    TempFile& operator=(const TempFile&) = delete;
};

void writeData(TempFile &handle, OString const & text);
void writeAttributeValue(TempFile &handle, OUString const & value);
void writeValueContent(TempFile &handle, OUString const & value);
void writeAttributeValue(TempFile &handle, o3tl::u16string_view value);
void writeValueContent(TempFile &handle, o3tl::u16string_view value);

void writeModFile(
    Components & components, OUString const & url, Data const & data);
diff --git a/include/o3tl/string_view.hxx b/include/o3tl/string_view.hxx
new file mode 100644
index 0000000..9230909
--- /dev/null
+++ b/include/o3tl/string_view.hxx
@@ -0,0 +1,867 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#ifndef INCLUDED_O3TL_STRING_VIEW_HXX
#define INCLUDED_O3TL_STRING_VIEW_HXX

#include <sal/config.h>

#include <algorithm>
#include <cstddef>
#include <ios>
#include <iterator>
#include <ostream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>

#include <config_global.h>
#include <rtl/string.hxx>
#include <rtl/ustring.hxx>
#include <sal/types.h>

// An approximation of C++17 <string_view>, including implicit conversion
// from rtl::OString and rtl::OUString.

namespace o3tl {

namespace detail {

template<typename T> struct CharPtrDetector {
    static constexpr bool ok = false;
};

template<> struct CharPtrDetector<char *> {
    static constexpr bool ok = true;
};

template<> struct CharPtrDetector<char const *> {
    static constexpr bool ok = true;
};

template<typename T> struct NonConstCharArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct NonConstCharArrayDetector<char [N]> {
    static constexpr bool ok = true;
};

template<typename T> struct ConstCharArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct ConstCharArrayDetector<char const[N]> {
    static constexpr bool ok = true;
    static constexpr std::size_t length = N - 1;
};

template<typename T> struct Char16PtrDetector {
    static constexpr bool ok = false;
};

template<> struct Char16PtrDetector<char16_t *> {
    static constexpr bool ok = true;
};

template<> struct Char16PtrDetector<char16_t const *> {
    static constexpr bool ok = true;
};

template<typename T> struct NonConstChar16ArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct NonConstChar16ArrayDetector<char16_t [N]> {
    static constexpr bool ok = true;
};

template<typename T> struct ConstChar16ArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct ConstChar16ArrayDetector<char16_t const[N]> {
    static constexpr bool ok = true;
    static constexpr std::size_t length = N - 1;
};

template<typename T> struct Char32PtrDetector {
    static constexpr bool ok = false;
};

template<> struct Char32PtrDetector<char32_t *> {
    static constexpr bool ok = true;
};

template<> struct Char32PtrDetector<char32_t const *> {
    static constexpr bool ok = true;
};

template<typename T> struct NonConstChar32ArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct NonConstChar32ArrayDetector<char32_t [N]> {
    static constexpr bool ok = true;
};

template<typename T> struct ConstChar32ArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct ConstChar32ArrayDetector<char32_t const[N]> {
    static constexpr bool ok = true;
    static constexpr std::size_t length = N - 1;
};

template<typename T> struct WcharPtrDetector {
    static constexpr bool ok = false;
};

template<> struct WcharPtrDetector<wchar_t *> {
    static constexpr bool ok = true;
};

template<> struct WcharPtrDetector<wchar_t const *> {
    static constexpr bool ok = true;
};

template<typename T> struct NonConstWcharArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct NonConstWcharArrayDetector<wchar_t [N]> {
    static constexpr bool ok = true;
};

template<typename T> struct ConstWcharArrayDetector {
    static constexpr bool ok = false;
};

template<std::size_t N> struct ConstWcharArrayDetector<wchar_t const[N]> {
    static constexpr bool ok = true;
    static constexpr std::size_t length = N - 1;
};

}

#if defined _MSC_VER
#pragma warning(push, 1)
#pragma warning(disable: 4814) // in C++14 'constexpr' will not imply 'const'
#endif

template<typename charT, typename traits = std::char_traits<charT>>
class basic_string_view {
public:
    using traits_type = traits;
    using value_type = charT;
    using pointer = value_type *;
    using const_pointer = value_type const *;
    using reference = value_type &;
    using const_reference = value_type const &;
    using const_iterator = const_pointer;
    using iterator = const_iterator;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
    using reverse_iterator = const_reverse_iterator;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;

    static constexpr size_type npos = size_type(-1);

    constexpr basic_string_view() noexcept: data_(nullptr), size_(0) {}

    constexpr basic_string_view(basic_string_view const &) noexcept = default;

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    basic_string_view & operator =(basic_string_view const & other) noexcept
#if defined _MSC_VER && _MSC_VER <= 1900 && !defined __clang__
    {
        data_ = other.data_;
        size_ = other.size_;
        return *this;
    }
#else
        = default;
#endif

    // The various character types are handled below in the "LO specifics, to
    // make up for traits::length not necessarily being constexpr yet for
    // literal arguments" section:
    template<typename T = charT> constexpr basic_string_view(
        charT const * str,
        typename std::enable_if<
            !(std::is_same<T, char>::value || std::is_same<T, char16_t>::value
              || std::is_same<T, char32_t>::value
              || std::is_same<T, wchar_t>::value),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(str), size_(traits::length(str)) {}

    constexpr basic_string_view(charT const * str, size_type len):
        data_(str), size_(len) {}

    constexpr const_iterator begin() const noexcept { return data_; }

    constexpr const_iterator end() const noexcept { return begin() + size(); }

    constexpr const_iterator cbegin() const noexcept { return begin(); }

    constexpr const_iterator cend() const noexcept { return end(); }

    constexpr const_reverse_iterator rbegin() const noexcept
    { return const_reverse_iterator(end()); }

    constexpr const_reverse_iterator rend() const noexcept
    { return const_reverse_iterator(begin()); }

    constexpr const_reverse_iterator crbegin() const noexcept
    { return rbegin(); }

    constexpr const_reverse_iterator crend() const noexcept { return rend(); }

    constexpr size_type size() const noexcept { return size_; }

    constexpr size_type length() const noexcept { return size(); }

#if !defined __clang__ || HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type max_size() const noexcept {
#if defined __clang__ // avoid constexpr issues with other, older compilers
        (void) this; // loplugin:staticmethods
#endif
        return npos - 1;
    }

    constexpr bool empty() const noexcept { return size_ == 0; }

    constexpr const_reference operator [](size_type pos) const {
#if HAVE_CXX14_CONSTEXPR
        assert(pos < size());
#endif
        return data_[pos];
    }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    const_reference at(size_type pos) const {
        if (pos >= size()) {
            throw std::out_of_range("o3tl::basic_string_view::at");
        }
        return operator [](pos);
    }

    constexpr const_reference front() const {
#if HAVE_CXX14_CONSTEXPR
        assert(!empty());
#endif
        return operator [](0);
    }

    constexpr const_reference back() const {
#if HAVE_CXX14_CONSTEXPR
        assert(!empty());
#endif
        return operator [](size() - 1);
    }

    constexpr const_pointer data() const noexcept { return data_; }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    void remove_prefix(size_type n) {
        assert(n <= size());
        data_ += n;
        size_ -= n;
    }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    void remove_suffix(size_type n) {
        assert(n <= size());
        size_ -= n;
    }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    void swap(basic_string_view & s) noexcept {
        std::swap(data_, s.data_);
        std::swap(size_, s.size_);
    }

    size_type copy(charT * s, size_type n, size_type pos = 0) const {
        if (pos > size()) {
            throw std::out_of_range("o3tl::basic_string_view::copy");
        }
        auto rlen = std::min(n, size_type(size() - pos));
        traits::copy(s, data() + pos, rlen);
        return rlen;
    }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    basic_string_view substr(size_type pos = 0, size_type n = npos) const {
        if (pos > size()) {
            throw std::out_of_range("o3tl::basic_string_view::copy");
        }
        return basic_string_view(
            data() + pos, std::min(n, size_type(size() - pos)));
    }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    int compare(basic_string_view s) const noexcept {
        auto n = traits::compare(data(), s.data(), std::min(size(), s.size()));
        return n == 0
            ? (size() < s.size() ? -1 : size() == s.size() ? 0 : 1) : n;
    }

    constexpr int compare(size_type pos1, size_type n1, basic_string_view s)
        const
    { return substr(pos1, n1).compare(s); }

    constexpr int compare(
        size_type pos1, size_type n1, basic_string_view s, size_type pos2,
        size_type n2) const
    { return substr(pos1, n1).compare(s.substr(pos2, n2)); }

    constexpr int compare(charT const * s) const
    { return compare(basic_string_view(s)); }

    constexpr int compare(size_type pos1, size_type n1, charT const * s) const
    { return substr(pos1, n1).compare(s); }

    constexpr int compare(
        size_type pos1, size_type n1, charT const * s, size_type n2) const
    { return substr(pos1, n1).compare(basic_string_view(s, n2)); }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type find(basic_string_view s, size_type pos = 0) const noexcept {
        if (s.size() <= size()) {
            for (auto xpos = pos; xpos <= size() - s.size(); ++xpos) {
                bool match = true;
                for (size_type i = 0; i != s.size(); ++i) {
                    if (!traits::eq(data_[xpos + i], s.data_[i])) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return xpos;
                }
            }
        }
        return npos;
    }

    constexpr size_type find(charT c, size_type pos = 0) const noexcept
    { return find(basic_string_view(&c, 1), pos); }

    constexpr size_type find(charT const * s, size_type pos, size_type n) const
    { return find(basic_string_view(s, n), pos); }

    constexpr size_type find(charT const * s, size_type pos = 0) const
    { return find(basic_string_view(s), pos); }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type rfind(basic_string_view s, size_type pos = npos) const noexcept {
        if (s.size() <= size()) {
            for (auto xpos = std::min<size_type>(size() - s.size(), pos);;
                 --xpos)
            {
                bool match = true;
                for (size_type i = 0; i != s.size(); ++i) {
                    if (!traits::eq(data_[xpos + i], s.data_[i])) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return xpos;
                }
                if (xpos == 0) {
                    break;
                }
            }
        }
        return npos;
    }

    constexpr size_type rfind(charT c, size_type pos = npos) const noexcept
    { return rfind(basic_string_view(&c, 1), pos); }

    constexpr size_type rfind(charT const * s, size_type pos, size_type n) const
    { return rfind(basic_string_view(s, n), pos); }

    constexpr size_type rfind(charT const * s, size_type pos = npos) const
    { return rfind(basic_string_view(s), pos); }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type find_first_of(basic_string_view s, size_type pos = 0) const
        noexcept
    {
        for (auto xpos = pos; xpos < size(); ++xpos) {
            for (size_type i = 0; i != s.size(); ++i) {
                if (traits::eq(data_[xpos], s.data_[i])) {
                    return xpos;
                }
            }
        }
        return npos;
    }

    constexpr size_type find_first_of(charT c, size_type pos = 0) const noexcept
    { return find_first_of(basic_string_view(&c, 1), pos); }

    constexpr size_type find_first_of(
        charT const * s, size_type pos, size_type n) const
    { return find_first_of(basic_string_view(s, n), pos); }

    constexpr size_type find_first_of(charT const * s, size_type pos = 0) const
    { return find_first_of(basic_string_view(s), pos); }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type find_last_of(basic_string_view s, size_type pos = npos) const
        noexcept
    {
        if (!empty()) {
            for (auto xpos = std::min<size_type>(size() - 1, pos);; --xpos) {
                for (size_type i = 0; i != s.size(); ++i) {
                    if (traits::eq(data_[xpos], s.data_[i])) {
                        return xpos;
                    }
                }
                if (xpos == 0) {
                    break;
                }
            }
        }
        return npos;
    }

    constexpr size_type find_last_of(charT c, size_type pos = npos) const
        noexcept
    { return find_last_of(basic_string_view(&c, 1), pos); }

    constexpr size_type find_last_of(
        charT const * s, size_type pos, size_type n) const
    { return find_last_of(basic_string_view(s, n), pos); }

    constexpr size_type find_last_of(charT const * s, size_type pos = npos)
        const
    { return find_last_of(basic_string_view(s), pos); }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type find_first_not_of(basic_string_view s, size_type pos = 0) const
        noexcept
    {
        for (auto xpos = pos; xpos < size(); ++xpos) {
            bool match = true;
            for (size_type i = 0; i != s.size(); ++i) {
                if (traits::eq(data_[xpos], s.data_[i])) {
                    match = false;
                    break;
                }
            }
            if (match) {
                return xpos;
            }
        }
        return npos;
    }

    constexpr size_type find_first_not_of(charT c, size_type pos = 0) const
        noexcept
    { return find_first_not_of(basic_string_view(&c, 1), pos); }

    constexpr size_type find_first_not_of(
        charT const * s, size_type pos, size_type n) const
    { return find_first_not_of(basic_string_view(s, n), pos); }

    constexpr size_type find_first_not_of(charT const * s, size_type pos = 0)
        const
    { return find_first_not_of(basic_string_view(s), pos); }

#if HAVE_CXX14_CONSTEXPR
    constexpr
#endif
    size_type find_last_not_of(basic_string_view s, size_type pos = npos) const
        noexcept
    {
        if (!empty()) {
            for (auto xpos = std::min<size_type>(size() - 1, pos);; --xpos) {
                bool match = true;
                for (size_type i = 0; i != s.size(); ++i) {
                    if (traits::eq(data_[xpos], s.data_[i])) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return xpos;
                }
                if (xpos == 0) {
                    break;
                }
            }
        }
        return npos;
    }

    constexpr size_type find_last_not_of(charT c, size_type pos = npos) const
        noexcept
    { return find_last_not_of(basic_string_view(&c, 1), pos); }

    constexpr size_type find_last_not_of(
        charT const * s, size_type pos, size_type n) const
    { return find_last_not_of(basic_string_view(s, n), pos); }

    constexpr size_type find_last_not_of(charT const * s, size_type pos = npos)
        const
    { return find_last_not_of(basic_string_view(s), pos); }

    // LO specifics:

    // For std::basic_string_view, this is provided via a non-explicit
    // conversion operator from std::basic_string:
    constexpr basic_string_view(std::basic_string<charT, traits> const & s):
        data_(s.data()), size_(s.size()) {}

    // For std::string_view, this will be provided by a (LIBO_INTERNAL_ONLY)
    // non-explicit conversion operator from rtl::OString:
    template<typename T = charT> basic_string_view(
        OString const & s,
        typename std::enable_if<
            std::is_same<T, char>::value,
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(s.getStr()), size_(s.getLength()) {}

    // For std::u16string_view, this will be provided by a (LIBO_INTERNAL_ONLY)
    // non-explicit conversion operator from rtl::OUString:
    template<typename T = charT> basic_string_view(
        OUString const & s,
        typename std::enable_if<
            std::is_same<T, sal_Unicode>::value,
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(s.getStr()), size_(s.getLength()) {}

    // For std::u16string_view, this would either be provided by a
    // (LIBO_INTERNAL_ONLY) non-explicit conversion operator from
    // rtl::OUStringLiteral, or rtl::OUStringLiteral would be given up in favor
    // of std::u16string_view anyway (but this constructor also serves to reject
    // as ambiguous construction of a o3tl::u16string_view from a narrow string
    // literal, which would otherwise go via the above rtl::OUString
    // constructor):
    template<typename T = charT> constexpr basic_string_view(
        OUStringLiteral literal,
        typename std::enable_if<
            std::is_same<T, sal_Unicode>::value,
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(literal.data), size_(literal.size) {}

    // LO specifics, to make up for traits::length not necessarily being
    // constexpr yet for literal arguments:

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 const & value,
        typename std::enable_if<
            std::is_same<T2, char>::value && detail::CharPtrDetector<T1>::ok,
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & value,
        typename std::enable_if<
            (std::is_same<T2, char>::value
             && detail::NonConstCharArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & literal,
        typename std::enable_if<
            (std::is_same<T2, char>::value
             && detail::ConstCharArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(literal), size_(detail::ConstCharArrayDetector<T1>::length)
    { /*assert(size_ == traits::length(literal);*/ }

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 const & value,
        typename std::enable_if<
            (std::is_same<T2, char16_t>::value
             && detail::Char16PtrDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & value,
        typename std::enable_if<
            (std::is_same<T2, char16_t>::value
             && detail::NonConstChar16ArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & literal,
        typename std::enable_if<
            (std::is_same<T2, char16_t>::value
             && detail::ConstChar16ArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(literal), size_(detail::ConstChar16ArrayDetector<T1>::length)
    { /*assert(size_ == traits::length(literal);*/ }

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 const & value,
        typename std::enable_if<
            (std::is_same<T2, char32_t>::value
             && detail::Char32PtrDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & value,
        typename std::enable_if<
            (std::is_same<T2, char32_t>::value
             && detail::NonConstChar32ArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & literal,
        typename std::enable_if<
            (std::is_same<T2, char32_t>::value
             && detail::ConstChar32ArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(literal), size_(detail::ConstChar32ArrayDetector<T1>::length)
    { /*assert(size_ == traits::length(literal);*/ }

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 const & value,
        typename std::enable_if<
            (std::is_same<T2, wchar_t>::value
             && detail::WcharPtrDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & value,
        typename std::enable_if<
            (std::is_same<T2, wchar_t>::value
             && detail::NonConstWcharArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(value), size_(traits::length(value)) {}

    template<typename T1, typename T2 = charT> constexpr basic_string_view(
        T1 & literal,
        typename std::enable_if<
            (std::is_same<T2, wchar_t>::value
             && detail::ConstWcharArrayDetector<T1>::ok),
            rtl::libreoffice_internal::Dummy>::type = {}):
        data_(literal), size_(detail::ConstWcharArrayDetector<T1>::length)
    { /*assert(size_ == traits::length(literal);*/ }

private:
    const_pointer data_;
    size_type size_;
};

template<class charT, class traits> constexpr bool operator ==(
    basic_string_view<charT, traits> x, basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) == 0; }

template<class charT, class traits> constexpr bool operator ==(
    basic_string_view<charT, traits> x,
    typename std::decay<basic_string_view<charT, traits>>::type y)
    noexcept
{ return x.compare(y) == 0; }

template<class charT, class traits> constexpr bool operator ==(
    typename std::decay<basic_string_view<charT, traits>>::type x,
    basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) == 0; }

template<class charT, class traits> constexpr bool operator !=(
    basic_string_view<charT, traits> x, basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) != 0; }

template<class charT, class traits> constexpr bool operator !=(
    basic_string_view<charT, traits> x,
    typename std::decay<basic_string_view<charT, traits>>::type y)
    noexcept
{ return x.compare(y) != 0; }

template<class charT, class traits> constexpr bool operator !=(
    typename std::decay<basic_string_view<charT, traits>>::type x,
    basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) != 0; }

template<class charT, class traits> constexpr bool operator <(
    basic_string_view<charT, traits> x, basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) < 0; }

template<class charT, class traits> constexpr bool operator <(
    basic_string_view<charT, traits> x,
    typename std::decay<basic_string_view<charT, traits>>::type y)
    noexcept
{ return x.compare(y) < 0; }

template<class charT, class traits> constexpr bool operator <(
    typename std::decay<basic_string_view<charT, traits>>::type x,
    basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) < 0; }

template<class charT, class traits> constexpr bool operator >(
    basic_string_view<charT, traits> x, basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) > 0; }

template<class charT, class traits> constexpr bool operator >(
    basic_string_view<charT, traits> x,
    typename std::decay<basic_string_view<charT, traits>>::type y)
    noexcept
{ return x.compare(y) > 0; }

template<class charT, class traits> constexpr bool operator >(
    typename std::decay<basic_string_view<charT, traits>>::type x,
    basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) > 0; }

template<class charT, class traits> constexpr bool operator <=(
    basic_string_view<charT, traits> x, basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) <= 0; }

template<class charT, class traits> constexpr bool operator <=(
    basic_string_view<charT, traits> x,
    typename std::decay<basic_string_view<charT, traits>>::type y)
    noexcept
{ return x.compare(y) <= 0; }

template<class charT, class traits> constexpr bool operator <=(
    typename std::decay<basic_string_view<charT, traits>>::type x,
    basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) <= 0; }

template<class charT, class traits> constexpr bool operator >=(
    basic_string_view<charT, traits> x, basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) >= 0; }

template<class charT, class traits> constexpr bool operator >=(
    basic_string_view<charT, traits> x,
    typename std::decay<basic_string_view<charT, traits>>::type y)
    noexcept
{ return x.compare(y) >= 0; }

template<class charT, class traits> constexpr bool operator >=(
    typename std::decay<basic_string_view<charT, traits>>::type x,
    basic_string_view<charT, traits> y)
    noexcept
{ return x.compare(y) >= 0; }

template<class charT, class traits> std::basic_ostream<charT, traits> &
operator <<(
    std::basic_ostream<charT, traits> & os,
    basic_string_view<charT, traits> str)
{
    typename std::basic_ostream<charT, traits>::sentry sentry;
    if (sentry) {
        auto const w = os.width();
        auto const pad
            = std::max<std::make_unsigned<decltype(w + str.size())>::type>(
                w < 0 ? 0 : w, str.size());
        auto const after = (os.flags() & std::ios_base::adjustfield)
            == std::ios_base::left;
        if (pad != 0 && !after) {
            auto const c = os.fill();
            for (; pad != 0; --pad) {
                os.rdbuf()->sputc(c);
            }
        }
        os.rdbuf()->sputn(str.data(), str.size());
        if (pad != 0 && after) {
            auto const c = os.fill();
            for (; pad != 0; --pad) {
                os.rdbuf()->sputc(c);
            }
        }
        os.width(0);
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

#if defined _MSC_VER
#pragma warning(pop)
#endif

using string_view = basic_string_view<char>;
using u16string_view = basic_string_view<char16_t>;
using u32string_view = basic_string_view<char32_t>;
using wstring_view = basic_string_view<wchar_t>;

// no literals::string_view_literals::operator "" sv

}

namespace std {

template<> struct hash<o3tl::string_view> {
    std::size_t operator ()(o3tl::string_view s)
    { return hash<string>()(string(s.data(), s.size())); }
};

template<> struct hash<o3tl::u16string_view> {
    std::size_t operator ()(o3tl::u16string_view s)
    { return hash<u16string>()(u16string(s.data(), s.size())); }
};

template<> struct hash<o3tl::u32string_view> {
    std::size_t operator ()(o3tl::u32string_view s)
    { return hash<u32string>()(u32string(s.data(), s.size())); }
};

template<> struct hash<o3tl::wstring_view> {
    std::size_t operator ()(o3tl::wstring_view s)
    { return hash<wstring>()(wstring(s.data(), s.size())); }
};

}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/o3tl/CppunitTest_o3tl_tests.mk b/o3tl/CppunitTest_o3tl_tests.mk
index 6d7a1a81..62a0fc9 100644
--- a/o3tl/CppunitTest_o3tl_tests.mk
+++ b/o3tl/CppunitTest_o3tl_tests.mk
@@ -31,6 +31,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,o3tl_tests,\
	o3tl/qa/test-cow_wrapper \
	o3tl/qa/test-lru_map \
	o3tl/qa/test-sorted_vector \
	o3tl/qa/test-string_view \
	o3tl/qa/test-typed_flags \
	o3tl/qa/test-vector_pool \
))
diff --git a/o3tl/qa/test-string_view.cxx b/o3tl/qa/test-string_view.cxx
new file mode 100644
index 0000000..977cfebc
--- /dev/null
+++ b/o3tl/qa/test-string_view.cxx
@@ -0,0 +1,212 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <sal/config.h>

#include <stdexcept>

#include <cppunit/TestAssert.h>
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>

#include <o3tl/string_view.hxx>

namespace {

class Test: public CppUnit::TestFixture {
private:
    CPPUNIT_TEST_SUITE(Test);
    CPPUNIT_TEST(testCharLiteral);
    CPPUNIT_TEST(testChar16Literal);
    CPPUNIT_TEST(testChar32Literal);
    CPPUNIT_TEST(testWcharLiteral);
    CPPUNIT_TEST(testOperations);
    CPPUNIT_TEST_SUITE_END();

    void testCharLiteral() {
        char * const s1 = const_cast<char *>("foo");
        o3tl::string_view v1(s1);
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v1.size());
        char const * const s2 = "foo";
        o3tl::string_view v2(s2);
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v2.size());
        char s3[] = "foo";
        o3tl::string_view v3(s3);
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v3.size());
        o3tl::string_view v4("foo");
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v4.size());
    }

    void testChar16Literal() {
        char16_t * const s1 = const_cast<char16_t *>(u"foo");
        o3tl::u16string_view v1(s1);
        CPPUNIT_ASSERT_EQUAL(o3tl::u16string_view::size_type(3), v1.size());
        char16_t const * const s2 = u"foo";
        o3tl::u16string_view v2(s2);
        CPPUNIT_ASSERT_EQUAL(o3tl::u16string_view::size_type(3), v2.size());
        char16_t s3[] = u"foo";
        o3tl::u16string_view v3(s3);
        CPPUNIT_ASSERT_EQUAL(o3tl::u16string_view::size_type(3), v3.size());
        o3tl::u16string_view v4(u"foo");
        CPPUNIT_ASSERT_EQUAL(o3tl::u16string_view::size_type(3), v4.size());
    }

    void testChar32Literal() {
        char32_t * const s1 = const_cast<char32_t *>(U"foo");
        o3tl::u32string_view v1(s1);
        CPPUNIT_ASSERT_EQUAL(o3tl::u32string_view::size_type(3), v1.size());
        char32_t const * const s2 = U"foo";
        o3tl::u32string_view v2(s2);
        CPPUNIT_ASSERT_EQUAL(o3tl::u32string_view::size_type(3), v2.size());
        char32_t s3[] = U"foo";
        o3tl::u32string_view v3(s3);
        CPPUNIT_ASSERT_EQUAL(o3tl::u32string_view::size_type(3), v3.size());
        o3tl::u32string_view v4(U"foo");
        CPPUNIT_ASSERT_EQUAL(o3tl::u32string_view::size_type(3), v4.size());
    }

    void testWcharLiteral() {
        wchar_t * const s1 = const_cast<wchar_t *>(L"foo");
        o3tl::wstring_view v1(s1);
        CPPUNIT_ASSERT_EQUAL(o3tl::wstring_view::size_type(3), v1.size());
        wchar_t const * const s2 = L"foo";
        o3tl::wstring_view v2(s2);
        CPPUNIT_ASSERT_EQUAL(o3tl::wstring_view::size_type(3), v2.size());
        wchar_t s3[] = L"foo";
        o3tl::wstring_view v3(s3);
        CPPUNIT_ASSERT_EQUAL(o3tl::wstring_view::size_type(3), v3.size());
        o3tl::wstring_view v4(L"foo");
        CPPUNIT_ASSERT_EQUAL(o3tl::wstring_view::size_type(3), v4.size());
    }

    void testOperations() {
        o3tl::string_view const v("fox");
        auto npos = o3tl::string_view::npos;
            // o3tl::basic_string_view::npos will be (implicitly) inline with
            // C++17, but for now can't be passed as 'const T& expected'
            // argument into CppUnit::assertEquals, so take this detour
        CPPUNIT_ASSERT_EQUAL('f', *v.begin());
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::difference_type(3), v.end() - v.begin());
        CPPUNIT_ASSERT_EQUAL('f', *v.cbegin());
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::difference_type(3), v.cend() - v.cbegin());
        CPPUNIT_ASSERT_EQUAL('x', *v.rbegin());
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::difference_type(3), v.rend() - v.rbegin());
        CPPUNIT_ASSERT_EQUAL('x', *v.crbegin());
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::difference_type(3), v.crend() - v.crbegin());
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v.size());
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v.length());
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::npos - 1, v.max_size());
        CPPUNIT_ASSERT(!v.empty());
        CPPUNIT_ASSERT_EQUAL('o', v[1]);
        try {
            v.at(o3tl::string_view::npos);
            CPPUNIT_FAIL("missing exception");
        } catch (std::out_of_range &) {}
        CPPUNIT_ASSERT_EQUAL('f', v.at(0));
        CPPUNIT_ASSERT_EQUAL('x', v.at(2));
        try {
            v.at(3);
            CPPUNIT_FAIL("missing exception");
        } catch (std::out_of_range &) {}
        CPPUNIT_ASSERT_EQUAL('f', v.front());
        CPPUNIT_ASSERT_EQUAL('x', v.back());
        CPPUNIT_ASSERT_EQUAL('f', *v.data());
        {
            o3tl::string_view v1("fox");
            v1.remove_prefix(2);
            CPPUNIT_ASSERT_EQUAL('x', v1.front());
            CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(1), v1.size());
        }
        {
            o3tl::string_view v1("fox");
            v1.remove_suffix(2);
            CPPUNIT_ASSERT_EQUAL('f', v1.front());
            CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(1), v1.size());
        }
        {
            o3tl::string_view v1("fox");
            o3tl::string_view v2("giraffe");
            v1.swap(v2);
            CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(7), v1.size());
            CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(3), v2.size());
        }
        {
            char a[2];
            auto n = v.copy(a, 10, 1);
            CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(2), n);
            CPPUNIT_ASSERT_EQUAL('o', a[0]);
            CPPUNIT_ASSERT_EQUAL('x', a[1]);
        }
        {
            auto v1 = v.substr(1);
            CPPUNIT_ASSERT_EQUAL('o', v1.front());
            CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(2), v1.size());
        }
        CPPUNIT_ASSERT(v.compare(o3tl::string_view("foo")) > 0);
        CPPUNIT_ASSERT(v.compare(0, 2, o3tl::string_view("foo")) < 0);
        CPPUNIT_ASSERT_EQUAL(
            0, v.compare(0, 2, o3tl::string_view("foo"), 0, 2));
        CPPUNIT_ASSERT_EQUAL(0, v.compare("fox"));
        CPPUNIT_ASSERT(v.compare(1, 2, "abc") > 0);
        CPPUNIT_ASSERT_EQUAL(0, v.compare(1, 2, "oxx", 2));
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(1), v.find("ox"));
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(1), v.find('o'));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find("oxx", 0, 2));
        CPPUNIT_ASSERT_EQUAL(npos, v.find("oxx"));
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(1), v.rfind("ox"));
        CPPUNIT_ASSERT_EQUAL(o3tl::string_view::size_type(1), v.rfind('o'));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1),
            v.rfind("oxx", o3tl::string_view::npos, 2));
        CPPUNIT_ASSERT_EQUAL(npos, v.rfind("oxx"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_first_of("nop"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_first_of('o'));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_first_of("nofx", 0, 2));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(0), v.find_first_of("nofx"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_last_of("nop"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_last_of('o'));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1),
            v.find_last_of("nofx", o3tl::string_view::npos, 2));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(2), v.find_last_of("nofx"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_first_not_of("fx"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_first_not_of('f'));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_first_not_of("fxo", 0, 2));
        CPPUNIT_ASSERT_EQUAL(npos, v.find_first_not_of("fxo"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_last_not_of("fx"));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1), v.find_last_not_of('x'));
        CPPUNIT_ASSERT_EQUAL(
            o3tl::string_view::size_type(1),
            v.find_last_not_of("fxo", o3tl::string_view::npos, 2));
        CPPUNIT_ASSERT_EQUAL(npos, v.find_last_not_of("fxo"));
    }
};

CPPUNIT_TEST_SUITE_REGISTRATION(Test);

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */