summaryrefslogtreecommitdiffstats
path: root/sw/source/core/edit/edfcol.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/edit/edfcol.cxx')
-rw-r--r--sw/source/core/edit/edfcol.cxx2329
1 files changed, 2329 insertions, 0 deletions
diff --git a/sw/source/core/edit/edfcol.cxx b/sw/source/core/edit/edfcol.cxx
new file mode 100644
index 000000000..fd1cd62cb
--- /dev/null
+++ b/sw/source/core/edit/edfcol.cxx
@@ -0,0 +1,2329 @@
+/* -*- 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 <unoparagraph.hxx>
+#include <strings.hrc>
+#include <undobj.hxx>
+#include <UndoParagraphSignature.hxx>
+#include <txtatr.hxx>
+#include <fmtmeta.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <IDocumentRedlineAccess.hxx>
+
+constexpr OUStringLiteral WATERMARK_NAME = u"PowerPlusWaterMarkObject";
+#define WATERMARK_AUTO_SIZE sal_uInt32(1)
+
+namespace
+{
+constexpr OUStringLiteral MetaFilename(u"tscp/bails.rdf");
+constexpr OUStringLiteral MetaNS(u"urn:bails");
+constexpr OUStringLiteral ParagraphSignatureRDFNamespace = u"urn:bails:loext:paragraph:signature:";
+constexpr OUStringLiteral ParagraphSignatureIdRDFName = u"urn:bails:loext:paragraph:signature:id";
+constexpr OUStringLiteral ParagraphSignatureDigestRDFName = u":digest";
+constexpr OUStringLiteral ParagraphSignatureDateRDFName = u":date";
+constexpr OUStringLiteral ParagraphSignatureUsageRDFName = u":usage";
+constexpr OUStringLiteral ParagraphSignatureLastIdRDFName = u"urn:bails:loext:paragraph:signature:lastid";
+constexpr OUStringLiteral ParagraphClassificationNameRDFName = u"urn:bails:loext:paragraph:classification:name";
+constexpr OUStringLiteral ParagraphClassificationValueRDFName = u"urn:bails:loext:paragraph:classification:value";
+constexpr OUStringLiteral ParagraphClassificationAbbrRDFName = u"urn:bails:loext:paragraph:classification:abbreviation";
+constexpr OUStringLiteral ParagraphClassificationFieldNamesRDFName = u"urn:bails:loext:paragraph:classification:fields";
+constexpr OUStringLiteral MetadataFieldServiceName = u"com.sun.star.text.textfield.MetadataField";
+constexpr OUStringLiteral DocInfoServiceName = u"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, 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("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,
+ 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 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,
+ 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 uno::Reference<frame::XModel>& 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 uno::Reference<frame::XModel>& 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& 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 + "): ") : 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 new signature on this paragraph.
+OUString lcl_getNextSignatureId(const uno::Reference<frame::XModel>& 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 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);
+ 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 uno::Reference<frame::XModel>& 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>& 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 uno::Reference<frame::XModel>& 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 uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& xParagraph,
+ std::u16string_view sKey = u"")
+{
+ uno::Reference<text::XTextField> xTextField;
+
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ return xTextField;
+
+ // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
+ 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))
+ {
+ 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& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove)
+{
+ SwDocShell* pDocShell = rDoc.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(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(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::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 & 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;
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ 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 = pObjSh->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::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(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::Any(awt::FontWeight::BOLD));
+ xFooterPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::BOLD));
+ }
+ else
+ {
+ xHeaderPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::NORMAL));
+ xFooterPropertySet->setPropertyValue("CharWeight", 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());
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ 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::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), 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);
+ 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::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, OUStringConcatenation(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::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(xMultiServiceFactory->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 uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& 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
+ aResults.erase(std::remove_if(aResults.begin(),
+ aResults.end(),
+ [](const svx::ClassificationResult& rResult)-> bool
+ { return rResult.meType == svx::ClassificationType::PARAGRAPH; }),
+ aResults.end());
+
+ 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()->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, css::uno::Reference<css::rdf::XResource>(xParent, uno::UNO_QUERY), std::move(aResults));
+}
+
+static 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;
+ static const OUStringLiteral sBlank(u"");
+ 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);
+}
+
+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);
+ 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() const
+{
+ 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::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);
+
+ 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 = "";
+ bool bSuccess = false;
+ uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, "com.sun.star.drawing.CustomShape", 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();
+}
+
+static 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;
+
+ const 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);
+
+ uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
+ xNamed->setName(sWatermark);
+
+ 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::Any(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::Any(static_cast<sal_Int32>(nColor)));
+ xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::Any(drawing::FillStyle_SOLID));
+ xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(nTransparency));
+ xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::Any(drawing::LineStyle_NONE));
+ xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::Any(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::Any(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::Any(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::Any(nHeight));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::Any(nWidth));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::Any(text::WrapTextMode_THROUGH));
+ xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
+ xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::Any(sFont));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::Any(WATERMARK_AUTO_SIZE));
+ xPropertySet->setPropertyValue("Transformation", uno::Any(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::Any(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::Any(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::Any(aMatrix));
+
+ xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::Any(text::HoriOrientation::CENTER));
+ xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::Any(text::VertOrientation::CENTER));
+
+ xLockable->removeActionLock();
+}
+
+void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return;
+ const bool bNoWatermark = rWatermark.GetText().isEmpty();
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), 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 the header is off, turn it on.
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ if (!bHeaderIsOn)
+ {
+ if (bNoWatermark)
+ continue; // the style doesn't have any watermark - no need to do anything
+
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(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::Any(sal_Int32(11)));
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(nOldValue));
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
+ }
+}
+
+SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc& rDoc,
+ const uno::Reference<text::XTextField>& xField,
+ const uno::Reference<text::XTextContent>& xParent,
+ const bool bRemove)
+ : SwUndo(SwUndoId::PARA_SIGN_ADD, &rDoc),
+ m_rDoc(rDoc),
+ m_xField(xField),
+ m_xParent(xParent),
+ m_bRemove(bRemove)
+{
+ // Save the metadata and field content to undo/redo.
+ uno::Reference<frame::XModel> xModel = m_rDoc.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_rDoc.GetIDocumentUndoRedo().DoesUndo();
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
+
+ // Prevent validation since this will trigger a premature validation
+ // upon inserting, but before setting the metadata.
+ SwEditShell* pEditSh = m_rDoc.GetEditShell();
+ const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([&] () {
+ if (pEditSh)
+ pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ m_xField = lcl_InsertParagraphSignature(m_rDoc.GetDocShell()->GetBaseModel(), m_xParent, m_signature, m_usage);
+ lcl_DoUpdateParagraphSignatureField(m_rDoc, m_xField, m_display);
+}
+
+void SwUndoParagraphSigning::Remove()
+{
+ // Disable undo to avoid introducing noise when we edit the metadata field.
+ const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
+
+ // Prevent validation since this will trigger a premature validation
+ // upon removing.
+ SwEditShell* pEditSh = m_rDoc.GetEditShell();
+ const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([&] () {
+ if (pEditSh)
+ pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ lcl_RemoveParagraphMetadataField(m_xField);
+}
+
+void SwEditShell::SignParagraph()
+{
+ SwDoc& rDoc = *GetDoc();
+ SwDocShell* pDocShell = rDoc.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(
+ // here none of the version-dependent methods are called
+ security::DocumentDigitalSignatures::createDefault(
+ comphelper::getProcessComponentContext()));
+
+ 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);
+
+ auto it = std::find_if(std::as_const(aProperties).begin(), std::as_const(aProperties).end(), [](const beans::PropertyValue& rValue)
+ {
+ return rValue.Name == "Usage";
+ });
+
+ OUString aUsage;
+ if (it != std::as_const(aProperties).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);
+ });
+
+ rDoc.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);
+
+ rDoc.GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, true));
+
+ rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
+}
+
+void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove)
+{
+ if (!pNode || !IsParagraphSignatureValidationEnabled())
+ return;
+
+ // Table text signing is not supported.
+ if (pNode->FindTableNode() != 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<text::XTextContent> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode);
+ lcl_ValidateParagraphSignatures(*GetDoc(), xParentText, updateDontRemove);
+}
+
+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();
+ ValidateParagraphSignatures(pNode, 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);
+ }
+}
+
+static 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::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::RestoreMetadataFieldsAndValidateParagraphSignatures()
+{
+ 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;
+
+ static constexpr OUStringLiteral sBlank(u"");
+ const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
+ const css::uno::Sequence<css::uno::Reference<rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS);
+
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
+
+ try
+ {
+ const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY);
+ const std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject);
+
+ const auto it = aStatements.find(ParagraphClassificationFieldNamesRDFName);
+ const OUString sFieldNames = (it != aStatements.end() ? it->second : sBlank);
+ std::vector<svx::ClassificationResult> aResults;
+ if (!sFieldNames.isEmpty())
+ {
+ assert(it != aStatements.end() && "can only be non-empty if it was valid");
+ // Order the fields
+ sal_Int32 nIndex = 0;
+ do
+ {
+ const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex);
+ if (sCurFieldName.isEmpty())
+ break;
+
+ const auto it2 = aStatements.find(sCurFieldName);
+ bool bStatementFound = it2 != aStatements.end();
+ const OUString sName = bStatementFound ? it->first : sBlank;
+ const OUString sValue = bStatementFound ? it->second : sBlank;
+
+ if (aKeyCreator.isMarkingTextKey(sName))
+ {
+ aResults.push_back({ svx::ClassificationType::TEXT, sValue, sValue, sBlank });
+ }
+ else if (aKeyCreator.isCategoryNameKey(sName))
+ {
+ const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName);
+ const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue);
+ aResults.push_back({ svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank });
+ }
+ else if (aKeyCreator.isCategoryIdentifierKey(sName))
+ {
+ const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName);
+ const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->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);
+ }
+
+ // Update classification based on results.
+ lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, aResults);
+
+ // Get Signatures
+ std::map<OUString, SignatureDescr> aSignatures;
+ for (const auto& pair : lcl_getRDFStatements(xModel, xParagraph))
+ {
+ const OUString& sName = pair.first;
+ if (sName.startsWith(ParagraphSignatureRDFNamespace))
+ {
+ const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength());
+ const sal_Int32 index = sSuffix.indexOf(":");
+ if (index >= 0)
+ {
+ const OUString id = sSuffix.copy(0, index);
+ const OUString type = sSuffix.copy(index);
+ const OUString& sValue = pair.second;
+ 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);
+ }
+ }
+
+ lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, true); // Validate and Update signatures.
+ }
+ 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;
+}
+
+static 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())
+ {
+ return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second;
+ }
+
+ return OUString();
+}
+
+static 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);
+ const 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);
+
+ // Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ // 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 in table cells select mode, ignore the cells that aren't actually selected
+ if (IsTableMode() && !rPaM.HasMark())
+ continue;
+
+ if ( !rPaM.HasReadonlySel( GetViewOptions()->IsFormView() ) )
+ {
+ // store previous paragraph style for track changes
+ OUString sParaStyleName;
+ sal_uInt16 nPoolId = USHRT_MAX;
+ SwContentNode * pCnt = rPaM.Start()->nNode.GetNode().GetContentNode();
+ if ( pCnt && pCnt->GetTextNode() && GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() )
+ {
+ const SwTextFormatColl* pTextFormatColl = pCnt->GetTextNode()->GetTextColl();
+ sal_uInt16 nStylePoolId = pTextFormatColl->GetPoolFormatId();
+ // default paragraph style
+ if ( nStylePoolId == RES_POOLCOLL_STANDARD )
+ nPoolId = nStylePoolId;
+ else
+ sParaStyleName = pTextFormatColl->GetName();
+ }
+
+ // Change the paragraph style to pLocal and remove all direct paragraph formatting.
+ GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, GetLayout());
+
+ // 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, GetLayout());
+
+ // add redline tracking the previous paragraph style
+ if ( GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() &&
+ // multi-paragraph ParagraphFormat redline ranges
+ // haven't supported by AppendRedline(), yet
+ // TODO handle multi-paragraph selections, too,
+ // e.g. by breaking them to single paragraphs
+ aPaM.Start()->nNode == aPaM.End()->nNode )
+ {
+ SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::ParagraphFormat, aPaM );
+ auto const result(GetDoc()->getIDocumentRedlineAccess().AppendRedline( pRedline, true));
+ // store original paragraph style to reject formatting change
+ if ( IDocumentRedlineAccess::AppendResult::IGNORED != result &&
+ ( nPoolId == RES_POOLCOLL_STANDARD || !sParaStyleName.isEmpty() ) )
+ {
+ std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
+ xExtra.reset(new SwRedlineExtraData_FormatColl(sParaStyleName, nPoolId, nullptr));
+ if (xExtra)
+ pRedline->SetExtraData( xExtra.get() );
+ }
+ }
+ }
+
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
+ EndAllAction();
+}
+
+SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
+ SwTextFormatColl* pParent)
+{
+ SwTextFormatColl *pColl;
+ if ( pParent == nullptr )
+ pParent = &GetTextFormatColl(0);
+ pColl = GetDoc()->MakeTextFormatColl(rFormatCollName, pParent);
+ if ( pColl == nullptr )
+ {
+ OSL_FAIL( "MakeTextFormatColl failed" );
+ }
+ return pColl;
+
+}
+
+void SwEditShell::FillByEx(SwTextFormatColl* pColl)
+{
+ SwPaM * pCursor = GetCursor();
+ SwContentNode * pCnt = pCursor->GetContentNode();
+ if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not?
+ { // only need properties-node because BREAK/PAGEDESC filtered anyway!
+ pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode);
+ }
+ const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
+ if( !pSet )
+ return;
+
+ // 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 SwNumRuleItem* pItem;
+ const SwNumRule* pRule = nullptr;
+ if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false)
+ || SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false)
+ || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
+ && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
+ && pRule->IsAutoRule()))
+ {
+ SfxItemSet aSet( *pSet );
+ aSet.ClearItem( RES_BREAK );
+ aSet.ClearItem( RES_PAGEDESC );
+
+ if (pRule
+ || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
+ && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
+ && 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: */