1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "datefunc.hxx"
21 #include <datefunc.hrc>
22 #include <strings.hrc>
23 #include <com/sun/star/util/Date.hpp>
24 #include <cppuhelper/factory.hxx>
25 #include <cppuhelper/supportsservice.hxx>
26 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
27 #include <rtl/ustrbuf.hxx>
28 #include <unotools/resmgr.hxx>
29 #include <i18nlangtag/languagetag.hxx>
31 #include "deffuncname.hxx"
33 using namespace ::com::sun::star
;
35 #define ADDIN_SERVICE "com.sun.star.sheet.AddIn"
36 #define MY_SERVICE "com.sun.star.sheet.addin.DateFunctions"
37 #define MY_IMPLNAME "com.sun.star.sheet.addin.DateFunctionsImpl"
39 #define UNIQUE false // function name does not exist in Calc
41 #define STDPAR false // all parameters are described
42 #define INTPAR true // first parameter is internal
44 #define FUNCDATA( FuncName, ParamCount, Category, Double, IntPar ) \
45 { "get" #FuncName, DATE_FUNCNAME_##FuncName, DATE_FUNCDESC_##FuncName, DATE_DEFFUNCNAME_##FuncName, ParamCount, Category, Double, IntPar }
47 const ScaFuncDataBase pFuncDataArr
[] =
49 FUNCDATA( DiffWeeks
, 3, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
50 FUNCDATA( DiffMonths
, 3, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
51 FUNCDATA( DiffYears
, 3, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
52 FUNCDATA( IsLeapYear
, 1, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
53 FUNCDATA( DaysInMonth
, 1, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
54 FUNCDATA( DaysInYear
, 1, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
55 FUNCDATA( WeeksInYear
, 1, ScaCategory::DateTime
, UNIQUE
, INTPAR
),
56 FUNCDATA( Rot13
, 1, ScaCategory::Text
, UNIQUE
, STDPAR
)
61 ScaFuncData::ScaFuncData(const ScaFuncDataBase
& rBaseData
) :
62 aIntName( OUString::createFromAscii( rBaseData
.pIntName
) ),
63 pUINameID( rBaseData
.pUINameID
),
64 pDescrID( rBaseData
.pDescrID
),
65 nParamCount( rBaseData
.nParamCount
),
66 eCat( rBaseData
.eCat
),
67 bDouble( rBaseData
.bDouble
),
68 bWithOpt( rBaseData
.bWithOpt
)
70 aCompList
.push_back(OUString::createFromAscii(rBaseData
.pCompListID
[0]));
71 aCompList
.push_back(OUString::createFromAscii(rBaseData
.pCompListID
[1]));
74 ScaFuncData::~ScaFuncData()
78 sal_uInt16
ScaFuncData::GetStrIndex( sal_uInt16 nParam
) const
82 return (nParam
> nParamCount
) ? (nParamCount
* 2) : (nParam
* 2);
85 static void InitScaFuncDataList(ScaFuncDataList
& rList
)
87 for (const auto & nIndex
: pFuncDataArr
)
88 rList
.push_back(ScaFuncData(nIndex
));
91 // entry points for service registration / instantiation
92 static uno::Reference
< uno::XInterface
> ScaDateAddIn_CreateInstance(
93 const uno::Reference
< lang::XMultiServiceFactory
>& )
95 return static_cast<cppu::OWeakObject
*>(new ScaDateAddIn());
100 SAL_DLLPUBLIC_EXPORT
void * date_component_getFactory(
101 const sal_Char
* pImplName
, void * pServiceManager
, void * /*pRegistryKey*/ )
103 void* pRet
= nullptr;
105 if ( pServiceManager
&&
106 OUString::createFromAscii( pImplName
) == ScaDateAddIn::getImplementationName_Static() )
108 uno::Reference
< lang::XSingleServiceFactory
> xFactory( cppu::createOneInstanceFactory(
109 static_cast< lang::XMultiServiceFactory
* >( pServiceManager
),
110 ScaDateAddIn::getImplementationName_Static(),
111 ScaDateAddIn_CreateInstance
,
112 ScaDateAddIn::getSupportedServiceNames_Static() ) );
117 pRet
= xFactory
.get();
126 // "normal" service implementation
127 ScaDateAddIn::ScaDateAddIn()
131 static const sal_Char
* pLang
[] = { "de", "en" };
132 static const sal_Char
* pCoun
[] = { "DE", "US" };
133 static const sal_uInt32 nNumOfLoc
= SAL_N_ELEMENTS( pLang
);
135 void ScaDateAddIn::InitDefLocales()
137 pDefLocales
.reset(new lang::Locale
[ nNumOfLoc
]);
139 for( sal_uInt32 nIndex
= 0; nIndex
< nNumOfLoc
; nIndex
++ )
141 pDefLocales
[ nIndex
].Language
= OUString::createFromAscii( pLang
[ nIndex
] );
142 pDefLocales
[ nIndex
].Country
= OUString::createFromAscii( pCoun
[ nIndex
] );
146 const lang::Locale
& ScaDateAddIn::GetLocale( sal_uInt32 nIndex
)
151 return (nIndex
< sizeof( pLang
)) ? pDefLocales
[ nIndex
] : aFuncLoc
;
154 void ScaDateAddIn::InitData()
156 aResLocale
= Translate::Create("sca", LanguageTag(aFuncLoc
));
157 pFuncDataList
.reset();
159 pFuncDataList
.reset(new ScaFuncDataList
);
160 InitScaFuncDataList(*pFuncDataList
);
168 OUString
ScaDateAddIn::GetFuncDescrStr(const char** pResId
, sal_uInt16 nStrIndex
)
170 return ScaResId(pResId
[nStrIndex
- 1]);
173 OUString
ScaDateAddIn::getImplementationName_Static()
178 uno::Sequence
< OUString
> ScaDateAddIn::getSupportedServiceNames_Static()
180 return { ADDIN_SERVICE
, MY_SERVICE
};
184 OUString SAL_CALL
ScaDateAddIn::getServiceName()
186 // name of specific AddIn service
191 OUString SAL_CALL
ScaDateAddIn::getImplementationName()
193 return getImplementationName_Static();
196 sal_Bool SAL_CALL
ScaDateAddIn::supportsService( const OUString
& aServiceName
)
198 return cppu::supportsService(this, aServiceName
);
201 uno::Sequence
< OUString
> SAL_CALL
ScaDateAddIn::getSupportedServiceNames()
203 return getSupportedServiceNames_Static();
207 void SAL_CALL
ScaDateAddIn::setLocale( const lang::Locale
& eLocale
)
210 InitData(); // change of locale invalidates resources!
213 lang::Locale SAL_CALL
ScaDateAddIn::getLocale()
218 OUString SAL_CALL
ScaDateAddIn::getProgrammaticFuntionName( const OUString
& )
221 // (but should be implemented for other uses of the AddIn service)
225 OUString SAL_CALL
ScaDateAddIn::getDisplayFunctionName( const OUString
& aProgrammaticName
)
229 auto fDataIt
= std::find_if(pFuncDataList
->begin(), pFuncDataList
->end(),
230 FindScaFuncData( aProgrammaticName
) );
231 if( fDataIt
!= pFuncDataList
->end() )
233 aRet
= ScaResId(fDataIt
->GetUINameID());
234 if( fDataIt
->IsDouble() )
239 aRet
= "UNKNOWNFUNC_" + aProgrammaticName
;
245 OUString SAL_CALL
ScaDateAddIn::getFunctionDescription( const OUString
& aProgrammaticName
)
249 auto fDataIt
= std::find_if(pFuncDataList
->begin(), pFuncDataList
->end(),
250 FindScaFuncData( aProgrammaticName
) );
251 if( fDataIt
!= pFuncDataList
->end() )
252 aRet
= GetFuncDescrStr( fDataIt
->GetDescrID(), 1 );
257 OUString SAL_CALL
ScaDateAddIn::getDisplayArgumentName(
258 const OUString
& aProgrammaticName
, sal_Int32 nArgument
)
262 auto fDataIt
= std::find_if(pFuncDataList
->begin(), pFuncDataList
->end(),
263 FindScaFuncData( aProgrammaticName
) );
264 if( fDataIt
!= pFuncDataList
->end() && (nArgument
<= 0xFFFF) )
266 sal_uInt16 nStr
= fDataIt
->GetStrIndex( static_cast< sal_uInt16
>( nArgument
) );
268 aRet
= GetFuncDescrStr( fDataIt
->GetDescrID(), nStr
);
276 OUString SAL_CALL
ScaDateAddIn::getArgumentDescription(
277 const OUString
& aProgrammaticName
, sal_Int32 nArgument
)
281 auto fDataIt
= std::find_if(pFuncDataList
->begin(), pFuncDataList
->end(),
282 FindScaFuncData( aProgrammaticName
) );
283 if( fDataIt
!= pFuncDataList
->end() && (nArgument
<= 0xFFFF) )
285 sal_uInt16 nStr
= fDataIt
->GetStrIndex( static_cast< sal_uInt16
>( nArgument
) );
287 aRet
= GetFuncDescrStr( fDataIt
->GetDescrID(), nStr
+ 1 );
289 aRet
= "for internal use only";
295 OUString SAL_CALL
ScaDateAddIn::getProgrammaticCategoryName(
296 const OUString
& aProgrammaticName
)
300 auto fDataIt
= std::find_if(pFuncDataList
->begin(), pFuncDataList
->end(),
301 FindScaFuncData( aProgrammaticName
) );
302 if( fDataIt
!= pFuncDataList
->end() )
304 switch( fDataIt
->GetCategory() )
306 case ScaCategory::DateTime
: aRet
= "Date&Time"; break;
307 case ScaCategory::Text
: aRet
= "Text"; break;
308 case ScaCategory::Finance
: aRet
= "Financial"; break;
309 case ScaCategory::Inf
: aRet
= "Information"; break;
310 case ScaCategory::Math
: aRet
= "Mathematical"; break;
311 case ScaCategory::Tech
: aRet
= "Technical"; break;
320 OUString SAL_CALL
ScaDateAddIn::getDisplayCategoryName(
321 const OUString
& aProgrammaticName
)
323 return getProgrammaticCategoryName( aProgrammaticName
);
326 // XCompatibilityNames
327 uno::Sequence
< sheet::LocalizedName
> SAL_CALL
ScaDateAddIn::getCompatibilityNames(
328 const OUString
& aProgrammaticName
)
330 auto fDataIt
= std::find_if(pFuncDataList
->begin(), pFuncDataList
->end(),
331 FindScaFuncData( aProgrammaticName
) );
332 if( fDataIt
== pFuncDataList
->end() )
333 return uno::Sequence
< sheet::LocalizedName
>( 0 );
335 const std::vector
<OUString
>& rStrList
= fDataIt
->GetCompNameList();
336 sal_uInt32 nCount
= rStrList
.size();
338 uno::Sequence
< sheet::LocalizedName
> aRet( nCount
);
339 sheet::LocalizedName
* pArray
= aRet
.getArray();
341 for( sal_uInt32 nIndex
= 0; nIndex
< nCount
; nIndex
++ )
342 pArray
[ nIndex
] = sheet::LocalizedName( GetLocale( nIndex
), rStrList
.at( nIndex
) );
349 // auxiliary functions
350 bool IsLeapYear( sal_uInt16 nYear
)
352 return ((((nYear
% 4) == 0) && ((nYear
% 100) != 0)) || ((nYear
% 400) == 0));
355 sal_uInt16
DaysInMonth( sal_uInt16 nMonth
, sal_uInt16 nYear
)
357 static const sal_uInt16 aDaysInMonth
[12] = { 31, 28, 31, 30, 31, 30,
358 31, 31, 30, 31, 30, 31 };
361 return aDaysInMonth
[nMonth
-1];
364 if ( IsLeapYear(nYear
) )
365 return aDaysInMonth
[nMonth
-1] + 1;
367 return aDaysInMonth
[nMonth
-1];
372 * Convert a date to a count of days starting from 01/01/0001
374 * The internal representation of a Date used in this Addin
375 * is the number of days between 01/01/0001 and the date
376 * this function converts a Day , Month, Year representation
377 * to this internal Date value.
380 sal_Int32
DateToDays( sal_uInt16 nDay
, sal_uInt16 nMonth
, sal_uInt16 nYear
)
382 sal_Int32 nDays
= (static_cast<sal_Int32
>(nYear
)-1) * 365;
383 nDays
+= ((nYear
-1) / 4) - ((nYear
-1) / 100) + ((nYear
-1) / 400);
385 for( sal_uInt16 i
= 1; i
< nMonth
; i
++ )
386 nDays
+= DaysInMonth(i
,nYear
);
393 * Convert a count of days starting from 01/01/0001 to a date
395 * The internal representation of a Date used in this Addin
396 * is the number of days between 01/01/0001 and the date
397 * this function converts this internal Date value
398 * to a Day , Month, Year representation of a Date.
400 * @throws lang::IllegalArgumentException
403 void DaysToDate( sal_Int32 nDays
,
404 sal_uInt16
& rDay
, sal_uInt16
& rMonth
, sal_uInt16
& rYear
)
407 throw lang::IllegalArgumentException();
416 rYear
= static_cast<sal_uInt16
>((nTempDays
/ 365) - i
);
417 nTempDays
-= (static_cast<sal_Int32
>(rYear
) -1) * 365;
418 nTempDays
-= (( rYear
-1) / 4) - (( rYear
-1) / 100) + ((rYear
-1) / 400);
427 if ( nTempDays
> 365 )
429 if ( (nTempDays
!= 366) || !IsLeapYear( rYear
) )
440 while ( nTempDays
> DaysInMonth( rMonth
, rYear
) )
442 nTempDays
-= DaysInMonth( rMonth
, rYear
);
445 rDay
= static_cast<sal_uInt16
>(nTempDays
);
449 * Get the null date used by the spreadsheet document
451 * The internal representation of a Date used in this Addin
452 * is the number of days between 01/01/0001 and the date
453 * this function returns this internal Date value for the document null date
455 * @throws uno::RuntimeException
457 sal_Int32
GetNullDate( const uno::Reference
< beans::XPropertySet
>& xOptions
)
463 uno::Any aAny
= xOptions
->getPropertyValue( "NullDate" );
465 if ( aAny
>>= aDate
)
466 return DateToDays( aDate
.Day
, aDate
.Month
, aDate
.Year
);
468 catch (uno::Exception
&)
473 // no null date available -> no calculations possible
474 throw uno::RuntimeException();
481 * Get week difference between 2 dates
483 * new Weeks(date1,date2,mode) function for StarCalc
485 * Two modes of operation are provided.
486 * The first is just a simple division by 7 calculation.
488 * The second calculates the difference by week of year.
490 * The International Standard IS-8601 has decreed that Monday
491 * shall be the first day of the week.
493 * A week that lies partly in one year and partly in another
494 * is assigned a number in the year in which most of its days lie.
496 * That means that week 1 of any year is the week that contains the 4. January
498 * The internal representation of a Date used in the Addin is the number of days based on 01/01/0001
500 * A WeekDay can be then calculated by subtracting 1 and calculating the rest of
501 * a division by 7, which gives a 0 - 6 value for Monday - Sunday
503 * Using the 4. January rule explained above the formula
505 * nWeek1= ( nDays1 - nJan4 + ( (nJan4-1) % 7 ) ) / 7 + 1;
507 * calculates a number between 0-53 for each day which is in the same year as nJan4
508 * where 0 means that this week belonged to the year before.
510 * If a day in the same or another year is used in this formula this calculates
511 * a calendar week offset from a given 4. January
513 * nWeek2 = ( nDays2 - nJan4 + ( (nJan4-1) % 7 ) ) / 7 + 1;
515 * The 4.January of first Date Argument can thus be used to calculate
516 * the week difference by calendar weeks which is then nWeek = nWeek2 - nWeek1
518 * which can be optimized to
520 * nWeek = ( (nDays2-nJan4+((nJan4-1)%7))/7 ) - ( (nDays1-nJan4+((nJan4-1)%7))/7 )
522 * Note: All calculations are operating on the long integer data type
523 * % is the modulo operator in C which calculates the rest of an Integer division
526 * mode 0 is the interval between the dates in month, that is days / 7
528 * mode 1 is the difference by week of year
532 sal_Int32 SAL_CALL
ScaDateAddIn::getDiffWeeks(
533 const uno::Reference
< beans::XPropertySet
>& xOptions
,
534 sal_Int32 nStartDate
, sal_Int32 nEndDate
,
537 if (nMode
!= 0 && nMode
!= 1)
538 throw lang::IllegalArgumentException();
540 sal_Int32 nNullDate
= GetNullDate( xOptions
);
542 sal_Int32 nDays1
= nStartDate
+ nNullDate
;
543 sal_Int32 nDays2
= nEndDate
+ nNullDate
;
549 sal_uInt16 nDay
,nMonth
,nYear
;
550 DaysToDate( nDays1
, nDay
, nMonth
, nYear
);
551 sal_Int32 nJan4
= DateToDays( 4, 1, nYear
);
553 nRet
= ( (nDays2
-nJan4
+((nJan4
-1)%7))/7 ) - ( (nDays1
-nJan4
+((nJan4
-1)%7))/7 );
557 nRet
= (nDays2
- nDays1
) / 7;
563 * Get month difference between 2 dates
564 * =Month(start, end, mode) Function for StarCalc
566 * two modes are provided
568 * mode 0 is the interval between the dates in month
570 * mode 1 is the difference in calendar month
572 sal_Int32 SAL_CALL
ScaDateAddIn::getDiffMonths(
573 const uno::Reference
< beans::XPropertySet
>& xOptions
,
574 sal_Int32 nStartDate
, sal_Int32 nEndDate
,
577 if (nMode
!= 0 && nMode
!= 1)
578 throw lang::IllegalArgumentException();
580 sal_Int32 nNullDate
= GetNullDate( xOptions
);
582 sal_Int32 nDays1
= nStartDate
+ nNullDate
;
583 sal_Int32 nDays2
= nEndDate
+ nNullDate
;
585 sal_uInt16 nDay1
,nMonth1
,nYear1
;
586 sal_uInt16 nDay2
,nMonth2
,nYear2
;
587 DaysToDate(nDays1
,nDay1
,nMonth1
,nYear1
);
588 DaysToDate(nDays2
,nDay2
,nMonth2
,nYear2
);
590 sal_Int32 nRet
= nMonth2
- nMonth1
+ (nYear2
- nYear1
) * 12;
591 if ( nMode
== 1 || nDays1
== nDays2
) return nRet
;
593 if ( nDays1
< nDays2
)
612 * Get Year difference between 2 dates
614 * two modes are provided
616 * mode 0 is the interval between the dates in years
618 * mode 1 is the difference in calendar years
620 sal_Int32 SAL_CALL
ScaDateAddIn::getDiffYears(
621 const uno::Reference
< beans::XPropertySet
>& xOptions
,
622 sal_Int32 nStartDate
, sal_Int32 nEndDate
,
625 if (nMode
!= 0 && nMode
!= 1)
626 throw lang::IllegalArgumentException();
629 return getDiffMonths( xOptions
, nStartDate
, nEndDate
, nMode
) / 12;
631 sal_Int32 nNullDate
= GetNullDate( xOptions
);
633 sal_Int32 nDays1
= nStartDate
+ nNullDate
;
634 sal_Int32 nDays2
= nEndDate
+ nNullDate
;
636 sal_uInt16 nDay1
,nMonth1
,nYear1
;
637 sal_uInt16 nDay2
,nMonth2
,nYear2
;
638 DaysToDate(nDays1
,nDay1
,nMonth1
,nYear1
);
639 DaysToDate(nDays2
,nDay2
,nMonth2
,nYear2
);
641 return nYear2
- nYear1
;
645 * Check if a Date is in a leap year in the Gregorian calendar
647 sal_Int32 SAL_CALL
ScaDateAddIn::getIsLeapYear(
648 const uno::Reference
< beans::XPropertySet
>& xOptions
,
651 sal_Int32 nNullDate
= GetNullDate( xOptions
);
652 sal_Int32 nDays
= nDate
+ nNullDate
;
654 sal_uInt16 nDay
, nMonth
, nYear
;
655 DaysToDate(nDays
,nDay
,nMonth
,nYear
);
657 return static_cast<sal_Int32
>(IsLeapYear(nYear
));
661 * Get the Number of Days in the month for a date
663 sal_Int32 SAL_CALL
ScaDateAddIn::getDaysInMonth(
664 const uno::Reference
<beans::XPropertySet
>& xOptions
,
667 sal_Int32 nNullDate
= GetNullDate( xOptions
);
668 sal_Int32 nDays
= nDate
+ nNullDate
;
670 sal_uInt16 nDay
, nMonth
, nYear
;
671 DaysToDate(nDays
,nDay
,nMonth
,nYear
);
673 return DaysInMonth( nMonth
, nYear
);
677 * Get number of days in the year of a date specified
679 sal_Int32 SAL_CALL
ScaDateAddIn::getDaysInYear(
680 const uno::Reference
< beans::XPropertySet
>& xOptions
,
683 sal_Int32 nNullDate
= GetNullDate( xOptions
);
684 sal_Int32 nDays
= nDate
+ nNullDate
;
686 sal_uInt16 nDay
, nMonth
, nYear
;
687 DaysToDate(nDays
,nDay
,nMonth
,nYear
);
689 return ( IsLeapYear(nYear
) ? 366 : 365 );
693 * Get number of weeks in the year for a date
695 * Most years have 52 weeks, but years that start on a Thursday
696 * and leap years that start on a Wednesday have 53 weeks
698 * The International Standard IS-8601 has decreed that Monday
699 * shall be the first day of the week.
701 * A WeekDay can be calculated by subtracting 1 and calculating the rest of
702 * a division by 7 from the internal date representation
703 * which gives a 0 - 6 value for Monday - Sunday
705 * @see #IsLeapYear #WeekNumber
707 sal_Int32 SAL_CALL
ScaDateAddIn::getWeeksInYear(
708 const uno::Reference
< beans::XPropertySet
>& xOptions
,
711 sal_Int32 nNullDate
= GetNullDate( xOptions
);
712 sal_Int32 nDays
= nDate
+ nNullDate
;
714 sal_uInt16 nDay
, nMonth
, nYear
;
715 DaysToDate(nDays
,nDay
,nMonth
,nYear
);
717 sal_Int32 nJan1WeekDay
= ( DateToDays(1,1,nYear
) - 1) % 7;
720 if ( nJan1WeekDay
== 3 ) /* Thursday */
722 else if ( nJan1WeekDay
== 2 ) /* Wednesday */
723 nRet
= ( IsLeapYear(nYear
) ? 53 : 52 );
731 * Encrypt or decrypt a string using ROT13 algorithm
733 * This function rotates each character by 13 in the alphabet.
734 * Only the characters 'a' ... 'z' and 'A' ... 'Z' are modified.
736 OUString SAL_CALL
ScaDateAddIn::getRot13( const OUString
& aSrcString
)
738 OUStringBuffer
aBuffer( aSrcString
);
739 for( sal_Int32 nIndex
= 0; nIndex
< aBuffer
.getLength(); nIndex
++ )
741 sal_Unicode cChar
= aBuffer
[nIndex
];
742 if( ((cChar
>= 'a') && (cChar
<= 'z') && ((cChar
+= 13) > 'z')) ||
743 ((cChar
>= 'A') && (cChar
<= 'Z') && ((cChar
+= 13) > 'Z')) )
745 aBuffer
[nIndex
] = cChar
;
747 return aBuffer
.makeStringAndClear();
750 OUString
ScaDateAddIn::ScaResId(const char* pId
)
752 return Translate::get(pId
, aResLocale
);
755 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */