From b480d5e4c03438487b645ae10347c5c22f36bb25 Mon Sep 17 00:00:00 2001 From: Ashod Nakashian Date: Tue, 24 Oct 2017 16:07:05 +0400 Subject: TSCP: bump the doc classification if lower than paragraph The document classification should not be lower than the highest-classificed paragraph. This insures that the document classification is as high as the highest classified paragraph upon saving. Change-Id: Ic838b886ecf97da2eca56870f68aa3e51c7291f6 Reviewed-on: https://gerrit.libreoffice.org/43772 Tested-by: Jenkins Reviewed-by: Ashod Nakashian --- include/sfx2/classificationhelper.hxx | 4 +- include/svx/ClassificationDialog.hxx | 2 +- include/svx/ClassificationField.hxx | 10 ++- sd/source/ui/view/drviews2.cxx | 9 +-- sfx2/source/view/classificationhelper.cxx | 32 +++++++-- svx/source/dialog/ClassificationDialog.cxx | 34 ++++++---- sw/inc/editsh.hxx | 4 ++ sw/source/core/edit/edfcol.cxx | 105 +++++++++++++++++++++++++---- sw/source/uibase/app/docsh.cxx | 2 + 9 files changed, 160 insertions(+), 42 deletions(-) diff --git a/include/sfx2/classificationhelper.hxx b/include/sfx2/classificationhelper.hxx index 036951fa0ab1..2672c51c167e 100644 --- a/include/sfx2/classificationhelper.hxx +++ b/include/sfx2/classificationhelper.hxx @@ -64,11 +64,13 @@ public: /// Return all possible valid category names, based on the policy. std::vector GetBACNames(); /// Get the currently selected category abbreviation for eType. Returns full name if no abbreviation defined. - const OUString& GetAbbreviatedBACName(SfxClassificationPolicyType eType); + const OUString& GetAbbreviatedBACName(const OUString& sFullName); /// Return all possible valid abbreviated category names, based on the policy. std::vector GetAbbreviatedBACNames(); /// Setting this sets all the other properties, based on the policy. void SetBACName(const OUString& rName, SfxClassificationPolicyType eType); + /// Returns the class with the higher priority (based on sensitivity). + OUString GetHigherClass(const OUString& first, const OUString& second); /// If GetImpactScale() and GetImpactLevel*() will return something meaningful. bool HasImpactLevel(); InfoBarType GetImpactLevelType(); diff --git a/include/svx/ClassificationDialog.hxx b/include/svx/ClassificationDialog.hxx index c2e48e4a760c..19e869a5f74d 100644 --- a/include/svx/ClassificationDialog.hxx +++ b/include/svx/ClassificationDialog.hxx @@ -49,7 +49,7 @@ private: DECL_LINK(SelectIPPartNumbersHdl, ListBox&, void); DECL_LINK(DoubleClickIPPartHdl, ListBox&, void); - void insertField(ClassificationType eType, OUString const & rString); + void insertField(ClassificationType eType, OUString const & rString, OUString const & rFullString); public: ClassificationDialog(vcl::Window* pParent, bool bPerParagraph, const std::function& rParagraphSignHandler = [](){}); diff --git a/include/svx/ClassificationField.hxx b/include/svx/ClassificationField.hxx index 53237dae1921..559ed66511d8 100644 --- a/include/svx/ClassificationField.hxx +++ b/include/svx/ClassificationField.hxx @@ -30,16 +30,18 @@ class SVX_DLLPUBLIC ClassificationField : public SvxFieldData public: ClassificationType meType; OUString msDescription; + OUString msFullClassName; - ClassificationField(ClassificationType eType, OUString const & sDescription) + ClassificationField(ClassificationType eType, OUString const & sDescription, OUString const & sFullClassName) : SvxFieldData() , meType(eType) , msDescription(sDescription) + , msFullClassName(sFullClassName) {} ClassificationField* Clone() const override { - return new ClassificationField(meType, msDescription); + return new ClassificationField(meType, msDescription, msFullClassName); } bool operator==(const SvxFieldData& rOther) const override @@ -49,7 +51,8 @@ public: const ClassificationField& rOtherField = static_cast(rOther); return (meType == rOtherField.meType && - msDescription == rOtherField.msDescription); + msDescription == rOtherField.msDescription && + msFullClassName == rOtherField.msFullClassName); } }; @@ -57,6 +60,7 @@ struct SVX_DLLPUBLIC ClassificationResult { ClassificationType meType; OUString msString; + OUString msAbbreviatedString; sal_Int32 mnParagraph; }; diff --git a/sd/source/ui/view/drviews2.cxx b/sd/source/ui/view/drviews2.cxx index 3b7f2346e219..436153a1ca94 100644 --- a/sd/source/ui/view/drviews2.cxx +++ b/sd/source/ui/view/drviews2.cxx @@ -322,6 +322,7 @@ public: { bFound = true; m_pRectObject = pRectObject; + const OUString sBlank(""); for (editeng::Section const & rSection : aSections) { const SvxFieldItem* pFieldItem = findField(rSection); @@ -332,22 +333,22 @@ public: if (aKey.startsWith(sPolicy + "Marking:Text:")) { OUString aValue = lcl_getProperty(xPropertyContainer, aKey); - m_aResults.push_back({ svx::ClassificationType::TEXT, aValue, nParagraph }); + m_aResults.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, nParagraph }); } else if (aKey.startsWith(sPolicy + "BusinessAuthorizationCategory:Name")) { OUString aValue = lcl_getProperty(xPropertyContainer, aKey); - m_aResults.push_back({ svx::ClassificationType::CATEGORY, aValue, nParagraph }); + m_aResults.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, nParagraph }); } else if (aKey.startsWith(sPolicy + "Extension:Marking")) { OUString aValue = lcl_getProperty(xPropertyContainer, aKey); - m_aResults.push_back({ svx::ClassificationType::MARKING, aValue, nParagraph }); + m_aResults.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, nParagraph }); } else if (aKey.startsWith(sPolicy + "Extension:IntellectualPropertyPart")) { OUString aValue = lcl_getProperty(xPropertyContainer, aKey); - m_aResults.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, nParagraph }); + m_aResults.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, nParagraph }); } } } diff --git a/sfx2/source/view/classificationhelper.cxx b/sfx2/source/view/classificationhelper.cxx index 68f6fddf8d27..5e38e3885bb1 100644 --- a/sfx2/source/view/classificationhelper.cxx +++ b/sfx2/source/view/classificationhelper.cxx @@ -93,7 +93,7 @@ public: /// PROP_BACNAME() is stored separately for easier lookup. OUString m_aName; OUString m_aAbbreviatedName; - size_t m_nSensitivity; //< 0 is the highest (most-sensitive). + size_t m_nConfidentiality; //< 0 is the lowest (least-sensitive). std::map m_aLabels; }; @@ -184,7 +184,6 @@ void SAL_CALL SfxClassificationParser::startElement(const OUString& rName, const rCategory.m_aName = aName; // Set the abbreviated name, if any, otherwise fallback on the full name. rCategory.m_aAbbreviatedName = !aAbbreviatedName.isEmpty() ? aAbbreviatedName : aName; - rCategory.m_nSensitivity = m_aCategories.size() - 1; // 0-based class sensitivity; first is highest. rCategory.m_aLabels["PolicyAuthority:Name"] = m_aPolicyAuthorityName; rCategory.m_aLabels["Policy:Name"] = m_aPolicyName; rCategory.m_aLabels["BusinessAuthorization:Identifier"] = m_aProgramID; @@ -281,6 +280,7 @@ void SAL_CALL SfxClassificationParser::endElement(const OUString& rName) { std::map& rLabels = m_pCategory->m_aLabels; rLabels[PROP_IMPACTLEVEL()] = m_aConfidentalityValue; + m_pCategory->m_nConfidentiality = m_aConfidentalityValue.toInt32(); // 0-based class sensitivity; 0 is lowest. // Set the two other type of levels as well, if they're not set // yet: they're optional in BAF, but not in BAILS. if (rLabels.find("Impact:Level:Integrity") == rLabels.end()) @@ -365,6 +365,7 @@ SfxClassificationHelper::Impl::Impl(uno::Referencem_aCategory[eType].m_aName; } -const OUString& SfxClassificationHelper::GetAbbreviatedBACName(SfxClassificationPolicyType eType) +const OUString& SfxClassificationHelper::GetAbbreviatedBACName(const OUString& sFullName) { - return m_pImpl->m_aCategory[eType].m_aAbbreviatedName; + for (const auto& category : m_pImpl->m_aCategories) + { + if (category.m_aName == sFullName) + return category.m_aAbbreviatedName; + } + + return sFullName; +} + +OUString SfxClassificationHelper::GetHigherClass(const OUString& first, const OUString& second) +{ + size_t nFirstConfidentiality = 0; + size_t nSecondConfidentiality = 0; + for (const auto& category : m_pImpl->m_aCategories) + { + if (category.m_aName == first) + nFirstConfidentiality = category.m_nConfidentiality; + if (category.m_aName == second) + nSecondConfidentiality = category.m_nConfidentiality; + } + + return nFirstConfidentiality >= nSecondConfidentiality ? first : second; } bool SfxClassificationHelper::HasImpactLevel() @@ -797,7 +819,7 @@ void SfxClassificationHelper::SetBACName(const OUString& rName, SfxClassificatio m_pImpl->m_aCategory[eType].m_aName = it->m_aName; m_pImpl->m_aCategory[eType].m_aAbbreviatedName = it->m_aAbbreviatedName; - m_pImpl->m_aCategory[eType].m_nSensitivity = it->m_nSensitivity; + m_pImpl->m_aCategory[eType].m_nConfidentiality = it->m_nConfidentiality; m_pImpl->m_aCategory[eType].m_aLabels.clear(); const OUString& rPrefix = policyTypeToString(eType); for (const auto& rLabel : it->m_aLabels) diff --git a/svx/source/dialog/ClassificationDialog.cxx b/svx/source/dialog/ClassificationDialog.cxx index c0d0e0691a6c..dbbd8dc6be6f 100644 --- a/svx/source/dialog/ClassificationDialog.cxx +++ b/svx/source/dialog/ClassificationDialog.cxx @@ -102,9 +102,9 @@ void ClassificationDialog::dispose() ModalDialog::dispose(); } -void ClassificationDialog::insertField(ClassificationType eType, OUString const & rString) +void ClassificationDialog::insertField(ClassificationType eType, OUString const & rString, OUString const & rFullString) { - ClassificationField aField(eType, rString); + ClassificationField aField(eType, rString, rFullString); m_pEditWindow->InsertField(SvxFieldItem(aField, EE_FEATURE_FIELD)); } @@ -112,6 +112,10 @@ void ClassificationDialog::setupValues(std::vector const & { for (ClassificationResult const & rClassificationResult : rInput) { + OUString msAbbreviatedString = rClassificationResult.msAbbreviatedString; + if (msAbbreviatedString.isEmpty()) + msAbbreviatedString = maHelper.GetAbbreviatedBACName(rClassificationResult.msString); + switch (rClassificationResult.meType) { case svx::ClassificationType::TEXT: @@ -124,20 +128,20 @@ void ClassificationDialog::setupValues(std::vector const & { m_pClassificationListBox->SelectEntry(rClassificationResult.msString); m_pInternationalClassificationListBox->SelectEntryPos(m_pClassificationListBox->GetSelectedEntryPos()); - insertField(rClassificationResult.meType, rClassificationResult.msString); + insertField(rClassificationResult.meType, msAbbreviatedString, rClassificationResult.msString); } break; case svx::ClassificationType::MARKING: { m_pMarkingListBox->SelectEntry(rClassificationResult.msString); - insertField(rClassificationResult.meType, rClassificationResult.msString); + insertField(rClassificationResult.meType, msAbbreviatedString, rClassificationResult.msString); } break; case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART: { - insertField(rClassificationResult.meType, rClassificationResult.msString); + insertField(rClassificationResult.meType, msAbbreviatedString, rClassificationResult.msString); } break; @@ -161,16 +165,16 @@ std::vector ClassificationDialog::getResult() const SvxFieldItem* pFieldItem = findField(rSection); ESelection aSelection(rSection.mnParagraph, rSection.mnStart, rSection.mnParagraph, rSection.mnEnd); - OUString sString = m_pEditWindow->pEdEngine->GetText(aSelection); + const OUString sDisplayString = m_pEditWindow->pEdEngine->GetText(aSelection); const ClassificationField* pClassificationField = pFieldItem ? dynamic_cast(pFieldItem->GetField()) : nullptr; if (pClassificationField) { - aClassificationResults.push_back({ pClassificationField->meType , sString, rSection.mnParagraph }); + aClassificationResults.push_back({ pClassificationField->meType, pClassificationField->msFullClassName, sDisplayString, rSection.mnParagraph }); } else { - aClassificationResults.push_back({ ClassificationType::TEXT, sString, rSection.mnParagraph }); + aClassificationResults.push_back({ ClassificationType::TEXT, pClassificationField->msFullClassName, sDisplayString, rSection.mnParagraph }); } } return aClassificationResults; @@ -198,8 +202,9 @@ IMPL_LINK(ClassificationDialog, SelectClassificationHdl, ListBox&, rBox, void) } } - const OUString aString = maHelper.GetAbbreviatedBACNames()[nSelected]; - insertField(ClassificationType::CATEGORY, aString); + const OUString aFullString = maHelper.GetBACNames()[nSelected]; + const OUString aAbbreviatedString = maHelper.GetAbbreviatedBACNames()[nSelected]; + insertField(ClassificationType::CATEGORY, aAbbreviatedString, aFullString); m_pInternationalClassificationListBox->SelectEntryPos(nSelected); m_pClassificationListBox->SelectEntryPos(nSelected); @@ -229,7 +234,7 @@ IMPL_LINK(ClassificationDialog, SelectMarkingHdl, ListBox&, rBox, void) } const OUString aString = maHelper.GetMarkings()[nSelected]; - insertField(ClassificationType::MARKING, aString); + insertField(ClassificationType::MARKING, aString, aString); } } @@ -245,10 +250,10 @@ IMPL_LINK(ClassificationDialog, SelectIPPartNumbersHdl, ListBox&, rBox, void) IMPL_LINK(ClassificationDialog, DoubleClickIPPartHdl, ListBox&, rBox, void) { - sal_Int32 nSelected = rBox.GetSelectedEntryPos(); + const sal_Int32 nSelected = rBox.GetSelectedEntryPos(); if (nSelected >= 0) { - OUString sString = maHelper.GetIntellectualPropertyParts()[nSelected]; + const OUString sString = maHelper.GetIntellectualPropertyParts()[nSelected]; m_pIntellectualPropertyPartEdit->SetText(m_pIntellectualPropertyPartEdit->GetText() + sString); } } @@ -265,7 +270,8 @@ IMPL_LINK(ClassificationDialog, ButtonClicked, Button*, pButton, void) } else if (pButton == m_pIntellectualPropertyPartAddButton) { - insertField(ClassificationType::INTELLECTUAL_PROPERTY_PART, m_pIntellectualPropertyPartEdit->GetText()); + const OUString sString = m_pIntellectualPropertyPartEdit->GetText(); + insertField(ClassificationType::INTELLECTUAL_PROPERTY_PART, sString, sString); } } diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx index 05648c40102d..285c4413c597 100644 --- a/sw/inc/editsh.hxx +++ b/sw/inc/editsh.hxx @@ -380,6 +380,10 @@ public: /// Validate paragraph signatures, if any, at the cursor. void ValidateParagraphSignatures(bool updateDontRemove); + /// Ensure that the classification of the doc is never lower than + /// the paragraph with the highest classification. + void ClassifyDocPerHighestParagraphClass(); + /// Apply the classification to the paragraph at cursor. void ApplyParagraphClassification(std::vector aResult); std::vector CollectParagraphClassification(); diff --git a/sw/source/core/edit/edfcol.cxx b/sw/source/core/edit/edfcol.cxx index 939344960775..97d956a09a5a 100644 --- a/sw/source/core/edit/edfcol.cxx +++ b/sw/source/core/edit/edfcol.cxx @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -516,7 +517,7 @@ void insertFieldToDocument(uno::Reference const & rx void SwEditShell::ApplyAdvancedClassification(std::vector const & rResults) { SwDocShell* pDocShell = GetDoc()->GetDocShell(); - if (!pDocShell) + if (!pDocShell || !SfxObjectShell::Current()) return; uno::Reference xModel = pDocShell->GetBaseModel(); @@ -690,8 +691,7 @@ std::vector SwEditShell::CollectAdvancedClassificatio std::vector aResult; SwDocShell* pDocShell = GetDoc()->GetDocShell(); - - if (!pDocShell) + if (!pDocShell || !SfxObjectShell::Current()) return aResult; uno::Reference xModel = pDocShell->GetBaseModel(); @@ -740,29 +740,30 @@ std::vector SwEditShell::CollectAdvancedClassificatio OUString aName; uno::Reference xPropertySet(xTextField, uno::UNO_QUERY); xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + const OUString sBlank(""); if (aName.startsWith(sPolicy + "Marking:Text:")) { const OUString aValue = lcl_getProperty(xPropertyContainer, aName); if (!aValue.isEmpty()) - aResult.push_back({ svx::ClassificationType::TEXT, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, nParagraph }); } else if (aName.startsWith(sPolicy + "BusinessAuthorizationCategory:Name")) { const OUString aValue = lcl_getProperty(xPropertyContainer, aName); if (!aValue.isEmpty()) - aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, nParagraph }); } else if (aName.startsWith(sPolicy + "Extension:Marking")) { const OUString aValue = lcl_getProperty(xPropertyContainer, aName); if (!aValue.isEmpty()) - aResult.push_back({ svx::ClassificationType::MARKING, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, nParagraph }); } else if (aName.startsWith(sPolicy + "Extension:IntellectualPropertyPart")) { const OUString aValue = lcl_getProperty(xPropertyContainer, aName); if (!aValue.isEmpty()) - aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, nParagraph }); } } ++nParagraph; @@ -928,11 +929,10 @@ void SwEditShell::ApplyParagraphClassification(std::vector xTextField = lcl_InsertParagraphClassification(xModel, xParent); - const OUString sValue = rResult.msString; - OUString sDisplayText = (isFirst ? ("(" + sValue) : sValue); + OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedString) : rResult.msAbbreviatedString); if (isLast) sDisplayText += ")"; - lcl_UpdateParagraphClassificationField(GetDoc(), xModel, xTextField, sKey, sValue, sDisplayText); + lcl_UpdateParagraphClassificationField(GetDoc(), xModel, xTextField, sKey, rResult.msString, sDisplayText); } } @@ -981,21 +981,22 @@ std::vector SwEditShell::CollectParagraphClassificati uno::Reference xTextRange(xField, uno::UNO_QUERY); const OUString aName = rdfNamePair.second; const OUString aValue = rdfValuePair.second; + const OUString sBlank(""); if (aName.startsWith(sPolicy + "Marking:Text:")) { - aResult.push_back({ svx::ClassificationType::TEXT, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, nParagraph }); } else if (aName.startsWith(sPolicy + "BusinessAuthorizationCategory:Name")) { - aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, nParagraph }); } else if (aName.startsWith(sPolicy + "Extension:Marking")) { - aResult.push_back({ svx::ClassificationType::MARKING, aValue, nParagraph }); + aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, nParagraph }); } else if (aName.startsWith(sPolicy + "Extension:IntellectualPropertyPart")) { - aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), nParagraph }); + aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, nParagraph }); } } @@ -1574,6 +1575,82 @@ bool SwEditShell::RemoveParagraphMetadataFieldAtCursor(const bool bBackspaceNotD return false; } +OUString lcl_GetParagraphClassification(const uno::Reference& xModel, const uno::Reference& xParagraph) +{ + const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType()); + uno::Reference xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, sPolicy + "BusinessAuthorizationCategory:Name"); + if (xTextField.is()) + { + const std::pair rdfValuePair = lcl_getFieldRDF(xModel, xTextField, ParagraphClassificationValueRDFName); + return rdfValuePair.second; + } + + return OUString(); +} + +OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor) +{ + OUString sHighestClass; + + SwTextNode* pNode = pCursor->Start()->nNode.GetNode().GetTextNode(); + if (pNode == nullptr) + return sHighestClass; + + SwDocShell* pDocShell = pNode->GetDoc()->GetDocShell(); + if (!pDocShell) + return sHighestClass; + + SfxClassificationHelper aHelper(pDocShell->getDocProperties()); + + uno::Reference xModel = pDocShell->GetBaseModel(); + const uno::Reference< text::XTextDocument > xDoc(xModel, uno::UNO_QUERY); + uno::Reference xParent = xDoc->getText(); + + uno::Reference xParagraphEnumerationAccess(xParent, uno::UNO_QUERY); + uno::Reference xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY); + sHighestClass = aHelper.GetHigherClass(sHighestClass, lcl_GetParagraphClassification(xModel, xParagraph)); + } + + return sHighestClass; +} + +void SwEditShell::ClassifyDocPerHighestParagraphClass() +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + + SfxClassificationHelper aHelper(pDocShell->getDocProperties()); + + const OUString sHighestParaClass = lcl_GetHighestClassificationParagraphClass(GetCursor()); + + std::vector results = CollectAdvancedClassification(); + for (const svx::ClassificationResult& rResult : results) + { + switch (rResult.meType) + { + case svx::ClassificationType::CATEGORY: + { + const OUString sHighestClass = aHelper.GetHigherClass(sHighestParaClass, rResult.msString); + const auto eType = SfxClassificationHelper::stringToPolicyType(sHighestClass); + SetClassification(sHighestClass, eType); + } + break; + default: + break; + } + } + + if (results.empty()) + { + const auto eType = SfxClassificationHelper::stringToPolicyType(sHighestParaClass); + SetClassification(sHighestParaClass, eType); + } +} + // #i62675# void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat, const bool bResetListAttrs) diff --git a/sw/source/uibase/app/docsh.cxx b/sw/source/uibase/app/docsh.cxx index ad2e6dbf4ac0..d5543c5af7cc 100644 --- a/sw/source/uibase/app/docsh.cxx +++ b/sw/source/uibase/app/docsh.cxx @@ -474,6 +474,8 @@ bool SwDocShell::SaveAs( SfxMedium& rMedium ) // Remove invalid signatures. m_pWrtShell->ValidateParagraphSignatures(false); + + m_pWrtShell->ClassifyDocPerHighestParagraphClass(); } // Remember and preserve Modified-Flag without calling the Link -- cgit