/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/// Covers sw/source/core/txtnode/ fixes. class SwCoreTxtnodeTest : public SwModelTestBase
{ public:
SwCoreTxtnodeTest()
: SwModelTestBase(u"/sw/qa/core/txtnode/data/"_ustr)
{
}
};
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testBtlrCellChinese)
{ // Load a document with a table cell, with btlr cell direction. The cell has text which is // classified as vertical, i.e. the glyph has the same direction in both the lrtb ("Latin") and // tbrl ("Chinese") directions. Make sure that Chinese text is handled the same way in the btlr // case as it's handled in the Latin case.
createSwDoc("btlr-cell-chinese.doc");
SwDocShell* pShell = getSwDocShell();
std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
MetafileXmlDump dumper;
xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
assertXPath(pXmlDoc, "//font[1]", "orientation", u"900"); // Without the accompanying fix in place, this test would have failed with: // - Expected: false // - Actual : true // i.e. the glyph was rotated further, so it was upside down.
assertXPath(pXmlDoc, "//font[1]", "vertical", u"false");
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSpecialInsertAfterMergedCells)
{ // Load a document with a table with bottom right cells merged vertically. // SpecialInsert with alt-Enter must work here, too.
createSwDoc("special-insert-after-merged-cells.fodt");
SwDoc* pDoc = getSwDoc();
SwNodeOffset const nNodes(pDoc->GetNodes().Count());
SwDocShell* pShell = getSwDocShell();
SwWrtShell* pWrtShell = pShell->GetWrtShell(); // go to the merged cell
pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
// When pressing alt-Enter on the keyboard:
SwEditWin& rEditWin = pWrtShell->GetView().GetEditWin();
vcl::KeyCode aKeyCode(KEY_RETURN, KEY_MOD2);
KeyEvent aKeyEvent(' ', aKeyCode);
rEditWin.KeyInput(aKeyEvent);
// Without the accompanying fix in place, this test would have failed with: // - Expected: nNodes + 1 // - Actual : nNodes // i.e. new empty paragraph wasn't inserted under the table
CPPUNIT_ASSERT_EQUAL(nNodes + 1, pDoc->GetNodes().Count());
}
constauto& rFormats = *pShell->GetDoc()->GetSpzFrameFormats(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 4 // - Actual : 6 // i.e. 2 fly frames were copied twice.
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rFormats.size());
SwPosition aDrawAnchor1 = *rFormats[0]->GetAnchor().GetContentAnchor();
SwPosition aFlyAnchor1 = *rFormats[1]->GetAnchor().GetContentAnchor();
CPPUNIT_ASSERT_EQUAL(aFlyAnchor1.GetNodeIndex(), aDrawAnchor1.GetNodeIndex());
SwPosition aDrawAnchor2 = *rFormats[2]->GetAnchor().GetContentAnchor();
SwPosition aFlyAnchor2 = *rFormats[3]->GetAnchor().GetContentAnchor(); // This also failed, aFlyAnchor2 was wrong, as it got out of sync with aDrawAnchor2.
CPPUNIT_ASSERT_EQUAL(aFlyAnchor2.GetNodeIndex(), aDrawAnchor2.GetNodeIndex());
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTextBoxNodeSplit)
{
createSwDoc("textbox-node-split.docx");
SwDocShell* pShell = getSwDocShell();
SwWrtShell* pWrtShell = pShell->GetWrtShell();
pWrtShell->SttEndDoc(/*bStart=*/false); // Without the accompanying fix in place, this would have crashed in // SwFlyAtContentFrame::SwClientNotify().
pWrtShell->SplitNode();
}
namespace
{ struct ViewCallback
{ int m_nInvalidations = 0;
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTitleFieldInvalidate)
{ // Set up LOK to track invalidations.
comphelper::LibreOfficeKit::setActive(true);
// Given a document with a title field:
createSwDoc("title-field-invalidate.fodt");
getSwTextDoc()->initializeForTiledRendering({});
SwDocShell* pShell = getSwDocShell();
SwDoc* pDoc = pShell->GetDoc();
SwWrtShell* pWrtShell = pShell->GetWrtShell();
pWrtShell->SttEndDoc(/*bStt=*/false);
ViewCallback aCallback;
TestLokCallbackWrapper aCallbackWrapper(&ViewCallback::callback, &aCallback);
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&aCallbackWrapper);
aCallbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell()));
Scheduler::ProcessEventsToIdle();
aCallback.m_nInvalidations = 0;
// When typing to the document:
pWrtShell->Insert(u"x"_ustr);
pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles();
// Then make sure that only the text frame at the cursor is invalidated:
pDoc->getIDocumentStatistics().GetUpdatedDocStat(/*bCompleteAsync=*/true, /*bFields=*/false); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1 // - Actual : 2 // i.e. the footer was also invalidated on each keypress.
CPPUNIT_ASSERT_EQUAL(1, aCallback.m_nInvalidations);
// Tear down LOK.
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(nullptr);
mxComponent->dispose();
mxComponent.clear();
comphelper::LibreOfficeKit::setActive(false);
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testFlyAnchorUndo)
{ // Given a document with a fly frame, anchored after the last char of the document:
createSwDoc("fly-anchor-undo.odt");
SwDocShell* pShell = getSwDocShell();
SwDoc* pDoc = pShell->GetDoc(); constauto& rSpz = *pDoc->GetSpzFrameFormats();
sal_Int32 nExpected = rSpz[0]->GetAnchor().GetAnchorContentOffset();
// When deleting that last character and undoing it:
SwWrtShell* pWrtShell = pShell->GetWrtShell();
pWrtShell->SttEndDoc(/*bStt=*/false);
pWrtShell->DelLeft();
pWrtShell->Undo();
// Then make sure the anchor position after the undo is the same as the original:
sal_Int32 nActual = rSpz[0]->GetAnchor().GetAnchorContentOffset(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 3 // - Actual : 2 // i.e. the anchor position was left unchanged by the undo.
CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSplitNodeSuperscriptCopy)
{ // Given a document with superscript text at the end of a paragraph:
createSwDoc();
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->Insert(u"1st"_ustr);
pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2, /*bBasicCall=*/false);
SfxItemSet aSet(SfxItemSet::makeFixedSfxItemSet<RES_CHRATR_ESCAPEMENT, RES_CHRATR_ESCAPEMENT>(
pWrtShell->GetAttrPool()));
SvxEscapementItem aItem(SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT);
aSet.Put(aItem);
pWrtShell->SetAttrSet(aSet);
// When hitting enter at the end of the paragraph:
pWrtShell->SttEndDoc(/*bStt=*/false);
pWrtShell->SplitNode(/*bAutoFormat=*/true);
// Then make sure that the superscript formatting doesn't appear on the next paragraph:
aSet.ClearItem(RES_CHRATR_ESCAPEMENT);
pWrtShell->GetCurAttr(aSet); // Without the accompanying fix in place, this test would have failed, the unexpected // superscript appeared in the next paragraph.
CPPUNIT_ASSERT(!aSet.HasItem(RES_CHRATR_ESCAPEMENT));
}
/* FIXME: behavior change reverted due to regression; * see sw/source/core/txtnode/atrref.cxx *CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDontExpandRefmark) *{ * // Given a document with a refmark: * createSwDoc(); * * uno::Sequence<css::beans::PropertyValue> aArgs = { * comphelper::makePropertyValue("TypeName", uno::Any(OUString("SetRef"))), * comphelper::makePropertyValue( * "Name", uno::Any(OUString("ZOTERO_ITEM CSL_CITATION {} RNDpyJknp173F"))), * comphelper::makePropertyValue("Content", uno::Any(OUString("foo"))), * }; * dispatchCommand(mxComponent, ".uno:InsertField", aArgs); * * SwDoc* pDoc = getSwDoc(); * SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); * SwPosition& rCursor = *pWrtShell->GetCursor()->GetPoint(); * SwTextNode* pTextNode = rCursor.GetNode().GetTextNode(); * std::vector<SwTextAttr*> aAttrs * = pTextNode->GetTextAttrsAt(rCursor.GetContentIndex(), RES_TXTATR_REFMARK); * * auto& rRefmark = const_cast<SwFormatRefMark&>(aAttrs[0]->GetRefMark()); * auto pTextRefMark = const_cast<SwTextRefMark*>(rRefmark.GetTextRefMark()); * * // When typing after the refmark... * pWrtShell->SttEndDoc(true); * pWrtShell->Right(SwCursorSkipMode::Chars, false, 3, false); * pWrtShell->Insert(" bar"); * * // and skipping back to insert a comma after the refmark * pWrtShell->Left(SwCursorSkipMode::Chars, false, 4, false); * pWrtShell->Insert(","); * * // Without the accompanying fix in place, this test would have failed with: * // - Expected: 3 * // - Actual : 4 * // i.e. the reference mark expanded * CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(*pTextRefMark->End())); *}
*/
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testInsertDropDownContentControlTwice)
{ // Given an already selected dropdown content control:
createSwDoc();
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST);
// When trying to insert an inner one, make sure that we don't crash:
pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST);
}
// When pressing space on the keyboard:
KeyEvent aKeyEvent(' ', KEY_SPACE);
rEditWin.KeyInput(aKeyEvent);
// Then make sure the state is toggled:
SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode();
SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL); auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); auto& rFormatContentControl
= static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); // Without the accompanying fix in place, this test would have failed, because the state // remained unchanged.
CPPUNIT_ASSERT(pContentControl->GetChecked());
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDropdownContentControlKeyboard)
{ // Given an already selected dropdown content control:
createSwDoc();
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST);
// When checking if alt-down should open a popup:
SwTextContentControl* pTextContentControl = pWrtShell->CursorInsideContentControl(); auto& rFormatContentControl
= static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl();
vcl::KeyCode aKeyCode(KEY_DOWN, KEY_MOD2); bool bShouldOpen = pContentControl->ShouldOpenPopup(aKeyCode);
// Then make sure that the answer is yes for dropdowns: // Without the accompanying fix in place, this test would have failed, the dropdown popup was // mouse-only.
CPPUNIT_ASSERT(bShouldOpen);
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testPictureContentControlKeyboard)
{ // Given an already selected picture content control:
createSwDoc();
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->InsertContentControl(SwContentControlType::PICTURE);
pWrtShell->GotoObj(/*bNext=*/true, GotoObjFlags::Any);
// When checking if enter should trigger the file picker: const SwFrameFormat* pFlyFormat = pWrtShell->GetFlyFrameFormat(); const SwFormatAnchor& rFormatAnchor = pFlyFormat->GetAnchor();
SwNode* pAnchorNode = rFormatAnchor.GetAnchorNode();
SwTextNode* pTextNode = pAnchorNode->GetTextNode();
SwTextAttr* pAttr
= pTextNode->GetTextAttrAt(rFormatAnchor.GetAnchorContentOffset(),
RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent); auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); auto& rFormatContentControl
= static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); bool bIsInteracting = pContentControl->IsInteractingCharacter('\r');
// Then make sure that the answer is yes for pictures: // Without the accompanying fix in place, this test would have failed, the picture replacement // file-picker was mouse-only.
CPPUNIT_ASSERT(bIsInteracting);
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDateContentControlKeyboard)
{ // Given an already selected date content control:
createSwDoc();
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->InsertContentControl(SwContentControlType::DATE);
// When checking if alt-down should open a popup:
SwTextContentControl* pTextContentControl = pWrtShell->CursorInsideContentControl(); auto& rFormatContentControl
= static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl();
vcl::KeyCode aKeyCode(KEY_DOWN, KEY_MOD2); bool bShouldOpen = pContentControl->ShouldOpenPopup(aKeyCode);
// Then make sure that the answer is yes for date: // Without the accompanying fix in place, this test would have failed, the date popup was // mouse-only.
CPPUNIT_ASSERT(bShouldOpen);
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testContentControlCopy)
{ // Given a document with a content control:
createSwDoc();
SwDoc* pDoc = getSwDoc();
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->InsertContentControl(SwContentControlType::CHECKBOX);
// When copying that content control:
pWrtShell->SelAll();
rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell);
xTransfer->Copy(); // Kill the selection, go to the end of the document:
pWrtShell->EndOfSection();
TransferableDataHelper aHelper(xTransfer);
SwTransferable::Paste(*pWrtShell, aHelper);
// Then make sure that the copy is also a checkbox:
SwContentControlManager& rManager = pDoc->GetContentControlManager();
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rManager.GetCount()); const SwFormatContentControl& rFormat1 = rManager.Get(0)->GetContentControl();
CPPUNIT_ASSERT_EQUAL(SwContentControlType::CHECKBOX, rFormat1.GetContentControl()->GetType()); const SwFormatContentControl& rFormat2 = rManager.Get(1)->GetContentControl(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1 (CHECKBOX) // - Actual : 0 (RICH_TEXT) // i.e. properties were not copied from the source to the destination content control.
CPPUNIT_ASSERT_EQUAL(SwContentControlType::CHECKBOX, rFormat2.GetContentControl()->GetType());
}
// Without the fix in place, this test would have failed with // - Expected: 120 // - Actual :
CPPUNIT_ASSERT_EQUAL(u"120"_ustr, xField->getPresentation(false));
}
// When inserting a footnote:
pWrtShell->InsertFootnote(OUString());
// Then make sure the footnote gets inserted to the doc model. // Without the accompanying fix in place, this test would have failed, insert code refused to // have footnotes in all fly frames.
CPPUNIT_ASSERT(!pDoc->GetFootnoteIdxs().empty());
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSplitFlyAnchorSplit)
{ // Given a document with a 2 pages long floating table:
createSwDoc("floattable-anchor-split.docx");
// When splitting the "AB" anchor text into "A" (remains as anchor text) and "B" (new text node // after it):
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->SttEndDoc(/*bStt=*/false);
pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); // Without the accompanying fix in place, this test would have failed with a layout loop.
pWrtShell->SplitNode();
// Then make sure the resulting layout is what we want:
SwDoc* pDoc = getSwDoc();
SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame();
CPPUNIT_ASSERT(pPage1); // Page 1 has the master fly:
CPPUNIT_ASSERT(pPage1->GetSortedObjs()); auto pPage2 = pPage1->GetNext()->DynCastPageFrame();
CPPUNIT_ASSERT(pPage2); // Page 2 has the follow fly:
CPPUNIT_ASSERT(pPage2->GetSortedObjs()); // Anchor text is now just "A": auto pText1 = pPage2->FindFirstBodyContent()->DynCastTextFrame();
CPPUNIT_ASSERT_EQUAL(u"A"_ustr, pText1->GetText()); // New text frame is just "B": auto pText2 = pText1->GetNext()->DynCastTextFrame();
CPPUNIT_ASSERT_EQUAL(u"B"_ustr, pText2->GetText());
// Also test that the new follow anchor text frame still has a fly portion, otherwise the anchor // text and the floating table would overlap:
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
OUString aPortionType = getXPath(
pXmlDoc, "//page[2]/body/txt[1]/SwParaPortion/SwLineLayout[1]/child::*[1]", "type"); // Without the accompanying fix in place, this test would have failed with: // - Expected: PortionType::Fly // - Actual : PortionType::Para // i.e. the fly portion was missing, text overlapped.
CPPUNIT_ASSERT_EQUAL(u"PortionType::Fly"_ustr, aPortionType);
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testPlainContentControlCopy)
{ // Given a document with a plain text content control, all text selected and copied to the // clipboard:
createSwDoc("plain-content-control-copy.docx");
SwDocShell* pDocShell = getSwDocShell();
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
pWrtShell->SelAll();
{
rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell);
xTransfer->Copy();
}
// When closing that document, then make sure we don't crash on shutdown:
uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
uno::Reference<util::XCloseable> xFrame(xModel->getCurrentController()->getFrame(),
uno::UNO_QUERY); // Without the accompanying fix in place, this resulted in an assertion failure, a char style // still had clients by the time it was deleted.
xFrame->close(false);
mxComponent.clear();
}
SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
SwDocShell* pShell = pTextDoc->GetDocShell();
SwWrtShell* pWrtShell = pShell->GetWrtShell();
SwDoc aClipboard;
pWrtShell->SelAll();
pWrtShell->Copy(aClipboard);
pWrtShell->SttEndDoc(/*bStart=*/false); // Send the cursor to the end of the document.
pWrtShell->Paste(aClipboard);
// Now we have selected all text (which is one line) and pasted it to the end. // A comment and its reply should also be copied to the end of the document. // We will check if our reply is referencing its copied parent instead of the source parent.
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testNodeSplitStyleListLevel)
{ // Given a document with a 3rd paragraph where the list level as direct formatting differs from // the list level from style:
createSwDoc("node-split-style-list-level.odt");
SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
pWrtShell->Down(/*bSelect=*/false, /*nCount=*/2);
pWrtShell->EndPara();
// When pressing enter at the end of the paragraph:
pWrtShell->SplitNode();
SwTextNode* pNext = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 4 // - Actual : 3 // i.e. the list level for the new paragraph changed on a simple node split.
CPPUNIT_ASSERT_EQUAL(4, pNext->GetAttrListLevel());
pWrtShell->Up(/*bSelect=*/false, /*nCount=*/1);
SwTextNode* pPrevious = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); // Same happened for the old paragraph.
CPPUNIT_ASSERT_EQUAL(4, pPrevious->GetAttrListLevel());
}
CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDOCXCommentImport)
{ // Given a DOCX file with a comment in it: // When loading that file:
createSwDoc("comment.docx");
// Then make sure that the postit field has a name that matches the name of an annotation mark:
SwDoc* pDoc = getSwDoc(); const SwFieldTypes* pFieldTypes = pDoc->getIDocumentFieldsAccess().GetFieldTypes(); const SwFieldType* pPostitFieldType = nullptr; for (constauto& pFieldType : *pFieldTypes)
{ if (pFieldType->Which() == SwFieldIds::Postit)
{
pPostitFieldType = pFieldType.get(); break;
}
}
CPPUNIT_ASSERT(pPostitFieldType);
std::vector<SwFormatField*> aFormatPostits;
pPostitFieldType->GatherFields(aFormatPostits);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aFormatPostits.size()); const SwFormatField* pFormatPostit = aFormatPostits[0]; auto pPostit = static_cast<const SwPostItField*>(pFormatPostit->GetField());
IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); auto it = pMarkAccess->findAnnotationMark(pPostit->GetName()); // Without the accompanying fix in place, this test would have failed, there were no annotation // marks with the name of pPostit.
CPPUNIT_ASSERT(it != pMarkAccess->getAnnotationMarksEnd());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.