LCOV - code coverage report
Current view: top level - usr/local/src/libreoffice/starmath/source - smdetect.cxx (source / functions) Hit Total Coverage
Test: libreoffice_filtered.info Lines: 94 217 43.3 %
Date: 2013-07-09 Functions: 9 12 75.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             :  * This file incorporates work covered by the following license notice:
      10             :  *
      11             :  *   Licensed to the Apache Software Foundation (ASF) under one or more
      12             :  *   contributor license agreements. See the NOTICE file distributed
      13             :  *   with this work for additional information regarding copyright
      14             :  *   ownership. The ASF licenses this file to you under the Apache
      15             :  *   License, Version 2.0 (the "License"); you may not use this file
      16             :  *   except in compliance with the License. You may obtain a copy of
      17             :  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
      18             :  */
      19             : 
      20             : 
      21             : #include "smdetect.hxx"
      22             : #include <com/sun/star/lang/XMultiServiceFactory.hpp>
      23             : #include <com/sun/star/beans/PropertyValue.hpp>
      24             : #include <com/sun/star/frame/XFrame.hpp>
      25             : #include <com/sun/star/frame/XModel.hpp>
      26             : #include <com/sun/star/awt/XWindow.hpp>
      27             : #include <com/sun/star/lang/XUnoTunnel.hpp>
      28             : #include <comphelper/processfactory.hxx>
      29             : #include <com/sun/star/io/XInputStream.hpp>
      30             : #include <com/sun/star/task/XInteractionHandler.hpp>
      31             : #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
      32             : #include <com/sun/star/ucb/CommandAbortedException.hpp>
      33             : #include <com/sun/star/ucb/InteractiveAppException.hpp>
      34             : #include <com/sun/star/ucb/XContent.hpp>
      35             : #include <com/sun/star/packages/zip/ZipIOException.hpp>
      36             : #include <toolkit/helper/vclunohelper.hxx>
      37             : #include <ucbhelper/simpleinteractionrequest.hxx>
      38             : #include <rtl/ustring.h>
      39             : #include <rtl/logfile.hxx>
      40             : #include <svl/itemset.hxx>
      41             : #include <vcl/window.hxx>
      42             : #include <svl/eitem.hxx>
      43             : #include <svl/stritem.hxx>
      44             : #include <tools/urlobj.hxx>
      45             : #include <osl/mutex.hxx>
      46             : #include <svtools/sfxecode.hxx>
      47             : #include <svtools/ehdl.hxx>
      48             : #include <sot/storinfo.hxx>
      49             : #include <vcl/svapp.hxx>
      50             : #include <sfx2/app.hxx>
      51             : #include <sfx2/sfxsids.hrc>
      52             : #include <sfx2/request.hxx>
      53             : #include <sfx2/docfile.hxx>
      54             : #include <sfx2/docfilt.hxx>
      55             : #include <sfx2/fcontnr.hxx>
      56             : #include <sfx2/brokenpackageint.hxx>
      57             : 
      58             : #include "document.hxx"
      59             : #include "eqnolefilehdr.hxx"
      60             : 
      61             : using namespace ::com::sun::star;
      62             : using namespace ::com::sun::star::uno;
      63             : using namespace ::com::sun::star::io;
      64             : using namespace ::com::sun::star::frame;
      65             : using namespace ::com::sun::star::task;
      66             : using namespace ::com::sun::star::beans;
      67             : using namespace ::com::sun::star::lang;
      68             : using namespace ::com::sun::star::ucb;
      69             : 
      70           1 : SmFilterDetect::SmFilterDetect( const Reference < XMultiServiceFactory >& /*xFactory*/ )
      71             : {
      72           1 : }
      73             : 
      74           2 : SmFilterDetect::~SmFilterDetect()
      75             : {
      76           2 : }
      77             : 
      78           1 : OUString SAL_CALL SmFilterDetect::detect( Sequence< PropertyValue >& lDescriptor ) throw( RuntimeException )
      79             : {
      80           1 :     Reference< XInputStream > xStream;
      81           2 :     Reference< XContent > xContent;
      82           2 :     Reference< XInteractionHandler > xInteraction;
      83           2 :     String aURL;
      84           2 :     OUString sTemp;
      85           2 :     String aTypeName;            // a name describing the type (from MediaDescriptor, usually from flat detection)
      86           2 :     String aPreselectedFilterName;      // a name describing the filter to use (from MediaDescriptor, usually from UI action)
      87             : 
      88           2 :     OUString aDocumentTitle; // interesting only if set in this method
      89             : 
      90             :     // opening as template is done when a parameter tells to do so and a template filter can be detected
      91             :     // (otherwise no valid filter would be found) or if the detected filter is a template filter and
      92             :     // there is no parameter that forbids to open as template
      93           1 :     sal_Bool bOpenAsTemplate = sal_False;
      94           1 :     sal_Bool bWasReadOnly = sal_False, bReadOnly = sal_False;
      95             : 
      96           1 :     sal_Bool bRepairPackage = sal_False;
      97           1 :     sal_Bool bRepairAllowed = sal_False;
      98           1 :     bool bDeepDetection = false;
      99             : 
     100             :     // now some parameters that can already be in the array, but may be overwritten or new inserted here
     101             :     // remember their indices in the case new values must be added to the array
     102           1 :     sal_Int32 nPropertyCount = lDescriptor.getLength();
     103           1 :     sal_Int32 nIndexOfInputStream = -1;
     104           1 :     sal_Int32 nIndexOfContent = -1;
     105           1 :     sal_Int32 nIndexOfReadOnlyFlag = -1;
     106           1 :     sal_Int32 nIndexOfTemplateFlag = -1;
     107           1 :     sal_Int32 nIndexOfDocumentTitle = -1;
     108             : 
     109           9 :     for( sal_Int32 nProperty=0; nProperty<nPropertyCount; ++nProperty )
     110             :     {
     111             :         // extract properties
     112           8 :         if( lDescriptor[nProperty].Name == "URL" )
     113             :         {
     114           1 :             lDescriptor[nProperty].Value >>= sTemp;
     115           1 :             aURL = sTemp;
     116             :         }
     117           7 :         else if( !aURL.Len() && lDescriptor[nProperty].Name == "FileName" )
     118             :         {
     119           0 :             lDescriptor[nProperty].Value >>= sTemp;
     120           0 :             aURL = sTemp;
     121             :         }
     122           7 :         else if( lDescriptor[nProperty].Name == "TypeName" )
     123             :         {
     124           1 :             lDescriptor[nProperty].Value >>= sTemp;
     125           1 :             aTypeName = sTemp;
     126             :         }
     127           6 :         else if( lDescriptor[nProperty].Name == "FilterName" )
     128             :         {
     129           0 :             lDescriptor[nProperty].Value >>= sTemp;
     130           0 :             aPreselectedFilterName = sTemp;
     131             :         }
     132           6 :         else if( lDescriptor[nProperty].Name == "InputStream" )
     133           1 :             nIndexOfInputStream = nProperty;
     134           5 :         else if( lDescriptor[nProperty].Name == "ReadOnly" )
     135           0 :             nIndexOfReadOnlyFlag = nProperty;
     136           5 :         else if( lDescriptor[nProperty].Name == "UCBContent" )
     137           1 :             nIndexOfContent = nProperty;
     138           4 :         else if( lDescriptor[nProperty].Name == "AsTemplate" )
     139             :         {
     140           0 :             lDescriptor[nProperty].Value >>= bOpenAsTemplate;
     141           0 :             nIndexOfTemplateFlag = nProperty;
     142             :         }
     143           4 :         else if( lDescriptor[nProperty].Name == "InteractionHandler" )
     144           1 :             lDescriptor[nProperty].Value >>= xInteraction;
     145           3 :         else if( lDescriptor[nProperty].Name == "RepairPackage" )
     146           0 :             lDescriptor[nProperty].Value >>= bRepairPackage;
     147           3 :         else if( lDescriptor[nProperty].Name == "DocumentTitle" )
     148           0 :             nIndexOfDocumentTitle = nProperty;
     149           3 :         else if (lDescriptor[nProperty].Name == "DeepDetection")
     150           0 :             bDeepDetection = lDescriptor[nProperty].Value.get<sal_Bool>();
     151             :     }
     152             : 
     153             :     // can't check the type for external filters, so set the "dont" flag accordingly
     154           2 :     SolarMutexGuard aGuard;
     155             : 
     156           1 :     SfxApplication* pApp = SFX_APP();
     157           1 :     SfxAllItemSet *pSet = new SfxAllItemSet( pApp->GetPool() );
     158           1 :     TransformParameters( SID_OPENDOC, lDescriptor, *pSet );
     159           1 :     SFX_ITEMSET_ARG( pSet, pItem, SfxBoolItem, SID_DOC_READONLY, sal_False );
     160             : 
     161           1 :     bWasReadOnly = pItem && pItem->GetValue();
     162             : 
     163           2 :     String aFilterName;
     164           2 :     String aPrefix = OUString( "private:factory/" );
     165           1 :     if( aURL.Match( aPrefix ) == aPrefix.Len() )
     166             :     {
     167           0 :         const SfxFilter* pFilter = 0;
     168           0 :         String aPattern( aPrefix );
     169           0 :         aPattern += OUString("smath");
     170           0 :         if ( aURL.Match( aPattern ) >= aPattern.Len() )
     171             :         {
     172           0 :             pFilter = SfxFilter::GetDefaultFilterFromFactory( aURL );
     173           0 :             aTypeName = pFilter->GetTypeName();
     174           0 :             aFilterName = pFilter->GetName();
     175           0 :         }
     176             :     }
     177             :     else
     178             :     {
     179             :         // ctor of SfxMedium uses owner transition of ItemSet
     180           1 :         SfxMedium aMedium( aURL, bWasReadOnly ? STREAM_STD_READ : STREAM_STD_READWRITE, NULL, pSet );
     181           1 :         aMedium.UseInteractionHandler( true );
     182             : 
     183           1 :         bool bIsStorage = aMedium.IsStorage();
     184           1 :         if ( aMedium.GetErrorCode() == ERRCODE_NONE )
     185             :         {
     186             :             // remember input stream and content and put them into the descriptor later
     187             :             // should be done here since later the medium can switch to a version
     188           1 :             xStream = aMedium.GetInputStream();
     189           1 :             xContent = aMedium.GetContent();
     190           1 :             bReadOnly = aMedium.IsReadOnly();
     191             : 
     192           1 :             if ( bIsStorage )
     193             :             {
     194             :                 //TODO/LATER: factor this out!
     195           1 :                 Reference < embed::XStorage > xStorage = aMedium.GetStorage( sal_False );
     196           1 :                 if ( aMedium.GetLastStorageCreationState() != ERRCODE_NONE )
     197             :                 {
     198             :                     // error during storage creation means _here_ that the medium
     199             :                     // is broken, but we can not handle it in medium since unpossibility
     200             :                     // to create a storage does not _always_ means that the medium is broken
     201           0 :                     aMedium.SetError( aMedium.GetLastStorageCreationState(), OSL_LOG_PREFIX );
     202           0 :                     if ( xInteraction.is() )
     203             :                     {
     204           0 :                         OUString empty;
     205             :                         try
     206             :                         {
     207             :                             InteractiveAppException xException( empty,
     208             :                                                             Reference< XInterface >(),
     209             :                                                             InteractionClassification_ERROR,
     210           0 :                                                             aMedium.GetError() );
     211             : 
     212             :                             Reference< XInteractionRequest > xRequest(
     213             :                                 new ucbhelper::SimpleInteractionRequest( makeAny( xException ),
     214           0 :                                                                       ucbhelper::CONTINUATION_APPROVE ) );
     215           0 :                             xInteraction->handle( xRequest );
     216             :                         }
     217           0 :                         catch ( Exception & ) {};
     218             :                     }
     219             :                 }
     220             :                 else
     221             :                 {
     222           1 :                     aFilterName.Erase();
     223             : 
     224             :                     try
     225             :                     {
     226           1 :                         const SfxFilter* pFilter = aPreselectedFilterName.Len() ?
     227           2 :                                 SfxFilterMatcher().GetFilter4FilterName( aPreselectedFilterName ) : aTypeName.Len() ?
     228           3 :                                 SfxFilterMatcher(OUString("smath")).GetFilter4EA( aTypeName ) : 0;
     229           1 :                         OUString aTmpFilterName;
     230           1 :                         if ( pFilter )
     231           1 :                             aTmpFilterName = pFilter->GetName();
     232           1 :                         aTypeName = SfxFilter::GetTypeFromStorage( xStorage, pFilter ? pFilter->IsAllowedAsTemplate() : sal_False, &aTmpFilterName );
     233             :                     }
     234           0 :                     catch( const WrappedTargetException& aWrap )
     235             :                     {
     236           0 :                         if (!bDeepDetection)
     237             :                             // Bail out early unless it's a deep detection.
     238           0 :                             return OUString();
     239             : 
     240           0 :                         packages::zip::ZipIOException aZipException;
     241             : 
     242             :                         // repairing is done only if this type is requested from outside
     243           0 :                         if ( ( aWrap.TargetException >>= aZipException ) && aTypeName.Len() )
     244             :                         {
     245           0 :                             if ( xInteraction.is() )
     246             :                             {
     247             :                                 // the package is broken one
     248           0 :                                    aDocumentTitle = aMedium.GetURLObject().getName(
     249             :                                                             INetURLObject::LAST_SEGMENT,
     250             :                                                             true,
     251           0 :                                                             INetURLObject::DECODE_WITH_CHARSET );
     252             : 
     253           0 :                                 if ( !bRepairPackage )
     254             :                                 {
     255             :                                     // ask the user whether he wants to try to repair
     256           0 :                                     RequestPackageReparation aRequest( aDocumentTitle );
     257           0 :                                     xInteraction->handle( aRequest.GetRequest() );
     258           0 :                                     bRepairAllowed = aRequest.isApproved();
     259             :                                 }
     260             : 
     261           0 :                                 if ( !bRepairAllowed )
     262             :                                 {
     263             :                                     // repair either not allowed or not successful
     264           0 :                                     NotifyBrokenPackage aNotifyRequest( aDocumentTitle );
     265           0 :                                     xInteraction->handle( aNotifyRequest.GetRequest() );
     266             :                                 }
     267             :                             }
     268             : 
     269           0 :                             if ( !bRepairAllowed )
     270           0 :                                 aTypeName.Erase();
     271           0 :                         }
     272           0 :                     }
     273           0 :                     catch( RuntimeException& )
     274             :                     {
     275           0 :                         throw;
     276             :                     }
     277           0 :                     catch( Exception& )
     278             :                     {
     279           0 :                         aTypeName.Erase();
     280             :                     }
     281             : 
     282           1 :                        if ( aTypeName.Len() )
     283             :                     {
     284             :                            const SfxFilter* pFilter =
     285           1 :                                     SfxFilterMatcher( OUString("smath") ).GetFilter4EA( aTypeName );
     286           1 :                         if ( pFilter )
     287           0 :                             aFilterName = pFilter->GetName();
     288             :                     }
     289           1 :                 }
     290             :             }
     291             :             else
     292             :             {
     293             :                 //Test to see if this begins with xml and if so run it through
     294             :                 //the MathML filter. There are all sorts of things wrong with
     295             :                 //this approach, to be fixed at a better level than here
     296           0 :                 SvStream *pStrm = aMedium.GetInStream();
     297           0 :                 aTypeName.Erase();
     298           0 :                 if (pStrm && !pStrm->GetError())
     299             :                 {
     300           0 :                     SotStorageRef aStorage = new SotStorage ( pStrm, sal_False );
     301           0 :                     if ( !aStorage->GetError() )
     302             :                     {
     303           0 :                         if (aStorage->IsStream(OUString("Equation Native")))
     304             :                         {
     305             :                             sal_uInt8 nVersion;
     306           0 :                             if (GetMathTypeVersion( aStorage, nVersion ) && nVersion <=3)
     307           0 :                                 aTypeName.AssignAscii( "math_MathType_3x" );
     308             :                         }
     309             :                     }
     310             :                     else
     311             :                     {
     312             :                         // 200 should be enough for the XML
     313             :                         // version, encoding and !DOCTYPE
     314             :                         // stuff I hope?
     315           0 :                         const sal_uInt16 nSize = 200;
     316             :                         sal_Char aBuffer[nSize+1];
     317           0 :                         aBuffer[nSize] = 0;
     318           0 :                         pStrm->Seek( STREAM_SEEK_TO_BEGIN );
     319           0 :                         pStrm->StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW); // avoid BOM marker
     320           0 :                         sal_uLong nBytesRead = pStrm->Read( aBuffer, nSize );
     321           0 :                         if (nBytesRead >= 6)
     322             :                         {
     323           0 :                             bool bIsMathType = false;
     324           0 :                             if (0 == strncmp( "<?xml", aBuffer, 5))
     325             :                             {
     326           0 :                                 if (strstr( aBuffer, "<math>" ) ||
     327           0 :                                         strstr( aBuffer, "<math " ) ||
     328           0 :                                         strstr( aBuffer, "<math:math " ))
     329           0 :                                     bIsMathType = true;
     330             :                             }
     331             :                             // this is the old <math tag to MathML in the beginning of the XML file
     332           0 :                             else if (0 == strncmp( "<math ", aBuffer, 6) ||
     333           0 :                                          0 == strncmp( "<math> ", aBuffer, 7) ||
     334           0 :                                          0 == strncmp( "<math:math> ", aBuffer, 12))
     335           0 :                                 bIsMathType = true;
     336             : 
     337           0 :                             if (bIsMathType){
     338             :                                 static const sal_Char sFltrNm_2[] = MATHML_XML;
     339             :                                 static const sal_Char sTypeNm_2[] = "math_MathML_XML_Math";
     340           0 :                                 aFilterName.AssignAscii( sFltrNm_2 );
     341           0 :                                 aTypeName.AssignAscii( sTypeNm_2 );
     342             :                             }
     343             :                         }
     344             :                     }
     345             : 
     346           0 :                     if ( aTypeName.Len() )
     347             :                     {
     348           0 :                         const SfxFilter* pFilt = SfxFilterMatcher( OUString("smath") ).GetFilter4EA( aTypeName );
     349           0 :                         if ( pFilt )
     350           0 :                             aFilterName = pFilt->GetName();
     351           0 :                     }
     352             :                 }
     353             :             }
     354           1 :         }
     355             :     }
     356             : 
     357           1 :     if ( nIndexOfInputStream == -1 && xStream.is() )
     358             :     {
     359             :         // if input stream wasn't part of the descriptor, now it should be, otherwise the content would be opend twice
     360           0 :         lDescriptor.realloc( nPropertyCount + 1 );
     361           0 :         lDescriptor[nPropertyCount].Name = "InputStream";
     362           0 :         lDescriptor[nPropertyCount].Value <<= xStream;
     363           0 :         nPropertyCount++;
     364             :     }
     365             : 
     366           1 :     if ( nIndexOfContent == -1 && xContent.is() )
     367             :     {
     368             :         // if input stream wasn't part of the descriptor, now it should be, otherwise the content would be opend twice
     369           0 :         lDescriptor.realloc( nPropertyCount + 1 );
     370           0 :         lDescriptor[nPropertyCount].Name = "UCBContent";
     371           0 :         lDescriptor[nPropertyCount].Value <<= xContent;
     372           0 :         nPropertyCount++;
     373             :     }
     374             : 
     375           1 :     if ( bReadOnly != bWasReadOnly )
     376             :     {
     377           0 :         if ( nIndexOfReadOnlyFlag == -1 )
     378             :         {
     379           0 :             lDescriptor.realloc( nPropertyCount + 1 );
     380           0 :             lDescriptor[nPropertyCount].Name = "ReadOnly";
     381           0 :             lDescriptor[nPropertyCount].Value <<= bReadOnly;
     382           0 :             nPropertyCount++;
     383             :         }
     384             :         else
     385           0 :             lDescriptor[nIndexOfReadOnlyFlag].Value <<= bReadOnly;
     386             :     }
     387             : 
     388           1 :     if ( !bRepairPackage && bRepairAllowed )
     389             :     {
     390           0 :         lDescriptor.realloc( nPropertyCount + 1 );
     391           0 :         lDescriptor[nPropertyCount].Name = "RepairPackage";
     392           0 :         lDescriptor[nPropertyCount].Value <<= bRepairAllowed;
     393           0 :         nPropertyCount++;
     394             : 
     395           0 :         bOpenAsTemplate = sal_True;
     396             : 
     397             :         // TODO/LATER: set progress bar that should be used
     398             :     }
     399             : 
     400           1 :     if ( bOpenAsTemplate )
     401             :     {
     402           0 :         if ( nIndexOfTemplateFlag == -1 )
     403             :         {
     404           0 :             lDescriptor.realloc( nPropertyCount + 1 );
     405           0 :             lDescriptor[nPropertyCount].Name = "AsTemplate";
     406           0 :             lDescriptor[nPropertyCount].Value <<= bOpenAsTemplate;
     407           0 :             nPropertyCount++;
     408             :         }
     409             :         else
     410           0 :             lDescriptor[nIndexOfTemplateFlag].Value <<= bOpenAsTemplate;
     411             :     }
     412             : 
     413           1 :     if ( !aDocumentTitle.isEmpty() )
     414             :     {
     415             :         // the title was set here
     416           0 :         if ( nIndexOfDocumentTitle == -1 )
     417             :         {
     418           0 :             lDescriptor.realloc( nPropertyCount + 1 );
     419           0 :             lDescriptor[nPropertyCount].Name = "DocumentTitle";
     420           0 :             lDescriptor[nPropertyCount].Value <<= aDocumentTitle;
     421           0 :             nPropertyCount++;
     422             :         }
     423             :         else
     424           0 :             lDescriptor[nIndexOfDocumentTitle].Value <<= aDocumentTitle;
     425             :     }
     426             : 
     427           1 :     if ( !aFilterName.Len() )
     428           1 :         aTypeName.Erase();
     429             : 
     430           2 :     return aTypeName;
     431             : }
     432             : 
     433             : /* XServiceInfo */
     434           0 : OUString SAL_CALL SmFilterDetect::getImplementationName() throw( RuntimeException )
     435             : {
     436           0 :     return impl_getStaticImplementationName();
     437             : }
     438             :                                                                                                                                 \
     439             : /* XServiceInfo */
     440           0 : sal_Bool SAL_CALL SmFilterDetect::supportsService( const OUString& sServiceName ) throw( RuntimeException )
     441             : {
     442           0 :     Sequence< OUString > seqServiceNames = getSupportedServiceNames();
     443           0 :     const OUString*      pArray          = seqServiceNames.getConstArray();
     444           0 :     for ( sal_Int32 nCounter=0; nCounter<seqServiceNames.getLength(); nCounter++ )
     445             :     {
     446           0 :         if ( pArray[nCounter] == sServiceName )
     447             :         {
     448           0 :             return sal_True ;
     449             :         }
     450             :     }
     451           0 :     return sal_False ;
     452             : }
     453             : 
     454             : /* XServiceInfo */
     455           0 : Sequence< OUString > SAL_CALL SmFilterDetect::getSupportedServiceNames() throw( RuntimeException )
     456             : {
     457           0 :     return impl_getStaticSupportedServiceNames();
     458             : }
     459             : 
     460             : /* Helper for XServiceInfo */
     461           1 : Sequence< OUString > SmFilterDetect::impl_getStaticSupportedServiceNames()
     462             : {
     463           1 :     Sequence< OUString > seqServiceNames( 1 );
     464           1 :     seqServiceNames.getArray() [0] = "com.sun.star.frame.ExtendedTypeDetection";
     465           1 :     return seqServiceNames ;
     466             : }
     467             : 
     468             : /* Helper for XServiceInfo */
     469           2 : OUString SmFilterDetect::impl_getStaticImplementationName()
     470             : {
     471           2 :     return OUString("com.sun.star.comp.math.FormatDetector");
     472             : }
     473             : 
     474             : /* Helper for registry */
     475           1 : Reference< XInterface > SAL_CALL SmFilterDetect::impl_createInstance( const Reference< XMultiServiceFactory >& xServiceManager ) throw( Exception )
     476             : {
     477           1 :     return Reference< XInterface >( *new SmFilterDetect( xServiceManager ) );
     478           3 : }
     479             : 
     480             : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Generated by: LCOV version 1.10