LibreOffice Module sw (master) 1
AccessibilityCheck.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 */
10
11#include <AccessibilityCheck.hxx>
12#include <AccessibilityIssue.hxx>
13#include <AccessibilityCheckStrings.hrc>
14#include <strings.hrc>
15#include <ndnotxt.hxx>
16#include <ndtxt.hxx>
17#include <docsh.hxx>
19#include <drawdoc.hxx>
20#include <svx/svdpage.hxx>
21#include <sortedobjs.hxx>
22#include <swtable.hxx>
23#include <com/sun/star/frame/XModel.hpp>
24#include <com/sun/star/text/XTextContent.hpp>
25#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
26#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
27#include <unoparagraph.hxx>
29#include <tools/urlobj.hxx>
30#include <editeng/langitem.hxx>
31#include <calbck.hxx>
32#include <charatr.hxx>
33#include <svx/xfillit0.hxx>
34#include <svx/xflclit.hxx>
35#include <ftnidx.hxx>
36#include <txtftn.hxx>
37#include <txtfrm.hxx>
38#include <svl/itemiter.hxx>
39#include <o3tl/string_view.hxx>
40#include <o3tl/vector_utils.hxx>
41#include <svx/swframetypes.hxx>
42#include <fmtanchr.hxx>
43#include <dcontact.hxx>
44#include <svx/svdoashp.hxx>
45#include <svx/sdasitm.hxx>
46
47namespace sw
48{
49namespace
50{
51SwTextNode* lclSearchNextTextNode(SwNode* pCurrent)
52{
53 SwTextNode* pTextNode = nullptr;
54
55 auto nIndex = pCurrent->GetIndex();
56 auto nCount = pCurrent->GetNodes().Count();
57
58 nIndex++; // go to next node
59
60 while (pTextNode == nullptr && nIndex < nCount)
61 {
62 auto pNode = pCurrent->GetNodes()[nIndex];
63 if (pNode->IsTextNode())
64 pTextNode = pNode->GetTextNode();
65 nIndex++;
66 }
67
68 return pTextNode;
69}
70
71std::shared_ptr<sw::AccessibilityIssue>
72lclAddIssue(sfx::AccessibilityIssueCollection& rIssueCollection, OUString const& rText,
74{
75 auto pIssue = std::make_shared<sw::AccessibilityIssue>(eIssue);
76 pIssue->m_aIssueText = rText;
77 rIssueCollection.getIssues().push_back(pIssue);
78 return pIssue;
79}
80
81class NodeCheck : public BaseCheck
82{
83public:
84 NodeCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
85 : BaseCheck(rIssueCollection)
86 {
87 }
88
89 virtual void check(SwNode* pCurrent) = 0;
90};
91
92// Check NoTextNodes: Graphic, OLE for alt (title) text
93class NoTextNodeAltTextCheck : public NodeCheck
94{
95 void checkNoTextNode(SwNoTextNode* pNoTextNode)
96 {
97 if (!pNoTextNode)
98 return;
99
100 if (!pNoTextNode->GetTitle().isEmpty() || !pNoTextNode->GetDescription().isEmpty())
101 return;
102
103 OUString sName = pNoTextNode->GetFlyFormat()->GetName();
104
105 OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName);
106
107 if (pNoTextNode->IsOLENode())
108 {
109 auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
111 pIssue->setDoc(pNoTextNode->GetDoc());
112 pIssue->setIssueObject(IssueObject::OLE);
113 pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName());
114 }
115 else if (pNoTextNode->IsGrfNode())
116 {
117 const SwFrameFormat* pFrameFormat = pNoTextNode->GetFlyFormat();
118 const SfxBoolItem* pIsDecorItem = pFrameFormat->GetItemIfSet(RES_DECORATIVE);
119 if (!(pIsDecorItem && pIsDecorItem->GetValue()))
120 {
121 auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
123 pIssue->setDoc(pNoTextNode->GetDoc());
124 pIssue->setIssueObject(IssueObject::GRAPHIC);
125 pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName());
126 pIssue->setNode(pNoTextNode);
127 }
128 }
129 }
130
131public:
132 NoTextNodeAltTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
133 : NodeCheck(rIssueCollection)
134 {
135 }
136
137 void check(SwNode* pCurrent) override
138 {
139 if (pCurrent->GetNodeType() & SwNodeType::NoTextMask)
140 {
141 SwNoTextNode* pNoTextNode = pCurrent->GetNoTextNode();
142 if (pNoTextNode)
143 checkNoTextNode(pNoTextNode);
144 }
145 }
146};
147
148// Check Table node if the table is merged and split.
149class TableNodeMergeSplitCheck : public NodeCheck
150{
151private:
152 void addTableIssue(SwTable const& rTable, SwDoc& rDoc)
153 {
154 const SwTableFormat* pFormat = rTable.GetFrameFormat();
155 OUString sName = pFormat->GetName();
156 OUString sIssueText = SwResId(STR_TABLE_MERGE_SPLIT).replaceAll("%OBJECT_NAME%", sName);
157 auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
159 pIssue->setDoc(rDoc);
160 pIssue->setIssueObject(IssueObject::TABLE);
161 pIssue->setObjectID(sName);
162 }
163
164 void checkTableNode(SwTableNode* pTableNode)
165 {
166 if (!pTableNode)
167 return;
168
169 SwTable const& rTable = pTableNode->GetTable();
170 SwDoc& rDoc = pTableNode->GetDoc();
171 if (rTable.IsTableComplex())
172 {
173 addTableIssue(rTable, rDoc);
174 }
175 else
176 {
177 if (rTable.GetTabLines().size() > 1)
178 {
179 int i = 0;
180 size_t nFirstLineSize = 0;
181 bool bAllColumnsSameSize = true;
182 bool bCellSpansOverMoreRows = false;
183
184 for (SwTableLine const* pTableLine : rTable.GetTabLines())
185 {
186 if (i == 0)
187 {
188 nFirstLineSize = pTableLine->GetTabBoxes().size();
189 }
190 else
191 {
192 size_t nLineSize = pTableLine->GetTabBoxes().size();
193 if (nFirstLineSize != nLineSize)
194 {
195 bAllColumnsSameSize = false;
196 }
197 }
198 i++;
199
200 // Check for row span in each table box (cell)
201 for (SwTableBox const* pBox : pTableLine->GetTabBoxes())
202 {
203 if (pBox->getRowSpan() > 1)
204 bCellSpansOverMoreRows = true;
205 }
206 }
207 if (!bAllColumnsSameSize || bCellSpansOverMoreRows)
208 {
209 addTableIssue(rTable, rDoc);
210 }
211 }
212 }
213 }
214
215public:
216 TableNodeMergeSplitCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
217 : NodeCheck(rIssueCollection)
218 {
219 }
220
221 void check(SwNode* pCurrent) override
222 {
223 if (pCurrent->GetNodeType() & SwNodeType::Table)
224 {
225 SwTableNode* pTableNode = pCurrent->GetTableNode();
226 if (pTableNode)
227 checkTableNode(pTableNode);
228 }
229 }
230};
231
232class TableFormattingCheck : public NodeCheck
233{
234private:
235 void checkTableNode(SwTableNode* pTableNode)
236 {
237 if (!pTableNode)
238 return;
239
240 const SwTable& rTable = pTableNode->GetTable();
241 if (!rTable.IsTableComplex())
242 {
243 size_t nEmptyBoxes = 0;
244 size_t nBoxCount = 0;
245 for (const SwTableLine* pTableLine : rTable.GetTabLines())
246 {
247 nBoxCount += pTableLine->GetTabBoxes().size();
248 for (const SwTableBox* pBox : pTableLine->GetTabBoxes())
249 if (pBox->IsEmpty())
250 ++nEmptyBoxes;
251 }
252 // If more than half of the boxes are empty we can assume that it is used for formatting
253 if (nEmptyBoxes > nBoxCount / 2)
254 lclAddIssue(m_rIssueCollection, SwResId(STR_TABLE_FORMATTING),
256 }
257 }
258
259public:
260 TableFormattingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
261 : NodeCheck(rIssueCollection)
262 {
263 }
264
265 void check(SwNode* pCurrent) override
266 {
267 if (pCurrent->GetNodeType() & SwNodeType::Table)
268 {
269 SwTableNode* pTableNode = pCurrent->GetTableNode();
270 if (pTableNode)
271 checkTableNode(pTableNode);
272 }
273 }
274};
275
276class NumberingCheck : public NodeCheck
277{
278private:
279 const std::vector<std::pair<OUString, OUString>> m_aNumberingCombinations{
280 { "1.", "2." }, { "(1)", "(2)" }, { "1)", "2)" }, { "a.", "b." }, { "(a)", "(b)" },
281 { "a)", "b)" }, { "A.", "B." }, { "(A)", "(B)" }, { "A)", "B)" }
282 };
283
284public:
285 NumberingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
286 : NodeCheck(rIssueCollection)
287 {
288 }
289
290 void check(SwNode* pCurrent) override
291 {
292 if (!pCurrent->IsTextNode())
293 return;
294
295 SwTextNode* pCurrentTextNode = pCurrent->GetTextNode();
296 SwTextNode* pNextTextNode = lclSearchNextTextNode(pCurrent);
297
298 if (!pNextTextNode)
299 return;
300
301 for (auto& rPair : m_aNumberingCombinations)
302 {
303 if (pCurrentTextNode->GetText().startsWith(rPair.first)
304 && pNextTextNode->GetText().startsWith(rPair.second))
305 {
306 OUString sNumbering = rPair.first + " " + rPair.second + "...";
307 OUString sIssueText
308 = SwResId(STR_FAKE_NUMBERING).replaceAll("%NUMBERING%", sNumbering);
309 auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
311 pIssue->setIssueObject(IssueObject::TEXT);
312 pIssue->setDoc(pCurrent->GetDoc());
313 pIssue->setNode(pCurrent);
314 }
315 }
316 }
317};
318
319class HyperlinkCheck : public NodeCheck
320{
321private:
322 void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange, SwTextNode* pTextNode,
323 sal_Int32 nStart)
324 {
325 uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
326 if (!xProperties->getPropertySetInfo()->hasPropertyByName("HyperLinkURL"))
327 return;
328
329 OUString sHyperlink;
330 xProperties->getPropertyValue("HyperLinkURL") >>= sHyperlink;
331 if (!sHyperlink.isEmpty())
332 {
333 OUString sText = xTextRange->getString();
334 if (INetURLObject(sText) == INetURLObject(sHyperlink))
335 {
336 OUString sIssueText
337 = SwResId(STR_HYPERLINK_TEXT_IS_LINK).replaceFirst("%LINK%", sHyperlink);
338 lclAddIssue(m_rIssueCollection, sIssueText,
340 }
341 else if (sText.getLength() <= 5)
342 {
343 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_HYPERLINK_TEXT_IS_SHORT),
345 pIssue->setIssueObject(IssueObject::TEXT);
346 pIssue->setNode(pTextNode);
347 SwDoc& rDocument = pTextNode->GetDoc();
348 pIssue->setDoc(rDocument);
349 pIssue->setStart(nStart);
350 pIssue->setEnd(nStart + sText.getLength());
351 }
352 }
353 }
354
355public:
356 HyperlinkCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
357 : NodeCheck(rIssueCollection)
358 {
359 }
360
361 void check(SwNode* pCurrent) override
362 {
363 if (!pCurrent->IsTextNode())
364 return;
365
366 SwTextNode* pTextNode = pCurrent->GetTextNode();
367 uno::Reference<text::XTextContent> xParagraph
368 = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
369 if (!xParagraph.is())
370 return;
371
372 uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
373 uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
374 sal_Int32 nStart = 0;
375 while (xRunEnum->hasMoreElements())
376 {
377 uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
378 if (xRun.is())
379 {
380 checkTextRange(xRun, pTextNode, nStart);
381 nStart += xRun->getString().getLength();
382 }
383 }
384 }
385};
386
387// Based on https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
388double calculateRelativeLuminance(Color const& rColor)
389{
390 // Convert to BColor which has R, G, B colors components
391 // represented by a floating point number from [0.0, 1.0]
392 const basegfx::BColor aBColor = rColor.getBColor();
393
394 double r = aBColor.getRed();
395 double g = aBColor.getGreen();
396 double b = aBColor.getBlue();
397
398 // Calculate the values according to the described algorithm
399 r = (r <= 0.03928) ? r / 12.92 : std::pow((r + 0.055) / 1.055, 2.4);
400 g = (g <= 0.03928) ? g / 12.92 : std::pow((g + 0.055) / 1.055, 2.4);
401 b = (b <= 0.03928) ? b / 12.92 : std::pow((b + 0.055) / 1.055, 2.4);
402
403 return 0.2126 * r + 0.7152 * g + 0.0722 * b;
404}
405
406// TODO move to common color tools (BColorTools maybe)
407// Based on https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
408double calculateContrastRatio(Color const& rColor1, Color const& rColor2)
409{
410 const double fLuminance1 = calculateRelativeLuminance(rColor1);
411 const double fLuminance2 = calculateRelativeLuminance(rColor2);
412 const std::pair<const double, const double> aMinMax = std::minmax(fLuminance1, fLuminance2);
413
414 // (L1 + 0.05) / (L2 + 0.05)
415 // L1 is the lighter color (greater luminance value)
416 // L2 is the darker color (smaller luminance value)
417 return (aMinMax.second + 0.05) / (aMinMax.first + 0.05);
418}
419
420class TextContrastCheck : public NodeCheck
421{
422private:
423 void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange,
424 uno::Reference<text::XTextContent> const& xParagraph, SwTextNode* pTextNode,
425 sal_Int32 nTextStart)
426 {
427 if (xTextRange->getString().isEmpty())
428 return;
429
430 Color nParaBackColor(COL_AUTO);
431 uno::Reference<beans::XPropertySet> xParagraphProperties(xParagraph, uno::UNO_QUERY);
432 if (!(xParagraphProperties->getPropertyValue("ParaBackColor") >>= nParaBackColor))
433 {
434 SAL_WARN("sw.a11y", "ParaBackColor void");
435 return;
436 }
437
438 uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
439 if (!xProperties.is())
440 return;
441
442 // Foreground color
443 sal_Int32 nCharColor = {}; // spurious -Werror=maybe-uninitialized
444 if (!(xProperties->getPropertyValue("CharColor") >>= nCharColor))
445 { // not sure this is impossible, can the default be void?
446 SAL_WARN("sw.a11y", "CharColor void");
447 return;
448 }
449
450 const SwPageDesc* pPageDescription = pTextNode->FindPageDesc();
451 if (!pPageDescription)
452 return;
453 const SwFrameFormat& rPageFormat = pPageDescription->GetMaster();
454 const SwAttrSet& rPageSet = rPageFormat.GetAttrSet();
455
456 const XFillStyleItem* pXFillStyleItem(
457 rPageSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE, false));
458 Color aPageBackground(COL_AUTO);
459
460 if (pXFillStyleItem && pXFillStyleItem->GetValue() == css::drawing::FillStyle_SOLID)
461 {
462 const XFillColorItem* rXFillColorItem
463 = rPageSet.GetItem<XFillColorItem>(XATTR_FILLCOLOR, false);
464 aPageBackground = rXFillColorItem->GetColorValue();
465 }
466
467 Color nCharBackColor(COL_AUTO);
468
469 if (!(xProperties->getPropertyValue("CharBackColor") >>= nCharBackColor))
470 {
471 SAL_WARN("sw.a11y", "CharBackColor void");
472 return;
473 }
474 // Determine the background color
475 // Try Character background (highlight)
476 Color aBackgroundColor(nCharBackColor);
477
478 // If not character background color, try paragraph background color
479 if (aBackgroundColor == COL_AUTO)
480 aBackgroundColor = nParaBackColor;
481 else
482 {
483 auto pIssue
484 = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING),
486 pIssue->setIssueObject(IssueObject::TEXT);
487 pIssue->setNode(pTextNode);
488 SwDoc& rDocument = pTextNode->GetDoc();
489 pIssue->setDoc(rDocument);
490 pIssue->setStart(nTextStart);
491 pIssue->setEnd(nTextStart + xTextRange->getString().getLength());
492 }
493
494 Color aForegroundColor(ColorTransparency, nCharColor);
495 if (aForegroundColor == COL_AUTO)
496 return;
497
498 // If not paragraph background color, try page color
499 if (aBackgroundColor == COL_AUTO)
500 aBackgroundColor = aPageBackground;
501
502 // If not page color, assume white background color
503 if (aBackgroundColor == COL_AUTO)
504 aBackgroundColor = COL_WHITE;
505
506 double fContrastRatio = calculateContrastRatio(aForegroundColor, aBackgroundColor);
507 if (fContrastRatio < 4.5)
508 {
509 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_CONTRAST));
510 pIssue->setIssueObject(IssueObject::TEXT);
511 pIssue->setNode(pTextNode);
512 pIssue->setDoc(pTextNode->GetDoc());
513 pIssue->setStart(nTextStart);
514 pIssue->setEnd(nTextStart + xTextRange->getString().getLength());
515 }
516 }
517
518public:
519 TextContrastCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
520 : NodeCheck(rIssueCollection)
521 {
522 }
523
524 void check(SwNode* pCurrent) override
525 {
526 if (!pCurrent->IsTextNode())
527 return;
528
529 SwTextNode* pTextNode = pCurrent->GetTextNode();
530 uno::Reference<text::XTextContent> xParagraph;
531 xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
532 if (!xParagraph.is())
533 return;
534
535 uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
536 uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
537 sal_Int32 nStart = 0;
538 while (xRunEnum->hasMoreElements())
539 {
540 uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
541 if (xRun.is())
542 {
543 checkTextRange(xRun, xParagraph, pTextNode, nStart);
544 nStart += xRun->getString().getLength();
545 }
546 }
547 }
548};
549
550class TextFormattingCheck : public NodeCheck
551{
552public:
553 TextFormattingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
554 : NodeCheck(rIssueCollection)
555 {
556 }
557
558 void checkAutoFormat(SwTextNode* pTextNode, const SwTextAttr* pTextAttr)
559 {
560 const SwFormatAutoFormat& rAutoFormat = pTextAttr->GetAutoFormat();
561 SfxItemIter aItemIter(*rAutoFormat.GetStyleHandle());
562 const SfxPoolItem* pItem = aItemIter.GetCurItem();
563 std::vector<OUString> aFormattings;
564 while (pItem)
565 {
566 OUString sFormattingType;
567 switch (pItem->Which())
568 {
572 sFormattingType = "Weight";
573 break;
577 sFormattingType = "Posture";
578 break;
579
581 sFormattingType = "Shadowed";
582 break;
583
584 case RES_CHRATR_COLOR:
585 sFormattingType = "Font Color";
586 break;
587
591 sFormattingType = "Font Size";
592 break;
593
594 case RES_CHRATR_FONT:
597 sFormattingType = "Font";
598 break;
599
601 sFormattingType = "Emphasis Mark";
602 break;
603
605 sFormattingType = "Underline";
606 break;
607
609 sFormattingType = "Overline";
610 break;
611
613 sFormattingType = "Strikethrough";
614 break;
615
617 sFormattingType = "Relief";
618 break;
619
621 sFormattingType = "Outline";
622 break;
623 default:
624 break;
625 }
626 if (!sFormattingType.isEmpty())
627 aFormattings.push_back(sFormattingType);
628 pItem = aItemIter.NextItem();
629 }
630 if (aFormattings.empty())
631 return;
632
633 o3tl::remove_duplicates(aFormattings);
634 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING),
636 pIssue->setIssueObject(IssueObject::TEXT);
637 pIssue->setNode(pTextNode);
638 SwDoc& rDocument = pTextNode->GetDoc();
639 pIssue->setDoc(rDocument);
640 pIssue->setStart(pTextAttr->GetStart());
641 pIssue->setEnd(pTextAttr->GetAnyEnd());
642 }
643
644 void check(SwNode* pCurrent) override
645 {
646 if (!pCurrent->IsTextNode())
647 return;
648
649 SwTextNode* pTextNode = pCurrent->GetTextNode();
650 if (pTextNode->HasHints())
651 {
652 SwpHints& rHints = pTextNode->GetSwpHints();
653 for (size_t i = 0; i < rHints.Count(); ++i)
654 {
655 const SwTextAttr* pTextAttr = rHints.Get(i);
656 if (pTextAttr->Which() == RES_TXTATR_AUTOFMT)
657 {
658 checkAutoFormat(pTextNode, pTextAttr);
659 }
660 }
661 }
662 else if (pTextNode->HasSwAttrSet())
663 {
664 // Paragraph doesn't have hints but the entire paragraph might have char attributes
665 auto& aSwAttrSet = pTextNode->GetSwAttrSet();
666 auto nParagraphLength = pTextNode->GetText().getLength();
667 if (nParagraphLength == 0)
668 return;
669 if (aSwAttrSet.GetItem(RES_CHRATR_WEIGHT, false)
670 || aSwAttrSet.GetItem(RES_CHRATR_CJK_WEIGHT, false)
671 || aSwAttrSet.GetItem(RES_CHRATR_CTL_WEIGHT, false)
672 || aSwAttrSet.GetItem(RES_CHRATR_POSTURE, false)
673 || aSwAttrSet.GetItem(RES_CHRATR_CJK_POSTURE, false)
674 || aSwAttrSet.GetItem(RES_CHRATR_CTL_POSTURE, false)
675 || aSwAttrSet.GetItem(RES_CHRATR_SHADOWED, false)
676 || aSwAttrSet.GetItem(RES_CHRATR_COLOR, false)
677 || aSwAttrSet.GetItem(RES_CHRATR_EMPHASIS_MARK, false)
678 || aSwAttrSet.GetItem(RES_CHRATR_UNDERLINE, false)
679 || aSwAttrSet.GetItem(RES_CHRATR_OVERLINE, false)
680 || aSwAttrSet.GetItem(RES_CHRATR_CROSSEDOUT, false)
681 || aSwAttrSet.GetItem(RES_CHRATR_RELIEF, false)
682 || aSwAttrSet.GetItem(RES_CHRATR_CONTOUR, false))
683 {
684 auto pIssue
685 = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING),
687 pIssue->setIssueObject(IssueObject::TEXT);
688 pIssue->setNode(pTextNode);
689 SwDoc& rDocument = pTextNode->GetDoc();
690 pIssue->setDoc(rDocument);
691 pIssue->setEnd(nParagraphLength);
692 }
693 }
694 }
695};
696
697class NewlineSpacingCheck : public NodeCheck
698{
699private:
700 static SwTextNode* getPrevTextNode(SwNode* pCurrent)
701 {
702 SwTextNode* pTextNode = nullptr;
703
704 auto nIndex = pCurrent->GetIndex();
705
706 nIndex--; // go to previous node
707
708 while (pTextNode == nullptr && nIndex >= SwNodeOffset(0))
709 {
710 auto pNode = pCurrent->GetNodes()[nIndex];
711 if (pNode->IsTextNode())
712 pTextNode = pNode->GetTextNode();
713 nIndex--;
714 }
715
716 return pTextNode;
717 }
718
719public:
720 NewlineSpacingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
721 : NodeCheck(rIssueCollection)
722 {
723 }
724 void check(SwNode* pCurrent) override
725 {
726 if (!pCurrent->IsTextNode())
727 return;
728
729 // Don't count empty table box text nodes
730 if (pCurrent->GetTableBox())
731 return;
732
733 SwTextNode* pTextNode = pCurrent->GetTextNode();
734 auto nParagraphLength = pTextNode->GetText().getLength();
735 if (nParagraphLength == 0)
736 {
737 SwTextNode* pPrevTextNode = getPrevTextNode(pCurrent);
738 if (!pPrevTextNode)
739 return;
740 if (pPrevTextNode->GetText().getLength() == 0)
741 {
742 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_NEWLINES_SPACE),
744 pIssue->setIssueObject(IssueObject::TEXT);
745 pIssue->setNode(pTextNode);
746 SwDoc& rDocument = pTextNode->GetDoc();
747 pIssue->setDoc(rDocument);
748 }
749 }
750 else
751 {
752 // Check for excess lines inside this paragraph
753 const OUString& sParagraphText = pTextNode->GetText();
754 int nLineCount = 0;
755 for (sal_Int32 i = 0; i < nParagraphLength; i++)
756 {
757 auto aChar = sParagraphText[i];
758 if (aChar == '\n')
759 {
760 nLineCount++;
761 // Looking for 2 newline characters and above as one can be part of the line
762 // break after a sentence
763 if (nLineCount > 2)
764 {
765 auto pIssue
766 = lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_NEWLINES_SPACE),
768 pIssue->setIssueObject(IssueObject::TEXT);
769 pIssue->setNode(pTextNode);
770 SwDoc& rDocument = pTextNode->GetDoc();
771 pIssue->setDoc(rDocument);
772 pIssue->setStart(i);
773 pIssue->setEnd(i);
774 }
775 }
776 // Don't count carriage return as normal character
777 else if (aChar != '\r')
778 {
779 nLineCount = 0;
780 }
781 }
782 }
783 }
784};
785
786class SpaceSpacingCheck : public NodeCheck
787{
788public:
789 SpaceSpacingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
790 : NodeCheck(rIssueCollection)
791 {
792 }
793 void check(SwNode* pCurrent) override
794 {
795 if (!pCurrent->IsTextNode())
796 return;
797 SwTextNode* pTextNode = pCurrent->GetTextNode();
798 auto nParagraphLength = pTextNode->GetText().getLength();
799 const OUString& sParagraphText = pTextNode->GetText();
800 sal_Int32 nSpaceCount = 0;
801 sal_Int32 nSpaceStart = 0;
802 sal_Int32 nTabCount = 0;
803 bool bNonSpaceFound = false;
804 bool bPreviousWasChar = false;
805 for (sal_Int32 i = 0; i < nParagraphLength; i++)
806 {
807 switch (sParagraphText[i])
808 {
809 case ' ':
810 {
811 if (bNonSpaceFound)
812 {
813 nSpaceCount++;
814 if (nSpaceCount == 2)
815 nSpaceStart = i;
816 }
817 break;
818 }
819 case '\t':
820 {
821 if (bPreviousWasChar)
822 {
823 ++nTabCount;
824 bPreviousWasChar = false;
825 if (nTabCount == 2)
826 {
827 auto pIssue = lclAddIssue(m_rIssueCollection,
828 SwResId(STR_AVOID_TABS_FORMATTING),
830 pIssue->setIssueObject(IssueObject::TEXT);
831 pIssue->setNode(pTextNode);
832 SwDoc& rDocument = pTextNode->GetDoc();
833 pIssue->setDoc(rDocument);
834 pIssue->setStart(0);
835 pIssue->setEnd(nParagraphLength);
836 }
837 }
838 break;
839 }
840 default:
841 {
842 if (nSpaceCount >= 2)
843 {
844 auto pIssue
845 = lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_SPACES_SPACE),
847 pIssue->setIssueObject(IssueObject::TEXT);
848 pIssue->setNode(pTextNode);
849 SwDoc& rDocument = pTextNode->GetDoc();
850 pIssue->setDoc(rDocument);
851 pIssue->setStart(nSpaceStart);
852 pIssue->setEnd(nSpaceStart + nSpaceCount - 1);
853 }
854 bNonSpaceFound = true;
855 bPreviousWasChar = true;
856 nSpaceCount = 0;
857 break;
858 }
859 }
860 }
861 }
862};
863
864class FakeFootnoteCheck : public NodeCheck
865{
866private:
867 void checkAutoFormat(SwTextNode* pTextNode, const SwTextAttr* pTextAttr)
868 {
869 const SwFormatAutoFormat& rAutoFormat = pTextAttr->GetAutoFormat();
870 SfxItemIter aItemIter(*rAutoFormat.GetStyleHandle());
871 const SfxPoolItem* pItem = aItemIter.GetCurItem();
872 while (pItem)
873 {
874 if (pItem->Which() == RES_CHRATR_ESCAPEMENT)
875 {
876 auto pEscapementItem = static_cast<const SvxEscapementItem*>(pItem);
877 if (pEscapementItem->GetEscapement() == SvxEscapement::Superscript
878 && pTextAttr->GetStart() == 0 && pTextAttr->GetAnyEnd() == 1)
879 {
880 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_FAKE_FOOTNOTES),
882 pIssue->setIssueObject(IssueObject::TEXT);
883 pIssue->setNode(pTextNode);
884 SwDoc& rDocument = pTextNode->GetDoc();
885 pIssue->setDoc(rDocument);
886 pIssue->setStart(0);
887 pIssue->setEnd(pTextNode->GetText().getLength());
888 break;
889 }
890 }
891 pItem = aItemIter.NextItem();
892 }
893 }
894
895public:
896 FakeFootnoteCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
897 : NodeCheck(rIssueCollection)
898 {
899 }
900 void check(SwNode* pCurrent) override
901 {
902 if (!pCurrent->IsTextNode())
903 return;
904 SwTextNode* pTextNode = pCurrent->GetTextNode();
905 if (pTextNode->GetText().getLength() == 0)
906 return;
907
908 if (pTextNode->GetText()[0] == '*')
909 {
910 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_FAKE_FOOTNOTES),
912 pIssue->setIssueObject(IssueObject::TEXT);
913 pIssue->setNode(pTextNode);
914 SwDoc& rDocument = pTextNode->GetDoc();
915 pIssue->setDoc(rDocument);
916 pIssue->setStart(0);
917 pIssue->setEnd(pTextNode->GetText().getLength());
918 }
919 else if (pTextNode->HasHints())
920 {
921 SwpHints& rHints = pTextNode->GetSwpHints();
922 for (size_t i = 0; i < rHints.Count(); ++i)
923 {
924 const SwTextAttr* pTextAttr = rHints.Get(i);
925 if (pTextAttr->Which() == RES_TXTATR_AUTOFMT)
926 {
927 checkAutoFormat(pTextNode, pTextAttr);
928 }
929 }
930 }
931 }
932};
933
934class FakeCaptionCheck : public NodeCheck
935{
936public:
937 FakeCaptionCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
938 : NodeCheck(rIssueCollection)
939 {
940 }
941 void check(SwNode* pCurrent) override
942 {
943 if (!pCurrent->IsTextNode())
944 return;
945
946 SwTextNode* pTextNode = pCurrent->GetTextNode();
947 const OUString& sText = pTextNode->GetText();
948
949 if (sText.getLength() == 0)
950 return;
951
952 // Check if it's a real caption
953 const SwNode* aStartFly = pCurrent->FindFlyStartNode();
954 if (aStartFly
955 && aStartFly->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
956 return;
957
959 auto nCount = 0;
960 for (auto aTextFrame = aIter.First(); aTextFrame; aTextFrame = aIter.Next())
961 {
962 auto aObjects = aTextFrame->GetDrawObjs();
963 if (aObjects)
964 nCount += aObjects->size();
965
966 if (nCount > 1)
967 return;
968 }
969
970 // Check that there's exactly 1 image anchored in this node
971 if (nCount == 1)
972 {
973 OString sTemp;
974 sText.convertToString(&sTemp, RTL_TEXTENCODING_ASCII_US, 0);
975 if (sText.startsWith(SwResId(STR_POOLCOLL_LABEL))
976 || sText.startsWith(SwResId(STR_POOLCOLL_LABEL_ABB))
977 || sText.startsWith(SwResId(STR_POOLCOLL_LABEL_TABLE))
978 || sText.startsWith(SwResId(STR_POOLCOLL_LABEL_FRAME))
979 || sText.startsWith(SwResId(STR_POOLCOLL_LABEL_DRAWING))
980 || sText.startsWith(SwResId(STR_POOLCOLL_LABEL_FIGURE)))
981 {
982 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_FAKE_CAPTIONS),
984 pIssue->setIssueObject(IssueObject::TEXT);
985 pIssue->setNode(pTextNode);
986 SwDoc& rDocument = pTextNode->GetDoc();
987 pIssue->setDoc(rDocument);
988 pIssue->setStart(0);
989 pIssue->setEnd(sText.getLength());
990 }
991 }
992 }
993};
994
995class BlinkingTextCheck : public NodeCheck
996{
997private:
998 void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange)
999 {
1000 uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
1001 if (xProperties.is() && xProperties->getPropertySetInfo()->hasPropertyByName("CharFlash"))
1002 {
1003 bool bBlinking = false;
1004 xProperties->getPropertyValue("CharFlash") >>= bBlinking;
1005
1006 if (bBlinking)
1007 {
1008 lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_BLINKING));
1009 }
1010 }
1011 }
1012
1013public:
1014 BlinkingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1015 : NodeCheck(rIssueCollection)
1016 {
1017 }
1018
1019 void check(SwNode* pCurrent) override
1020 {
1021 if (!pCurrent->IsTextNode())
1022 return;
1023
1024 SwTextNode* pTextNode = pCurrent->GetTextNode();
1025 uno::Reference<text::XTextContent> xParagraph;
1026 xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
1027 if (!xParagraph.is())
1028 return;
1029
1030 uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
1031 uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
1032 while (xRunEnum->hasMoreElements())
1033 {
1034 uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
1035 if (xRun.is())
1036 checkTextRange(xRun);
1037 }
1038 }
1039};
1040
1041class HeaderCheck : public NodeCheck
1042{
1043private:
1045
1046public:
1047 HeaderCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1048 : NodeCheck(rIssueCollection)
1049 , m_nPreviousLevel(0)
1050 {
1051 }
1052
1053 void check(SwNode* pCurrent) override
1054 {
1055 if (!pCurrent->IsTextNode())
1056 return;
1057
1058 SwTextNode* pTextNode = pCurrent->GetTextNode();
1059 SwTextFormatColl* pCollection = pTextNode->GetTextColl();
1060 if (!pCollection->IsAssignedToListLevelOfOutlineStyle())
1061 return;
1062
1063 int nLevel = pCollection->GetAssignedOutlineStyleLevel();
1064 assert(nLevel >= 0);
1065 if (nLevel > m_nPreviousLevel && std::abs(nLevel - m_nPreviousLevel) > 1)
1066 {
1067 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_HEADINGS_NOT_IN_ORDER));
1068 pIssue->setIssueObject(IssueObject::TEXT);
1069 pIssue->setDoc(pCurrent->GetDoc());
1070 pIssue->setNode(pCurrent);
1071 }
1072 m_nPreviousLevel = nLevel;
1073 }
1074};
1075
1076// ISO 142891-1 : 7.14
1077class NonInteractiveFormCheck : public NodeCheck
1078{
1079public:
1080 NonInteractiveFormCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1081 : NodeCheck(rIssueCollection)
1082 {
1083 }
1084
1085 void check(SwNode* pCurrent) override
1086 {
1087 if (!pCurrent->IsTextNode())
1088 return;
1089
1090 const auto& text = pCurrent->GetTextNode()->GetText();
1091
1092 // Series of tests to detect if there are fake forms in the text.
1093
1094 bool bCheck = text.indexOf("___") == -1; // Repeated underscores.
1095
1096 if (bCheck)
1097 bCheck = text.indexOf("....") == -1; // Repeated dots.
1098
1099 if (bCheck)
1100 bCheck = text.indexOf(u"……") == -1; // Repeated ellipsis.
1101
1102 if (bCheck)
1103 bCheck = text.indexOf(u"….") == -1; // A dot after an ellipsis.
1104
1105 if (bCheck)
1106 bCheck = text.indexOf(u".…") == -1; // An ellipsis after a dot.
1107
1108 // Checking if all the tests are passed successfully. If not, adding a warning.
1109 if (!bCheck)
1110 lclAddIssue(m_rIssueCollection, SwResId(STR_NON_INTERACTIVE_FORMS));
1111 }
1112};
1113
1115class FloatingTextCheck : public NodeCheck
1116{
1117public:
1118 FloatingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1119 : NodeCheck(rIssueCollection)
1120 {
1121 }
1122
1123 void check(SwNode* pCurrent) override
1124 {
1125 // if node is a text-node and if it has text, we proceed. Otherwise - return.
1126 const SwTextNode* textNode = pCurrent->GetTextNode();
1127 if (!textNode || textNode->GetText().isEmpty())
1128 return;
1129
1130 // If a node is in fly and if it is not anchored as char, throw warning.
1131 const SwNode* startFly = pCurrent->FindFlyStartNode();
1132 if (startFly
1133 && startFly->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
1134 {
1135 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_FLOATING_TEXT));
1136 pIssue->setIssueObject(IssueObject::TEXTFRAME);
1137 pIssue->setObjectID(startFly->GetFlyFormat()->GetName());
1138 pIssue->setDoc(pCurrent->GetDoc());
1139 pIssue->setNode(pCurrent);
1140 }
1141 }
1142};
1143
1145class TableHeadingCheck : public NodeCheck
1146{
1147private:
1148 // Boolean indicating if heading-in-table warning is already triggered.
1150
1151public:
1152 TableHeadingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1153 : NodeCheck(rIssueCollection)
1154 , m_bPrevPassed(true)
1155 {
1156 }
1157
1158 void check(SwNode* pCurrent) override
1159 {
1160 if (!m_bPrevPassed)
1161 return;
1162
1163 const SwTextNode* textNode = pCurrent->GetTextNode();
1164
1165 if (textNode && textNode->GetAttrOutlineLevel() != 0)
1166 {
1167 const SwTableNode* parentTable = pCurrent->FindTableNode();
1168
1169 if (parentTable)
1170 {
1171 m_bPrevPassed = false;
1172 lclAddIssue(m_rIssueCollection, SwResId(STR_HEADING_IN_TABLE));
1173 }
1174 }
1175 }
1176};
1177
1179class HeadingOrderCheck : public NodeCheck
1180{
1181public:
1182 HeadingOrderCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1183 : NodeCheck(rIssueCollection)
1184 {
1185 }
1186
1187 void check(SwNode* pCurrent) override
1188 {
1189 const SwTextNode* pTextNode = pCurrent->GetTextNode();
1190 if (!pTextNode)
1191 return;
1192
1193 // If outline level stands for heading level...
1194 const int currentLevel = pTextNode->GetAttrOutlineLevel();
1195 if (!currentLevel)
1196 return;
1197
1198 // ... and if is bigger than previous by more than 1, warn.
1199 if (currentLevel - m_prevLevel > 1)
1200 {
1201 // Preparing and posting a warning.
1202 OUString resultString;
1203 if (!m_prevLevel)
1204 {
1205 resultString = SwResId(STR_HEADING_START);
1206 }
1207 else
1208 {
1209 resultString = SwResId(STR_HEADING_ORDER);
1210 resultString
1211 = resultString.replaceAll("%LEVEL_PREV%", OUString::number(m_prevLevel));
1212 }
1213 resultString
1214 = resultString.replaceAll("%LEVEL_CURRENT%", OUString::number(currentLevel));
1215 lclAddIssue(m_rIssueCollection, resultString);
1216 }
1217
1218 // Updating previous level.
1219 m_prevLevel = currentLevel;
1220 }
1221
1222private:
1223 // Previous heading level to compare with.
1225};
1226
1228class ContentControlCheck : public NodeCheck
1229{
1230private:
1231 // Boolean indicating if content controls in header or footer warning is already triggered.
1232 bool m_bPrevPassed;
1233
1234public:
1235 ContentControlCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1236 : NodeCheck(rIssueCollection)
1237 , m_bPrevPassed(true)
1238 {
1239 }
1240
1241 void check(SwNode* pCurrent) override
1242 {
1243 if (!m_bPrevPassed)
1244 return;
1245
1246 const SwTextNode* pTextNode = pCurrent->GetTextNode();
1247 if (pTextNode)
1248 {
1249 if (pCurrent->FindHeaderStartNode() || pCurrent->FindFooterStartNode())
1250 {
1251 const SwpHints* pHts = pTextNode->GetpSwpHints();
1252 if (pHts)
1253 {
1254 for (size_t i = 0; i < pHts->Count(); ++i)
1255 {
1256 const SwTextAttr* pHt = pHts->Get(i);
1257 if (pHt->Which() == RES_TXTATR_CONTENTCONTROL)
1258 {
1259 m_bPrevPassed = false;
1260 lclAddIssue(m_rIssueCollection,
1261 SwResId(STR_CONTENT_CONTROL_IN_HEADER_OR_FOOTER));
1262 break;
1263 }
1264 }
1265 }
1266 }
1267 }
1268 }
1269};
1270
1271class DocumentCheck : public BaseCheck
1272{
1273public:
1274 DocumentCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1275 : BaseCheck(rIssueCollection)
1276 {
1277 }
1278
1279 virtual void check(SwDoc* pDoc) = 0;
1280};
1281
1282// Check default language
1283class DocumentDefaultLanguageCheck : public DocumentCheck
1284{
1285public:
1286 DocumentDefaultLanguageCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1287 : DocumentCheck(rIssueCollection)
1288 {
1289 }
1290
1291 void check(SwDoc* pDoc) override
1292 {
1293 // TODO maybe - also check RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE if CJK or CTL are enabled
1294 const SvxLanguageItem& rLang = pDoc->GetDefault(RES_CHRATR_LANGUAGE);
1295 LanguageType eLanguage = rLang.GetLanguage();
1296 if (eLanguage == LANGUAGE_NONE)
1297 {
1298 lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_DEFAULT_LANGUAGE),
1300 }
1301 else
1302 {
1303 for (SwTextFormatColl* pTextFormatCollection : *pDoc->GetTextFormatColls())
1304 {
1305 const SwAttrSet& rAttrSet = pTextFormatCollection->GetAttrSet();
1306 if (rAttrSet.GetLanguage(false).GetLanguage() == LANGUAGE_NONE)
1307 {
1308 OUString sName = pTextFormatCollection->GetName();
1309 OUString sIssueText
1310 = SwResId(STR_STYLE_NO_LANGUAGE).replaceAll("%STYLE_NAME%", sName);
1311 lclAddIssue(m_rIssueCollection, sIssueText,
1313 }
1314 }
1315 }
1316 }
1317};
1318
1319class DocumentTitleCheck : public DocumentCheck
1320{
1321public:
1322 DocumentTitleCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1323 : DocumentCheck(rIssueCollection)
1324 {
1325 }
1326
1327 void check(SwDoc* pDoc) override
1328 {
1329 SwDocShell* pShell = pDoc->GetDocShell();
1330 if (!pShell)
1331 return;
1332
1333 const uno::Reference<document::XDocumentPropertiesSupplier> xDPS(pShell->GetModel(),
1334 uno::UNO_QUERY_THROW);
1335 const uno::Reference<document::XDocumentProperties> xDocumentProperties(
1336 xDPS->getDocumentProperties());
1337 OUString sTitle = xDocumentProperties->getTitle();
1338 if (o3tl::trim(sTitle).empty())
1339 {
1340 auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_TITLE),
1342 pIssue->setDoc(*pDoc);
1343 pIssue->setIssueObject(IssueObject::DOCUMENT_TITLE);
1344 }
1345 }
1346};
1347
1348class FootnoteEndnoteCheck : public DocumentCheck
1349{
1350public:
1351 FootnoteEndnoteCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1352 : DocumentCheck(rIssueCollection)
1353 {
1354 }
1355
1356 void check(SwDoc* pDoc) override
1357 {
1358 for (SwTextFootnote* pTextFootnote : pDoc->GetFootnoteIdxs())
1359 {
1360 SwFormatFootnote const& rFootnote = pTextFootnote->GetFootnote();
1361 auto pIssue = lclAddIssue(m_rIssueCollection, rFootnote.IsEndNote()
1362 ? SwResId(STR_AVOID_ENDNOTES)
1363 : SwResId(STR_AVOID_FOOTNOTES));
1364 pIssue->setDoc(*pDoc);
1365 pIssue->setIssueObject(IssueObject::FOOTENDNOTE);
1366 pIssue->setTextFootnote(pTextFootnote);
1367 }
1368 }
1369};
1370
1371class BackgroundImageCheck : public DocumentCheck
1372{
1373public:
1374 BackgroundImageCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
1375 : DocumentCheck(rIssueCollection)
1376 {
1377 }
1378 void check(SwDoc* pDoc) override
1379 {
1380 uno::Reference<lang::XComponent> xDoc = pDoc->GetDocShell()->GetBaseModel();
1381 uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xDoc, uno::UNO_QUERY);
1382 if (!xStyleFamiliesSupplier.is())
1383 return;
1384 uno::Reference<container::XNameAccess> xStyleFamilies
1385 = xStyleFamiliesSupplier->getStyleFamilies();
1386 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"),
1387 uno::UNO_QUERY);
1388 if (!xStyleFamily.is())
1389 return;
1390 const uno::Sequence<OUString>& xStyleFamilyNames = xStyleFamily->getElementNames();
1391 for (const OUString& rStyleFamilyName : xStyleFamilyNames)
1392 {
1393 uno::Reference<beans::XPropertySet> xPropertySet(
1394 xStyleFamily->getByName(rStyleFamilyName), uno::UNO_QUERY);
1395 if (!xPropertySet.is())
1396 continue;
1397 auto aFillStyleContainer = xPropertySet->getPropertyValue("FillStyle");
1398 if (aFillStyleContainer.has<drawing::FillStyle>())
1399 {
1400 drawing::FillStyle aFillStyle = aFillStyleContainer.get<drawing::FillStyle>();
1401 if (aFillStyle == drawing::FillStyle_BITMAP)
1402 {
1403 lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_BACKGROUND_IMAGES),
1405 }
1406 }
1407 }
1408 }
1409};
1410
1411} // end anonymous namespace
1412
1413// Check Shapes, TextBox
1415{
1416 if (!pObject)
1417 return;
1418
1419 // Check for fontworks.
1420 if (SdrObjCustomShape* pCustomShape = dynamic_cast<SdrObjCustomShape*>(pObject))
1421 {
1422 const SdrCustomShapeGeometryItem& rGeometryItem
1423 = pCustomShape->GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY);
1424
1425 if (const uno::Any* pAny = rGeometryItem.GetPropertyValueByName("Type"))
1426 if (pAny->get<OUString>().startsWith("fontwork-"))
1427 lclAddIssue(m_aIssueCollection, SwResId(STR_FONTWORKS));
1428 }
1429
1430 // Checking if there is floating Writer text draw object and if so, throwing a warning.
1431 // (Floating objects with text create problems with reading order)
1432 if (pObject->HasText()
1433 && FindFrameFormat(pObject)->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
1434 {
1435 auto pIssue = lclAddIssue(m_aIssueCollection, SwResId(STR_FLOATING_TEXT));
1436 pIssue->setIssueObject(IssueObject::TEXTFRAME);
1437 pIssue->setObjectID(pObject->GetName());
1438 pIssue->setDoc(*m_pDoc);
1439 if (pCurrent)
1440 pIssue->setNode(pCurrent);
1441 }
1442
1443 const SdrObjKind nObjId = pObject->GetObjIdentifier();
1444 const SdrInventor nInv = pObject->GetObjInventor();
1445
1446 if (nObjId == SdrObjKind::CustomShape || nObjId == SdrObjKind::Text
1447 || nObjId == SdrObjKind::Media || nObjId == SdrObjKind::Group
1448 || nObjId == SdrObjKind::Graphic || nInv == SdrInventor::FmForm)
1449 {
1450 OUString sAlternative = pObject->GetTitle();
1451 if (sAlternative.isEmpty())
1452 {
1453 OUString sName = pObject->GetName();
1454 OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName);
1455 auto pIssue = lclAddIssue(m_aIssueCollection, sIssueText,
1457 // Set FORM Issue for Form objects because of the design mode
1458 if (nInv == SdrInventor::FmForm)
1459 pIssue->setIssueObject(IssueObject::FORM);
1460 else
1461 pIssue->setIssueObject(IssueObject::SHAPE);
1462
1463 pIssue->setObjectID(pObject->GetName());
1464 pIssue->setDoc(*m_pDoc);
1465 if (pCurrent)
1466 pIssue->setNode(pCurrent);
1467 }
1468 }
1469}
1470
1472{
1473 if (m_aDocumentChecks.empty())
1474 {
1475 m_aDocumentChecks.emplace_back(new DocumentDefaultLanguageCheck(m_aIssueCollection));
1476 m_aDocumentChecks.emplace_back(new DocumentTitleCheck(m_aIssueCollection));
1477 m_aDocumentChecks.emplace_back(new FootnoteEndnoteCheck(m_aIssueCollection));
1478 m_aDocumentChecks.emplace_back(new BackgroundImageCheck(m_aIssueCollection));
1479 }
1480
1481 if (m_aNodeChecks.empty())
1482 {
1483 m_aNodeChecks.emplace_back(new NoTextNodeAltTextCheck(m_aIssueCollection));
1484 m_aNodeChecks.emplace_back(new TableNodeMergeSplitCheck(m_aIssueCollection));
1485 m_aNodeChecks.emplace_back(new TableFormattingCheck(m_aIssueCollection));
1486 m_aNodeChecks.emplace_back(new NumberingCheck(m_aIssueCollection));
1487 m_aNodeChecks.emplace_back(new HyperlinkCheck(m_aIssueCollection));
1488 m_aNodeChecks.emplace_back(new TextContrastCheck(m_aIssueCollection));
1489 m_aNodeChecks.emplace_back(new BlinkingTextCheck(m_aIssueCollection));
1490 m_aNodeChecks.emplace_back(new HeaderCheck(m_aIssueCollection));
1491 m_aNodeChecks.emplace_back(new TextFormattingCheck(m_aIssueCollection));
1492 m_aNodeChecks.emplace_back(new NonInteractiveFormCheck(m_aIssueCollection));
1493 m_aNodeChecks.emplace_back(new FloatingTextCheck(m_aIssueCollection));
1494 m_aNodeChecks.emplace_back(new TableHeadingCheck(m_aIssueCollection));
1495 m_aNodeChecks.emplace_back(new HeadingOrderCheck(m_aIssueCollection));
1496 m_aNodeChecks.emplace_back(new NewlineSpacingCheck(m_aIssueCollection));
1497 m_aNodeChecks.emplace_back(new SpaceSpacingCheck(m_aIssueCollection));
1498 m_aNodeChecks.emplace_back(new FakeFootnoteCheck(m_aIssueCollection));
1499 m_aNodeChecks.emplace_back(new FakeCaptionCheck(m_aIssueCollection));
1500 m_aNodeChecks.emplace_back(new ContentControlCheck(m_aIssueCollection));
1501 }
1502}
1503
1505{
1506 if (m_pDoc == nullptr || pNode == nullptr)
1507 return;
1508
1509 init();
1510
1511 for (std::shared_ptr<BaseCheck>& rpNodeCheck : m_aNodeChecks)
1512 {
1513 auto pNodeCheck = dynamic_cast<NodeCheck*>(rpNodeCheck.get());
1514 if (pNodeCheck)
1515 pNodeCheck->check(pNode);
1516 }
1517}
1518
1520{
1521 if (m_pDoc == nullptr)
1522 return;
1523
1524 init();
1525
1526 for (std::shared_ptr<BaseCheck>& rpDocumentCheck : m_aDocumentChecks)
1527 {
1528 auto pDocumentCheck = dynamic_cast<DocumentCheck*>(rpDocumentCheck.get());
1529 if (pDocumentCheck)
1530 pDocumentCheck->check(m_pDoc);
1531 }
1532}
1533
1535{
1536 if (m_pDoc == nullptr)
1537 return;
1538
1539 init();
1540
1542
1543 auto const& pNodes = m_pDoc->GetNodes();
1544 SwNode* pNode = nullptr;
1545 for (SwNodeOffset n(0); n < pNodes.Count(); ++n)
1546 {
1547 pNode = pNodes[n];
1548 if (pNode)
1549 {
1550 for (std::shared_ptr<BaseCheck>& rpNodeCheck : m_aNodeChecks)
1551 {
1552 auto pNodeCheck = dynamic_cast<NodeCheck*>(rpNodeCheck.get());
1553 if (pNodeCheck)
1554 pNodeCheck->check(pNode);
1555 }
1556
1557 for (SwFrameFormat* const& pFrameFormat : pNode->GetAnchoredFlys())
1558 {
1559 SdrObject* pObject = pFrameFormat->FindSdrObject();
1560 if (pObject)
1561 checkObject(pNode, pObject);
1562 }
1563 }
1564 }
1565}
1566
1567} // end sw namespace
1568
1569/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool m_bPrevPassed
int m_nPreviousLevel
const std::vector< std::pair< OUString, OUString > > m_aNumberingCombinations
int m_prevLevel
basegfx::BColor getBColor() const
css::uno::Any * GetPropertyValueByName(const OUString &rPropName)
bool GetValue() const
const SfxPoolItem * GetItem(sal_uInt16 nWhich, bool bSearchInParent=true) const
css::uno::Reference< css::frame::XModel3 > GetModel() const
css::uno::Reference< css::frame::XModel3 > GetBaseModel() const
sal_uInt16 Which() const
LanguageType GetLanguage() const
const SvxLanguageItem & GetLanguage(bool=true) const
Definition: charatr.hxx:91
bool HasSwAttrSet() const
Definition: node.hxx:494
const SwAttrSet & GetSwAttrSet() const
Does node has already its own auto-attributes? Access to SwAttrSet.
Definition: node.hxx:727
Definition: doc.hxx:197
SwNodes & GetNodes()
Definition: doc.hxx:422
SwFootnoteIdxs & GetFootnoteIdxs()
Definition: doc.hxx:649
const SfxPoolItem & GetDefault(sal_uInt16 nFormatHint) const
Get the default attribute in this document.
Definition: docfmt.cxx:672
const SwTextFormatColls * GetTextFormatColls() const
Definition: doc.hxx:793
SwDocShell * GetDocShell()
Definition: doc.hxx:1370
RndStdIds GetAnchorId() const
Definition: fmtanchr.hxx:67
const std::shared_ptr< SfxItemSet > & GetStyleHandle() const
Definition: fmtautofmt.hxx:49
SfxPoolItem subclass for footnotes and endnotes, stored in the anchor text node.
Definition: fmtftn.hxx:47
bool IsEndNote() const
Definition: fmtftn.hxx:75
const OUString & GetName() const
Definition: format.hxx:131
const SwFormatAnchor & GetAnchor(bool=true) const
Definition: fmtanchr.hxx:88
const SwAttrSet & GetAttrSet() const
For querying the attribute array.
Definition: format.hxx:136
const T * GetItemIfSet(TypedWhichId< T > nWhich, bool bSrchInParent=true) const
Templatized version of GetItemState() to directly return the correct type.
Definition: format.hxx:111
Style of a layout element.
Definition: frmfmt.hxx:72
Layout frame for SwNoTextNode, i.e. graphics and OLE nodes (including charts).
Definition: ndnotxt.hxx:30
OUString GetDescription() const
Definition: ndnotxt.cxx:282
OUString GetTitle() const
Definition: ndnotxt.cxx:258
Base class of the Writer document model elements.
Definition: node.hxx:98
bool IsGrfNode() const
Definition: node.hxx:195
SwFrameFormat * GetFlyFormat() const
If node is in a fly return the respective format.
Definition: node.cxx:738
SwTextNode * GetTextNode()
Inline methods from Node.hxx.
Definition: ndtxt.hxx:901
SwNodeOffset GetIndex() const
Definition: node.hxx:312
const SwStartNode * FindFooterStartNode() const
Definition: node.hxx:226
SwNodes & GetNodes()
Node is in which nodes-array/doc?
Definition: node.hxx:706
SwTableBox * GetTableBox() const
If node is in a table return the respective table box.
Definition: node.cxx:772
const SwStartNode * FindHeaderStartNode() const
Definition: node.hxx:224
SwDoc & GetDoc()
Definition: node.hxx:233
const SwStartNode * FindFlyStartNode() const
Definition: node.hxx:220
const SwPageDesc * FindPageDesc(SwNodeOffset *pPgDescNdIdx=nullptr) const
Search PageDesc with which this node is formatted.
Definition: node.cxx:496
bool IsTextNode() const
Definition: node.hxx:190
SwTableNode * FindTableNode()
Search table node, in which it is.
Definition: node.cxx:380
SwNoTextNode * GetNoTextNode()
Definition: ndnotxt.hxx:95
SwTableNode * GetTableNode()
Definition: node.hxx:650
SwNodeType GetNodeType() const
Definition: node.hxx:166
std::vector< SwFrameFormat * > const & GetAnchoredFlys() const
Definition: node.hxx:318
bool IsOLENode() const
Definition: node.hxx:193
SwNodeOffset Count() const
Definition: ndarr.hxx:142
SwFrameFormat & GetMaster()
Definition: pagedesc.hxx:238
SwTableBox is one table cell in the document model.
Definition: swtable.hxx:443
SwTableLine is one table row in the document model.
Definition: swtable.hxx:376
size_type size() const
Definition: swtable.hxx:76
const SwTable & GetTable() const
Definition: node.hxx:542
SwTable is one table in the document model, containing rows (which contain cells).
Definition: swtable.hxx:113
SwTableLines & GetTabLines()
Definition: swtable.hxx:206
SwTableFormat * GetFrameFormat()
Definition: swtable.hxx:209
bool IsTableComplex() const
Definition: swtable.cxx:1445
A wrapper around SfxPoolItem to store the start position of (usually) a text portion,...
Definition: txatbase.hxx:44
sal_Int32 GetAnyEnd() const
end (if available), else start
Definition: txatbase.hxx:161
sal_Int32 GetStart() const
Definition: txatbase.hxx:88
const SwFormatAutoFormat & GetAutoFormat() const
Definition: txatbase.hxx:193
sal_uInt16 Which() const
Definition: txatbase.hxx:116
SwTextAttr subclass for footnotes and endnotes.
Definition: txtftn.hxx:34
Represents the style of a paragraph.
Definition: fmtcol.hxx:61
bool IsAssignedToListLevelOfOutlineStyle() const
Definition: fmtcol.hxx:122
int GetAssignedOutlineStyleLevel() const
Definition: fmtcol.cxx:678
SwTextNode is a paragraph in the document model.
Definition: ndtxt.hxx:112
bool HasHints() const
Definition: ndtxt.hxx:254
SwpHints & GetSwpHints()
getters for SwpHints
Definition: ndtxt.hxx:867
int GetAttrOutlineLevel() const
Returns outline level of this text node.
Definition: ndtxt.cxx:4168
SwpHints * GetpSwpHints()
Definition: ndtxt.hxx:252
const OUString & GetText() const
Definition: ndtxt.hxx:244
SwTextFormatColl * GetTextColl() const
Definition: ndtxt.hxx:895
static rtl::Reference< SwXParagraph > CreateXParagraph(SwDoc &rDoc, SwTextNode *pTextNode, css::uno::Reference< css::text::XText > const &xParentText=nullptr, const sal_Int32 nSelStart=-1, const sal_Int32 nSelEnd=- 1)
An SwTextAttr container, stores all directly formatted text portions for a text node.
Definition: ndhints.hxx:68
SwTextAttr * Get(size_t nPos) const
Definition: ndhints.hxx:144
size_t Count() const
Definition: ndhints.hxx:142
const Color & GetColorValue() const
double getBlue() const
double getRed() const
double getGreen() const
AccessibilityIssueCollection m_aIssueCollection
std::vector< std::shared_ptr< AccessibilityIssue > > & getIssues()
std::vector< std::shared_ptr< BaseCheck > > m_aDocumentChecks
void checkNode(SwNode *pNode)
void checkObject(SwNode *pNode, SdrObject *pObject)
std::vector< std::shared_ptr< BaseCheck > > m_aNodeChecks
Base class for accessibility checks.
constexpr ::Color COL_WHITE(0xFF, 0xFF, 0xFF)
int nCount
SwFrameFormat * FindFrameFormat(SdrObject *pObj)
The Get reverse way: seeks the format to the specified object.
Definition: dcontact.cxx:121
EmbeddedObjectRef * pObject
OUString sName
constexpr TypedWhichId< SvxFontHeightItem > RES_CHRATR_CTL_FONTSIZE(28)
constexpr TypedWhichId< SvxCrossedOutItem > RES_CHRATR_CROSSEDOUT(5)
constexpr TypedWhichId< SvxFontItem > RES_CHRATR_CJK_FONT(22)
constexpr TypedWhichId< SvxUnderlineItem > RES_CHRATR_UNDERLINE(14)
constexpr TypedWhichId< SvxFontHeightItem > RES_CHRATR_FONTSIZE(8)
constexpr TypedWhichId< SvxLanguageItem > RES_CHRATR_LANGUAGE(10)
constexpr TypedWhichId< SvxWeightItem > RES_CHRATR_WEIGHT(15)
constexpr TypedWhichId< SvxShadowedItem > RES_CHRATR_SHADOWED(13)
constexpr TypedWhichId< SvxFontHeightItem > RES_CHRATR_CJK_FONTSIZE(23)
constexpr TypedWhichId< SvxFontItem > RES_CHRATR_CTL_FONT(27)
constexpr TypedWhichId< SwFormatAutoFormat > RES_TXTATR_AUTOFMT(50)
constexpr TypedWhichId< SvxWeightItem > RES_CHRATR_CTL_WEIGHT(31)
constexpr TypedWhichId< SvxContourItem > RES_CHRATR_CONTOUR(4)
constexpr TypedWhichId< SvxCharReliefItem > RES_CHRATR_RELIEF(36)
constexpr TypedWhichId< SvxEscapementItem > RES_CHRATR_ESCAPEMENT(6)
constexpr TypedWhichId< SvxPostureItem > RES_CHRATR_CTL_POSTURE(30)
constexpr TypedWhichId< SvxEmphasisMarkItem > RES_CHRATR_EMPHASIS_MARK(33)
constexpr TypedWhichId< SvxPostureItem > RES_CHRATR_POSTURE(11)
constexpr TypedWhichId< SwFormatContentControl > RES_TXTATR_CONTENTCONTROL(56)
constexpr TypedWhichId< SvxOverlineItem > RES_CHRATR_OVERLINE(38)
constexpr TypedWhichId< SfxBoolItem > RES_DECORATIVE(140)
constexpr TypedWhichId< SvxWeightItem > RES_CHRATR_CJK_WEIGHT(26)
constexpr TypedWhichId< SvxFontItem > RES_CHRATR_FONT(7)
constexpr TypedWhichId< SvxPostureItem > RES_CHRATR_CJK_POSTURE(25)
constexpr TypedWhichId< SvxColorItem > RES_CHRATR_COLOR(3)
sal_Int32 nIndex
sal_Int64 n
#define SAL_WARN(area, stream)
def text(shape, orig_st)
DESKTOP_DEPLOYMENTMISC_DLLPUBLIC css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > > check(dp_misc::DescriptionInfoset const &infoset)
int i
std::basic_string_view< charT, traits > trim(std::basic_string_view< charT, traits > str)
void remove_duplicates(std::vector< T > &rVector)
AccessibilityIssueID
Dialog to specify the properties of date form field.
@ Table
SwTableNode is derived from SwStartNode.
o3tl::strong_int< sal_Int32, struct Tag_SwNodeOffset > SwNodeOffset
Definition: nodeoffset.hxx:16
SwNodeOffset abs(const SwNodeOffset &a)
Definition: nodeoffset.hxx:34
constexpr TypedWhichId< SdrCustomShapeGeometryItem > SDRATTR_CUSTOMSHAPE_GEOMETRY(SDRATTR_CUSTOMSHAPE_FIRST+2)
SdrInventor
SdrObjKind
OUString SwResId(TranslateId aId)
Definition: swmodule.cxx:168
constexpr TypedWhichId< XFillColorItem > XATTR_FILLCOLOR(XATTR_FILL_FIRST+1)