    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * 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/.
     *
     * This file incorporates work covered by the following license notice:
     *
     *   Licensed to the Apache Software Foundation (ASF) under one or more
     *   contributor license agreements. See the NOTICE file distributed
     *   with this work for additional information regarding copyright
     *   ownership. The ASF licenses this file to you under the Apache
     *   License, Version 2.0 (the "License"); you may not use this file
     *   except in compliance with the License. You may obtain a copy of
     *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
     */

    #include <editsh.hxx>

    #include <com/sun/star/container/XEnumerationAccess.hpp>
    #include <com/sun/star/container/XContentEnumerationAccess.hpp>
    #include <com/sun/star/document/XActionLockable.hpp>
    #include <com/sun/star/drawing/FillStyle.hpp>
    #include <com/sun/star/drawing/HomogenMatrix3.hpp>
    #include <com/sun/star/drawing/LineStyle.hpp>
    #include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
    #include <com/sun/star/lang/XServiceInfo.hpp>
    #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
    #include <com/sun/star/text/RelOrientation.hpp>
    #include <com/sun/star/text/TextContentAnchorType.hpp>
    #include <com/sun/star/text/VertOrientation.hpp>
    #include <com/sun/star/text/WrapTextMode.hpp>
    #include <com/sun/star/text/XTextContent.hpp>
    #include <com/sun/star/text/XTextDocument.hpp>
    #include <com/sun/star/text/XTextField.hpp>
    #include <com/sun/star/text/XTextRange.hpp>
    #include <com/sun/star/text/XParagraphAppend.hpp>
    #include <com/sun/star/text/XParagraphCursor.hpp>
    #include <com/sun/star/awt/FontWeight.hpp>
    #include <com/sun/star/xml/crypto/SEInitializer.hpp>
    #include <com/sun/star/rdf/XMetadatable.hpp>
    #include <com/sun/star/security/DocumentDigitalSignatures.hpp>
    #include <com/sun/star/security/XCertificate.hpp>

    #include <basegfx/matrix/b2dhommatrix.hxx>
    #include <comphelper/propertysequence.hxx>
    #include <comphelper/propertyvalue.hxx>
    #include <comphelper/processfactory.hxx>
    #include <comphelper/sequence.hxx>
    #include <comphelper/scopeguard.hxx>
    #include <comphelper/string.hxx>
    #include <editeng/formatbreakitem.hxx>
    #include <editeng/unoprnms.hxx>
    #include <sfx2/classificationhelper.hxx>
    #include <svx/ClassificationCommon.hxx>
    #include <svl/cryptosign.hxx>
    #include <utility>
    #include <vcl/svapp.hxx>
    #include <vcl/weld.hxx>

    #include <hintids.hxx>
    #include <doc.hxx>
    #include <IDocumentUndoRedo.hxx>
    #include <edimp.hxx>
    #include <ndtxt.hxx>
    #include <paratr.hxx>
    #include <fmtpdsc.hxx>
    #include <viewopt.hxx>
    #include <SwRewriter.hxx>
    #include <numrule.hxx>
    #include <swundo.hxx>
    #include <docary.hxx>
    #include <docsh.hxx>
    #include <unoprnms.hxx>
    #include <rootfrm.hxx>
    #include <pagefrm.hxx>
    #include <rdfhelper.hxx>
    #include <sfx2/watermarkitem.hxx>

    #include <unoparagraph.hxx>
    #include <unotextrange.hxx>
    #include <cppuhelper/bootstrap.hxx>
    #include <modeltoviewhelper.hxx>
    #include <strings.hrc>
    #include <undobj.hxx>
    #include <UndoParagraphSignature.hxx>
    #include <txtatr.hxx>
    #include <fmtmeta.hxx>

    #include <com/sun/star/beans/PropertyAttribute.hpp>
    #include <tools/diagnose_ex.h>

    #define WATERMARK_NAME "PowerPlusWaterMarkObject"
    #define WATERMARK_AUTO_SIZE sal_uInt32(1)

    namespace
    {
    static const OUString MetaFilename("tscp/bails.rdf");
    static const OUString MetaNS("urn:bails");
    static const OUString ParagraphSignatureRDFNamespace = "urn:bails:loext:paragraph:signature:";
    static const OUString ParagraphSignatureIdRDFName = "urn:bails:loext:paragraph:signature:id";
    static const OUString ParagraphSignatureDigestRDFName = ":digest";
    static const OUString ParagraphSignatureDateRDFName = ":date";
    static const OUString ParagraphSignatureUsageRDFName = ":usage";
    static const OUString ParagraphSignatureLastIdRDFName = "urn:bails:loext:paragraph:signature:lastid";
    static const OUString ParagraphClassificationNameRDFName = "urn:bails:loext:paragraph:classification:name";
    static const OUString ParagraphClassificationValueRDFName = "urn:bails:loext:paragraph:classification:value";
    static const OUString ParagraphClassificationAbbrRDFName = "urn:bails:loext:paragraph:classification:abbreviation";
    static const OUString ParagraphClassificationFieldNamesRDFName = "urn:bails:loext:paragraph:classification:fields";
    static const OUString MetadataFieldServiceName = "com.sun.star.text.textfield.MetadataField";
    static const OUString DocInfoServiceName = "com.sun.star.text.TextField.DocInfo.Custom";

    /// Find all page styles which are currently used in the document.
    std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell)
    {
        std::vector<OUString> aReturn;

        SwRootFrame* pLayout = pShell->GetLayout();
        for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
        {
            SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame);
            if (const SwPageDesc *pDesc = pPage->FindPageDesc())
                aReturn.push_back(pDesc->GetName());
        }

        return aReturn;
    }

    /// Search for a field named rFieldName of type rServiceName in xText and return it.
    uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, const OUString& rFieldName)
    {
        uno::Reference<text::XTextField> xField;
        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
            uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
            while (xTextPortions->hasMoreElements())
            {
                uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
                OUString aTextPortionType;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
                if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                    continue;

                uno::Reference<lang::XServiceInfo> xTextField;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
                if (!xTextField->supportsService(rServiceName))
                    continue;

                OUString aName;
                uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
                xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
                if (aName == rFieldName)
                {
                    xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY);
                    break;
                }
            }
        }

        return xField;
    }

    /// Search for a field named rFieldName of type rServiceName in xText and return true iff found.
    bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, const OUString& rFieldName)
    {
        return lcl_findField(xText, rServiceName, rFieldName).is();
    }

    /// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName.
    uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText,
        const OUString& rServiceName, OUString& rShapeName, bool& bSuccess)
    {
        bSuccess = false;
        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
            if (!xTextPortionEnumerationAccess.is())
                continue;

            bSuccess = true;

            uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
            while (xTextPortions->hasMoreElements())
            {
                uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
                OUString aTextPortionType;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
                if (aTextPortionType != "Frame")
                    continue;

                uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY);
                if (!xContentEnumerationAccess.is())
                    continue;

                uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration("com.sun.star.text.TextContent");
                if (!xEnumeration->hasMoreElements())
                    continue;

                uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY);
                if (!xWatermark->supportsService(rServiceName))
                    continue;

                uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY);

                if (!xNamed->getName().match(WATERMARK_NAME))
                    continue;

                rShapeName = xNamed->getName();

                uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY);
                return xShape;
            }
        }

        return uno::Reference<drawing::XShape>();
    }

    /// Extract the text of the paragraph without any of the fields.
    /// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper.
    OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText)
    {
        OUStringBuffer strBuf;
        uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY);
        if (!xTextPortionEnumerationAccess.is())
            return OString();

        uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
        while (xTextPortions->hasMoreElements())
        {
            uno::Any elem = xTextPortions->nextElement();

            //TODO: Consider including hidden and conditional texts/portions.
            OUString aTextPortionType;
            uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY);
            xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
            if (aTextPortionType == "Text")
            {
                uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY);
                if (xTextRange.is())
                    strBuf.append(xTextRange->getString());
            }
        }

        // Cleanup the dummy characters added by fields (which we exclude).
        comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART);
        comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND);
        comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD);

        return strBuf.makeStringAndClear().trim().toUtf8();
    }

    template <typename T>
    std::map<OUString, OUString> lcl_getRDFStatements(const uno::Reference<frame::XModel>& xModel,
                                                      const T& xRef)
    {
        try
        {
            const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY);
            return SwRDFHelper::getStatements(xModel, MetaNS, xSubject);
        }
        catch (const ::css::uno::Exception&)
        {
        }

        return std::map<OUString, OUString>();
    }

    /// Returns RDF (key, value) pair associated with the field, if any.
    std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const uno::Reference<frame::XModel>& xModel,
                                                          const uno::Reference<css::text::XTextField>& xField,
                                                          const OUString& sPrefix)
    {
        for (const auto& pair : lcl_getRDFStatements(xModel, xField))
        {
            if (pair.first.startsWith(sPrefix))
                return pair;
        }

        return std::make_pair(OUString(), OUString());
    }

    /// Returns RDF (key, value) pair associated with the field, if any.
    template <typename T>
    std::pair<OUString, OUString> lcl_getRDF(const uno::Reference<frame::XModel>& xModel,
                                             const T& xRef,
                                             const OUString& sRDFName)
    {
        const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef);
        const auto it = aStatements.find(sRDFName);
        return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString());
    }

    /// Returns true iff the field in question is paragraph signature.
    /// Note: must have associated RDF, since signatures are otherwise just metadata fields.
    bool lcl_IsParagraphSignatureField(const uno::Reference<frame::XModel>& xModel,
                                       const uno::Reference<css::text::XTextField>& xField)
    {
        return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName);
    }

    uno::Reference<text::XTextField> lcl_findFieldByRDF(const uno::Reference<frame::XModel>& xModel,
                                                        const uno::Reference<text::XTextContent>& xParagraph,
                                                        const OUString& sRDFName,
                                                        const OUString& sRDFValue)
    {
        uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
        if (!xTextPortionEnumerationAccess.is())
            return uno::Reference<text::XTextField>();

        uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
        if (!xTextPortions.is())
            return uno::Reference<text::XTextField>();

        while (xTextPortions->hasMoreElements())
        {
            uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
            OUString aTextPortionType;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
            if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                continue;

            uno::Reference<lang::XServiceInfo> xTextField;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
            if (!xTextField->supportsService(MetadataFieldServiceName))
                continue;

            uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
            const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName);
            if (pair.first == sRDFName && (sRDFValue.isEmpty() || sRDFValue == pair.second))
                return xField;
        }

        return uno::Reference<text::XTextField>();
    }

    struct SignatureDescr
    {
        OUString msSignature;
        OUString msUsage;
        OUString msDate;
        OUString msId;

        bool isValid() const { return !msSignature.isEmpty(); }
    };

    SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel,
                                         const uno::Reference<css::text::XTextContent>& xParagraph,
                                         const OUString& sFieldId)
    {
        SignatureDescr aDescr;
        aDescr.msId = sFieldId;

        const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph);
        const auto itSig = aStatements.find(ParagraphSignatureRDFNamespace + sFieldId + ParagraphSignatureDigestRDFName);
        aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString());

        const auto itDate = aStatements.find(ParagraphSignatureRDFNamespace + sFieldId + ParagraphSignatureDateRDFName);
        aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString());

        const auto itUsage = aStatements.find(ParagraphSignatureRDFNamespace + sFieldId + ParagraphSignatureUsageRDFName);
        aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString());

        return aDescr;
    }

    SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel,
                                         const uno::Reference<css::text::XTextContent>& xParagraph,
                                         const uno::Reference<css::text::XTextField>& xField)
    {
        const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName);
        return (!pair.second.isEmpty() ? lcl_getSignatureDescr(xModel, xParagraph, pair.second) : SignatureDescr());
    }

    /// Validate and create the signature field display text from the fields.
    std::pair<bool, OUString>
    lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr,
                                        const OString& utf8Text)
    {
        OUString msg = SwResId(STR_INVALID_SIGNATURE);
        bool valid = false;

        if (aDescr.isValid())
        {
            const sal_Char* pData = utf8Text.getStr();
            const std::vector<unsigned char> data(pData, pData + utf8Text.getLength());

            OString encSignature;
            if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0))
            {
                const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature));
                SignatureInformation aInfo(0);
                valid = svl::crypto::Signing::Verify(data, false, sig, aInfo);
                valid = valid && aInfo.nStatus == css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;

                msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.ouSubject + ", ";
                msg += aDescr.msDate;
                msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : OUString(": "));
                msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID));
            }
        }

        return std::make_pair(valid, msg);
    }

    /// Validate and return validation result and signature field display text.
    std::pair<bool, OUString>
    lcl_MakeParagraphSignatureFieldText(const uno::Reference<frame::XModel>& xModel,
                                        const uno::Reference<css::text::XTextContent>& xParagraph,
                                        const uno::Reference<css::text::XTextField>& xField,
                                        const OString& utf8Text)
    {
        const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField);
        return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text);
    }

    /// Generate the next valid ID for the a new signature on this paragraph.
    OUString lcl_getNextSignatureId(const uno::Reference<frame::XModel>& xModel,
                                    const uno::Reference<text::XTextContent>& xParagraph)
    {
        const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName);
        return OUString::number(!pair.second.isEmpty() ? pair.second.toInt32() + 1 : 1);
    }

    /// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry
    uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const uno::Reference<frame::XModel>& xModel,
                                                                  const uno::Reference<text::XTextContent>& xParagraph,
                                                                  const OUString& signature,
                                                                  const OUString& usage)
    {
        uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
        auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);

        // Add the signature at the end.
        xField->attach(xParagraph->getAnchor()->getEnd());

        const OUString sId = lcl_getNextSignatureId(xModel, xParagraph);

        const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId);

        // First convert the UTC UNIX timestamp to a tools::DateTime then to local time.
        DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr));
        aDateTime.ConvertToLocalTime();
        OUStringBuffer rBuffer;
        rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
        rBuffer.append('-');
        if (aDateTime.GetMonth() < 10)
            rBuffer.append('0');
        rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
        rBuffer.append('-');
        if (aDateTime.GetDay() < 10)
            rBuffer.append('0');
        rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));

        // Now set the RDF on the paragraph, since that's what is preserved in .doc(x).
        const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureRDFNamespace + sId + ParagraphSignatureDigestRDFName, signature);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureRDFNamespace + sId + ParagraphSignatureUsageRDFName, usage);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureRDFNamespace + sId + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear());

        return xField;
    }

    /// Updates the signature field text if changed and returns true only iff updated.
    bool lcl_DoUpdateParagraphSignatureField(SwDoc* pDoc,
                                             const uno::Reference<css::text::XTextField>& xField,
                                             const OUString& sDisplayText)
    {
        // Disable undo to avoid introducing noise when we edit the metadata field.
        const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo();
        pDoc->GetIDocumentUndoRedo().DoUndo(false);
        comphelper::ScopeGuard const g([pDoc, isUndoEnabled]() {
            pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
        });

        try
        {
            uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY);
            const OUString curText = xText->getString();
            if (curText != sDisplayText)
            {
                xText->setString(sDisplayText);
                return true;
            }
        }
        catch (const uno::Exception&)
        {
            // We failed; avoid crashing.
            DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature");
        }

        return false;
    }

    /// Updates the signature field text if changed and returns true only iff updated.
    bool lcl_UpdateParagraphSignatureField(SwDoc* pDoc,
                                           const uno::Reference<frame::XModel>& xModel,
                                           const uno::Reference<css::text::XTextContent>& xParagraph,
                                           const uno::Reference<css::text::XTextField>& xField,
                                           const OString& utf8Text)
    {
        const std::pair<bool, OUString> res = lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text);
        return lcl_DoUpdateParagraphSignatureField(pDoc, xField, res.second);
    }

    void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField)
    {
        uno::Reference<css::text::XTextContent> xFieldTextContent(xField, uno::UNO_QUERY);
        uno::Reference<css::text::XTextRange> xParagraph(xFieldTextContent->getAnchor());
        uno::Reference<css::text::XText> xParagraphText(xParagraph->getText(), uno::UNO_QUERY);
        xParagraphText->removeTextContent(xFieldTextContent);
    }

    /// Returns true iff the field in question is paragraph classification.
    /// Note: must have associated RDF, since classifications are otherwise just metadata fields.
    bool lcl_IsParagraphClassificationField(const uno::Reference<frame::XModel>& xModel,
                                            const uno::Reference<css::text::XTextField>& xField,
                                            const OUString& sKey)
    {
        const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
        return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.isEmpty() || rdfPair.second == sKey);
    }

    uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const uno::Reference<frame::XModel>& xModel,
                                                                          const uno::Reference<text::XTextContent>& xParagraph,
                                                                          const OUString& sKey = OUString())
    {
        uno::Reference<text::XTextField> xTextField;

        uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
        if (!xTextPortionEnumerationAccess.is())
            return xTextField;

        uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
        while (xTextPortions->hasMoreElements())
        {
            uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
            OUString aTextPortionType;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
            if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                continue;

            uno::Reference<lang::XServiceInfo> xServiceInfo;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo;
            if (!xServiceInfo->supportsService(MetadataFieldServiceName))
                continue;

            uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY);
            if (lcl_IsParagraphClassificationField(xModel, xField, sKey))
            {
                uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY);
                xTextField = xField;
                break;
            }
        }

        return xTextField;
    }

    /// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry
    uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const uno::Reference<frame::XModel>& xModel,
                                                                       const uno::Reference<text::XTextContent>& xParent)
    {
        uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
        auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);

        // Add the classification at the start.
        xField->attach(xParent->getAnchor()->getStart());
        return xField;
    }

    /// Updates the paragraph classification field text if changed and returns true only iff updated.
    bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc,
                                                const uno::Reference<frame::XModel>& xModel,
                                                const uno::Reference<css::text::XTextContent>& xTextNode,
                                                const OUString& sKey,
                                                const OUString& sValue,
                                                const OUString& sDisplayText)
    {
        // Disable undo to avoid introducing noise when we edit the metadata field.
        const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo();
        pDoc->GetIDocumentUndoRedo().DoUndo(false);
        comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () {
                pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
            });

        uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode);

        css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue);

        css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue);

        return lcl_DoUpdateParagraphSignatureField(pDoc, xField, sDisplayText);
    }

    void lcl_ValidateParagraphSignatures(SwDoc* pDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove)
    {
        SwDocShell* pDocShell = pDoc->GetDocShell();
        if (!pDocShell)
            return;

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();

        // Check if the paragraph is signed.
        try
        {
            const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName);
            if (pair.second.isEmpty())
                return;
        }
        catch (const ::css::uno::Exception&)
        {
            return;
        }

        uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
        if (!xTextPortionEnumerationAccess.is())
            return;

        uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
        if (!xTextPortions.is())
            return;

        // Get the text (without fields).
        const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
        if (utf8Text.isEmpty())
            return;

        while (xTextPortions->hasMoreElements())
        {
            uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
            OUString aTextPortionType;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
            if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                continue;

            uno::Reference<lang::XServiceInfo> xTextField;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
            if (!xTextField->supportsService(MetadataFieldServiceName))
                continue;

            uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
            if (!lcl_IsParagraphSignatureField(xModel, xField))
            {
                continue;
            }

            if (updateDontRemove)
            {
                lcl_UpdateParagraphSignatureField(pDoc, xModel, xParagraph, xField, utf8Text);
            }
            else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
            {
                pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
                SwUndoParagraphSigning* pUndo = new SwUndoParagraphSigning(pDoc, xField, xParagraph, false);
                pDoc->GetIDocumentUndoRedo().AppendUndo(pUndo);
                lcl_RemoveParagraphMetadataField(xField);
                pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
            }
        }
    }

    } // anonymous namespace

    SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const
    {
        return *GetDoc()->GetDfltTextFormatColl();
    }

    sal_uInt16 SwEditShell::GetTextFormatCollCount() const
    {
        return GetDoc()->GetTextFormatColls()->size();
    }

    SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const
    {
        return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]);
    }

    void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory,
                               uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor,
                               OUString const & rsKey)
    {
        uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
        xField->setPropertyValue(UNO_NAME_NAME, uno::makeAny(rsKey));
        uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);

        rxText->insertTextContent(rxParagraphCursor, xTextContent, false);
    }

    void removeAllClassificationFields(OUString const & rPolicy, uno::Reference<text::XText> const & rxText)
    {
        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
            uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
            while (xTextPortions->hasMoreElements())
            {
                uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
                OUString aTextPortionType;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
                if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                    continue;

                uno::Reference<lang::XServiceInfo> xTextField;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
                if (!xTextField->supportsService(DocInfoServiceName))
                    continue;

                OUString aName;
                uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
                xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
                if (aName.startsWith(rPolicy))
                {
                    uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
                    rxText->removeTextContent(xField);
                }
            }
        }
    }

    sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText)
    {
        uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration();
        sal_Int32 nResult = 0;
        while (xParagraphEnum->hasMoreElements())
        {
            xParagraphEnum->nextElement();
            nResult++;
        }
        return nResult;
    }

    void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText)
    {
        sal_Int32 nNumberOfParagraphs = 0;
        for (svx::ClassificationResult const & rResult : rResults)
        {
            if (rResult.meType == svx::ClassificationType::PARAGRAPH)
                nNumberOfParagraphs++;
        }

        while (getNumberOfParagraphs(xText) < nNumberOfParagraphs)
        {
            uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY);
            xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>());
        }
    }

    void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults)
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !SfxObjectShell::Current())
            return;

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);

        uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);

        uno::Reference<document::XDocumentProperties> xDocumentProperties = SfxObjectShell::Current()->getDocProperties();

        const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
        const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
        for (const OUString& rPageStyleName : aUsedPageStyles)
        {
            uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);

            // HEADER
            bool bHeaderIsOn = false;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
            uno::Reference<text::XText> xHeaderText;
            if (bHeaderIsOn)
                xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
            if (xHeaderText.is())
                removeAllClassificationFields(sPolicy, xHeaderText);

            // FOOTER
            bool bFooterIsOn = false;
            xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
            uno::Reference<text::XText> xFooterText;
            if (bFooterIsOn)
                xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
            if (xFooterText.is())
                removeAllClassificationFields(sPolicy, xFooterText);
        }

        // Clear properties
        uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
        svx::classification::removeAllProperties(xPropertyContainer);

        SfxClassificationHelper aHelper(xDocumentProperties);

        // Apply properties from the BA policy
        for (svx::ClassificationResult const & rResult : rResults)
        {
            if (rResult.meType == svx::ClassificationType::CATEGORY)
            {
                aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType());
            }
        }

        sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());

        // Insert origin document property
        svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL);

        // Insert full text as document property
        svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults);

        for (const OUString& rPageStyleName : aUsedPageStyles)
        {
            uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);

            // HEADER
            bool bHeaderIsOn = false;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
            if (!bHeaderIsOn)
                xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::makeAny(true));
            uno::Reference<text::XText> xHeaderText;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
            equaliseNumberOfParagraph(rResults, xHeaderText);

            // FOOTER
            bool bFooterIsOn = false;
            xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
            if (!bFooterIsOn)
                xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::makeAny(true));
            uno::Reference<text::XText> xFooterText;
            xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
            equaliseNumberOfParagraph(rResults, xFooterText);

            // SET/DELETE WATERMARK
            SfxWatermarkItem aWatermarkItem;
            aWatermarkItem.SetText(aHelper.GetDocumentWatermark());
            SetWatermark(aWatermarkItem);

            uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY);
            uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY);

            sal_Int32 nParagraph = -1;

            for (svx::ClassificationResult const & rResult : rResults)
            {
                switch(rResult.meType)
                {
                    case svx::ClassificationType::TEXT:
                    {
                        OUString sKey = aCreator.makeNumberedTextKey();

                        svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
                        insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
                        insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
                    }
                    break;

                    case svx::ClassificationType::CATEGORY:
                    {
                        OUString sKey = aCreator.makeCategoryNameKey();
                        insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
                        insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
                    }
                    break;

                    case svx::ClassificationType::MARKING:
                    {
                        OUString sKey = aCreator.makeNumberedMarkingKey();
                        svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
                        insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
                        insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
                    }
                    break;

                    case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
                    {
                        OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey();
                        svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
                        insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
                        insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
                    }
                    break;

                    case svx::ClassificationType::PARAGRAPH:
                    {
                        nParagraph++;

                        if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph
                        {
                            xHeaderParagraphCursor->gotoNextParagraph(false);
                            xFooterParagraphCursor->gotoNextParagraph(false);
                        }

                        xHeaderParagraphCursor->gotoStartOfParagraph(false);
                        xFooterParagraphCursor->gotoStartOfParagraph(false);

                        uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW);
                        uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW);
                        if (rResult.msName == "BOLD")
                        {
                            xHeaderPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::BOLD));
                            xFooterPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::BOLD));
                        }
                        else
                        {
                            xHeaderPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::NORMAL));
                            xFooterPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::NORMAL));
                        }
                    }
                    break;

                    default:
                    break;
                }
            }
        }
    }

    std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification()
    {
        std::vector<svx::ClassificationResult> aResult;

        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !SfxObjectShell::Current())
            return aResult;

        const OUString sBlank;

        uno::Reference<document::XDocumentProperties> xDocumentProperties = SfxObjectShell::Current()->getDocProperties();
        uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
        sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);

        std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this);
        OUString aPageStyleString = aPageStyles.back();
        uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY);

        bool bHeaderIsOn = false;
        xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
        if (!bHeaderIsOn)
        {
            const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
            if (!aValue.isEmpty())
                aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });

            return aResult;
        }

        uno::Reference<text::XText> xHeaderText;
        xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;

        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();

        // set to true if category was found in the header
        bool bFoundClassificationCategory = false;

        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
            if (!xTextPortionEnumerationAccess.is())
                continue;
            uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();

            // Check font weight
            uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW);
            uno::Any aAny = xParagraphPropertySet->getPropertyValue("CharWeight");

            OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? OUString("BOLD") : OUString("NORMAL");

            aResult.push_back({ svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank });

            // Process portions
            while (xTextPortions->hasMoreElements())
            {
                uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
                OUString aTextPortionType;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
                if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                    continue;

                uno::Reference<lang::XServiceInfo> xTextField;
                xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
                if (!xTextField->supportsService(DocInfoServiceName))
                    continue;

                OUString aName;
                uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
                xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;

                if (aCreator.isMarkingTextKey(aName))
                {
                    const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                    if (!aValue.isEmpty())
                        aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank });
                }
                else if (aCreator.isCategoryNameKey(aName))
                {
                    const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                    if (!aValue.isEmpty())
                        aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
                    bFoundClassificationCategory = true;
                }
                else if (aCreator.isCategoryIdentifierKey(aName))
                {
                    const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                    if (!aValue.isEmpty())
                        aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue });
                    bFoundClassificationCategory = true;
                }
                else if (aCreator.isMarkingKey(aName))
                {
                    const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                    if (!aValue.isEmpty())
                        aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank });
                }
                else if (aCreator.isIntellectualPropertyPartKey(aName))
                {
                    const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                    if (!aValue.isEmpty())
                        aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank });
                }
            }
        }

        if (!bFoundClassificationCategory)
        {
            const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
            if (!aValue.isEmpty())
                aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
        }

        return aResult;
    }

    void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType)
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell)
            return;

        SfxClassificationHelper aHelper(pDocShell->getDocProperties());

        const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty();

        // This updates the infobar as well.
        aHelper.SetBACName(rName, eType);

        // Insert origin document property
        uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties();
        sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
        svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY);

        bool bHeaderIsNeeded = aHelper.HasDocumentHeader();
        bool bFooterIsNeeded = aHelper.HasDocumentFooter();
        OUString aWatermark = aHelper.GetDocumentWatermark();
        bool bWatermarkIsNeeded = !aWatermark.isEmpty();

        if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark)
            return;

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
        uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();

        for (const OUString& rPageStyleName : aStyles)
        {
            uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
            uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);

            if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark)
            {
                // If the header is off, turn it on.
                bool bHeaderIsOn = false;
                xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
                if (!bHeaderIsOn)
                    xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::makeAny(true));

                // If the header already contains a document header field, no need to do anything.
                uno::Reference<text::XText> xHeaderText;
                xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;

                if (bHeaderIsNeeded)
                {
                    if (!lcl_hasField(xHeaderText, DocInfoServiceName, SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()))
                    {
                        // Append a field to the end of the header text.
                        uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
                        xField->setPropertyValue(UNO_NAME_NAME, uno::makeAny(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()));
                        uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
                        xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false);
                    }
                }

                SfxWatermarkItem aWatermarkItem;
                aWatermarkItem.SetText(aWatermark);
                SetWatermark(aWatermarkItem);
            }

            if (bFooterIsNeeded)
            {
                // If the footer is off, turn it on.
                bool bFooterIsOn = false;
                xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
                if (!bFooterIsOn)
                    xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::makeAny(true));

                // If the footer already contains a document header field, no need to do anything.
                uno::Reference<text::XText> xFooterText;
                xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
                static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER();
                if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter))
                {
                    // Append a field to the end of the footer text.
                    uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
                    xField->setPropertyValue(UNO_NAME_NAME, uno::makeAny(sFooter));
                    uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
                    xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false);
                }
            }
        }
    }

    void lcl_ApplyParagraphClassification(SwDoc* pDoc,
                                          const uno::Reference<frame::XModel>& xModel,
                                          const uno::Reference<text::XTextContent>& xParent,
                                          std::vector<svx::ClassificationResult> aResults)
    {
        css::uno::Reference<css::rdf::XResource> xNodeSubject(xParent, uno::UNO_QUERY);
        if (!xNodeSubject.is())
            return;

        uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);

        sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());

        // Remove all paragraph classification fields.
        for (;;)
        {
            uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent);
            if (!xTextField.is())
                break;
            lcl_RemoveParagraphMetadataField(xTextField);
        }

        if (aResults.empty())
            return;

        // Since we always insert at the start of the paragraph,
        // need to insert in reverse order.
        std::reverse(aResults.begin(), aResults.end());
        // Ignore "PARAGRAPH" types
        aResults.erase(std::remove_if(aResults.begin(),
                                      aResults.end(),
                                      [&](const svx::ClassificationResult& rResult)-> bool
                                                { return rResult.meType == svx::ClassificationType::PARAGRAPH; }),
                                      aResults.end());

        std::vector<OUString> aFieldNames;
        for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex)
        {
            const svx::ClassificationResult& rResult = aResults[nIndex];

            const bool isLast = nIndex == 0;
            const bool isFirst = nIndex == aResults.size() - 1;
            OUString sKey;
            OUString sValue = rResult.msName;
            switch (rResult.meType)
            {
                case svx::ClassificationType::TEXT:
                {
                    sKey = aKeyCreator.makeNumberedTextKey();
                }
                break;

                case svx::ClassificationType::CATEGORY:
                {
                    if (rResult.msIdentifier.isEmpty())
                    {
                        sKey = aKeyCreator.makeCategoryNameKey();
                    }
                    else
                    {
                        sValue = rResult.msIdentifier;
                        sKey = aKeyCreator.makeCategoryIdentifierKey();
                    }
                    SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName);
                }
                break;

                case svx::ClassificationType::MARKING:
                {
                    sKey = aKeyCreator.makeNumberedMarkingKey();
                }
                break;

                case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
                {
                    sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey();
                }
                break;

                default:
                break;
            }

            OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName);
            if (isLast)
                sDisplayText += ")";
            lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText);
            aFieldNames.emplace_back(sKey);
        }

        // Correct the order
        std::reverse(aFieldNames.begin(), aFieldNames.end());
        OUStringBuffer sFieldNames;
        bool first = true;
        for (const OUString& rFieldName : aFieldNames)
        {
            if (!first)
                sFieldNames.append("/");
            sFieldNames.append(rFieldName);
            first = false;
        }

        const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second;
        SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames);
        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear());
    }

    void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults)
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !GetCursor() || !GetCursor()->Start())
            return;

        SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode();
        if (pNode == nullptr)
            return;

        // Prevent recursive validation since this is triggered on node updates, which we do below.
        const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
            SetParagraphSignatureValidation(bOldValidationFlag);
        });

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
        lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, std::move(aResults));
    }

    std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const uno::Reference<frame::XModel>& xModel, const uno::Reference<text::XTextContent>& xParagraph)
    {
        std::vector<svx::ClassificationResult> aResult;

        uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
        if (!xTextPortionEnumerationAccess.is())
            return aResult;

        uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();

        const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());

        while (xTextPortions->hasMoreElements())
        {
            uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
            OUString aTextPortionType;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
            if (aTextPortionType != UNO_NAME_TEXT_FIELD)
                continue;

            uno::Reference<lang::XServiceInfo> xField;
            xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField;
            if (!xField->supportsService(MetadataFieldServiceName))
                continue;

            uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY);
            const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
            const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy);

            uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY);
            const OUString aName = rdfNamePair.first;
            const OUString aValue = rdfNamePair.second;
            const OUString sBlank("");
            if (aKeyCreator.isMarkingTextKey(aName))
            {
                aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank });
            }
            else if (aKeyCreator.isCategoryNameKey(aName))
            {
                aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
            }
            else if (aKeyCreator.isCategoryIdentifierKey(aName))
            {
                aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue });
            }
            else if (aKeyCreator.isMarkingKey(aName))
            {
                aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank });
            }
            else if (aKeyCreator.isIntellectualPropertyPartKey(aName))
            {
                aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank });
            }
        }

        return aResult;
    }

    std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification()
    {
        std::vector<svx::ClassificationResult> aResult;

        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !GetCursor() || !GetCursor()->Start())
            return aResult;

        SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode();
        if (pNode == nullptr)
            return aResult;

        uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        return lcl_CollectParagraphClassification(xModel, xParent);
    }

    sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix)
    {
        basegfx::B2DHomMatrix aTransformation;
        basegfx::B2DTuple aScale;
        basegfx::B2DTuple aTranslate;
        double fRotate = 0;
        double fShear = 0;

        aTransformation.set(0, 0, rMatrix.Line1.Column1);
        aTransformation.set(0, 1, rMatrix.Line1.Column2);
        aTransformation.set(0, 2, rMatrix.Line1.Column3);
        aTransformation.set(1, 0, rMatrix.Line2.Column1);
        aTransformation.set(1, 1, rMatrix.Line2.Column2);
        aTransformation.set(1, 2, rMatrix.Line2.Column3);
        aTransformation.set(2, 0, rMatrix.Line3.Column1);
        aTransformation.set(2, 1, rMatrix.Line3.Column2);
        aTransformation.set(2, 2, rMatrix.Line3.Column3);

        aTransformation.decompose(aScale, aTranslate, fRotate, fShear);
        sal_Int16 nDeg = round(basegfx::rad2deg(fRotate));
        return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg);
    }

    SfxWatermarkItem SwEditShell::GetWatermark()
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell)
            return SfxWatermarkItem();

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
        std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
        for (const OUString& rPageStyleName : aUsedPageStyles)
        {
            uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
            uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);

            bool bHeaderIsOn = false;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
            if (!bHeaderIsOn)
                return SfxWatermarkItem();

            uno::Reference<text::XText> xHeaderText;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;

            OUString aShapeServiceName = "com.sun.star.drawing.CustomShape";
            OUString sWatermark = "";
            bool bSuccess = false;
            uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess);

            if (xWatermark.is())
            {
                SfxWatermarkItem aItem;
                uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
                uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
                Color nColor;
                sal_Int16 nTransparency;
                OUString aFont;
                drawing::HomogenMatrix3 aMatrix;

                aItem.SetText(xTextRange->getString());

                if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont)
                    aItem.SetFont(aFont);
                if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor)
                    aItem.SetColor(nColor);
                if (xPropertySet->getPropertyValue("Transformation") >>= aMatrix)
                    aItem.SetAngle(lcl_GetAngle(aMatrix));
                if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency)
                    aItem.SetTransparency(nTransparency);

                return aItem;
            }
        }
        return SfxWatermarkItem();
    }

    void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark,
                                const uno::Reference<frame::XModel>& xModel,
                                const uno::Reference<beans::XPropertySet>& xPageStyle,
                                const uno::Reference<text::XText>& xHeaderText)
    {
        if (!xHeaderText.is())
            return;

        uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
        OUString aShapeServiceName = "com.sun.star.drawing.CustomShape";
        OUString sWatermark = WATERMARK_NAME;
        bool bSuccess = false;
        uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess);

        bool bDeleteWatermark = rWatermark.GetText().isEmpty();
        if (xWatermark.is())
        {
            drawing::HomogenMatrix3 aMatrix;
            Color nColor = 0xc0c0c0;
            sal_Int16 nTransparency = 50;
            sal_Int16 nAngle = 45;
            OUString aFont = "";

            uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
            xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont;
            xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor;
            xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency;
            xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
            nAngle = lcl_GetAngle(aMatrix);

            // If the header already contains a watermark, see if it its text is up to date.
            uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
            if (xTextRange->getString() != rWatermark.GetText()
                || aFont != rWatermark.GetFont()
                || nColor != rWatermark.GetColor()
                || nAngle != rWatermark.GetAngle()
                || nTransparency != rWatermark.GetTransparency()
                || bDeleteWatermark)
            {
                // No: delete it and we'll insert a replacement.
                uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY);
                xComponent->dispose();
                xWatermark.clear();
            }
        }

        if (!bSuccess || xWatermark.is() || bDeleteWatermark)
            return;

        OUString sFont = rWatermark.GetFont();
        sal_Int16 nAngle = rWatermark.GetAngle();
        sal_Int16 nTransparency = rWatermark.GetTransparency();
        Color nColor = rWatermark.GetColor();

        // Calc the ratio.
        double fRatio = 0;

        ScopedVclPtrInstance<VirtualDevice> pDevice;
        vcl::Font aFont = pDevice->GetFont();
        aFont.SetFamilyName(sFont);
        aFont.SetFontSize(Size(0, 96));
        pDevice->SetFont(aFont);

        auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText());
        if (nTextWidth)
        {
            fRatio = pDevice->GetTextHeight();
            fRatio /= nTextWidth;
        }

        // Calc the size.
        sal_Int32 nWidth = 0;
        awt::Size aSize;
        xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize;
        if (aSize.Width < aSize.Height)
        {
            // Portrait.
            sal_Int32 nLeftMargin = 0;
            xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin;
            sal_Int32 nRightMargin = 0;
            xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin;
            nWidth = aSize.Width - nLeftMargin - nRightMargin;
        }
        else
        {
            // Landscape.
            sal_Int32 nTopMargin = 0;
            xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin;
            sal_Int32 nBottomMargin = 0;
            xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin;
            nWidth = aSize.Height - nTopMargin - nBottomMargin;
        }
        sal_Int32 nHeight = fRatio * nWidth;

        // Create and insert the shape.
        uno::Reference<drawing::XShape> xShape(xMultiServiceFactory->createInstance(aShapeServiceName), uno::UNO_QUERY);
        basegfx::B2DHomMatrix aTransformation;
        aTransformation.identity();
        aTransformation.scale(nWidth, nHeight);
        aTransformation.rotate(-basegfx::deg2rad(nAngle));
        drawing::HomogenMatrix3 aMatrix;
        aMatrix.Line1.Column1 = aTransformation.get(0, 0);
        aMatrix.Line1.Column2 = aTransformation.get(0, 1);
        aMatrix.Line1.Column3 = aTransformation.get(0, 2);
        aMatrix.Line2.Column1 = aTransformation.get(1, 0);
        aMatrix.Line2.Column2 = aTransformation.get(1, 1);
        aMatrix.Line2.Column3 = aTransformation.get(1, 2);
        aMatrix.Line3.Column1 = aTransformation.get(2, 0);
        aMatrix.Line3.Column2 = aTransformation.get(2, 1);
        aMatrix.Line3.Column3 = aTransformation.get(2, 2);
        uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
        xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::makeAny(text::TextContentAnchorType_AT_CHARACTER));
        uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
        xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false);

        // The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering.
        uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY);
        xLockable->addActionLock();
        xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::makeAny(static_cast<sal_Int32>(nColor)));
        xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::makeAny(drawing::FillStyle_SOLID));
        xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::makeAny(nTransparency));
        xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::makeAny(drawing::LineStyle_NONE));
        xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::makeAny(false));
        xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::makeAny(false));
        xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::makeAny(false));
        xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::makeAny(nHeight));
        xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::makeAny(nWidth));
        xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::makeAny(text::WrapTextMode_THROUGH));
        xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::makeAny(static_cast<sal_Int16>(text::RelOrientation::PAGE_PRINT_AREA)));
        xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::makeAny(static_cast<sal_Int16>(text::RelOrientation::PAGE_PRINT_AREA)));
        xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::makeAny(sFont));
        xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::makeAny(WATERMARK_AUTO_SIZE));
        xPropertySet->setPropertyValue("Transformation", uno::makeAny(aMatrix));

        uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
        xTextRange->setString(rWatermark.GetText());

        uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
        xDefaulter->createCustomShapeDefaults("fontwork-plain-text");

        auto aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry").get< uno::Sequence<beans::PropertyValue> >();
        auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq);
        uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
        {
            {"TextPath", uno::makeAny(true)},
        }));
        auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue)
        {
            return rValue.Name == "TextPath";
        });
        if (it == aGeomPropVec.end())
            aGeomPropVec.push_back(comphelper::makePropertyValue("TextPath", aPropertyValues));
        else
            it->Value <<= aPropertyValues;
        xPropertySet->setPropertyValue("CustomShapeGeometry", uno::makeAny(comphelper::containerToSequence(aGeomPropVec)));

        // tdf#108494, tdf#109313 the header height was switched to height of a watermark
        // and shape was moved to the lower part of a page, force position update
        xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
        xPropertySet->setPropertyValue("Transformation", uno::makeAny(aMatrix));

        xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::makeAny(static_cast<sal_Int16>(text::HoriOrientation::CENTER)));
        xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::makeAny(static_cast<sal_Int16>(text::VertOrientation::CENTER)));

        uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
        xNamed->setName(sWatermark);
        xLockable->removeActionLock();
    }

    void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark)
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell)
            return;

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
        uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();

        for (const OUString& rPageStyleName : aStyles)
        {
            uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);

            // If the header is off, turn it on.
            bool bHeaderIsOn = false;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
            if (!bHeaderIsOn)
                xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::makeAny(true));

            // backup header height
            bool bDynamicHeight = true;
            sal_Int32 nOldValue;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue;
            xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight;
            xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false));

            // If the header already contains a document header field, no need to do anything.
            uno::Reference<text::XText> xHeaderText;
            uno::Reference<text::XText> xHeaderTextFirst;
            uno::Reference<text::XText> xHeaderTextLeft;
            uno::Reference<text::XText> xHeaderTextRight;

            xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
            lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText);

            xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst;
            lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst);

            xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft;
            lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft);

            xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight;
            lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight);

            // tdf#108494 the header height was switched to height of a watermark
            // and shape was moved to the lower part of a page
            xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::makeAny(sal_Int32(11)));
            xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::makeAny(nOldValue));
            xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
        }
    }

    SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc* pDoc,
                                                   const uno::Reference<text::XTextField>& xField,
                                                   const uno::Reference<text::XTextContent>& xParent,
                                                   const bool bRemove)
      : SwUndo(SwUndoId::PARA_SIGN_ADD, pDoc),
        m_pDoc(pDoc),
        m_xField(xField),
        m_xParent(xParent),
        m_bRemove(bRemove)
    {
        // Save the metadata and field content to undo/redo.
        uno::Reference<frame::XModel> xModel = m_pDoc->GetDocShell()->GetBaseModel();
        const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField);
        const auto it = aStatements.find(ParagraphSignatureIdRDFName);
        if (it != aStatements.end())
            m_signature = it->second;

        const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName);
        if (it2 != aStatements.end())
            m_usage = it2->second;

        uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY);
        m_display = xText->getString();
    }

    void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&)
    {
        if (m_bRemove)
            Remove();
        else
            Insert();
    }

    void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&)
    {
        if (m_bRemove)
            Insert();
        else
            Remove();
    }

    void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&)
    {
    }

    void SwUndoParagraphSigning::Insert()
    {
        // Disable undo to avoid introducing noise when we edit the metadata field.
        const bool isUndoEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo();
        m_pDoc->GetIDocumentUndoRedo().DoUndo(false);

        // Prevent validation since this will trigger a premature validation
        // upon inserting, but before setting the metadata.
        SwEditShell* pEditSh = m_pDoc->GetEditShell();
        const bool bOldValidationFlag = pEditSh->SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([&] () {
                pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
                m_pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
            });

        m_xField = lcl_InsertParagraphSignature(m_pDoc->GetDocShell()->GetBaseModel(), m_xParent, m_signature, m_usage);
        lcl_DoUpdateParagraphSignatureField(m_pDoc, m_xField, m_display);
    }

    void SwUndoParagraphSigning::Remove()
    {
        // Disable undo to avoid introducing noise when we edit the metadata field.
        const bool isUndoEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo();
        m_pDoc->GetIDocumentUndoRedo().DoUndo(false);

        // Prevent validation since this will trigger a premature validation
        // upon removing.
        SwEditShell* pEditSh = m_pDoc->GetEditShell();
        const bool bOldValidationFlag = pEditSh->SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([&] () {
                pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
                m_pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
            });

        lcl_RemoveParagraphMetadataField(m_xField);
    }

    void SwEditShell::SignParagraph()
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !GetCursor() || !GetCursor()->Start())
            return;
        const SwPosition* pPosStart = GetCursor()->Start();
        if (!pPosStart)
            return;
        SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode();
        if (!pNode)
            return;

        // Table text signing is not supported.
        if (pNode->FindTableNode() != nullptr)
            return;

        // 1. Get the text (without fields).
        const uno::Reference<text::XTextContent> xParagraph = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
        const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
        if (utf8Text.isEmpty())
            return;

        // 2. Get certificate.
        uno::Reference<security::XDocumentDigitalSignatures> xSigner(
            security::DocumentDigitalSignatures::createWithVersion(
                comphelper::getProcessComponentContext(), "1.2" ) );

        uno::Sequence<css::beans::PropertyValue> aProperties;
        uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties);
        if (!xCertificate.is())
            return;

        // 3. Sign it.
        svl::crypto::Signing signing(xCertificate);
        signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength());
        OStringBuffer sigBuf;
        if (!signing.Sign(sigBuf))
            return;

        const OUString signature = OStringToOUString(sigBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8, 0);

        std::vector<css::beans::PropertyValue> vec = comphelper::sequenceToContainer<std::vector<css::beans::PropertyValue>>(aProperties);
        auto it = std::find_if(vec.begin(), vec.end(), [](const beans::PropertyValue& rValue)
                                                        {
                                                            return rValue.Name == "Usage";
                                                        });

        OUString aUsage;
        if (it != vec.end())
            it->Value >>= aUsage;

        // 4. Add metadata
        // Prevent validation since this will trigger a premature validation
        // upon inserting, but before setting the metadata.
        const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
                SetParagraphSignatureValidation(bOldValidationFlag);
            });

        GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);

        const uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage);

        lcl_UpdateParagraphSignatureField(GetDoc(), xModel, xParagraph, xField, utf8Text);

        SwUndoParagraphSigning* pUndo = new SwUndoParagraphSigning(GetDoc(), xField, xParagraph, true);
        GetDoc()->GetIDocumentUndoRedo().AppendUndo(pUndo);

        GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
    }

    void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove)
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled())
            return;

        SwPaM* pPaM = GetCursor();
        const SwPosition* pPosStart = pPaM->Start();
        SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode();
        if (!pNode)
            return;

        // Prevent recursive validation since this is triggered on node updates, which we do below.
        const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
                SetParagraphSignatureValidation(bOldValidationFlag);
            });

        // Table text signing is not supported.
        if (pNode->FindTableNode() != nullptr)
            return;

        uno::Reference<text::XTextContent> xParentText = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
        lcl_ValidateParagraphSignatures(GetDoc(), xParentText, updateDontRemove);
    }

    void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove)
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !IsParagraphSignatureValidationEnabled())
            return;

        // Prevent recursive validation since this is triggered on node updates, which we do below.
        const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
                SetParagraphSignatureValidation(bOldValidationFlag);
            });

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        const uno::Reference<text::XTextDocument> xDoc(xModel, uno::UNO_QUERY);
        uno::Reference<text::XText> xParent = xDoc->getText();
        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
        if (!xParagraphEnumerationAccess.is())
            return;
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
        if (!xParagraphs.is())
            return;
        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
            lcl_ValidateParagraphSignatures(GetDoc(), xParagraph, updateDontRemove);
        }
    }

    uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index)
    {
        uno::Reference<text::XTextField> xTextField;
        if (pNode != nullptr && pDocSh != nullptr)
        {
            SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD);
            SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
            if (pTextMeta != nullptr)
            {
                SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr()));
                if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
                {
                    const css::uno::Reference<css::rdf::XResource> xSubject(pMeta->MakeUnoObject(), uno::UNO_QUERY);
                    uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel();
                    const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject);
                    if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() ||
                        aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end())
                    {
                        xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY);
                    }
                }
            }
        }

        return xTextField;
    }

    void SwEditShell::RestoreMetadataFields()
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell || !IsParagraphSignatureValidationEnabled())
            return;

        // Prevent recursive validation since this is triggered on node updates, which we do below.
        const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
        comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
                SetParagraphSignatureValidation(bOldValidationFlag);
            });

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        const uno::Reference<text::XTextDocument> xDoc(xModel, uno::UNO_QUERY);
        uno::Reference<text::XText> xParent = xDoc->getText();
        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
        if (!xParagraphEnumerationAccess.is())
            return;
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
        if (!xParagraphs.is())
            return;
        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);

            std::map<OUString, SignatureDescr> aSignatures;
            std::vector<svx::ClassificationResult> aResults;

            try
            {
                const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
                const OUString sBlank("");

                const OUString sFieldNames = lcl_getRDF(xModel, xParagraph, ParagraphClassificationFieldNamesRDFName).second;
                if (!sFieldNames.isEmpty())
                {
                    // Order the fields
                    sal_Int32 nIndex = 0;
                    do
                    {
                        const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex);
                        if (sCurFieldName.isEmpty())
                            break;
                        std::pair<OUString, OUString> fieldNameValue = lcl_getRDF(xModel, xParagraph, sCurFieldName);
                        const OUString sName = fieldNameValue.first;
                        const OUString sValue = fieldNameValue.second;

                        if (aKeyCreator.isMarkingTextKey(sName))
                        {
                            aResults.push_back({ svx::ClassificationType::TEXT, sValue, sValue, sBlank });
                        }
                        else if (aKeyCreator.isCategoryNameKey(sName))
                        {
                            const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xParent, ParagraphClassificationAbbrRDFName);
                            const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
                            aResults.push_back({ svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank });
                        }
                        else if (aKeyCreator.isCategoryIdentifierKey(sName))
                        {
                            const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xParent, ParagraphClassificationAbbrRDFName);
                            const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
                            aResults.push_back({ svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue });
                        }
                        else if (aKeyCreator.isMarkingKey(sName))
                        {
                            aResults.push_back({ svx::ClassificationType::MARKING, sValue, sValue, sBlank });
                        }
                        else if (aKeyCreator.isIntellectualPropertyPartKey(sName))
                        {
                            aResults.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank });
                        }
                    }
                    while (nIndex >= 0);
                }

                lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, aResults);

                // Get Signatures
                for (const auto& pair : lcl_getRDFStatements(xModel, xParagraph))
                {
                    const OUString sName = pair.first;
                    const OUString sValue = pair.second;
                    if (sName.startsWith(ParagraphSignatureRDFNamespace))
                    {
                        OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength());
                        sal_Int32 index = sSuffix.indexOf(":");
                        if (index >= 0)
                        {
                            OUString id = sSuffix.copy(0, index);
                            OUString type = sSuffix.copy(index);
                            if (type == ParagraphSignatureDateRDFName)
                                aSignatures[id].msDate = sValue;
                            else if (type == ParagraphSignatureUsageRDFName)
                                aSignatures[id].msUsage = sValue;
                            else if (type == ParagraphSignatureDigestRDFName)
                                aSignatures[id].msSignature = sValue;
                        }
                    }
                }

                for (const auto& pair : aSignatures)
                {
                    uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first);
                    if (!xField.is())
                    {
                        uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
                        xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);

                        // Add the signature at the end.
                        xField->attach(xParagraph->getAnchor()->getEnd());

                        const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
                        SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first);

                        const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
                        lcl_UpdateParagraphSignatureField(GetDoc(), xModel, xParagraph, xField, utf8Text);
                    }
                }
            }
            catch (const std::exception&)
            {
            }
        }
    }

    bool SwEditShell::IsCursorInParagraphMetadataField() const
    {
        if (GetCursor() && GetCursor()->Start())
        {
            SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode();
            const sal_uLong index = GetCursor()->Start()->nContent.GetIndex();
            uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
            return xField.is();
        }

        return false;
    }

    bool SwEditShell::RemoveParagraphMetadataFieldAtCursor()
    {
        if (GetCursor() && GetCursor()->Start())
        {
            SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode();
            sal_uLong index = GetCursor()->Start()->nContent.GetIndex();
            uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
            if (!xField.is())
            {
                // Try moving the cursor to see if we're _facing_ a metafield or not,
                // as opposed to being within one.
                index--; // Backspace moves left

                xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
            }

            if (xField.is())
            {
                lcl_RemoveParagraphMetadataField(xField);
                return true;
            }
        }

        return false;
    }

    OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator,
                                            const uno::Reference<frame::XModel>& xModel, const uno::Reference<text::XTextContent>& xParagraph)
    {
        uno::Reference<text::XTextField> xTextField;
        xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey());
        if (xTextField.is())
        {
            const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName);
            return rHelper.GetBACNameForIdentifier(rdfValuePair.second);
        }

        xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey());
        if (xTextField.is())
        {
            const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName);
            return rdfValuePair.second;
        }

        return OUString();
    }

    OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor)
    {
        OUString sHighestClass;

        SwTextNode* pNode = pCursor->Start()->nNode.GetNode().GetTextNode();
        if (pNode == nullptr)
            return sHighestClass;

        SwDocShell* pDocShell = pNode->GetDoc()->GetDocShell();
        if (!pDocShell)
            return sHighestClass;

        SfxClassificationHelper aHelper(pDocShell->getDocProperties());
        sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());

        uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
        const uno::Reference< text::XTextDocument > xDoc(xModel, uno::UNO_QUERY);
        uno::Reference<text::XText> xParent = xDoc->getText();

        uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
        while (xParagraphs->hasMoreElements())
        {
            uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
            OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph);
            sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass);
        }

        return sHighestClass;
    }

    void SwEditShell::ClassifyDocPerHighestParagraphClass()
    {
        SwDocShell* pDocShell = GetDoc()->GetDocShell();
        if (!pDocShell)
            return;

        // bail out as early as possible if we don't have paragraph classification
        if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS))
            return;

        uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties();
        uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();

        sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
        SfxClassificationHelper aHelper(xDocumentProperties);

        OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor());

        const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey());

        if (!aClassificationCategory.isEmpty())
        {
            sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory);
        }

        if (aClassificationCategory != sHighestClass)
        {
            std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
                                                           VclMessageType::Question, VclButtonsType::Ok,
                                                           SwResId(STR_CLASSIFICATION_LEVEL_CHANGED)));
            xQueryBox->run();
        }

        const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass);

        // Check the origin, if "manual" (created via advanced classification dialog),
        // then we just need to set the category name.
        if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL)
        {
            aHelper.SetBACName(sHighestClass, eHighestClassType);
            ApplyAdvancedClassification(CollectAdvancedClassification());
        }
        else
        {
            SetClassification(sHighestClass, eHighestClassType);
        }
    }

    // #i62675#
    void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat,
                                    const bool bResetListAttrs)
    {
        SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0];
        StartAllAction();

        SwRewriter aRewriter;
        aRewriter.AddRule(UndoArg1, pLocal->GetName());

        GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
        for(SwPaM& rPaM : GetCursor()->GetRingContainer())
        {

            if ( !rPaM.HasReadonlySel( GetViewOptions()->IsFormView() ) )
            {
                // Change the paragraph style to pLocal and remove all direct paragraph formatting.
                GetDoc()->SetTextFormatColl( rPaM, pLocal, true, bResetListAttrs );

                // If there are hints on the nodes which cover the whole node, then remove those, too.
                SwPaM aPaM(*rPaM.Start(), *rPaM.End());
                if (SwTextNode* pEndTextNode = aPaM.End()->nNode.GetNode().GetTextNode())
                {
                    aPaM.Start()->nContent = 0;
                    aPaM.End()->nContent = pEndTextNode->GetText().getLength();
                }
                GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true);
            }

        }
        GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
        EndAllAction();
    }

    SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
            SwTextFormatColl* pParent)
    {
        SwTextFormatColl *pColl;
        if ( pParent == nullptr )
            pParent = &GetTextFormatColl(0);
        if (  (pColl=GetDoc()->MakeTextFormatColl(rFormatCollName, pParent)) == nullptr )
        {
            OSL_FAIL( "MakeTextFormatColl failed" );
        }
        return pColl;

    }

    void SwEditShell::FillByEx(SwTextFormatColl* pColl)
    {
        SwPaM * pCursor = GetCursor();
        SwContentNode * pCnt = pCursor->GetContentNode();
        const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
        if( pSet )
        {
            // JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is
            //      in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!)
            //      Bug 57568

            // Do NOT copy AutoNumRules into the template
            const SfxPoolItem* pItem;
            const SwNumRule* pRule = nullptr;
            if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false ) ||
                SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,false ) ||
                ( SfxItemState::SET == pSet->GetItemState( RES_PARATR_NUMRULE,
                    false, &pItem ) && nullptr != (pRule = GetDoc()->FindNumRulePtr(
                    static_cast<const SwNumRuleItem*>(pItem)->GetValue() )) &&
                    pRule && pRule->IsAutoRule() )
                )
            {
                SfxItemSet aSet( *pSet );
                aSet.ClearItem( RES_BREAK );
                aSet.ClearItem( RES_PAGEDESC );

                if( pRule || (SfxItemState::SET == pSet->GetItemState( RES_PARATR_NUMRULE,
                    false, &pItem ) && nullptr != (pRule = GetDoc()->FindNumRulePtr(
                    static_cast<const SwNumRuleItem*>(pItem)->GetValue() )) &&
                    pRule && pRule->IsAutoRule() ))
                    aSet.ClearItem( RES_PARATR_NUMRULE );

                if( aSet.Count() )
                    GetDoc()->ChgFormat(*pColl, aSet );
            }
            else
                GetDoc()->ChgFormat(*pColl, *pSet );
        }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V560 A part of conditional expression is always true: pRule.

↑ V560 A part of conditional expression is always true: pRule.