Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sw/source/core/edit/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 100 kB image not shown  

Quelle  edfcol.cxx   Sprache: C

 
/* -*- 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 <sal/config.h>

#include <string_view>

#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/document/XDocumentProperties.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/XShape.hpp>
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.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/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/unoprnms.hxx>
#include <sfx2/classificationhelper.hxx>
#include <svx/ClassificationCommon.hxx>
#include <svx/ClassificationField.hxx>
#include <svl/cryptosign.hxx>
#include <svl/sigstruct.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/virdev.hxx>

#include <redline.hxx>
#include <poolfmt.hxx>
#include <hintids.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <ndtxt.hxx>
#include <paratr.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 <txtfrm.hxx>
#include <rdfhelper.hxx>
#include <sfx2/watermarkitem.hxx>

#include <SwStyleNameMapper.hxx>
#include <unoparagraph.hxx>
#include <strings.hrc>
#include <undobj.hxx>
#include <UndoParagraphSignature.hxx>
#include <txtatr.hxx>
#include <fmtmeta.hxx>
#include <unotxdoc.hxx>
#include <unotextbodyhf.hxx>
#include <unoport.hxx>

#include <comphelper/diagnose_ex.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <names.hxx>

constexpr OUString WATERMARK_NAME = u"PowerPlusWaterMarkObject"_ustr;
#define WATERMARK_AUTO_SIZE sal_uInt32(1)

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

/// Find all page styles which are currently used in the document.
std::vector<ProgName> lcl_getUsedPageStyles(SwViewShell const * pShell)
{
    std::vector<ProgName> 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())
        {
            ProgName sStyleName;
            SwStyleNameMapper::FillProgName(pDesc->GetName(), sStyleName, SwGetPoolIdFromName::PageDesc);
            aReturn.push_back(sStyleName);
        }
    }

    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, std::u16string_view 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, std::u16string_view 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(u"com.sun.star.text.TextContent"_ustr);
            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 rtl::Reference<SwXTextDocument>&&nbsp;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 rtl::Reference<SwXTextDocument>& xModel,
                                                      const uno::Reference<css::text::XTextField>& xField,
                                                      std::u16string_view 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 rtl::Reference<SwXTextDocument>& 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 rtl::Reference<SwXTextDocument>& xModel,
                                   const uno::Reference<css::text::XTextField>& xField)
{
    return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName);
}

uno::Reference<text::XTextField> lcl_findFieldByRDF(const rtl::Reference<SwXTextDocument>& xModel,
                                                    const uno::Reference<text::XTextContent>& xParagraph,
                                                    const OUString& sRDFName,
                                                    std::u16string_view 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.empty() || sRDFValue == pair.second))
            return xField;
    }

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

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

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

SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
                                     const uno::Reference<css::text::XTextContent>& xParagraph,
                                     std::u16string_view sFieldId)
{
    SignatureDescr aDescr;

    const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId;
    const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph);

    const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName);
    aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString());

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

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

    return aDescr;
}

SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
                                     const uno::Reference<css::text::XTextContent>& xParagraph,
                                     const uno::Reference<css::text::XTextField>& xField)
{
    const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second;
    if (!sFieldId.isEmpty())
        return lcl_getSignatureDescr(xModel, xParagraph, sFieldId);

    return SignatureDescr();
}

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

    if (aDescr.isValid())
    {
        const 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 == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;

            assert(aInfo.GetSigningCertificate()); // it was valid
            msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.GetSigningCertificate()->X509Subject + ", " +
                aDescr.msDate;
            msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : u": "_ustr);
            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 rtl::Reference<SwXTextDocument>& 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 new signature on this paragraph.
OUString lcl_getNextSignatureId(const rtl::Reference<SwXTextDocument>& xModel,
                                const uno::Reference<text::XTextContent>& xParagraph)
{
    const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second;
    return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1);
}

/// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry
uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const rtl::Reference<SwXTextDocument>& xModel,
                                                              const uno::Reference<text::XTextContent>& xParagraph,
                                                              const OUString& signature,
                                                              const OUString& usage)
{
    auto xField = uno::Reference<text::XTextField>(xModel->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);
    const OUString prefix = ParagraphSignatureRDFNamespace + sId;
    SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId);
    SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature);
    SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage);
    SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear());

    return xField;
}

/// Updates the signature field text if changed and returns true only iff updated.
bool lcl_DoUpdateParagraphSignatureField(SwDoc& rDoc,
                                         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 = rDoc.GetIDocumentUndoRedo().DoesUndo();
    rDoc.GetIDocumentUndoRedo().DoUndo(false);
    comphelper::ScopeGuard const g([&rDoc, isUndoEnabled]() {
        rDoc.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& rDoc,
                                       const rtl::Reference<SwXTextDocument>& xModel,
                                       const uno::Reference<css::text::XTextContent>& xParagraph,
                                       const uno::Reference<css::text::XTextField>& xField,
                                       const OString& utf8Text)
{
    const OUString sDisplayText
        = lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second;
    return lcl_DoUpdateParagraphSignatureField(rDoc, xField, sDisplayText);
}

void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>&&nbsp;xField)
{
    uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor());
    xParagraph->getText()->removeTextContent(xField);
}

/// 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 rtl::Reference<SwXTextDocument>& xModel,
                                        const uno::Reference<css::text::XTextField>& xField,
                                        std::u16string_view sKey)
{
    const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
    return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.empty() || rdfPair.second == sKey);
}

uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
                                                                      const rtl::Reference<SwXParagraph>& xParagraph,
                                                                      std::u16string_view sKey = u"")
{
    uno::Reference<text::XTextField> xTextField;

    if (!xParagraph.is())
        return xTextField;

    // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
    rtl::Reference<SwXTextPortionEnumeration> xTextPortions = xParagraph->createTextFieldsEnumeration();
    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))
        {
            xTextField = std::move(xField);
            break;
        }
    }

    return xTextField;
}

/// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry
uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel,
                                                                   const uno::Reference<text::XTextContent>& xParent)
{
    auto xField = uno::Reference<text::XTextField>(xModel->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 rtl::Reference<SwXTextDocument>& 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& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove, const uno::Sequence<uno::Reference<css::rdf::XURI>>& rGraphNames)
{
    SwDocShell* pDocShell = rDoc.GetDocShell();
    if (!pDocShell)
        return;

    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();

    // Check if the paragraph is signed.
    try
    {
        const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY);
        std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, rGraphNames, xSubject);
        const auto it = aStatements.find(ParagraphSignatureLastIdRDFName);
        if (it == aStatements.end() || it->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(rDoc, xModel, xParagraph, xField, utf8Text);
        }
        else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
        {
            rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
            rDoc.GetIDocumentUndoRedo().AppendUndo(
                std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, false));
            lcl_RemoveParagraphMetadataField(xField);
            rDoc.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]);
}

static void insertFieldToDocument(rtl::Reference<SwXTextDocument> 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::Any(rsKey));
    uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);

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

static void removeAllClassificationFields(std::u16string_view 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);
            }
        }
    }
}

static 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;
}

static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const &&nbsp;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)
        return;

    const SfxObjectShell* pObjSh = SfxObjectShell::Current();
    if (!pObjSh)
        return;

    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
    uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
    uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);

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

    const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
    const std::vector<ProgName> aUsedPageStyles = lcl_getUsedPageStyles(this);
    for (const ProgName& rPageStyleName : aUsedPageStyles)
    {
        uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName.toString()), 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 ProgName& rPageStyleName : aUsedPageStyles)
    {
        uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName.toString()), 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::Any(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::Any(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(xModel, xHeaderText, xHeaderParagraphCursor, sKey);
                    insertFieldToDocument(xModel, xFooterText, xFooterParagraphCursor, sKey);
                }
                break;

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

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

                case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
                {
                    OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey();
                    svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
                    insertFieldToDocument(xModel, xHeaderText, xHeaderParagraphCursor, sKey);
                    insertFieldToDocument(xModel, 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(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
                        xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
                    }
                    else
                    {
                        xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
                        xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
                    }
                }
                break;

                default:
                break;
            }
        }
    }
}

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

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

    const SfxObjectShell* pObjSh = SfxObjectShell::Current();
    if (!pObjSh)
        return aResult;

    const OUString sBlank;

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

    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
    uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
    uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);

    std::vector<ProgName> aPageStyles = lcl_getUsedPageStyles(this);
    const ProgName& aPageStyleString = aPageStyles.back();
    uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString.toString()), 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.emplace_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(u"CharWeight"_ustr);

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

        aResult.emplace_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.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
            }
            else if (aCreator.isCategoryNameKey(aName))
            {
                const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                if (!aValue.isEmpty())
                    aResult.emplace_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.emplace_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.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
            }
            else if (aCreator.isIntellectualPropertyPartKey(aName))
            {
                const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
                if (!aValue.isEmpty())
                    aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank);
            }
        }
    }

    if (!bFoundClassificationCategory)
    {
        const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
        if (!aValue.isEmpty())
            aResult.emplace_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;

    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
    uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
    uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
    const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();

    for (const OUString& rPageStyleName : aStyles)
    {
        uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), 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::Any(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, Concat2View(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())))
                {
                    // Append a field to the end of the header text.
                    uno::Reference<beans::XPropertySet> xField(xModel->createInstance(DocInfoServiceName), uno::UNO_QUERY);
                    xField->setPropertyValue(UNO_NAME_NAME, uno::Any(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::Any(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(xModel->createInstance(DocInfoServiceName), uno::UNO_QUERY);
                xField->setPropertyValue(UNO_NAME_NAME, uno::Any(sFooter));
                uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
                xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false);
            }
        }
    }
}

// We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is
// on a performance-sensitive path.
static void lcl_ApplyParagraphClassification(SwDoc* pDoc,
                                      const rtl::Reference<SwXTextDocument>& xModel,
                                      const rtl::Reference<SwXParagraph>& xParent,
                                      const css::uno::Reference<css::rdf::XResource>& xNodeSubject,
                                      std::vector<svx::ClassificationResult> aResults)
{
    if (!xNodeSubject.is())
        return;

    // 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
    std::erase_if(aResults,
                                  [](const svx::ClassificationResult& rResult)-> bool
                                            { return rResult.meType == svx::ClassificationType::PARAGRAPH; });

    sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
    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()->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);
    });

    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
    rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
    lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent), std::move(aResults));
}

static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel, const uno::Reference<text::XTextContent>&&nbsp;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;
        static constexpr OUString sBlank(u""_ustr);
        if (aKeyCreator.isMarkingTextKey(aName))
        {
            aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
        }
        else if (aKeyCreator.isCategoryNameKey(aName))
        {
            aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
        }
        else if (aKeyCreator.isCategoryIdentifierKey(aName))
        {
            aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
        }
        else if (aKeyCreator.isMarkingKey(aName))
        {
            aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
        }
        else if (aKeyCreator.isIntellectualPropertyPartKey(aName))
        {
            aResult.emplace_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()->GetNode().GetTextNode();
    if (pNode == nullptr)
        return aResult;

    rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
    return lcl_CollectParagraphClassification(xModel, xParent);
}

static 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);
    // For this to be a valid 2D transform matrix, the last row must be [0,0,1]
    assert( rMatrix.Line3.Column1 == 0 );
    assert( rMatrix.Line3.Column2 == 0 );
    assert( rMatrix.Line3.Column3 == 1 );

    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() const
{
    SwDocShell* pDocShell = GetDoc()->GetDocShell();
    if (!pDocShell)
        return SfxWatermarkItem();

    rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
    uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
    uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
    std::vector<ProgName> aUsedPageStyles = lcl_getUsedPageStyles(this);
    for (const ProgName& rPageStyleName : aUsedPageStyles)
    {
        uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName.toString()), 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 sWatermark = u""_ustr;
        bool bSuccess = false;
        uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, u"com.sun.star.drawing.CustomShape"_ustr, 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(u"Transformation"_ustr) >>= aMatrix)
                aItem.SetAngle(lcl_GetAngle(aMatrix));
            if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency)
                aItem.SetTransparency(nTransparency);

            return aItem;
        }
    }
    return SfxWatermarkItem();
}

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

    OUString aShapeServiceName = u"com.sun.star.drawing.CustomShape"_ustr;
    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 = u""_ustr;

        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(u"Transformation"_ustr) >>= 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);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=92 G=93

¤ Dauer der Verarbeitung: 0.39 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.