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 <ndnotxt.hxx>
15 #include <ndtxt.hxx>
16 #include <docsh.hxx>
18 #include <drawdoc.hxx>
19 #include <svx/svdpage.hxx>
20 #include <swtable.hxx>
21 #include <com/sun/star/frame/XModel.hpp>
22 #include <com/sun/star/text/XTextContent.hpp>
23 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
24 #include <unoparagraph.hxx>
25 #include <tools/urlobj.hxx>
26 #include <editeng/langitem.hxx>
27 #include <charatr.hxx>
28 #include <svx/xfillit0.hxx>
29 #include <svx/xflclit.hxx>
30 #include <ftnidx.hxx>
31 #include <txtftn.hxx>
32 #include <svl/itemiter.hxx>
33 #include <o3tl/vector_utils.hxx>
34 #include <svx/swframetypes.hxx>
35 #include <fmtanchr.hxx>
36 #include <dcontact.hxx>
37 #include <svx/svdoashp.hxx>
38 #include <svx/sdasitm.hxx>
39 
40 namespace sw
41 {
42 namespace
43 {
44 std::shared_ptr<sw::AccessibilityIssue>
45 lclAddIssue(sfx::AccessibilityIssueCollection& rIssueCollection, OUString const& rText,
47 {
48  auto pIssue = std::make_shared<sw::AccessibilityIssue>(eIssue);
49  pIssue->m_aIssueText = rText;
50  rIssueCollection.getIssues().push_back(pIssue);
51  return pIssue;
52 }
53 
54 class BaseCheck
55 {
56 protected:
58 
59 public:
60  BaseCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
61  : m_rIssueCollection(rIssueCollection)
62  {
63  }
64  virtual ~BaseCheck() {}
65 };
66 
67 class NodeCheck : public BaseCheck
68 {
69 public:
70  NodeCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
71  : BaseCheck(rIssueCollection)
72  {
73  }
74 
75  virtual void check(SwNode* pCurrent) = 0;
76 };
77 
78 // Check NoTextNodes: Graphic, OLE for alt (title) text
79 class NoTextNodeAltTextCheck : public NodeCheck
80 {
81  void checkNoTextNode(SwNoTextNode* pNoTextNode)
82  {
83  if (!pNoTextNode)
84  return;
85 
86  OUString sAlternative = pNoTextNode->GetTitle();
87  if (!sAlternative.isEmpty())
88  return;
89 
90  OUString sName = pNoTextNode->GetFlyFormat()->GetName();
91 
92  OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName);
93 
94  if (pNoTextNode->IsOLENode())
95  {
96  auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
98  pIssue->setDoc(pNoTextNode->GetDoc());
99  pIssue->setIssueObject(IssueObject::OLE);
100  pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName());
101  }
102  else if (pNoTextNode->IsGrfNode())
103  {
104  auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
106  pIssue->setDoc(pNoTextNode->GetDoc());
107  pIssue->setIssueObject(IssueObject::GRAPHIC);
108  pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName());
109  }
110  }
111 
112 public:
113  NoTextNodeAltTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
114  : NodeCheck(rIssueCollection)
115  {
116  }
117 
118  void check(SwNode* pCurrent) override
119  {
120  if (pCurrent->GetNodeType() & SwNodeType::NoTextMask)
121  {
122  SwNoTextNode* pNoTextNode = pCurrent->GetNoTextNode();
123  if (pNoTextNode)
124  checkNoTextNode(pNoTextNode);
125  }
126  }
127 };
128 
129 // Check Table node if the table is merged and split.
130 class TableNodeMergeSplitCheck : public NodeCheck
131 {
132 private:
133  void addTableIssue(SwTable const& rTable, SwDoc& rDoc)
134  {
135  const SwTableFormat* pFormat = rTable.GetFrameFormat();
136  OUString sName = pFormat->GetName();
137  OUString sIssueText = SwResId(STR_TABLE_MERGE_SPLIT).replaceAll("%OBJECT_NAME%", sName);
138  auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText,
140  pIssue->setDoc(rDoc);
141  pIssue->setIssueObject(IssueObject::TABLE);
142  pIssue->setObjectID(sName);
143  }
144 
145  void checkTableNode(SwTableNode* pTableNode)
146  {
147  if (!pTableNode)
148  return;
149 
150  SwTable const& rTable = pTableNode->GetTable();
151  SwDoc& rDoc = pTableNode->GetDoc();
152  if (rTable.IsTableComplex())
153  {
154  addTableIssue(rTable, rDoc);
155  }
156  else
157  {
158  if (rTable.GetTabLines().size() > 1)
159  {
160  int i = 0;
161  size_t nFirstLineSize = 0;
162  bool bAllColumnsSameSize = true;
163  bool bCellSpansOverMoreRows = false;
164 
165  for (SwTableLine const* pTableLine : rTable.GetTabLines())
166  {
167  if (i == 0)
168  {
169  nFirstLineSize = pTableLine->GetTabBoxes().size();
170  }
171  else
172  {
173  size_t nLineSize = pTableLine->GetTabBoxes().size();
174  if (nFirstLineSize != nLineSize)
175  {
176  bAllColumnsSameSize = false;
177  }
178  }
179  i++;
180 
181  // Check for row span in each table box (cell)
182  for (SwTableBox const* pBox : pTableLine->GetTabBoxes())
183  {
184  if (pBox->getRowSpan() > 1)
185  bCellSpansOverMoreRows = true;
186  }
187  }
188  if (!bAllColumnsSameSize || bCellSpansOverMoreRows)
189  {
190  addTableIssue(rTable, rDoc);
191  }
192  }
193  }
194  }
195 
196 public:
197  TableNodeMergeSplitCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
198  : NodeCheck(rIssueCollection)
199  {
200  }
201 
202  void check(SwNode* pCurrent) override
203  {
204  if (pCurrent->GetNodeType() & SwNodeType::Table)
205  {
206  SwTableNode* pTableNode = pCurrent->GetTableNode();
207  if (pTableNode)
208  checkTableNode(pTableNode);
209  }
210  }
211 };
212 
213 class NumberingCheck : public NodeCheck
214 {
215 private:
217 
218  const std::vector<std::pair<OUString, OUString>> m_aNumberingCombinations{
219  { "1.", "2." }, { "(1)", "(2)" }, { "1)", "2)" }, { "a.", "b." }, { "(a)", "(b)" },
220  { "a)", "b)" }, { "A.", "B." }, { "(A)", "(B)" }, { "A)", "B)" }
221  };
222 
223 public:
224  NumberingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
225  : NodeCheck(rIssueCollection)
226  , m_pPreviousTextNode(nullptr)
227  {
228  }
229 
230  void check(SwNode* pCurrent) override
231  {
232  if (!pCurrent->IsTextNode())
233  return;
234 
235  if (m_pPreviousTextNode)
236  {
237  for (auto& rPair : m_aNumberingCombinations)
238  {
239  if (pCurrent->GetTextNode()->GetText().startsWith(rPair.second)
240  && m_pPreviousTextNode->GetText().startsWith(rPair.first))
241  {
242  OUString sNumbering = rPair.first + " " + rPair.second + "...";
243  OUString sIssueText
244  = SwResId(STR_FAKE_NUMBERING).replaceAll("%NUMBERING%", sNumbering);
245  lclAddIssue(m_rIssueCollection, sIssueText);
246  }
247  }
248  }
249  m_pPreviousTextNode = pCurrent->GetTextNode();
250  }
251 };
252 
253 class HyperlinkCheck : public NodeCheck
254 {
255 private:
256  void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange)
257  {
258  uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
259  if (!xProperties->getPropertySetInfo()->hasPropertyByName("HyperLinkURL"))
260  return;
261 
262  OUString sHyperlink;
263  xProperties->getPropertyValue("HyperLinkURL") >>= sHyperlink;
264  if (!sHyperlink.isEmpty())
265  {
266  OUString sText = xTextRange->getString();
267  if (INetURLObject(sText) == INetURLObject(sHyperlink))
268  {
269  OUString sIssueText
270  = SwResId(STR_HYPERLINK_TEXT_IS_LINK).replaceFirst("%LINK%", sHyperlink);
271  lclAddIssue(m_rIssueCollection, sIssueText);
272  }
273  }
274  }
275 
276 public:
277  HyperlinkCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
278  : NodeCheck(rIssueCollection)
279  {
280  }
281 
282  void check(SwNode* pCurrent) override
283  {
284  if (!pCurrent->IsTextNode())
285  return;
286 
287  SwTextNode* pTextNode = pCurrent->GetTextNode();
288  uno::Reference<text::XTextContent> xParagraph
289  = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
290  if (!xParagraph.is())
291  return;
292 
293  uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
294  uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
295  while (xRunEnum->hasMoreElements())
296  {
297  uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
298  if (xRun.is())
299  {
300  checkTextRange(xRun);
301  }
302  }
303  }
304 };
305 
306 // Based on https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
307 double calculateRelativeLuminance(Color const& rColor)
308 {
309  // Convert to BColor which has R, G, B colors components
310  // represented by a floating point number from [0.0, 1.0]
311  const basegfx::BColor aBColor = rColor.getBColor();
312 
313  double r = aBColor.getRed();
314  double g = aBColor.getGreen();
315  double b = aBColor.getBlue();
316 
317  // Calculate the values according to the described algorithm
318  r = (r <= 0.03928) ? r / 12.92 : std::pow((r + 0.055) / 1.055, 2.4);
319  g = (g <= 0.03928) ? g / 12.92 : std::pow((g + 0.055) / 1.055, 2.4);
320  b = (b <= 0.03928) ? b / 12.92 : std::pow((b + 0.055) / 1.055, 2.4);
321 
322  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
323 }
324 
325 // TODO move to common color tools (BColorTools maybe)
326 // Based on https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
327 double calculateContrastRatio(Color const& rColor1, Color const& rColor2)
328 {
329  const double fLuminance1 = calculateRelativeLuminance(rColor1);
330  const double fLuminance2 = calculateRelativeLuminance(rColor2);
331  const std::pair<const double, const double> aMinMax = std::minmax(fLuminance1, fLuminance2);
332 
333  // (L1 + 0.05) / (L2 + 0.05)
334  // L1 is the lighter color (greater luminance value)
335  // L2 is the darker color (smaller luminance value)
336  return (aMinMax.second + 0.05) / (aMinMax.first + 0.05);
337 }
338 
339 class TextContrastCheck : public NodeCheck
340 {
341 private:
342  void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange,
343  uno::Reference<text::XTextContent> const& xParagraph,
344  const SwTextNode* pTextNode)
345  {
346  Color nParaBackColor(COL_AUTO);
347  uno::Reference<beans::XPropertySet> xParagraphProperties(xParagraph, uno::UNO_QUERY);
348  if (!(xParagraphProperties->getPropertyValue("ParaBackColor") >>= nParaBackColor))
349  {
350  SAL_WARN("sw.a11y", "ParaBackColor void");
351  return;
352  }
353 
354  uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
355  if (!xProperties.is())
356  return;
357 
358  // Foreground color
359  sal_Int32 nCharColor = {}; // spurious -Werror=maybe-uninitialized
360  if (!(xProperties->getPropertyValue("CharColor") >>= nCharColor))
361  { // not sure this is impossible, can the default be void?
362  SAL_WARN("sw.a11y", "CharColor void");
363  return;
364  }
365  Color aForegroundColor(ColorTransparency, nCharColor);
366  if (aForegroundColor == COL_AUTO)
367  return;
368 
369  const SwPageDesc* pPageDescription = pTextNode->FindPageDesc();
370  const SwFrameFormat& rPageFormat = pPageDescription->GetMaster();
371  const SwAttrSet& rPageSet = rPageFormat.GetAttrSet();
372 
373  const XFillStyleItem* pXFillStyleItem(
374  rPageSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE, false));
375  Color aPageBackground(COL_AUTO);
376 
377  if (pXFillStyleItem && pXFillStyleItem->GetValue() == css::drawing::FillStyle_SOLID)
378  {
379  const XFillColorItem* rXFillColorItem
380  = rPageSet.GetItem<XFillColorItem>(XATTR_FILLCOLOR, false);
381  aPageBackground = rXFillColorItem->GetColorValue();
382  }
383 
384  Color nCharBackColor(COL_AUTO);
385 
386  if (!(xProperties->getPropertyValue("CharBackColor") >>= nCharBackColor))
387  {
388  SAL_WARN("sw.a11y", "CharBackColor void");
389  return;
390  }
391  // Determine the background color
392  // Try Character background (highlight)
393  Color aBackgroundColor(nCharBackColor);
394 
395  // If not character background color, try paragraph background color
396  if (aBackgroundColor == COL_AUTO)
397  aBackgroundColor = nParaBackColor;
398 
399  // If not paragraph background color, try page color
400  if (aBackgroundColor == COL_AUTO)
401  aBackgroundColor = aPageBackground;
402 
403  // If not page color, assume white background color
404  if (aBackgroundColor == COL_AUTO)
405  aBackgroundColor = COL_WHITE;
406 
407  double fContrastRatio = calculateContrastRatio(aForegroundColor, aBackgroundColor);
408  if (fContrastRatio < 4.5)
409  {
410  lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_CONTRAST));
411  }
412  }
413 
414 public:
415  TextContrastCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
416  : NodeCheck(rIssueCollection)
417  {
418  }
419 
420  void check(SwNode* pCurrent) override
421  {
422  if (!pCurrent->IsTextNode())
423  return;
424 
425  SwTextNode* pTextNode = pCurrent->GetTextNode();
426  uno::Reference<text::XTextContent> xParagraph;
427  xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
428  if (!xParagraph.is())
429  return;
430 
431  uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
432  uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
433  while (xRunEnum->hasMoreElements())
434  {
435  uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
436  if (xRun.is())
437  checkTextRange(xRun, xParagraph, pTextNode);
438  }
439  }
440 };
441 
442 class TextFormattingCheck : public NodeCheck
443 {
444 private:
445 public:
446  TextFormattingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
447  : NodeCheck(rIssueCollection)
448  {
449  }
450 
451  void checkAutoFormat(SwTextNode* pTextNode, const SwTextAttr* pTextAttr)
452  {
453  const SwFormatAutoFormat& rAutoFormat = pTextAttr->GetAutoFormat();
454  SfxItemIter aItemIter(*rAutoFormat.GetStyleHandle());
455  const SfxPoolItem* pItem = aItemIter.GetCurItem();
456  std::vector<OUString> aFormattings;
457  while (pItem)
458  {
459  OUString sFormattingType;
460  switch (pItem->Which())
461  {
462  case RES_CHRATR_WEIGHT:
465  sFormattingType = "Weight";
466  break;
467  case RES_CHRATR_POSTURE:
470  sFormattingType = "Posture";
471  break;
472 
473  case RES_CHRATR_SHADOWED:
474  sFormattingType = "Shadowed";
475  break;
476 
477  case RES_CHRATR_COLOR:
478  sFormattingType = "Font Color";
479  break;
480 
481  case RES_CHRATR_FONTSIZE:
484  sFormattingType = "Font Size";
485  break;
486 
487  case RES_CHRATR_FONT:
488  case RES_CHRATR_CJK_FONT:
489  case RES_CHRATR_CTL_FONT:
490  sFormattingType = "Font";
491  break;
492 
494  sFormattingType = "Emphasis Mark";
495  break;
496 
498  sFormattingType = "Underline";
499  break;
500 
501  case RES_CHRATR_OVERLINE:
502  sFormattingType = "Overline";
503  break;
504 
506  sFormattingType = "Strikethrough";
507  break;
508 
509  case RES_CHRATR_RELIEF:
510  sFormattingType = "Relief";
511  break;
512 
513  case RES_CHRATR_CONTOUR:
514  sFormattingType = "Outline";
515  break;
516  default:
517  break;
518  }
519  if (!sFormattingType.isEmpty())
520  aFormattings.push_back(sFormattingType);
521  pItem = aItemIter.NextItem();
522  }
523  if (aFormattings.empty())
524  return;
525 
526  o3tl::remove_duplicates(aFormattings);
527  auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING),
529  pIssue->setIssueObject(IssueObject::TEXT);
530  pIssue->setNode(pTextNode);
531  SwDoc& rDocument = pTextNode->GetDoc();
532  pIssue->setDoc(rDocument);
533  pIssue->setStart(pTextAttr->GetStart());
534  pIssue->setEnd(pTextAttr->GetAnyEnd());
535  }
536  void check(SwNode* pCurrent) override
537  {
538  if (!pCurrent->IsTextNode())
539  return;
540 
541  SwTextNode* pTextNode = pCurrent->GetTextNode();
542  if (pTextNode->HasHints())
543  {
544  SwpHints& rHints = pTextNode->GetSwpHints();
545  for (size_t i = 0; i < rHints.Count(); ++i)
546  {
547  const SwTextAttr* pTextAttr = rHints.Get(i);
548  if (pTextAttr->Which() == RES_TXTATR_AUTOFMT)
549  {
550  checkAutoFormat(pTextNode, pTextAttr);
551  }
552  }
553  }
554  }
555 };
556 
557 class BlinkingTextCheck : public NodeCheck
558 {
559 private:
560  void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange)
561  {
562  uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY);
563  if (xProperties.is() && xProperties->getPropertySetInfo()->hasPropertyByName("CharFlash"))
564  {
565  bool bBlinking = false;
566  xProperties->getPropertyValue("CharFlash") >>= bBlinking;
567 
568  if (bBlinking)
569  {
570  lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_BLINKING));
571  }
572  }
573  }
574 
575 public:
576  BlinkingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
577  : NodeCheck(rIssueCollection)
578  {
579  }
580 
581  void check(SwNode* pCurrent) override
582  {
583  if (!pCurrent->IsTextNode())
584  return;
585 
586  SwTextNode* pTextNode = pCurrent->GetTextNode();
587  uno::Reference<text::XTextContent> xParagraph;
588  xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode);
589  if (!xParagraph.is())
590  return;
591 
592  uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY);
593  uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
594  while (xRunEnum->hasMoreElements())
595  {
596  uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY);
597  if (xRun.is())
598  checkTextRange(xRun);
599  }
600  }
601 };
602 
603 class HeaderCheck : public NodeCheck
604 {
605 private:
607 
608 public:
609  HeaderCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
610  : NodeCheck(rIssueCollection)
611  , m_nPreviousLevel(0)
612  {
613  }
614 
615  void check(SwNode* pCurrent) override
616  {
617  if (!pCurrent->IsTextNode())
618  return;
619 
620  SwTextNode* pTextNode = pCurrent->GetTextNode();
621  SwTextFormatColl* pCollection = pTextNode->GetTextColl();
622  int nLevel = pCollection->GetAssignedOutlineStyleLevel();
623  if (nLevel < 0)
624  return;
625 
626  if (nLevel > m_nPreviousLevel && std::abs(nLevel - m_nPreviousLevel) > 1)
627  {
628  lclAddIssue(m_rIssueCollection, SwResId(STR_HEADINGS_NOT_IN_ORDER));
629  }
630  m_nPreviousLevel = nLevel;
631  }
632 };
633 
634 // ISO 142891-1 : 7.14
635 class NonInteractiveFormCheck : public NodeCheck
636 {
637 public:
638  NonInteractiveFormCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
639  : NodeCheck(rIssueCollection)
640  {
641  }
642 
643  void check(SwNode* pCurrent) override
644  {
645  if (!pCurrent->IsTextNode())
646  return;
647 
648  const auto& text = pCurrent->GetTextNode()->GetText();
649 
650  // Series of tests to detect if there are fake forms in the text.
651 
652  bool bCheck = text.indexOf("___") == -1; // Repeated underscores.
653 
654  if (bCheck)
655  bCheck = text.indexOf("....") == -1; // Repeated dots.
656 
657  if (bCheck)
658  bCheck = text.indexOf(u"……") == -1; // Repeated ellipsis.
659 
660  if (bCheck)
661  bCheck = text.indexOf(u"….") == -1; // A dot after an ellipsis.
662 
663  if (bCheck)
664  bCheck = text.indexOf(u".…") == -1; // An ellipsis after a dot.
665 
666  // Checking if all the tests are passed successfully. If not, adding a warning.
667  if (!bCheck)
668  lclAddIssue(m_rIssueCollection, SwResId(STR_NON_INTERACTIVE_FORMS));
669  }
670 };
671 
673 class FloatingTextCheck : public NodeCheck
674 {
675 public:
676  FloatingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
677  : NodeCheck(rIssueCollection)
678  {
679  }
680 
681  void check(SwNode* pCurrent) override
682  {
683  // if node is a text-node and if it has text, we proceed. Otherwise - return.
684  const SwTextNode* textNode = pCurrent->GetTextNode();
685  if (!textNode || textNode->GetText().isEmpty())
686  return;
687 
688  // If a node is in fly and if it is not anchored as char, throw warning.
689  const SwNode* startFly = pCurrent->FindFlyStartNode();
690  if (startFly
691  && startFly->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
692  lclAddIssue(m_rIssueCollection, SwResId(STR_FLOATING_TEXT));
693  }
694 };
695 
697 class TableHeadingCheck : public NodeCheck
698 {
699 private:
700  // Boolean indicating if heading-in-table warning is already triggered.
702 
703 public:
704  TableHeadingCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
705  : NodeCheck(rIssueCollection)
706  , m_bPrevPassed(true)
707  {
708  }
709 
710  void check(SwNode* pCurrent) override
711  {
712  if (!m_bPrevPassed)
713  return;
714 
715  const SwTextNode* textNode = pCurrent->GetTextNode();
716 
717  if (textNode && textNode->GetAttrOutlineLevel() != 0)
718  {
719  const SwTableNode* parentTable = pCurrent->FindTableNode();
720 
721  if (parentTable)
722  {
723  m_bPrevPassed = false;
724  lclAddIssue(m_rIssueCollection, SwResId(STR_HEADING_IN_TABLE));
725  }
726  }
727  }
728 };
729 
731 class HeadingOrderCheck : public NodeCheck
732 {
733 public:
734  HeadingOrderCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
735  : NodeCheck(rIssueCollection)
736  {
737  }
738 
739  void check(SwNode* pCurrent) override
740  {
741  const SwTextNode* pTextNode = pCurrent->GetTextNode();
742  if (!pTextNode)
743  return;
744 
745  // If outline level stands for heading level...
746  const int currentLevel = pTextNode->GetAttrOutlineLevel();
747  if (!currentLevel)
748  return;
749 
750  // ... and if is bigger than previous by more than 1, warn.
751  if (currentLevel - m_prevLevel > 1)
752  {
753  // Preparing and posting a warning.
754  OUString resultString = SwResId(STR_HEADING_ORDER);
755  resultString
756  = resultString.replaceAll("%LEVEL_CURRENT%", OUString::number(currentLevel));
757  resultString = resultString.replaceAll("%LEVEL_PREV%", OUString::number(m_prevLevel));
758 
759  lclAddIssue(m_rIssueCollection, resultString);
760  }
761 
762  // Updating previous level.
763  m_prevLevel = currentLevel;
764  }
765 
766 private:
767  // Previous heading level to compare with.
768  int m_prevLevel = 0;
769 };
770 
771 class DocumentCheck : public BaseCheck
772 {
773 public:
774  DocumentCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
775  : BaseCheck(rIssueCollection)
776  {
777  }
778 
779  virtual void check(SwDoc* pDoc) = 0;
780 };
781 
782 // Check default language
783 class DocumentDefaultLanguageCheck : public DocumentCheck
784 {
785 public:
786  DocumentDefaultLanguageCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
787  : DocumentCheck(rIssueCollection)
788  {
789  }
790 
791  void check(SwDoc* pDoc) override
792  {
793  // TODO maybe - also check RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE if CJK or CTL are enabled
794  const SvxLanguageItem& rLang = pDoc->GetDefault(RES_CHRATR_LANGUAGE);
795  LanguageType eLanguage = rLang.GetLanguage();
796  if (eLanguage == LANGUAGE_NONE)
797  {
798  lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_DEFAULT_LANGUAGE),
800  }
801  else
802  {
803  for (SwTextFormatColl* pTextFormatCollection : *pDoc->GetTextFormatColls())
804  {
805  const SwAttrSet& rAttrSet = pTextFormatCollection->GetAttrSet();
806  if (rAttrSet.GetLanguage(false).GetLanguage() == LANGUAGE_NONE)
807  {
808  OUString sName = pTextFormatCollection->GetName();
809  OUString sIssueText
810  = SwResId(STR_STYLE_NO_LANGUAGE).replaceAll("%STYLE_NAME%", sName);
811  lclAddIssue(m_rIssueCollection, sIssueText,
813  }
814  }
815  }
816  }
817 };
818 
819 class DocumentTitleCheck : public DocumentCheck
820 {
821 public:
822  DocumentTitleCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
823  : DocumentCheck(rIssueCollection)
824  {
825  }
826 
827  void check(SwDoc* pDoc) override
828  {
829  SwDocShell* pShell = pDoc->GetDocShell();
830  if (!pShell)
831  return;
832 
833  const uno::Reference<document::XDocumentPropertiesSupplier> xDPS(pShell->GetModel(),
834  uno::UNO_QUERY_THROW);
835  const uno::Reference<document::XDocumentProperties> xDocumentProperties(
836  xDPS->getDocumentProperties());
837  OUString sTitle = xDocumentProperties->getTitle();
838  if (sTitle.isEmpty())
839  {
840  lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_TITLE),
842  }
843  }
844 };
845 
846 class FootnoteEndnoteCheck : public DocumentCheck
847 {
848 public:
849  FootnoteEndnoteCheck(sfx::AccessibilityIssueCollection& rIssueCollection)
850  : DocumentCheck(rIssueCollection)
851  {
852  }
853 
854  void check(SwDoc* pDoc) override
855  {
856  for (SwTextFootnote const* pTextFootnote : pDoc->GetFootnoteIdxs())
857  {
858  SwFormatFootnote const& rFootnote = pTextFootnote->GetFootnote();
859  if (rFootnote.IsEndNote())
860  {
861  lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_ENDNOTES));
862  }
863  else
864  {
865  lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_FOOTNOTES));
866  }
867  }
868  }
869 };
870 
871 } // end anonymous namespace
872 
873 // Check Shapes, TextBox
875 {
876  if (!pObject)
877  return;
878 
879  // Check for fontworks.
880  if (SdrObjCustomShape* pCustomShape = dynamic_cast<SdrObjCustomShape*>(pObject))
881  {
882  const SdrCustomShapeGeometryItem& rGeometryItem
883  = pCustomShape->GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY);
884 
885  if (const uno::Any* pAny = rGeometryItem.GetPropertyValueByName("Type"))
886  if (pAny->get<OUString>().startsWith("fontwork-"))
887  lclAddIssue(m_aIssueCollection, SwResId(STR_FONTWORKS));
888  }
889 
890  // Checking if there is floating Writer text draw object and if so, throwing a warning.
891  // (Floating objects with text create problems with reading order)
892  if (pObject->HasText()
893  && FindFrameFormat(pObject)->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
894  lclAddIssue(m_aIssueCollection, SwResId(STR_FLOATING_TEXT));
895 
896  if (pObject->GetObjIdentifier() == OBJ_CUSTOMSHAPE || pObject->GetObjIdentifier() == OBJ_TEXT)
897  {
898  OUString sAlternative = pObject->GetTitle();
899  if (sAlternative.isEmpty())
900  {
901  OUString sName = pObject->GetName();
902  OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName);
904  }
905  }
906 }
907 
909 {
910  if (m_pDoc == nullptr)
911  return;
912 
913  std::vector<std::unique_ptr<DocumentCheck>> aDocumentChecks;
914  aDocumentChecks.push_back(std::make_unique<DocumentDefaultLanguageCheck>(m_aIssueCollection));
915  aDocumentChecks.push_back(std::make_unique<DocumentTitleCheck>(m_aIssueCollection));
916  aDocumentChecks.push_back(std::make_unique<FootnoteEndnoteCheck>(m_aIssueCollection));
917 
918  for (std::unique_ptr<DocumentCheck>& rpDocumentCheck : aDocumentChecks)
919  {
920  rpDocumentCheck->check(m_pDoc);
921  }
922 
923  std::vector<std::unique_ptr<NodeCheck>> aNodeChecks;
924  aNodeChecks.push_back(std::make_unique<NoTextNodeAltTextCheck>(m_aIssueCollection));
925  aNodeChecks.push_back(std::make_unique<TableNodeMergeSplitCheck>(m_aIssueCollection));
926  aNodeChecks.push_back(std::make_unique<NumberingCheck>(m_aIssueCollection));
927  aNodeChecks.push_back(std::make_unique<HyperlinkCheck>(m_aIssueCollection));
928  aNodeChecks.push_back(std::make_unique<TextContrastCheck>(m_aIssueCollection));
929  aNodeChecks.push_back(std::make_unique<BlinkingTextCheck>(m_aIssueCollection));
930  aNodeChecks.push_back(std::make_unique<HeaderCheck>(m_aIssueCollection));
931  aNodeChecks.push_back(std::make_unique<TextFormattingCheck>(m_aIssueCollection));
932  aNodeChecks.push_back(std::make_unique<NonInteractiveFormCheck>(m_aIssueCollection));
933  aNodeChecks.push_back(std::make_unique<FloatingTextCheck>(m_aIssueCollection));
934  aNodeChecks.push_back(std::make_unique<TableHeadingCheck>(m_aIssueCollection));
935  aNodeChecks.push_back(std::make_unique<HeadingOrderCheck>(m_aIssueCollection));
936 
937  auto const& pNodes = m_pDoc->GetNodes();
938  SwNode* pNode = nullptr;
939  for (sal_uLong n = 0; n < pNodes.Count(); ++n)
940  {
941  pNode = pNodes[n];
942  if (pNode)
943  {
944  for (std::unique_ptr<NodeCheck>& rpNodeCheck : aNodeChecks)
945  {
946  rpNodeCheck->check(pNode);
947  }
948  }
949  }
950 
952  auto* pModel = rDrawModelAccess.GetDrawModel();
953  for (sal_uInt16 nPage = 0; nPage < pModel->GetPageCount(); ++nPage)
954  {
955  SdrPage* pPage = pModel->GetPage(nPage);
956  for (size_t nObject = 0; nObject < pPage->GetObjCount(); ++nObject)
957  {
958  SdrObject* pObject = pPage->GetObj(nObject);
959  if (pObject)
960  checkObject(pObject);
961  }
962  }
963 }
964 
965 } // end sw namespace
966 
967 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool IsTableComplex() const
Definition: swtable.cxx:1438
#define LANGUAGE_NONE
constexpr TypedWhichId< SvxCrossedOutItem > RES_CHRATR_CROSSEDOUT(5)
int m_nPreviousLevel
int GetAssignedOutlineStyleLevel() const
Definition: fmtcol.cxx:613
Represents the style of a paragraph.
Definition: fmtcol.hxx:56
constexpr TypedWhichId< SvxFontItem > RES_CHRATR_CTL_FONT(27)
bool IsGrfNode() const
Definition: node.hxx:657
const OUString & GetText() const
Definition: ndtxt.hxx:218
SwDocShell * GetDocShell()
Definition: doc.hxx:1352
int m_prevLevel
sal_uIntPtr sal_uLong
constexpr TypedWhichId< SvxLanguageItem > RES_CHRATR_LANGUAGE(10)
static css::uno::Reference< css::text::XTextContent > CreateXParagraph(SwDoc &rDoc, SwTextNode *pTextNode, css::uno::Reference< css::text::XText > const &xParentText=nullptr, const sal_Int32 nSelStart=-1, const sal_Int32 nSelEnd=-1)
constexpr TypedWhichId< SvxFontHeightItem > RES_CHRATR_FONTSIZE(8)
const SwPageDesc * FindPageDesc(size_t *pPgDescNdIdx=nullptr) const
Search PageDesc with which this node is formatted.
Definition: node.cxx:474
const SvxLanguageItem & GetLanguage(bool=true) const
Definition: charatr.hxx:91
sal_Int64 n
css::uno::Reference< css::frame::XModel3 > GetModel() const
virtual bool HasText() const
SdrObject * GetObj(size_t nNum) const
Definition: doc.hxx:188
size_t GetObjCount() const
const std::vector< std::pair< OUString, OUString > > m_aNumberingCombinations
constexpr TypedWhichId< SvxUnderlineItem > RES_CHRATR_UNDERLINE(14)
SwTableLine is one table row in the document model.
Definition: swtable.hxx:357
const std::shared_ptr< SfxItemSet > & GetStyleHandle() const
Definition: fmtautofmt.hxx:49
bool IsEndNote() const
Definition: fmtftn.hxx:71
Dialog to specify the properties of date form field.
void checkObject(SdrObject *pObject)
sal_uInt16 Which() const
Definition: txatbase.hxx:114
sal_Int32 GetAnyEnd() const
end (if available), else start
Definition: txatbase.hxx:157
virtual SdrObjKind GetObjIdentifier() const
EmbeddedObjectRef * pObject
constexpr TypedWhichId< SvxPostureItem > RES_CHRATR_CJK_POSTURE(25)
constexpr TypedWhichId< SvxFontItem > RES_CHRATR_FONT(7)
SwTableFormat * GetFrameFormat()
Definition: swtable.hxx:204
int GetAttrOutlineLevel() const
Returns outline level of this text node.
Definition: ndtxt.cxx:3989
SwTextNode * m_pPreviousTextNode
size_type size() const
Definition: swtable.hxx:76
IDocumentDrawModelAccess const & getIDocumentDrawModelAccess() const
Definition: doc.cxx:155
OUString SwResId(TranslateId aId)
Definition: swmodule.cxx:165
SwTableNode * GetTableNode()
Definition: node.hxx:600
SwNodeType GetNodeType() const
Definition: node.hxx:145
A wrapper around SfxPoolItem to store the start position of (usually) a text portion, with an optional end.
Definition: txatbase.hxx:41
const OUString & GetName() const
Definition: format.hxx:115
const SfxPoolItem & GetDefault(sal_uInt16 nFormatHint) const
Get the default attribute in this document.
Definition: docfmt.cxx:667
sal_Int32 GetStart() const
Definition: txatbase.hxx:86
const SwTable & GetTable() const
Definition: node.hxx:499
constexpr TypedWhichId< SvxCharReliefItem > RES_CHRATR_RELIEF(36)
double getBlue() const
SwFrameFormat * FindFrameFormat(SdrObject *pObj)
The Get reverse way: seeks the format to the specified object.
Definition: dcontact.cxx:120
constexpr TypedWhichId< SwFormatAutoFormat > RES_TXTATR_AUTOFMT(50)
const char * sName
const Color & GetColorValue() const
sfx::AccessibilityIssueCollection & m_rIssueCollection
constexpr TypedWhichId< SvxWeightItem > RES_CHRATR_WEIGHT(15)
bool IsOLENode() const
Definition: node.hxx:653
exports com.sun.star. text
double getRed() const
Style of a layout element.
Definition: frmfmt.hxx:59
size_t Count() const
Definition: ndhints.hxx:142
SwTextAttr * Get(size_t nPos) const
Definition: ndhints.hxx:144
const SwFormatAnchor & GetAnchor(bool=true) const
Definition: fmtanchr.hxx:81
constexpr TypedWhichId< SdrCustomShapeGeometryItem > SDRATTR_CUSTOMSHAPE_GEOMETRY(SDRATTR_CUSTOMSHAPE_FIRST+2)
int i
std::vector< std::shared_ptr< AccessibilityIssue > > & getIssues()
SwDoc & GetDoc()
Definition: node.hxx:212
RndStdIds GetAnchorId() const
Definition: fmtanchr.hxx:65
SwNoTextNode * GetNoTextNode()
Definition: ndnotxt.hxx:96
constexpr TypedWhichId< SvxShadowedItem > RES_CHRATR_SHADOWED(13)
virtual const SwDrawModel * GetDrawModel() const =0
Draw Model and id accessors.
OUString GetTitle() const
bool m_bPrevPassed
SwpHints & GetSwpHints()
getters for SwpHints
Definition: ndtxt.hxx:822
constexpr TypedWhichId< SvxOverlineItem > RES_CHRATR_OVERLINE(38)
constexpr TypedWhichId< SvxEmphasisMarkItem > RES_CHRATR_EMPHASIS_MARK(33)
SwTableLines & GetTabLines()
Definition: swtable.hxx:201
OUString GetName() const
SwTable is one table in the document model, containing rows (which contain cells).
Definition: swtable.hxx:112
constexpr TypedWhichId< SvxColorItem > RES_CHRATR_COLOR(3)
constexpr TypedWhichId< XFillColorItem > XATTR_FILLCOLOR(XATTR_FILL_FIRST+1)
DESKTOP_DEPLOYMENTMISC_DLLPUBLIC css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > > check(dp_misc::DescriptionInfoset const &infoset)
SwFrameFormat & GetMaster()
Definition: pagedesc.hxx:238
SwTextNode is a paragraph in the document model.
Definition: ndtxt.hxx:79
LanguageType GetLanguage() const
An SwTextAttr container, stores all directly formatted text portions for a text node.
Definition: ndhints.hxx:67
constexpr TypedWhichId< SvxWeightItem > RES_CHRATR_CJK_WEIGHT(26)
const SwTextFormatColls * GetTextFormatColls() const
Definition: doc.hxx:778
double getGreen() const
constexpr TypedWhichId< SvxWeightItem > RES_CHRATR_CTL_WEIGHT(31)
OUString GetTitle() const
Definition: ndnotxt.cxx:258
const ::std::vector< Color > ImpSvNumberformatScan::StandardColor COL_WHITE
SwNodes & GetNodes()
Definition: doc.hxx:409
constexpr TypedWhichId< SvxFontItem > RES_CHRATR_CJK_FONT(22)
constexpr TypedWhichId< SvxPostureItem > RES_CHRATR_CTL_POSTURE(30)
SwTableBox is one table cell in the document model.
Definition: swtable.hxx:405
SwTableNode is derived from SwStartNode.
AccessibilityIssueID
css::uno::Any * GetPropertyValueByName(const OUString &rPropName)
basegfx::BColor getBColor() const
SwTableNode * FindTableNode()
Search table node, in which it is.
Definition: node.cxx:357
OBJ_CUSTOMSHAPE
const SwStartNode * FindFlyStartNode() const
Definition: node.hxx:199
SwFootnoteIdxs & GetFootnoteIdxs()
Definition: doc.hxx:634
#define SAL_WARN(area, stream)
const SwFormatAutoFormat & GetAutoFormat() const
Definition: txatbase.hxx:189
const SwAttrSet & GetAttrSet() const
For querying the attribute array.
Definition: format.hxx:120
const SfxPoolItem * GetItem(sal_uInt16 nWhich, bool bSearchInParent=true) const
constexpr TypedWhichId< SvxFontHeightItem > RES_CHRATR_CTL_FONTSIZE(28)
OBJ_TEXT
bool IsTextNode() const
Definition: node.hxx:637
constexpr TypedWhichId< SvxFontHeightItem > RES_CHRATR_CJK_FONTSIZE(23)
SwFrameFormat * GetFlyFormat() const
If node is in a fly return the respective format.
Definition: node.cxx:719
void remove_duplicates(std::vector< T > &rVector)
constexpr TypedWhichId< SvxPostureItem > RES_CHRATR_POSTURE(11)
SwTextNode * GetTextNode()
Inline methods from Node.hxx.
Definition: ndtxt.hxx:856
AccessibilityIssueCollection m_aIssueCollection
bool HasHints() const
Definition: ndtxt.hxx:228
Base class of the Writer document model elements.
Definition: node.hxx:80
SwTextFormatColl * GetTextColl() const
Definition: ndtxt.hxx:850
constexpr TypedWhichId< SvxContourItem > RES_CHRATR_CONTOUR(4)