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