/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
DBG_ASSERT( xVersion.is(), "The method must throw an exception if the storage can not be opened!" ); if ( !xVersion.is() ) throw uno::RuntimeException();
uno::Reference< io::XStream > xVerStream = xVersion->openStreamElement(
aStreamName,
embed::ElementModes::READWRITE );
DBG_ASSERT( xVerStream.is(), "The method must throw an exception if the storage can not be opened!" ); if ( !xVerStream.is() ) throw uno::RuntimeException();
// the password will be transferred from the xStorage to xTempStorage by storage implementation
xStorage->copyToStorage( xTempStorage );
// the temporary storage was committed by the previous method and it will die by refcount
} catch ( uno::Exception& )
{
SAL_WARN( "sfx.doc", "Creation of a storage copy is failed!" );
::utl::UCBContentHelper::Kill( aTempURL );
aTempURL.clear();
// TODO/LATER: may need error code setting based on exception
SetError(ERRCODE_IO_GENERAL);
}
}
if ( nClipFormat == SotClipboardFormatId::NONE ) return;
// basic doesn't have a ClipFormat // without MediaType the storage is not really usable, but currently the BasicIDE still // is an SfxObjectShell and so we can't take this as an error
datatransfer::DataFlavor aDataFlavor;
SotExchange::GetFormatDataFlavor( nClipFormat, aDataFlavor ); if ( aDataFlavor.MimeType.isEmpty() ) return;
// the default values, that should be used for ODF1.1 and older formats
uno::Sequence< beans::NamedValue > aEncryptionAlgs
{
{ u"StartKeyGenerationAlgorithm"_ustr, css::uno::Any(xml::crypto::DigestID::SHA1) },
{ u"EncryptionAlgorithm"_ustr, css::uno::Any(xml::crypto::CipherID::BLOWFISH_CFB_8) },
{ u"ChecksumAlgorithm"_ustr, css::uno::Any(xml::crypto::DigestID::SHA1_1K) },
{ u"KeyDerivationFunction"_ustr, css::uno::Any(xml::crypto::KDFID::PBKDF2) },
};
if (nDefVersion >= SvtSaveOptions::ODFSVER_012)
{ try
{ // older versions can not have this property set, it exists only starting from ODF1.2
xProps->setPropertyValue(u"Version"_ustr, getODFVersionAny(nDefVersion));
} catch( uno::Exception& )
{
}
try
{ // set the encryption algorithms accordingly; // the setting does not trigger encryption, // it just provides the format for the case that contents should be encrypted
uno::Reference< embed::XEncryptionProtectedStorage > xEncr( xStorage, uno::UNO_QUERY_THROW );
xEncr->setEncryptionAlgorithms( aEncryptionAlgs );
} catch( uno::Exception& )
{ const_cast<SfxObjectShell*>( this )->SetError(ERRCODE_IO_GENERAL);
}
}
void SfxObjectShell::PrepareSecondTryLoad_Impl()
{ // only for internal use
pImpl->m_xDocStorage.clear();
pImpl->mxObjectContainer.reset();
pImpl->nDocumentSignatureState = SignatureState::UNKNOWN;
pImpl->nScriptingSignatureState = SignatureState::UNKNOWN;
pImpl->m_bIsInit = false;
ResetError();
}
void SfxObjectShell::DoInitUnitTest()
{
pMedium = new SfxMedium;
}
bool SfxObjectShell::DoInitNew() /* [Description]
This from SvPersist inherited virtual method is called to initialize the SfxObjectShell instance from a storage (PStore! = 0) or (PStore == 0)
Like with all Do...-methods there is a from a control, the actual implementation is done by the virtual method in which also the InitNew(SvStorate *) from the SfxObjectShell-Subclass is implemented.
For pStore == 0 the SfxObjectShell-instance is connected to an empty SfxMedium, otherwise a SfxMedium, which refers to the SotStorage passed as a parameter.
The object is only initialized correctly after InitNew() or Load().
[Return value] true The object has been initialized. false The object could not be initialized
*/
{
ModifyBlocker_Impl aBlock( this );
pMedium = new SfxMedium;
pMedium->CanDisposeStorage_Impl( true );
if ( InitNew( nullptr ) )
{ // empty documents always get their macros from the user, so there is no reason to restrict access
pImpl->aMacroMode.allowMacroExecution(); if ( SfxObjectCreateMode::EMBEDDED == eCreateMode )
SetTitle(SfxResId(STR_NONAME));
// now the medium can be disconnected from the storage // the medium is not allowed to dispose the storage so CloseStorage() can be used
pMedium->CloseStorage();
} catch( uno::Exception& )
{
}
return bResult;
}
bool SfxObjectShell::DoLoad( SfxMedium *pMed )
{
ModifyBlocker_Impl aBlock( this );
// initialize static language table so language-related extensions are learned before the document loads
(void)SvtLanguageTable::GetLanguageEntryCount();
//TODO/LATER: make a clear strategy how to handle "UsesStorage" etc. bool bOwnStorageFormat = IsOwnStorageFormat( *pMedium ); bool bHasStorage = IsPackageStorageFormat_Impl( *pMedium ); if ( pMedium->GetFilter() )
{
ErrCode nError = HandleFilter( pMedium, this ); if ( nError != ERRCODE_NONE )
SetError(nError);
if (pMedium->GetFilter()->GetFilterFlags() & SfxFilterFlags::STARTPRESENTATION)
rSet.Put(SfxUInt16Item(SID_DOC_STARTPRESENTATION, 1));
}
EnableSetModified( false );
// tdf#53614 - don't try to lock file after cancelling the import process if (GetErrorIgnoreWarning() != ERRCODE_ABORT)
pMedium->LockOrigFileOnDemand( true, false ); if ( GetErrorIgnoreWarning() == ERRCODE_NONE && bOwnStorageFormat && ( !pFilter || !( pFilter->GetFilterFlags() & SfxFilterFlags::STARONEFILTER ) ) )
{
uno::Reference< embed::XStorage > xStorage; if ( pMedium->GetErrorIgnoreWarning() == ERRCODE_NONE )
xStorage = pMedium->GetStorage();
if( xStorage.is() && pMedium->GetLastStorageCreationState() == ERRCODE_NONE )
{
DBG_ASSERT( pFilter, "No filter for storage found!" );
try
{ bool bWarnMediaTypeFallback = false;
// treat the package as broken if the mediatype was retrieved as a fallback
uno::Reference< beans::XPropertySet > xStorProps( xStorage, uno::UNO_QUERY_THROW );
xStorProps->getPropertyValue(u"MediaTypeFallbackUsed"_ustr)
>>= bWarnMediaTypeFallback;
if (pMedium->IsRepairPackage())
{ // the macros in repaired documents should be disabled
pMedium->GetItemSet().Put( SfxUInt16Item( SID_MACROEXECMODE, document::MacroExecMode::NEVER_EXECUTE ) );
// the mediatype was retrieved by using fallback solution but this is a repairing mode // so it is acceptable to open the document if there is no contents that required manifest.xml
bWarnMediaTypeFallback = false;
}
if (bWarnMediaTypeFallback || !xStorage->getElementNames().hasElements())
SetError(ERRCODE_IO_BROKENPACKAGE);
} catch( uno::Exception& )
{ // TODO/LATER: may need error code setting based on exception
SetError(ERRCODE_IO_GENERAL);
}
// Load if ( !GetErrorIgnoreWarning() )
{
pImpl->nLoadedFlags = SfxLoadedFlags::NONE;
pImpl->bModelInitialized = false;
bOk = xStorage.is() && LoadOwnFormat( *pMed ); if ( bOk )
{ // the document loaded from template has no name const SfxBoolItem* pTemplateItem = rSet.GetItem(SID_TEMPLATE, false); if ( !pTemplateItem || !pTemplateItem->GetValue() )
bHasName = true;
} else
SetError(ERRCODE_ABORT);
}
} else
SetError(pMed->GetLastStorageCreationState());
} elseif ( GetErrorIgnoreWarning() == ERRCODE_NONE && InitNew(nullptr) )
{ // set name before ConvertFrom, so that GetSbxObject() already works
bHasName = true;
SetName( SfxResId(STR_NONAME) );
if ( GetErrorIgnoreWarning() == ERRCODE_NONE )
{ // Experimental PDF importing using PDFium. This is currently enabled for LOK only and // we handle it not via XmlFilterAdaptor but a new SdPdfFilter. #if !HAVE_FEATURE_POPPLER
constexpr bool bUsePdfium = true; #else constbool bUsePdfium
= comphelper::LibreOfficeKit::isActive() || getenv("LO_IMPORT_USE_PDFIUM"); #endif constbool bPdfiumImport
= bUsePdfium && pMedium->GetFilter()
&& (pMedium->GetFilter()->GetFilterName() == "draw_pdf_import");
bool bReconnectDde = true; // by default, we try to auto-connect DDE connections. if (pDdeReconnectItem)
bReconnectDde = pDdeReconnectItem->GetValue();
// Count the occurrences of each character within the line. // Skip strings. const sal_Unicode *pEnd = sLine.getStr() + sLine.getLength(); for (const sal_Unicode *p = sLine.getStr(); p < pEnd; p++)
{ if (*p == cStringDelimiter)
{
bIsDelimiter = !bIsDelimiter; continue;
} if (bIsDelimiter) continue;
// If restricted only to common separators then skip the rest if (usetCommonSeps.find(*p) == usetCommonSeps.end()) continue;
auto it_elem = aCharsCount.find(*p); if (it_elem == aCharsCount.cend())
aCharsCount.insert(std::pair<sal_uInt32, sal_uInt32>(*p, 1)); else
it_elem->second ++;
}
if (bIsDelimiter) continue;
nLinesCount ++;
// For each character count the lines that contain it and different number of occurrences. // And the global maximum for the first statistic. for (auto aCurLineChar=aCharsCount.cbegin(); aCurLineChar != aCharsCount.cend(); aCurLineChar++)
{ auto aCurStats = aStats.find(aCurLineChar->first); if (aCurStats == aStats.cend())
aCurStats = aStats.insert(std::pair<sal_Unicode, std::pair<sal_uInt32, sal_uInt32>>(aCurLineChar->first, std::pair<sal_uInt32, sal_uInt32>(1, 1))).first; else
{
aCurStats->second.first ++;// Increment number of lines that contain the current character
std::vector<std::unordered_map<sal_Unicode, sal_uInt32>>::const_iterator aPrevLineChar; for (aPrevLineChar=aLinesCharsCount.cbegin(); aPrevLineChar != aLinesCharsCount.cend(); aPrevLineChar++)
{ auto aPrevStats = aPrevLineChar->find(aCurLineChar->first); if (aPrevStats != aPrevLineChar->cend() && aPrevStats->second == aCurLineChar->second) break;
} if (aPrevLineChar == aLinesCharsCount.cend())
aCurStats->second.second ++;// Increment number of different number of occurrences.
}
// Update the maximum of number of lines that contain the same character. This is a global value. if (nMaxLinesSameChar < aCurStats->second.first)
nMaxLinesSameChar = aCurStats->second.first;
}
SAL_INFO("sfx.doc", "" << nLinesCount << " lines processed in " << tools::Time::GetSystemTicks() - nStartTime << " ms while detecting separator.");
// Compute the global minimum of different number of occurrences. // But only for characters which occur in a maximum number of lines (previously computed). for (auto it=aStats.cbegin(); it != aStats.cend(); it++) if (it->second.first == nMaxLinesSameChar && nMinDiffs > it->second.second)
nMinDiffs = it->second.second;
// Compute the initial list of separators: those with the maximum lines of occurrence and // the minimum of different number of occurrences. for (auto it=aStats.cbegin(); it != aStats.cend(); it++) if (it->second.first == nMaxLinesSameChar && it->second.second == nMinDiffs)
sInitSeps += OUStringChar(it->first);
// If forced to most common or there are multiple separators then pick up only the most common by importance.
sal_Int32 nInitSepIdx;
sal_Int32 nComSepIdx; for (nComSepIdx = 0; nComSepIdx < sCommonSeps.getLength(); nComSepIdx++)
{
sal_Unicode c = sCommonSeps[nComSepIdx]; for (nInitSepIdx = sInitSeps.getLength() - 1; nInitSepIdx >= 0; nInitSepIdx --)
{ if (c == sInitSeps[nInitSepIdx])
{
separators += OUStringChar(c); break;
}
}
}
stream.Seek(nInitPos);
}
void SfxObjectShell::DetectCsvFilterOptions(SvStream& stream, OUString& aFilterOptions)
{
rtl_TextEncoding eCharSet = RTL_TEXTENCODING_DONTKNOW;
std::u16string_view aSeps;
std::u16string_view aDelimiter;
std::u16string_view aCharSet;
std::u16string_view aRest;
OUString aOrigFilterOpts = aFilterOptions; bool bDelimiter = false, bCharSet = false, bRest = false; // This indicates the presence of the token even if empty ;)
// Process earlier as the input could contain express detection instructions. // This is relevant for "automatic" use case. For interactive use case the // FilterOptions should not be detected here (the detection is done before entering // interactive state). For now this is focused on CSV files.
DetectFilterOptions(pMedium);
bool SfxObjectShell::DoSave() // DoSave is only invoked for OLE. Save your own documents in the SFX through // DoSave_Impl order to allow for the creation of backups. // Save in your own format again.
{ bool bOk = false ;
{
ModifyBlocker_Impl aBlock( this );
pImpl->bIsSaving = true;
if (IsOwnStorageFormat(*GetMedium()))
{
SvtSaveOptions::ODFSaneDefaultVersion nDefVersion = SvtSaveOptions::ODFSVER_013; if (!comphelper::IsFuzzing())
{
nDefVersion = GetODFSaneDefaultVersion();
}
uno::Reference<beans::XPropertySet> const xProps(GetMedium()->GetStorage(), uno::UNO_QUERY);
assert(xProps.is()); if (nDefVersion >= SvtSaveOptions::ODFSVER_012) // property exists only since ODF 1.2
{ try// tdf#134582 set Version on embedded objects as they
{ // could have been loaded with a different/old version
xProps->setPropertyValue(u"Version"_ustr, getODFVersionAny(nDefVersion));
} catch (uno::Exception&)
{
TOOLS_WARN_EXCEPTION("sfx.doc", "SfxObjectShell::DoSave");
}
}
}
if ( IsPackageStorageFormat_Impl( *GetMedium() ) )
{
GetMedium()->GetStorage(); // sets encryption properties if necessary if (GetMedium()->GetErrorCode())
{
SetError(ERRCODE_IO_GENERAL);
} else
{
bOk = true;
} #if HAVE_FEATURE_SCRIPTING if ( HasBasic() )
{ try
{ // The basic and dialogs related contents are still not able to proceed with save operation ( saveTo only ) // so since the document storage is locked a workaround has to be used
uno::Reference< embed::XStorage > xTmpStorage = ::comphelper::OStorageHelper::GetTemporaryStorage();
DBG_ASSERT( xTmpStorage.is(), "If a storage can not be created an exception must be thrown!\n" ); if ( !xTmpStorage.is() ) throw uno::RuntimeException();
// disconnect from the current storage
pImpl->aBasicManager.setStorage( xTmpStorage );
// store to the current storage
pImpl->aBasicManager.storeLibrariesToStorage( GetMedium()->GetStorage() );
// connect to the current storage back
pImpl->aBasicManager.setStorage( GetMedium()->GetStorage() );
} catch( uno::Exception& )
{
SetError(ERRCODE_IO_GENERAL);
bOk = false;
}
} #endif
}
bool SfxObjectShell::SaveTo_Impl
(
SfxMedium &rMedium, // Medium, in which it will be stored const SfxItemSet* pSet
)
/* [Description]
Writes the current contents to the medium rMedium. If the target medium is no storage, then saving to a temporary storage, or directly if the medium is transacted, if we ourselves have opened it, and if we are a server either the container a transacted storage provides or created a temporary storage by one self.
*/
std::shared_ptr<const SfxFilter> pFilter = rMedium.GetFilter(); if ( !pFilter )
{ // if no filter was set, use the default filter // this should be changed in the feature, it should be an error!
SAL_WARN( "sfx.doc","No filter set!");
pFilter = GetFactory().GetFilterContainer()->GetAnyFilter( SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT );
rMedium.SetFilter(pFilter);
}
// Examine target format to determine whether to query if any password // protected libraries exceed the size we can handler if ( bOwnTarget && !QuerySaveSizeExceededModules_Impl( rMedium.GetInteractionHandler() ) )
{
SetError(ERRCODE_IO_ABORT); returnfalse;
}
// the detection whether the script is changed should be done before saving bool bTryToPreserveScriptSignature = false; // no way to detect whether a filter is oasis format, have to wait for saving process bool bNoPreserveForOasis = false; if ( bOwnSource && bOwnTarget
&& ( pImpl->nScriptingSignatureState == SignatureState::OK
|| pImpl->nScriptingSignatureState == SignatureState::NOTVALIDATED
|| pImpl->nScriptingSignatureState == SignatureState::INVALID ) )
{ // the checking of the library modified state iterates over the libraries, should be done only when required // currently the check is commented out since it is broken, we have to check the signature every time we save // TODO/LATER: let isAnyContainerModified() work!
bTryToPreserveScriptSignature = true; // !pImpl->pBasicManager->isAnyContainerModified(); if ( bTryToPreserveScriptSignature )
{ // check that the storage format stays the same
// preserve only if the same filter has been used // for templates, strip the _template from the filter name for comparison const OUString aMediumFilter = lcl_strip_template(pMedium->GetFilter()->GetFilterName());
bTryToPreserveScriptSignature = pMedium->GetFilter() && pFilter && aMediumFilter == lcl_strip_template(pFilter->GetFilterName());
// signatures were specified in ODF 1.2 but were used since much longer. // LO will still correctly validate an old style signature on an ODF 1.2 // document, but technically this is not correct, so this prevents old // signatures to be copied over to a version 1.2 document
bNoPreserveForOasis = (
(0 <= aODFVersion.compareTo(ODFVER_012_TEXT) && nVersion < SvtSaveOptions::ODFSVER_012) ||
(aODFVersion.isEmpty() && nVersion >= SvtSaveOptions::ODFSVER_012)
);
}
}
uno::Reference<io::XStream> xODFDecryptedInnerPackageStream;
uno::Reference<embed::XStorage> xODFDecryptedInnerPackage;
uno::Sequence<beans::NamedValue> aEncryptionData; if (GetEncryptionData_Impl(&rMedium.GetItemSet(), aEncryptionData))
{
assert(aEncryptionData.getLength() != 0); if (bOwnTarget && ::sfx2::UseODFWholesomeEncryption(nVersion))
{ // when embedded objects are stored here, it should be called from // this function for the root document and encryption data was cleared
assert(GetCreateMode() != SfxObjectCreateMode::EMBEDDED); // clear now to store inner package (+ embedded objects) unencrypted
rMedium.GetItemSet().ClearItem(SID_ENCRYPTIONDATA);
rMedium.GetItemSet().ClearItem(SID_PASSWORD);
xODFDecryptedInnerPackageStream.set(
xContext->getServiceManager()->createInstanceWithContext(
u"com.sun.star.comp.MemoryStream"_ustr, xContext),
UNO_QUERY_THROW);
xODFDecryptedInnerPackage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
PACKAGE_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream,
css::embed::ElementModes::WRITE, xContext, false);
assert(xODFDecryptedInnerPackage.is());
}
}
bool isStreamAndInputStreamCleared(false); // use UCB for case sensitive/insensitive file name comparison if ( !pMedium->GetName().equalsIgnoreAsciiCase("private:stream")
&& !rMedium.GetName().equalsIgnoreAsciiCase("private:stream")
&& ::utl::UCBContentHelper::EqualURLs( pMedium->GetName(), rMedium.GetName() ) )
{ // Do not unlock the file during saving. // need to modify this for WebDAV if this method is called outside of // the process of saving a file
pMedium->DisableUnlockWebDAV();
bStoreToSameLocation = true;
if ( pMedium->DocNeedsFileDateCheck() )
{
rMedium.CheckFileDate( pMedium->GetInitFileDate( false ) ); if (rMedium.GetErrorCode() == ERRCODE_ABORT)
{ // if user cancels the save, exit early to avoid resetting SfxMedium values that // would cause an invalid subsequent filedate check returnfalse;
}
}
// before we overwrite the original file, we will make a backup if there is a demand for that // if the backup is not created here it will be created internally and will be removed in case of successful saving constbool bDoBackup = officecfg::Office::Common::Save::Document::CreateBackup::get() && !comphelper::LibreOfficeKit::isActive(); if ( bDoBackup )
{
rMedium.DoBackup_Impl(/*bForceUsingBackupPath=*/false); if ( rMedium.GetErrorIgnoreWarning() )
{
SetError(rMedium.GetErrorCode());
rMedium.ResetError();
}
}
if ( bStorageBasedSource && bStorageBasedTarget )
{ // The active storage must be switched. The simple saving is not enough. // The problem is that the target medium contains target MediaDescriptor.
// In future the switch of the persistence could be done on stream level: // a new wrapper service will be implemented that allows to exchange // persistence on the fly. So the real persistence will be set // to that stream only after successful commit of the storage. // TODO/LATER: // create wrapper stream based on the URL // create a new storage based on this stream // store to this new storage // commit the new storage // call saveCompleted based with this new storage ( get rid of old storage and "frees" URL ) // commit the wrapper stream ( the stream will connect the URL only on commit, after that it will hold it ) // if the last step is failed the stream should stay to be transacted and should be committed on any flush // so we can forget the stream in any way and the next storage commit will flush it
// TODO/LATER: for now the medium must be closed since it can already contain streams from old medium // in future those streams should not be copied in case a valid target url is provided, // if the url is not provided ( means the document is based on a stream ) this code is not // reachable.
rMedium.CloseAndRelease();
rMedium.SetHasEmbeddedObjects(GetEmbeddedObjectContainer().HasEmbeddedObjects()); if (xODFDecryptedInnerPackageStream.is())
{
assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
} else
{
rMedium.GetOutputStorage();
}
rMedium.SetHasEmbeddedObjects(false);
}
} elseif ( !bStorageBasedSource && !bStorageBasedTarget )
{ // the source and the target formats are alien // just disconnect the stream from the source format // so that the target medium can use it
pMedium->CloseAndRelease();
rMedium.CloseAndRelease();
isStreamAndInputStreamCleared = true;
rMedium.CreateTempFileNoCopy();
rMedium.GetOutStream();
} elseif ( !bStorageBasedSource && bStorageBasedTarget )
{ // the source format is an alien one but the target // format is an own one so just disconnect the source // medium
pMedium->CloseAndRelease();
rMedium.CloseAndRelease();
isStreamAndInputStreamCleared = true; if (xODFDecryptedInnerPackageStream.is())
{
assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
} else
{
rMedium.GetOutputStorage();
}
} else// means if ( bStorageBasedSource && !bStorageBasedTarget )
{ // the source format is an own one but the target is // an alien format, just connect the source to temporary // storage
bNeedsDisconnectionOnFail = DisconnectStorage_Impl(
*pMedium, rMedium ); if ( bNeedsDisconnectionOnFail
|| ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) )
{
pMedium->CloseAndRelease();
rMedium.CloseAndRelease();
isStreamAndInputStreamCleared = true;
rMedium.CreateTempFileNoCopy();
rMedium.GetOutStream();
}
}
pMedium->DisableUnlockWebDAV(false);
} else
{ // This is SaveAs or export action, prepare the target medium // the alien filters still might write directly to the file, that is of course a bug,
--> --------------------
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.