Add Marathi autocorrect
[LibreOffice.git] / sw / source / core / edit / edfcol.cxx
blob165ef5eec35482b80926d29d81568ede469c2dab
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <string_view>
24 #include <editsh.hxx>
26 #include <com/sun/star/container/XEnumerationAccess.hpp>
27 #include <com/sun/star/container/XContentEnumerationAccess.hpp>
28 #include <com/sun/star/document/XActionLockable.hpp>
29 #include <com/sun/star/document/XDocumentProperties.hpp>
30 #include <com/sun/star/drawing/FillStyle.hpp>
31 #include <com/sun/star/drawing/HomogenMatrix3.hpp>
32 #include <com/sun/star/drawing/LineStyle.hpp>
33 #include <com/sun/star/drawing/XShape.hpp>
34 #include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
35 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
36 #include <com/sun/star/lang/XServiceInfo.hpp>
37 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
38 #include <com/sun/star/text/RelOrientation.hpp>
39 #include <com/sun/star/text/TextContentAnchorType.hpp>
40 #include <com/sun/star/text/VertOrientation.hpp>
41 #include <com/sun/star/text/WrapTextMode.hpp>
42 #include <com/sun/star/text/XTextContent.hpp>
43 #include <com/sun/star/text/XTextDocument.hpp>
44 #include <com/sun/star/text/XTextField.hpp>
45 #include <com/sun/star/text/XTextRange.hpp>
46 #include <com/sun/star/text/XParagraphAppend.hpp>
47 #include <com/sun/star/text/XParagraphCursor.hpp>
48 #include <com/sun/star/awt/FontWeight.hpp>
49 #include <com/sun/star/rdf/XMetadatable.hpp>
50 #include <com/sun/star/security/DocumentDigitalSignatures.hpp>
51 #include <com/sun/star/security/XCertificate.hpp>
53 #include <basegfx/matrix/b2dhommatrix.hxx>
54 #include <comphelper/propertysequence.hxx>
55 #include <comphelper/propertyvalue.hxx>
56 #include <comphelper/processfactory.hxx>
57 #include <comphelper/sequence.hxx>
58 #include <comphelper/scopeguard.hxx>
59 #include <comphelper/string.hxx>
60 #include <editeng/unoprnms.hxx>
61 #include <sfx2/classificationhelper.hxx>
62 #include <svx/ClassificationCommon.hxx>
63 #include <svx/ClassificationField.hxx>
64 #include <svl/cryptosign.hxx>
65 #include <svl/sigstruct.hxx>
66 #include <utility>
67 #include <vcl/svapp.hxx>
68 #include <vcl/weld.hxx>
69 #include <vcl/virdev.hxx>
71 #include <redline.hxx>
72 #include <poolfmt.hxx>
73 #include <hintids.hxx>
74 #include <doc.hxx>
75 #include <IDocumentUndoRedo.hxx>
76 #include <ndtxt.hxx>
77 #include <paratr.hxx>
78 #include <viewopt.hxx>
79 #include <SwRewriter.hxx>
80 #include <numrule.hxx>
81 #include <swundo.hxx>
82 #include <docary.hxx>
83 #include <docsh.hxx>
84 #include <unoprnms.hxx>
85 #include <rootfrm.hxx>
86 #include <pagefrm.hxx>
87 #include <txtfrm.hxx>
88 #include <rdfhelper.hxx>
89 #include <sfx2/watermarkitem.hxx>
91 #include <SwStyleNameMapper.hxx>
92 #include <unoparagraph.hxx>
93 #include <strings.hrc>
94 #include <undobj.hxx>
95 #include <UndoParagraphSignature.hxx>
96 #include <txtatr.hxx>
97 #include <fmtmeta.hxx>
98 #include <unotxdoc.hxx>
99 #include <unotextbodyhf.hxx>
100 #include <unoport.hxx>
102 #include <comphelper/diagnose_ex.hxx>
103 #include <IDocumentRedlineAccess.hxx>
105 constexpr OUString WATERMARK_NAME = u"PowerPlusWaterMarkObject"_ustr;
106 #define WATERMARK_AUTO_SIZE sal_uInt32(1)
108 namespace
110 constexpr OUString MetaFilename(u"tscp/bails.rdf"_ustr);
111 constexpr OUString MetaNS(u"urn:bails"_ustr);
112 constexpr OUString ParagraphSignatureRDFNamespace = u"urn:bails:loext:paragraph:signature:"_ustr;
113 constexpr OUString ParagraphSignatureIdRDFName = u"urn:bails:loext:paragraph:signature:id"_ustr;
114 constexpr OUString ParagraphSignatureDigestRDFName = u":digest"_ustr;
115 constexpr OUString ParagraphSignatureDateRDFName = u":date"_ustr;
116 constexpr OUString ParagraphSignatureUsageRDFName = u":usage"_ustr;
117 constexpr OUString ParagraphSignatureLastIdRDFName = u"urn:bails:loext:paragraph:signature:lastid"_ustr;
118 constexpr OUString ParagraphClassificationNameRDFName = u"urn:bails:loext:paragraph:classification:name"_ustr;
119 constexpr OUString ParagraphClassificationValueRDFName = u"urn:bails:loext:paragraph:classification:value"_ustr;
120 constexpr OUString ParagraphClassificationAbbrRDFName = u"urn:bails:loext:paragraph:classification:abbreviation"_ustr;
121 constexpr OUString ParagraphClassificationFieldNamesRDFName = u"urn:bails:loext:paragraph:classification:fields"_ustr;
122 constexpr OUString MetadataFieldServiceName = u"com.sun.star.text.textfield.MetadataField"_ustr;
123 constexpr OUString DocInfoServiceName = u"com.sun.star.text.TextField.DocInfo.Custom"_ustr;
125 /// Find all page styles which are currently used in the document.
126 std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell)
128 std::vector<OUString> aReturn;
130 SwRootFrame* pLayout = pShell->GetLayout();
131 for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
133 SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame);
134 if (const SwPageDesc *pDesc = pPage->FindPageDesc())
136 OUString sStyleName;
137 SwStyleNameMapper::FillProgName(pDesc->GetName(), sStyleName, SwGetPoolIdFromName::PageDesc);
138 aReturn.push_back(sStyleName);
142 return aReturn;
145 /// Search for a field named rFieldName of type rServiceName in xText and return it.
146 uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
148 uno::Reference<text::XTextField> xField;
149 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
150 uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
151 while (xParagraphs->hasMoreElements())
153 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
154 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
155 while (xTextPortions->hasMoreElements())
157 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
158 OUString aTextPortionType;
159 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
160 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
161 continue;
163 uno::Reference<lang::XServiceInfo> xTextField;
164 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
165 if (!xTextField->supportsService(rServiceName))
166 continue;
168 OUString aName;
169 uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
170 xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
171 if (aName == rFieldName)
173 xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY);
174 break;
179 return xField;
182 /// Search for a field named rFieldName of type rServiceName in xText and return true iff found.
183 bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
185 return lcl_findField(xText, rServiceName, rFieldName).is();
188 /// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName.
189 uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText,
190 const OUString& rServiceName, OUString& rShapeName, bool& bSuccess)
192 bSuccess = false;
193 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
194 uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
195 while (xParagraphs->hasMoreElements())
197 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
198 if (!xTextPortionEnumerationAccess.is())
199 continue;
201 bSuccess = true;
203 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
204 while (xTextPortions->hasMoreElements())
206 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
207 OUString aTextPortionType;
208 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
209 if (aTextPortionType != "Frame")
210 continue;
212 uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY);
213 if (!xContentEnumerationAccess.is())
214 continue;
216 uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration(u"com.sun.star.text.TextContent"_ustr);
217 if (!xEnumeration->hasMoreElements())
218 continue;
220 uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY);
221 if (!xWatermark->supportsService(rServiceName))
222 continue;
224 uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY);
226 if (!xNamed->getName().match(WATERMARK_NAME))
227 continue;
229 rShapeName = xNamed->getName();
231 uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY);
232 return xShape;
236 return uno::Reference<drawing::XShape>();
239 /// Extract the text of the paragraph without any of the fields.
240 /// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper.
241 OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText)
243 OUStringBuffer strBuf;
244 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY);
245 if (!xTextPortionEnumerationAccess.is())
246 return OString();
248 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
249 while (xTextPortions->hasMoreElements())
251 uno::Any elem = xTextPortions->nextElement();
253 //TODO: Consider including hidden and conditional texts/portions.
254 OUString aTextPortionType;
255 uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY);
256 xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
257 if (aTextPortionType == "Text")
259 uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY);
260 if (xTextRange.is())
261 strBuf.append(xTextRange->getString());
265 // Cleanup the dummy characters added by fields (which we exclude).
266 comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART);
267 comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND);
268 comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD);
270 return strBuf.makeStringAndClear().trim().toUtf8();
273 template <typename T>
274 std::map<OUString, OUString> lcl_getRDFStatements(const rtl::Reference<SwXTextDocument>& xModel,
275 const T& xRef)
279 const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY);
280 return SwRDFHelper::getStatements(xModel, MetaNS, xSubject);
282 catch (const ::css::uno::Exception&)
286 return std::map<OUString, OUString>();
289 /// Returns RDF (key, value) pair associated with the field, if any.
290 std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const rtl::Reference<SwXTextDocument>& xModel,
291 const uno::Reference<css::text::XTextField>& xField,
292 std::u16string_view sPrefix)
294 for (const auto& pair : lcl_getRDFStatements(xModel, xField))
296 if (pair.first.startsWith(sPrefix))
297 return pair;
300 return std::make_pair(OUString(), OUString());
303 /// Returns RDF (key, value) pair associated with the field, if any.
304 template <typename T>
305 std::pair<OUString, OUString> lcl_getRDF(const rtl::Reference<SwXTextDocument>& xModel,
306 const T& xRef,
307 const OUString& sRDFName)
309 const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef);
310 const auto it = aStatements.find(sRDFName);
311 return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString());
314 /// Returns true iff the field in question is paragraph signature.
315 /// Note: must have associated RDF, since signatures are otherwise just metadata fields.
316 bool lcl_IsParagraphSignatureField(const rtl::Reference<SwXTextDocument>& xModel,
317 const uno::Reference<css::text::XTextField>& xField)
319 return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName);
322 uno::Reference<text::XTextField> lcl_findFieldByRDF(const rtl::Reference<SwXTextDocument>& xModel,
323 const uno::Reference<text::XTextContent>& xParagraph,
324 const OUString& sRDFName,
325 std::u16string_view sRDFValue)
327 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
328 if (!xTextPortionEnumerationAccess.is())
329 return uno::Reference<text::XTextField>();
331 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
332 if (!xTextPortions.is())
333 return uno::Reference<text::XTextField>();
335 while (xTextPortions->hasMoreElements())
337 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
338 OUString aTextPortionType;
339 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
340 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
341 continue;
343 uno::Reference<lang::XServiceInfo> xTextField;
344 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
345 if (!xTextField->supportsService(MetadataFieldServiceName))
346 continue;
348 uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
349 const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName);
350 if (pair.first == sRDFName && (sRDFValue.empty() || sRDFValue == pair.second))
351 return xField;
354 return uno::Reference<text::XTextField>();
357 struct SignatureDescr
359 OUString msSignature;
360 OUString msUsage;
361 OUString msDate;
363 bool isValid() const { return !msSignature.isEmpty(); }
366 SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
367 const uno::Reference<css::text::XTextContent>& xParagraph,
368 std::u16string_view sFieldId)
370 SignatureDescr aDescr;
372 const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId;
373 const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph);
375 const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName);
376 aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString());
378 const auto itDate = aStatements.find(prefix + ParagraphSignatureDateRDFName);
379 aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString());
381 const auto itUsage = aStatements.find(prefix + ParagraphSignatureUsageRDFName);
382 aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString());
384 return aDescr;
387 SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
388 const uno::Reference<css::text::XTextContent>& xParagraph,
389 const uno::Reference<css::text::XTextField>& xField)
391 const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second;
392 if (!sFieldId.isEmpty())
393 return lcl_getSignatureDescr(xModel, xParagraph, sFieldId);
395 return SignatureDescr();
398 /// Validate and create the signature field display text from the fields.
399 std::pair<bool, OUString> lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr,
400 const OString& utf8Text)
402 OUString msg = SwResId(STR_INVALID_SIGNATURE);
403 bool valid = false;
405 if (aDescr.isValid())
407 const char* pData = utf8Text.getStr();
408 const std::vector<unsigned char> data(pData, pData + utf8Text.getLength());
410 OString encSignature;
411 if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0))
413 const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature));
414 SignatureInformation aInfo(0);
415 valid = svl::crypto::Signing::Verify(data, false, sig, aInfo);
416 valid = valid
417 && aInfo.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
419 assert(aInfo.GetSigningCertificate()); // it was valid
420 msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.GetSigningCertificate()->X509Subject + ", " +
421 aDescr.msDate;
422 msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : u": "_ustr);
423 msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID));
427 return std::make_pair(valid, msg);
430 /// Validate and return validation result and signature field display text.
431 std::pair<bool, OUString>
432 lcl_MakeParagraphSignatureFieldText(const rtl::Reference<SwXTextDocument>& xModel,
433 const uno::Reference<css::text::XTextContent>& xParagraph,
434 const uno::Reference<css::text::XTextField>& xField,
435 const OString& utf8Text)
437 const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField);
438 return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text);
441 /// Generate the next valid ID for the new signature on this paragraph.
442 OUString lcl_getNextSignatureId(const rtl::Reference<SwXTextDocument>& xModel,
443 const uno::Reference<text::XTextContent>& xParagraph)
445 const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second;
446 return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1);
449 /// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry
450 uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const rtl::Reference<SwXTextDocument>& xModel,
451 const uno::Reference<text::XTextContent>& xParagraph,
452 const OUString& signature,
453 const OUString& usage)
455 uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
456 auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
458 // Add the signature at the end.
459 xField->attach(xParagraph->getAnchor()->getEnd());
461 const OUString sId = lcl_getNextSignatureId(xModel, xParagraph);
463 const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY);
464 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId);
466 // First convert the UTC UNIX timestamp to a tools::DateTime then to local time.
467 DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr));
468 aDateTime.ConvertToLocalTime();
469 OUStringBuffer rBuffer;
470 rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
471 rBuffer.append('-');
472 if (aDateTime.GetMonth() < 10)
473 rBuffer.append('0');
474 rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
475 rBuffer.append('-');
476 if (aDateTime.GetDay() < 10)
477 rBuffer.append('0');
478 rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
480 // Now set the RDF on the paragraph, since that's what is preserved in .doc(x).
481 const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY);
482 const OUString prefix = ParagraphSignatureRDFNamespace + sId;
483 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId);
484 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature);
485 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage);
486 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear());
488 return xField;
491 /// Updates the signature field text if changed and returns true only iff updated.
492 bool lcl_DoUpdateParagraphSignatureField(SwDoc& rDoc,
493 const uno::Reference<css::text::XTextField>& xField,
494 const OUString& sDisplayText)
496 // Disable undo to avoid introducing noise when we edit the metadata field.
497 const bool isUndoEnabled = rDoc.GetIDocumentUndoRedo().DoesUndo();
498 rDoc.GetIDocumentUndoRedo().DoUndo(false);
499 comphelper::ScopeGuard const g([&rDoc, isUndoEnabled]() {
500 rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
505 uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY);
506 const OUString curText = xText->getString();
507 if (curText != sDisplayText)
509 xText->setString(sDisplayText);
510 return true;
513 catch (const uno::Exception&)
515 // We failed; avoid crashing.
516 DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature");
519 return false;
522 /// Updates the signature field text if changed and returns true only iff updated.
523 bool lcl_UpdateParagraphSignatureField(SwDoc& rDoc,
524 const rtl::Reference<SwXTextDocument>& xModel,
525 const uno::Reference<css::text::XTextContent>& xParagraph,
526 const uno::Reference<css::text::XTextField>& xField,
527 const OString& utf8Text)
529 const OUString sDisplayText
530 = lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second;
531 return lcl_DoUpdateParagraphSignatureField(rDoc, xField, sDisplayText);
534 void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField)
536 uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor());
537 xParagraph->getText()->removeTextContent(xField);
540 /// Returns true iff the field in question is paragraph classification.
541 /// Note: must have associated RDF, since classifications are otherwise just metadata fields.
542 bool lcl_IsParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
543 const uno::Reference<css::text::XTextField>& xField,
544 std::u16string_view sKey)
546 const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
547 return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.empty() || rdfPair.second == sKey);
550 uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
551 const rtl::Reference<SwXParagraph>& xParagraph,
552 std::u16string_view sKey = u"")
554 uno::Reference<text::XTextField> xTextField;
556 if (!xParagraph.is())
557 return xTextField;
559 // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
560 rtl::Reference<SwXTextPortionEnumeration> xTextPortions = xParagraph->createTextFieldsEnumeration();
561 while (xTextPortions->hasMoreElements())
563 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
564 OUString aTextPortionType;
565 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
566 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
567 continue;
569 uno::Reference<lang::XServiceInfo> xServiceInfo;
570 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo;
571 if (!xServiceInfo->supportsService(MetadataFieldServiceName))
572 continue;
574 uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY);
575 if (lcl_IsParagraphClassificationField(xModel, xField, sKey))
577 xTextField = std::move(xField);
578 break;
582 return xTextField;
585 /// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry
586 uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel,
587 const uno::Reference<text::XTextContent>& xParent)
589 auto xField = uno::Reference<text::XTextField>(xModel->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
591 // Add the classification at the start.
592 xField->attach(xParent->getAnchor()->getStart());
593 return xField;
596 /// Updates the paragraph classification field text if changed and returns true only iff updated.
597 bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc,
598 const rtl::Reference<SwXTextDocument>& xModel,
599 const uno::Reference<css::text::XTextContent>& xTextNode,
600 const OUString& sKey,
601 const OUString& sValue,
602 const OUString& sDisplayText)
604 // Disable undo to avoid introducing noise when we edit the metadata field.
605 const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo();
606 pDoc->GetIDocumentUndoRedo().DoUndo(false);
607 comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () {
608 pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
611 uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode);
613 css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
614 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue);
615 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey);
616 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue);
618 css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY);
619 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue);
621 return lcl_DoUpdateParagraphSignatureField(*pDoc, xField, sDisplayText);
624 void lcl_ValidateParagraphSignatures(SwDoc& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove, const uno::Sequence<uno::Reference<css::rdf::XURI>>& rGraphNames)
626 SwDocShell* pDocShell = rDoc.GetDocShell();
627 if (!pDocShell)
628 return;
630 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
632 // Check if the paragraph is signed.
635 const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY);
636 std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, rGraphNames, xSubject);
637 const auto it = aStatements.find(ParagraphSignatureLastIdRDFName);
638 if (it == aStatements.end() || it->second.isEmpty())
639 return;
641 catch (const ::css::uno::Exception&)
643 return;
646 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
647 if (!xTextPortionEnumerationAccess.is())
648 return;
650 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
651 if (!xTextPortions.is())
652 return;
654 // Get the text (without fields).
655 const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
656 if (utf8Text.isEmpty())
657 return;
659 while (xTextPortions->hasMoreElements())
661 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
662 OUString aTextPortionType;
663 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
664 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
665 continue;
667 uno::Reference<lang::XServiceInfo> xTextField;
668 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
669 if (!xTextField->supportsService(MetadataFieldServiceName))
670 continue;
672 uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
673 if (!lcl_IsParagraphSignatureField(xModel, xField))
675 continue;
678 if (updateDontRemove)
680 lcl_UpdateParagraphSignatureField(rDoc, xModel, xParagraph, xField, utf8Text);
682 else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
684 rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
685 rDoc.GetIDocumentUndoRedo().AppendUndo(
686 std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, false));
687 lcl_RemoveParagraphMetadataField(xField);
688 rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
693 } // anonymous namespace
695 SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const
697 return *GetDoc()->GetDfltTextFormatColl();
700 sal_uInt16 SwEditShell::GetTextFormatCollCount() const
702 return GetDoc()->GetTextFormatColls()->size();
705 SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const
707 return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]);
710 static void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory,
711 uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor,
712 OUString const & rsKey)
714 uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
715 xField->setPropertyValue(UNO_NAME_NAME, uno::Any(rsKey));
716 uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
718 rxText->insertTextContent(rxParagraphCursor, xTextContent, false);
721 static void removeAllClassificationFields(std::u16string_view rPolicy, uno::Reference<text::XText> const & rxText)
723 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY);
724 uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
725 while (xParagraphs->hasMoreElements())
727 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
728 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
729 while (xTextPortions->hasMoreElements())
731 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
732 OUString aTextPortionType;
733 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
734 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
735 continue;
737 uno::Reference<lang::XServiceInfo> xTextField;
738 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
739 if (!xTextField->supportsService(DocInfoServiceName))
740 continue;
742 OUString aName;
743 uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
744 xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
745 if (aName.startsWith(rPolicy))
747 uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
748 rxText->removeTextContent(xField);
754 static sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText)
756 uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY);
757 uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration();
758 sal_Int32 nResult = 0;
759 while (xParagraphEnum->hasMoreElements())
761 xParagraphEnum->nextElement();
762 nResult++;
764 return nResult;
767 static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText)
769 sal_Int32 nNumberOfParagraphs = 0;
770 for (svx::ClassificationResult const & rResult : rResults)
772 if (rResult.meType == svx::ClassificationType::PARAGRAPH)
773 nNumberOfParagraphs++;
776 while (getNumberOfParagraphs(xText) < nNumberOfParagraphs)
778 uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY);
779 xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>());
783 void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults)
785 SwDocShell* pDocShell = GetDoc()->GetDocShell();
786 if (!pDocShell)
787 return;
789 const SfxObjectShell* pObjSh = SfxObjectShell::Current();
790 if (!pObjSh)
791 return;
793 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
794 uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
795 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
797 uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
799 uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
801 const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
802 const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
803 for (const OUString& rPageStyleName : aUsedPageStyles)
805 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
807 // HEADER
808 bool bHeaderIsOn = false;
809 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
810 uno::Reference<text::XText> xHeaderText;
811 if (bHeaderIsOn)
812 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
813 if (xHeaderText.is())
814 removeAllClassificationFields(sPolicy, xHeaderText);
816 // FOOTER
817 bool bFooterIsOn = false;
818 xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
819 uno::Reference<text::XText> xFooterText;
820 if (bFooterIsOn)
821 xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
822 if (xFooterText.is())
823 removeAllClassificationFields(sPolicy, xFooterText);
826 // Clear properties
827 uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
828 svx::classification::removeAllProperties(xPropertyContainer);
830 SfxClassificationHelper aHelper(xDocumentProperties);
832 // Apply properties from the BA policy
833 for (svx::ClassificationResult const & rResult : rResults)
835 if (rResult.meType == svx::ClassificationType::CATEGORY)
837 aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType());
841 sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
843 // Insert origin document property
844 svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL);
846 // Insert full text as document property
847 svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults);
849 for (const OUString& rPageStyleName : aUsedPageStyles)
851 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
853 // HEADER
854 bool bHeaderIsOn = false;
855 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
856 if (!bHeaderIsOn)
857 xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
858 uno::Reference<text::XText> xHeaderText;
859 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
860 equaliseNumberOfParagraph(rResults, xHeaderText);
862 // FOOTER
863 bool bFooterIsOn = false;
864 xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
865 if (!bFooterIsOn)
866 xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
867 uno::Reference<text::XText> xFooterText;
868 xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
869 equaliseNumberOfParagraph(rResults, xFooterText);
871 // SET/DELETE WATERMARK
872 SfxWatermarkItem aWatermarkItem;
873 aWatermarkItem.SetText(aHelper.GetDocumentWatermark());
874 SetWatermark(aWatermarkItem);
876 uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY);
877 uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY);
879 sal_Int32 nParagraph = -1;
881 for (svx::ClassificationResult const & rResult : rResults)
883 switch(rResult.meType)
885 case svx::ClassificationType::TEXT:
887 OUString sKey = aCreator.makeNumberedTextKey();
889 svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
890 insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
891 insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
893 break;
895 case svx::ClassificationType::CATEGORY:
897 OUString sKey = aCreator.makeCategoryNameKey();
898 insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
899 insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
901 break;
903 case svx::ClassificationType::MARKING:
905 OUString sKey = aCreator.makeNumberedMarkingKey();
906 svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
907 insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
908 insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
910 break;
912 case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
914 OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey();
915 svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
916 insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
917 insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
919 break;
921 case svx::ClassificationType::PARAGRAPH:
923 nParagraph++;
925 if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph
927 xHeaderParagraphCursor->gotoNextParagraph(false);
928 xFooterParagraphCursor->gotoNextParagraph(false);
931 xHeaderParagraphCursor->gotoStartOfParagraph(false);
932 xFooterParagraphCursor->gotoStartOfParagraph(false);
934 uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW);
935 uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW);
936 if (rResult.msName == "BOLD")
938 xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
939 xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
941 else
943 xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
944 xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
947 break;
949 default:
950 break;
956 std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification()
958 std::vector<svx::ClassificationResult> aResult;
960 SwDocShell* pDocShell = GetDoc()->GetDocShell();
961 if (!pDocShell)
962 return aResult;
964 const SfxObjectShell* pObjSh = SfxObjectShell::Current();
965 if (!pObjSh)
966 return aResult;
968 const OUString sBlank;
970 uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
971 uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
972 sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
974 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
975 uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
976 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
978 std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this);
979 const OUString& aPageStyleString = aPageStyles.back();
980 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY);
982 bool bHeaderIsOn = false;
983 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
984 if (!bHeaderIsOn)
986 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
987 if (!aValue.isEmpty())
988 aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
990 return aResult;
993 uno::Reference<text::XText> xHeaderText;
994 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
996 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY);
997 uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
999 // set to true if category was found in the header
1000 bool bFoundClassificationCategory = false;
1002 while (xParagraphs->hasMoreElements())
1004 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
1005 if (!xTextPortionEnumerationAccess.is())
1006 continue;
1007 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
1009 // Check font weight
1010 uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW);
1011 uno::Any aAny = xParagraphPropertySet->getPropertyValue(u"CharWeight"_ustr);
1013 OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? u"BOLD"_ustr : u"NORMAL"_ustr;
1015 aResult.emplace_back(svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank);
1017 // Process portions
1018 while (xTextPortions->hasMoreElements())
1020 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
1021 OUString aTextPortionType;
1022 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
1023 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
1024 continue;
1026 uno::Reference<lang::XServiceInfo> xTextField;
1027 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
1028 if (!xTextField->supportsService(DocInfoServiceName))
1029 continue;
1031 OUString aName;
1032 uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
1033 xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
1035 if (aCreator.isMarkingTextKey(aName))
1037 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
1038 if (!aValue.isEmpty())
1039 aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
1041 else if (aCreator.isCategoryNameKey(aName))
1043 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
1044 if (!aValue.isEmpty())
1045 aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
1046 bFoundClassificationCategory = true;
1048 else if (aCreator.isCategoryIdentifierKey(aName))
1050 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
1051 if (!aValue.isEmpty())
1052 aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
1053 bFoundClassificationCategory = true;
1055 else if (aCreator.isMarkingKey(aName))
1057 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
1058 if (!aValue.isEmpty())
1059 aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
1061 else if (aCreator.isIntellectualPropertyPartKey(aName))
1063 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
1064 if (!aValue.isEmpty())
1065 aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank);
1070 if (!bFoundClassificationCategory)
1072 const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
1073 if (!aValue.isEmpty())
1074 aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
1077 return aResult;
1080 void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType)
1082 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1083 if (!pDocShell)
1084 return;
1086 SfxClassificationHelper aHelper(pDocShell->getDocProperties());
1088 const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty();
1090 // This updates the infobar as well.
1091 aHelper.SetBACName(rName, eType);
1093 // Insert origin document property
1094 uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties();
1095 sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
1096 svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY);
1098 bool bHeaderIsNeeded = aHelper.HasDocumentHeader();
1099 bool bFooterIsNeeded = aHelper.HasDocumentFooter();
1100 OUString aWatermark = aHelper.GetDocumentWatermark();
1101 bool bWatermarkIsNeeded = !aWatermark.isEmpty();
1103 if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark)
1104 return;
1106 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1107 uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
1108 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
1109 const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
1111 for (const OUString& rPageStyleName : aStyles)
1113 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
1114 uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
1116 if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark)
1118 // If the header is off, turn it on.
1119 bool bHeaderIsOn = false;
1120 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
1121 if (!bHeaderIsOn)
1122 xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
1124 // If the header already contains a document header field, no need to do anything.
1125 uno::Reference<text::XText> xHeaderText;
1126 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
1128 if (bHeaderIsNeeded)
1130 if (!lcl_hasField(xHeaderText, DocInfoServiceName, Concat2View(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())))
1132 // Append a field to the end of the header text.
1133 uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
1134 xField->setPropertyValue(UNO_NAME_NAME, uno::Any(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()));
1135 uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
1136 xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false);
1140 SfxWatermarkItem aWatermarkItem;
1141 aWatermarkItem.SetText(aWatermark);
1142 SetWatermark(aWatermarkItem);
1145 if (bFooterIsNeeded)
1147 // If the footer is off, turn it on.
1148 bool bFooterIsOn = false;
1149 xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
1150 if (!bFooterIsOn)
1151 xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
1153 // If the footer already contains a document header field, no need to do anything.
1154 uno::Reference<text::XText> xFooterText;
1155 xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
1156 static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER();
1157 if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter))
1159 // Append a field to the end of the footer text.
1160 uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
1161 xField->setPropertyValue(UNO_NAME_NAME, uno::Any(sFooter));
1162 uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
1163 xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false);
1169 // We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is
1170 // on a performance-sensitive path.
1171 static void lcl_ApplyParagraphClassification(SwDoc* pDoc,
1172 const rtl::Reference<SwXTextDocument>& xModel,
1173 const rtl::Reference<SwXParagraph>& xParent,
1174 const css::uno::Reference<css::rdf::XResource>& xNodeSubject,
1175 std::vector<svx::ClassificationResult> aResults)
1177 if (!xNodeSubject.is())
1178 return;
1180 // Remove all paragraph classification fields.
1181 for (;;)
1183 uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent);
1184 if (!xTextField.is())
1185 break;
1186 lcl_RemoveParagraphMetadataField(xTextField);
1189 if (aResults.empty())
1190 return;
1192 // Since we always insert at the start of the paragraph,
1193 // need to insert in reverse order.
1194 std::reverse(aResults.begin(), aResults.end());
1195 // Ignore "PARAGRAPH" types
1196 std::erase_if(aResults,
1197 [](const svx::ClassificationResult& rResult)-> bool
1198 { return rResult.meType == svx::ClassificationType::PARAGRAPH; });
1200 sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
1201 std::vector<OUString> aFieldNames;
1202 for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex)
1204 const svx::ClassificationResult& rResult = aResults[nIndex];
1206 const bool isLast = nIndex == 0;
1207 const bool isFirst = (nIndex == aResults.size() - 1);
1208 OUString sKey;
1209 OUString sValue = rResult.msName;
1210 switch (rResult.meType)
1212 case svx::ClassificationType::TEXT:
1214 sKey = aKeyCreator.makeNumberedTextKey();
1216 break;
1218 case svx::ClassificationType::CATEGORY:
1220 if (rResult.msIdentifier.isEmpty())
1222 sKey = aKeyCreator.makeCategoryNameKey();
1224 else
1226 sValue = rResult.msIdentifier;
1227 sKey = aKeyCreator.makeCategoryIdentifierKey();
1229 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName);
1231 break;
1233 case svx::ClassificationType::MARKING:
1235 sKey = aKeyCreator.makeNumberedMarkingKey();
1237 break;
1239 case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
1241 sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey();
1243 break;
1245 default:
1246 break;
1249 OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName);
1250 if (isLast)
1251 sDisplayText += ")";
1252 lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText);
1253 aFieldNames.emplace_back(sKey);
1256 // Correct the order
1257 std::reverse(aFieldNames.begin(), aFieldNames.end());
1258 OUStringBuffer sFieldNames;
1259 bool first = true;
1260 for (const OUString& rFieldName : aFieldNames)
1262 if (!first)
1263 sFieldNames.append("/");
1264 sFieldNames.append(rFieldName);
1265 first = false;
1268 const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second;
1269 SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames);
1270 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear());
1273 void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults)
1275 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1276 if (!pDocShell || !GetCursor() || !GetCursor()->Start())
1277 return;
1279 SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
1280 if (pNode == nullptr)
1281 return;
1283 // Prevent recursive validation since this is triggered on node updates, which we do below.
1284 const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
1285 comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
1286 SetParagraphSignatureValidation(bOldValidationFlag);
1289 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1290 rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
1291 lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent), std::move(aResults));
1294 static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel, const uno::Reference<text::XTextContent>& xParagraph)
1296 std::vector<svx::ClassificationResult> aResult;
1298 uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
1299 if (!xTextPortionEnumerationAccess.is())
1300 return aResult;
1302 uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
1304 const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
1306 while (xTextPortions->hasMoreElements())
1308 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
1309 OUString aTextPortionType;
1310 xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
1311 if (aTextPortionType != UNO_NAME_TEXT_FIELD)
1312 continue;
1314 uno::Reference<lang::XServiceInfo> xField;
1315 xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField;
1316 if (!xField->supportsService(MetadataFieldServiceName))
1317 continue;
1319 uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY);
1320 const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
1321 const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy);
1323 uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY);
1324 const OUString aName = rdfNamePair.first;
1325 const OUString aValue = rdfNamePair.second;
1326 static constexpr OUString sBlank(u""_ustr);
1327 if (aKeyCreator.isMarkingTextKey(aName))
1329 aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
1331 else if (aKeyCreator.isCategoryNameKey(aName))
1333 aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
1335 else if (aKeyCreator.isCategoryIdentifierKey(aName))
1337 aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
1339 else if (aKeyCreator.isMarkingKey(aName))
1341 aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
1343 else if (aKeyCreator.isIntellectualPropertyPartKey(aName))
1345 aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank);
1349 return aResult;
1352 std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification()
1354 std::vector<svx::ClassificationResult> aResult;
1356 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1357 if (!pDocShell || !GetCursor() || !GetCursor()->Start())
1358 return aResult;
1360 SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
1361 if (pNode == nullptr)
1362 return aResult;
1364 rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
1365 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1366 return lcl_CollectParagraphClassification(xModel, xParent);
1369 static sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix)
1371 basegfx::B2DHomMatrix aTransformation;
1372 basegfx::B2DTuple aScale;
1373 basegfx::B2DTuple aTranslate;
1374 double fRotate = 0;
1375 double fShear = 0;
1377 aTransformation.set(0, 0, rMatrix.Line1.Column1);
1378 aTransformation.set(0, 1, rMatrix.Line1.Column2);
1379 aTransformation.set(0, 2, rMatrix.Line1.Column3);
1380 aTransformation.set(1, 0, rMatrix.Line2.Column1);
1381 aTransformation.set(1, 1, rMatrix.Line2.Column2);
1382 aTransformation.set(1, 2, rMatrix.Line2.Column3);
1383 // For this to be a valid 2D transform matrix, the last row must be [0,0,1]
1384 assert( rMatrix.Line3.Column1 == 0 );
1385 assert( rMatrix.Line3.Column2 == 0 );
1386 assert( rMatrix.Line3.Column3 == 1 );
1388 aTransformation.decompose(aScale, aTranslate, fRotate, fShear);
1389 sal_Int16 nDeg = round(basegfx::rad2deg(fRotate));
1390 return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg);
1393 SfxWatermarkItem SwEditShell::GetWatermark() const
1395 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1396 if (!pDocShell)
1397 return SfxWatermarkItem();
1399 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1400 uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
1401 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
1402 std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
1403 for (const OUString& rPageStyleName : aUsedPageStyles)
1405 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
1407 bool bHeaderIsOn = false;
1408 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
1409 if (!bHeaderIsOn)
1410 return SfxWatermarkItem();
1412 uno::Reference<text::XText> xHeaderText;
1413 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
1415 OUString sWatermark = u""_ustr;
1416 bool bSuccess = false;
1417 uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, u"com.sun.star.drawing.CustomShape"_ustr, sWatermark, bSuccess);
1419 if (xWatermark.is())
1421 SfxWatermarkItem aItem;
1422 uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
1423 uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
1424 Color nColor;
1425 sal_Int16 nTransparency;
1426 OUString aFont;
1427 drawing::HomogenMatrix3 aMatrix;
1429 aItem.SetText(xTextRange->getString());
1431 if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont)
1432 aItem.SetFont(aFont);
1433 if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor)
1434 aItem.SetColor(nColor);
1435 if (xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix)
1436 aItem.SetAngle(lcl_GetAngle(aMatrix));
1437 if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency)
1438 aItem.SetTransparency(nTransparency);
1440 return aItem;
1443 return SfxWatermarkItem();
1446 static void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark,
1447 const rtl::Reference<SwXTextDocument>& xModel,
1448 const uno::Reference<beans::XPropertySet>& xPageStyle,
1449 const uno::Reference<text::XText>& xHeaderText)
1451 if (!xHeaderText.is())
1452 return;
1454 OUString aShapeServiceName = u"com.sun.star.drawing.CustomShape"_ustr;
1455 OUString sWatermark = WATERMARK_NAME;
1456 bool bSuccess = false;
1457 uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess);
1459 bool bDeleteWatermark = rWatermark.GetText().isEmpty();
1460 if (xWatermark.is())
1462 drawing::HomogenMatrix3 aMatrix;
1463 Color nColor = 0xc0c0c0;
1464 sal_Int16 nTransparency = 50;
1465 sal_Int16 nAngle = 45;
1466 OUString aFont = u""_ustr;
1468 uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
1469 xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont;
1470 xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor;
1471 xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency;
1472 xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix;
1473 nAngle = lcl_GetAngle(aMatrix);
1475 // If the header already contains a watermark, see if it its text is up to date.
1476 uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
1477 if (xTextRange->getString() != rWatermark.GetText()
1478 || aFont != rWatermark.GetFont()
1479 || nColor != rWatermark.GetColor()
1480 || nAngle != rWatermark.GetAngle()
1481 || nTransparency != rWatermark.GetTransparency()
1482 || bDeleteWatermark)
1484 // No: delete it and we'll insert a replacement.
1485 uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY);
1486 xComponent->dispose();
1487 xWatermark.clear();
1491 if (!bSuccess || xWatermark.is() || bDeleteWatermark)
1492 return;
1494 const OUString& sFont = rWatermark.GetFont();
1495 sal_Int16 nAngle = rWatermark.GetAngle();
1496 sal_Int16 nTransparency = rWatermark.GetTransparency();
1497 Color nColor = rWatermark.GetColor();
1499 // Calc the ratio.
1500 double fRatio = 0;
1502 ScopedVclPtrInstance<VirtualDevice> pDevice;
1503 vcl::Font aFont = pDevice->GetFont();
1504 aFont.SetFamilyName(sFont);
1505 aFont.SetFontSize(Size(0, 96));
1506 pDevice->SetFont(aFont);
1508 auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText());
1509 if (nTextWidth)
1511 fRatio = pDevice->GetTextHeight();
1512 fRatio /= nTextWidth;
1515 // Calc the size.
1516 sal_Int32 nWidth = 0;
1517 awt::Size aSize;
1518 xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize;
1519 if (aSize.Width < aSize.Height)
1521 // Portrait.
1522 sal_Int32 nLeftMargin = 0;
1523 xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin;
1524 sal_Int32 nRightMargin = 0;
1525 xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin;
1526 nWidth = aSize.Width - nLeftMargin - nRightMargin;
1528 else
1530 // Landscape.
1531 sal_Int32 nTopMargin = 0;
1532 xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin;
1533 sal_Int32 nBottomMargin = 0;
1534 xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin;
1535 nWidth = aSize.Height - nTopMargin - nBottomMargin;
1537 sal_Int32 nHeight = fRatio * nWidth;
1539 // Create and insert the shape.
1540 uno::Reference<drawing::XShape> xShape(xModel->createInstance(aShapeServiceName), uno::UNO_QUERY);
1542 uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
1543 xNamed->setName(sWatermark);
1545 basegfx::B2DHomMatrix aTransformation;
1546 aTransformation.identity();
1547 aTransformation.scale(nWidth, nHeight);
1548 aTransformation.rotate(-basegfx::deg2rad(nAngle));
1549 drawing::HomogenMatrix3 aMatrix;
1550 aMatrix.Line1.Column1 = aTransformation.get(0, 0);
1551 aMatrix.Line1.Column2 = aTransformation.get(0, 1);
1552 aMatrix.Line1.Column3 = aTransformation.get(0, 2);
1553 aMatrix.Line2.Column1 = aTransformation.get(1, 0);
1554 aMatrix.Line2.Column2 = aTransformation.get(1, 1);
1555 aMatrix.Line2.Column3 = aTransformation.get(1, 2);
1556 aMatrix.Line3.Column1 = 0;
1557 aMatrix.Line3.Column2 = 0;
1558 aMatrix.Line3.Column3 = 1;
1559 uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
1560 xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::Any(text::TextContentAnchorType_AT_CHARACTER));
1561 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
1562 xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false);
1564 // The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering.
1565 uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY);
1566 xLockable->addActionLock();
1567 xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::Any(static_cast<sal_Int32>(nColor)));
1568 xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::Any(drawing::FillStyle_SOLID));
1569 xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(nTransparency));
1570 xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::Any(drawing::LineStyle_NONE));
1571 xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::Any(false));
1572 xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::Any(false));
1573 xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::Any(false));
1574 xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::Any(nHeight));
1575 xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::Any(nWidth));
1576 xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::Any(text::WrapTextMode_THROUGH));
1577 xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
1578 xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
1579 xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::Any(sFont));
1580 xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_ASIAN, uno::Any(sFont));
1581 xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_COMPLEX, uno::Any(sFont));
1582 xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::Any(WATERMARK_AUTO_SIZE));
1583 xPropertySet->setPropertyValue(u"Transformation"_ustr, uno::Any(aMatrix));
1585 uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
1586 xTextRange->setString(rWatermark.GetText());
1588 uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
1589 xDefaulter->createCustomShapeDefaults(u"fontwork-plain-text"_ustr);
1591 auto aGeomPropSeq = xPropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr).get< uno::Sequence<beans::PropertyValue> >();
1592 auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq);
1593 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
1595 {"TextPath", uno::Any(true)},
1596 }));
1597 auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue)
1599 return rValue.Name == "TextPath";
1601 if (it == aGeomPropVec.end())
1602 aGeomPropVec.push_back(comphelper::makePropertyValue(u"TextPath"_ustr, aPropertyValues));
1603 else
1604 it->Value <<= aPropertyValues;
1605 xPropertySet->setPropertyValue(u"CustomShapeGeometry"_ustr, uno::Any(comphelper::containerToSequence(aGeomPropVec)));
1607 // tdf#108494, tdf#109313 the header height was switched to height of a watermark
1608 // and shape was moved to the lower part of a page, force position update
1609 xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix;
1610 xPropertySet->setPropertyValue(u"Transformation"_ustr, uno::Any(aMatrix));
1612 xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::Any(text::HoriOrientation::CENTER));
1613 xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::Any(text::VertOrientation::CENTER));
1615 xLockable->removeActionLock();
1618 void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark)
1620 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1621 if (!pDocShell)
1622 return;
1623 const bool bNoWatermark = rWatermark.GetText().isEmpty();
1625 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1626 uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
1627 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
1628 const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
1630 for (const OUString& rPageStyleName : aStyles)
1632 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
1634 // If the header is off, turn it on.
1635 bool bHeaderIsOn = false;
1636 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
1637 if (!bHeaderIsOn)
1639 if (bNoWatermark)
1640 continue; // the style doesn't have any watermark - no need to do anything
1642 xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
1645 // backup header height
1646 bool bDynamicHeight = true;
1647 sal_Int32 nOldValue;
1648 xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue;
1649 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight;
1650 xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false));
1652 // If the header already contains a document header field, no need to do anything.
1653 uno::Reference<text::XText> xHeaderText;
1654 uno::Reference<text::XText> xHeaderTextFirst;
1655 uno::Reference<text::XText> xHeaderTextLeft;
1656 uno::Reference<text::XText> xHeaderTextRight;
1658 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
1659 lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText);
1661 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst;
1662 lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst);
1664 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft;
1665 lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft);
1667 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight;
1668 lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight);
1670 // tdf#108494 the header height was switched to height of a watermark
1671 // and shape was moved to the lower part of a page
1672 xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(sal_Int32(11)));
1673 xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(nOldValue));
1674 xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
1678 SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc& rDoc,
1679 uno::Reference<text::XTextField> xField,
1680 uno::Reference<text::XTextContent> xParent,
1681 const bool bRemove)
1682 : SwUndo(SwUndoId::PARA_SIGN_ADD, &rDoc),
1683 m_rDoc(rDoc),
1684 m_xField(std::move(xField)),
1685 m_xParent(std::move(xParent)),
1686 m_bRemove(bRemove)
1688 // Save the metadata and field content to undo/redo.
1689 if (SwDocShell* pShell = m_rDoc.GetDocShell())
1691 rtl::Reference<SwXTextDocument> xModel = pShell->GetBaseModel();
1692 const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField);
1693 const auto it = aStatements.find(ParagraphSignatureIdRDFName);
1694 if (it != aStatements.end())
1695 m_signature = it->second;
1697 const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName);
1698 if (it2 != aStatements.end())
1699 m_usage = it2->second;
1701 uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY);
1702 m_display = xText->getString();
1706 void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&)
1708 if (m_bRemove)
1709 Remove();
1710 else
1711 Insert();
1714 void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&)
1716 if (m_bRemove)
1717 Insert();
1718 else
1719 Remove();
1722 void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&)
1726 void SwUndoParagraphSigning::Insert()
1728 // Disable undo to avoid introducing noise when we edit the metadata field.
1729 const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
1730 m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
1732 // Prevent validation since this will trigger a premature validation
1733 // upon inserting, but before setting the metadata.
1734 SwEditShell* pEditSh = m_rDoc.GetEditShell();
1735 const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
1736 comphelper::ScopeGuard const g([&] () {
1737 if (pEditSh)
1738 pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
1739 m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
1742 if (SwDocShell* pShell = m_rDoc.GetDocShell())
1744 m_xField = lcl_InsertParagraphSignature(pShell->GetBaseModel(), m_xParent, m_signature, m_usage);
1745 lcl_DoUpdateParagraphSignatureField(m_rDoc, m_xField, m_display);
1749 void SwUndoParagraphSigning::Remove()
1751 // Disable undo to avoid introducing noise when we edit the metadata field.
1752 const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
1753 m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
1755 // Prevent validation since this will trigger a premature validation
1756 // upon removing.
1757 SwEditShell* pEditSh = m_rDoc.GetEditShell();
1758 const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
1759 comphelper::ScopeGuard const g([&] () {
1760 if (pEditSh)
1761 pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
1762 m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
1765 lcl_RemoveParagraphMetadataField(m_xField);
1768 void SwEditShell::SignParagraph()
1770 SwDoc& rDoc = *GetDoc();
1771 SwDocShell* pDocShell = rDoc.GetDocShell();
1772 if (!pDocShell || !GetCursor() || !GetCursor()->Start())
1773 return;
1774 const SwPosition* pPosStart = GetCursor()->Start();
1775 if (!pPosStart)
1776 return;
1777 SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
1778 if (!pNode)
1779 return;
1781 // Table text signing is not supported.
1782 if (pNode->FindTableNode() != nullptr)
1783 return;
1785 // 1. Get the text (without fields).
1786 const rtl::Reference<SwXParagraph> xParagraph = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
1787 const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
1788 if (utf8Text.isEmpty())
1789 return;
1791 // 2. Get certificate.
1792 uno::Reference<security::XDocumentDigitalSignatures> xSigner(
1793 // here none of the version-dependent methods are called
1794 security::DocumentDigitalSignatures::createDefault(
1795 comphelper::getProcessComponentContext()));
1797 uno::Sequence<css::beans::PropertyValue> aProperties;
1798 uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties);
1799 if (!xCertificate.is())
1800 return;
1802 // 3. Sign it.
1803 svl::crypto::SigningContext aSigningContext;
1804 aSigningContext.m_xCertificate = std::move(xCertificate);
1805 svl::crypto::Signing signing(aSigningContext);
1806 signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength());
1807 OStringBuffer sigBuf;
1808 if (!signing.Sign(sigBuf))
1809 return;
1811 const OUString signature = OStringToOUString(sigBuf, RTL_TEXTENCODING_UTF8, 0);
1813 auto it = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
1815 return rValue.Name == "Usage";
1818 OUString aUsage;
1819 if (it != aProperties.end())
1820 it->Value >>= aUsage;
1822 // 4. Add metadata
1823 // Prevent validation since this will trigger a premature validation
1824 // upon inserting, but before setting the metadata.
1825 const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
1826 comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
1827 SetParagraphSignatureValidation(bOldValidationFlag);
1830 rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
1832 const rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1833 uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage);
1835 lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
1837 rDoc.GetIDocumentUndoRedo().AppendUndo(
1838 std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, true));
1840 rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
1843 void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove)
1845 if (!pNode || !IsParagraphSignatureValidationEnabled())
1846 return;
1848 // Table text signing is not supported.
1849 if (pNode->FindTableNode() != nullptr)
1850 return;
1852 // Prevent recursive validation since this is triggered on node updates, which we do below.
1853 const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
1854 comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
1855 SetParagraphSignatureValidation(bOldValidationFlag);
1858 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1859 if (!pDocShell)
1860 return;
1862 uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(pDocShell->GetBaseModel(), MetaNS);
1863 rtl::Reference<SwXParagraph> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode, nullptr);
1864 lcl_ValidateParagraphSignatures(*GetDoc(), xParentText, updateDontRemove, aGraphNames);
1867 void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove)
1869 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1870 if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled())
1871 return;
1873 SwPaM* pPaM = GetCursor();
1874 const SwPosition* pPosStart = pPaM->Start();
1875 SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
1876 ValidateParagraphSignatures(pNode, updateDontRemove);
1879 void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove)
1881 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1882 if (!pDocShell || !IsParagraphSignatureValidationEnabled())
1883 return;
1885 // Prevent recursive validation since this is triggered on node updates, which we do below.
1886 const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
1887 comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
1888 SetParagraphSignatureValidation(bOldValidationFlag);
1891 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1892 uno::Reference<text::XText> xParent = xModel->getText();
1893 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
1894 if (!xParagraphEnumerationAccess.is())
1895 return;
1896 uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
1897 if (!xParagraphs.is())
1898 return;
1899 uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(pDocShell->GetBaseModel(), MetaNS);
1900 while (xParagraphs->hasMoreElements())
1902 uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
1903 lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, updateDontRemove, aGraphNames);
1907 static uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index)
1909 uno::Reference<text::XTextField> xTextField;
1910 if (pNode != nullptr && pDocSh != nullptr)
1912 SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD);
1913 SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
1914 if (pTextMeta != nullptr)
1916 SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr()));
1917 if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
1919 const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
1920 rtl::Reference<SwXTextDocument> xModel = pDocSh->GetBaseModel();
1921 const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject);
1922 if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() ||
1923 aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end())
1925 xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY);
1931 return xTextField;
1934 void SwEditShell::RestoreMetadataFieldsAndValidateParagraphSignatures()
1936 SwDocShell* pDocShell = GetDoc()->GetDocShell();
1937 if (!pDocShell || !IsParagraphSignatureValidationEnabled())
1938 return;
1940 // Prevent recursive validation since this is triggered on node updates, which we do below.
1941 const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
1942 comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
1943 SetParagraphSignatureValidation(bOldValidationFlag);
1946 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
1947 rtl::Reference<SwXBodyText> xBodyText = xModel->getBodyText();
1948 if (!xBodyText.is())
1949 return;
1950 rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
1952 static constexpr OUString sBlank(u""_ustr);
1953 const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
1954 uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS);
1955 while (xParagraphs->hasMoreElements())
1957 uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
1958 rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
1962 const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph);
1963 std::map<OUString, OUString> aParagraphStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject);
1964 auto it = aParagraphStatements.find(ParagraphClassificationFieldNamesRDFName);
1965 const OUString sFieldNames = (it != aParagraphStatements.end()) ? it->second : OUString();
1967 std::vector<svx::ClassificationResult> aResults;
1968 if (!sFieldNames.isEmpty())
1970 // Order the fields
1971 sal_Int32 nIndex = 0;
1974 const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex);
1975 if (sCurFieldName.isEmpty())
1976 break;
1978 OUString sName;
1979 OUString sValue;
1980 it = aParagraphStatements.find(sCurFieldName);
1981 if (it != aParagraphStatements.end())
1983 sName = it->first;
1984 sValue = it->second;
1987 if (aKeyCreator.isMarkingTextKey(sName))
1989 aResults.emplace_back(svx::ClassificationType::TEXT, sValue, sValue, sBlank);
1991 else if (aKeyCreator.isCategoryNameKey(sName))
1993 const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xSubject, ParagraphClassificationAbbrRDFName);
1994 const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
1995 aResults.emplace_back(svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank);
1997 else if (aKeyCreator.isCategoryIdentifierKey(sName))
1999 const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xSubject, ParagraphClassificationAbbrRDFName);
2000 const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
2001 aResults.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue);
2003 else if (aKeyCreator.isMarkingKey(sName))
2005 aResults.emplace_back(svx::ClassificationType::MARKING, sValue, sValue, sBlank);
2007 else if (aKeyCreator.isIntellectualPropertyPartKey(sName))
2009 aResults.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank);
2012 while (nIndex >= 0);
2015 // Update classification based on results.
2016 lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, std::move(aResults));
2018 // Get Signatures
2019 std::map<OUString, SignatureDescr> aSignatures;
2020 for (const auto& pair : aParagraphStatements)
2022 const OUString& sName = pair.first;
2023 if (sName.startsWith(ParagraphSignatureRDFNamespace))
2025 const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength());
2026 const sal_Int32 index = sSuffix.indexOf(":");
2027 if (index >= 0)
2029 const OUString id = sSuffix.copy(0, index);
2030 const OUString type = sSuffix.copy(index);
2031 const OUString& sValue = pair.second;
2032 if (type == ParagraphSignatureDateRDFName)
2033 aSignatures[id].msDate = sValue;
2034 else if (type == ParagraphSignatureUsageRDFName)
2035 aSignatures[id].msUsage = sValue;
2036 else if (type == ParagraphSignatureDigestRDFName)
2037 aSignatures[id].msSignature = sValue;
2042 for (const auto& pair : aSignatures)
2044 uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first);
2045 if (!xField.is())
2047 xField = uno::Reference<text::XTextField>(xModel->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
2049 // Add the signature at the end.
2050 xField->attach(xParagraph->getAnchor()->getEnd());
2052 const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
2053 SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first);
2055 const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
2056 lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
2060 lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, true, aGraphNames); // Validate and Update signatures.
2062 catch (const std::exception&)
2068 bool SwEditShell::IsCursorInParagraphMetadataField() const
2070 if (GetCursor() && GetCursor()->Start())
2072 SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
2073 const sal_uLong index = GetCursor()->Start()->GetContentIndex();
2074 uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
2075 return xField.is();
2078 return false;
2081 bool SwEditShell::RemoveParagraphMetadataFieldAtCursor()
2083 if (GetCursor() && GetCursor()->Start())
2085 SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
2086 sal_uLong index = GetCursor()->Start()->GetContentIndex();
2087 uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
2088 if (!xField.is())
2090 // Try moving the cursor to see if we're _facing_ a metafield or not,
2091 // as opposed to being within one.
2092 index--; // Backspace moves left
2094 xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
2097 if (xField.is())
2099 lcl_RemoveParagraphMetadataField(xField);
2100 return true;
2104 return false;
2107 static OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator,
2108 const rtl::Reference<SwXTextDocument>& xModel, const rtl::Reference<SwXParagraph>& xParagraph)
2110 uno::Reference<text::XTextField> xTextField;
2111 xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey());
2112 if (xTextField.is())
2114 const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName);
2115 return rHelper.GetBACNameForIdentifier(rdfValuePair.second);
2118 xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey());
2119 if (xTextField.is())
2121 return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second;
2124 return OUString();
2127 static OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor)
2129 OUString sHighestClass;
2131 SwTextNode* pNode = pCursor->Start()->GetNode().GetTextNode();
2132 if (pNode == nullptr)
2133 return sHighestClass;
2135 SwDocShell* pDocShell = pNode->GetDoc().GetDocShell();
2136 if (!pDocShell)
2137 return sHighestClass;
2139 SfxClassificationHelper aHelper(pDocShell->getDocProperties());
2140 sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
2142 rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
2143 rtl::Reference<SwXBodyText> xBodyText = xModel->getBodyText();
2145 rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
2146 while (xParagraphs->hasMoreElements())
2148 uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
2149 rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
2150 const OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph);
2151 sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass);
2154 return sHighestClass;
2157 void SwEditShell::ClassifyDocPerHighestParagraphClass()
2159 SwDocShell* pDocShell = GetDoc()->GetDocShell();
2160 if (!pDocShell)
2161 return;
2163 // Bail out as early as possible if we don't have paragraph classification.
2164 if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS))
2165 return;
2167 uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties();
2168 uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
2170 sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
2171 SfxClassificationHelper aHelper(xDocumentProperties);
2173 OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor());
2175 const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey());
2177 if (!aClassificationCategory.isEmpty())
2179 sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory);
2182 if (aClassificationCategory != sHighestClass)
2184 std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
2185 VclMessageType::Question, VclButtonsType::Ok,
2186 SwResId(STR_CLASSIFICATION_LEVEL_CHANGED)));
2187 xQueryBox->run();
2190 const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass);
2192 // Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign.
2193 const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
2194 comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
2195 SetParagraphSignatureValidation(bOldValidationFlag);
2198 // Check the origin, if "manual" (created via advanced classification dialog),
2199 // then we just need to set the category name.
2200 if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL)
2202 aHelper.SetBACName(sHighestClass, eHighestClassType);
2203 ApplyAdvancedClassification(CollectAdvancedClassification());
2205 else
2207 SetClassification(sHighestClass, eHighestClassType);
2211 // #i62675#
2212 void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat,
2213 const bool bResetListAttrs,
2214 SetAttrMode nMode)
2216 SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0];
2217 StartAllAction();
2219 SwRewriter aRewriter;
2221 aRewriter.AddRule(UndoArg1, pLocal->GetName());
2223 GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
2224 for(SwPaM& rPaM : GetCursor()->GetRingContainer())
2226 if (!rPaM.HasReadonlySel( GetViewOptions()->IsFormView(), true))
2228 // store previous paragraph style for track changes
2229 OUString sParaStyleName;
2230 sal_uInt16 nPoolId = USHRT_MAX;
2231 SwContentNode * pCnt = rPaM.Start()->GetNode().GetContentNode();
2232 if ( pCnt && pCnt->GetTextNode() && GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() )
2234 const SwTextFormatColl* pTextFormatColl = pCnt->GetTextNode()->GetTextColl();
2235 sal_uInt16 nStylePoolId = pTextFormatColl->GetPoolFormatId();
2236 // default paragraph style
2237 if ( nStylePoolId == RES_POOLCOLL_STANDARD )
2238 nPoolId = nStylePoolId;
2239 else
2240 sParaStyleName = pTextFormatColl->GetName();
2243 // Change the paragraph style to pLocal and remove all direct paragraph formatting.
2244 GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, !!(nMode & SetAttrMode::REMOVE_ALL_ATTR), GetLayout());
2246 // If there are hints on the nodes which cover the whole node, then remove those, too.
2247 SwPaM aPaM(*rPaM.Start(), *rPaM.End());
2248 if (SwTextNode* pEndTextNode = aPaM.End()->GetNode().GetTextNode())
2250 aPaM.Start()->SetContent(0);
2251 aPaM.End()->SetContent(pEndTextNode->GetText().getLength());
2253 GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout());
2255 // add redline tracking the previous paragraph style
2256 if ( GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() &&
2257 // multi-paragraph ParagraphFormat redline ranges
2258 // haven't supported by AppendRedline(), yet
2259 // TODO handle multi-paragraph selections, too,
2260 // e.g. by breaking them to single paragraphs
2261 aPaM.Start()->GetNode() == aPaM.End()->GetNode() )
2263 SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::ParagraphFormat, aPaM );
2264 auto const result(GetDoc()->getIDocumentRedlineAccess().AppendRedline( pRedline, true));
2265 // store original paragraph style to reject formatting change
2266 if ( IDocumentRedlineAccess::AppendResult::IGNORED != result &&
2267 ( nPoolId == RES_POOLCOLL_STANDARD || !sParaStyleName.isEmpty() ) )
2269 std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
2270 xExtra.reset(new SwRedlineExtraData_FormatColl(sParaStyleName, nPoolId, nullptr));
2271 pRedline->SetExtraData( xExtra.get() );
2277 GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
2278 EndAllAction();
2281 SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
2282 SwTextFormatColl* pParent)
2284 SwTextFormatColl *pColl;
2285 if ( pParent == nullptr )
2286 pParent = &GetTextFormatColl(0);
2287 pColl = GetDoc()->MakeTextFormatColl(rFormatCollName, pParent);
2288 if ( pColl == nullptr )
2290 OSL_FAIL( "MakeTextFormatColl failed" );
2292 return pColl;
2296 void SwEditShell::FillByEx(SwTextFormatColl* pColl)
2298 SwPaM * pCursor = GetCursor();
2299 SwContentNode * pCnt = pCursor->GetPointContentNode();
2300 if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not?
2301 { // only need properties-node because BREAK/PAGEDESC filtered anyway!
2302 pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
2304 const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
2305 if( !pSet )
2306 return;
2308 // JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is
2309 // in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!)
2310 // Bug 57568
2312 // Do NOT copy AutoNumRules into the template
2313 const SwNumRuleItem* pItem;
2314 const SwNumRule* pRule = nullptr;
2315 if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false)
2316 || SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false)
2317 || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
2318 && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
2319 && pRule->IsAutoRule()))
2321 SfxItemSet aSet( *pSet );
2322 aSet.ClearItem( RES_BREAK );
2323 aSet.ClearItem( RES_PAGEDESC );
2325 if (pRule
2326 || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
2327 && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
2328 && pRule->IsAutoRule()))
2329 aSet.ClearItem( RES_PARATR_NUMRULE );
2331 if( aSet.Count() )
2332 GetDoc()->ChgFormat(*pColl, aSet );
2334 else
2335 GetDoc()->ChgFormat(*pColl, *pSet );
2338 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */