| File: | l10ntools/source/helpmerge.cxx |
| Location: | line 394, column 21 |
| Description: | Called C++ object pointer is null |
| 1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ | |||
| 2 | /************************************************************************* | |||
| 3 | * | |||
| 4 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |||
| 5 | * | |||
| 6 | * Copyright 2000, 2010 Oracle and/or its affiliates. | |||
| 7 | * | |||
| 8 | * OpenOffice.org - a multi-platform office productivity suite | |||
| 9 | * | |||
| 10 | * This file is part of OpenOffice.org. | |||
| 11 | * | |||
| 12 | * OpenOffice.org is free software: you can redistribute it and/or modify | |||
| 13 | * it under the terms of the GNU Lesser General Public License version 3 | |||
| 14 | * only, as published by the Free Software Foundation. | |||
| 15 | * | |||
| 16 | * OpenOffice.org is distributed in the hope that it will be useful, | |||
| 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| 19 | * GNU Lesser General Public License version 3 for more details | |||
| 20 | * (a copy is included in the LICENSE file that accompanied this code). | |||
| 21 | * | |||
| 22 | * You should have received a copy of the GNU Lesser General Public License | |||
| 23 | * version 3 along with OpenOffice.org. If not, see | |||
| 24 | * <http://www.openoffice.org/license.html> | |||
| 25 | * for a copy of the LGPLv3 License. | |||
| 26 | * | |||
| 27 | ************************************************************************/ | |||
| 28 | ||||
| 29 | #include "sal/config.h" | |||
| 30 | ||||
| 31 | #include <fstream> | |||
| 32 | #include <functional> | |||
| 33 | ||||
| 34 | #include <osl/file.hxx> | |||
| 35 | #include <sal/log.hxx> | |||
| 36 | ||||
| 37 | #include <stdio.h> | |||
| 38 | #include <stdlib.h> | |||
| 39 | #include "helpmerge.hxx" | |||
| 40 | #include <algorithm> | |||
| 41 | #include <sys/types.h> | |||
| 42 | #include <sys/stat.h> | |||
| 43 | #include <iostream> | |||
| 44 | #include <fstream> | |||
| 45 | #include <vector> | |||
| 46 | #include <rtl/strbuf.hxx> | |||
| 47 | #ifdef WNT | |||
| 48 | #include <windows.h> | |||
| 49 | #undef CopyFile | |||
| 50 | #include <direct.h> | |||
| 51 | #endif | |||
| 52 | ||||
| 53 | #include "common.hxx" | |||
| 54 | #include "helper.hxx" | |||
| 55 | ||||
| 56 | #if OSL_DEBUG_LEVEL1 > 2 | |||
| 57 | void HelpParser::Dump(XMLHashMap* rElem_in) | |||
| 58 | { | |||
| 59 | for(XMLHashMap::iterator pos = rElem_in->begin();pos != rElem_in->end(); ++pos) | |||
| 60 | { | |||
| 61 | Dump(pos->second,pos->first); | |||
| 62 | } | |||
| 63 | } | |||
| 64 | ||||
| 65 | void HelpParser::Dump(LangHashMap* rElem_in,const rtl::OString & sKey_in) | |||
| 66 | { | |||
| 67 | rtl::OString x; | |||
| 68 | OString y; | |||
| 69 | fprintf(stdoutstdout,"+------------%s-----------+\n",sKey_in.getStr() ); | |||
| 70 | for(LangHashMap::iterator posn=rElem_in->begin();posn!=rElem_in->end();++posn) | |||
| 71 | { | |||
| 72 | x=posn->first; | |||
| 73 | y=posn->second->ToOString(); | |||
| 74 | fprintf(stdoutstdout,"key=%s value=%s\n",x.getStr(),y.getStr()); | |||
| 75 | } | |||
| 76 | fprintf(stdoutstdout,"+--------------------------+\n"); | |||
| 77 | } | |||
| 78 | #endif | |||
| 79 | ||||
| 80 | HelpParser::HelpParser( const rtl::OString &rHelpFile ) | |||
| 81 | : sHelpFile( rHelpFile ) | |||
| 82 | {}; | |||
| 83 | ||||
| 84 | /*****************************************************************************/ | |||
| 85 | bool HelpParser::CreateSDF( | |||
| 86 | /*****************************************************************************/ | |||
| 87 | const rtl::OString &rSDFFile_in, const rtl::OString &rPrj_in,const rtl::OString &rRoot_in, | |||
| 88 | const rtl::OString &sHelpFile, XMLFile *pXmlFile, const rtl::OString &rGsi1){ | |||
| 89 | SimpleXMLParser aParser; | |||
| 90 | rtl::OUString sXmlFile( | |||
| 91 | rtl::OStringToOUString(sHelpFile, RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)))); | |||
| 92 | //TODO: explicit BOM handling? | |||
| 93 | ||||
| 94 | std::auto_ptr <XMLFile> file ( aParser.Execute( sXmlFile, pXmlFile ) ); | |||
| 95 | ||||
| 96 | if(file.get() == NULL__null) | |||
| 97 | { | |||
| 98 | printf( | |||
| 99 | "%s: %s\n", | |||
| 100 | sHelpFile.getStr(), | |||
| 101 | (rtl::OUStringToOString( | |||
| 102 | aParser.GetError().sMessage, RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11))). | |||
| 103 | getStr())); | |||
| 104 | exit(-1); | |||
| 105 | } | |||
| 106 | file->Extract(); | |||
| 107 | if( !file->CheckExportStatus() ){ | |||
| 108 | return true; | |||
| 109 | } | |||
| 110 | std::ofstream aSDFStream( | |||
| 111 | rSDFFile_in.getStr(), std::ios_base::out | std::ios_base::trunc); | |||
| 112 | ||||
| 113 | if (!aSDFStream.is_open()) { | |||
| 114 | fprintf(stdoutstdout,"Can't open file %s\n",rSDFFile_in.getStr()); | |||
| 115 | return false; | |||
| 116 | } | |||
| 117 | ||||
| 118 | rtl::OString sActFileName( | |||
| 119 | common::pathnameToken(sHelpFile.getStr(), rRoot_in.getStr())); | |||
| 120 | ||||
| 121 | XMLHashMap* aXMLStrHM = file->GetStrings(); | |||
| 122 | LangHashMap* pElem; | |||
| 123 | XMLElement* pXMLElement = NULL__null; | |||
| 124 | ||||
| 125 | OUStringBuffer sBuffer; | |||
| 126 | const OUString sOUPrj( rPrj_in.getStr() , rPrj_in.getLength() , RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)) ); | |||
| 127 | const OUString sOUActFileName(sActFileName.getStr() , sActFileName.getLength() , RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)) ); | |||
| 128 | const OUString sOUGsi1( rGsi1.getStr() , rGsi1.getLength() , RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)) ); | |||
| 129 | ||||
| 130 | Export::InitLanguages( false ); | |||
| 131 | std::vector<rtl::OString> aLanguages = Export::GetLanguages(); | |||
| 132 | ||||
| 133 | std::vector<rtl::OString> order = file->getOrder(); | |||
| 134 | std::vector<rtl::OString>::iterator pos; | |||
| 135 | XMLHashMap::iterator posm; | |||
| 136 | ||||
| 137 | for( pos = order.begin(); pos != order.end() ; ++pos ) | |||
| 138 | { | |||
| 139 | posm = aXMLStrHM->find( *pos ); | |||
| 140 | pElem = posm->second; | |||
| 141 | rtl::OString sCur; | |||
| 142 | ||||
| 143 | for( unsigned int n = 0; n < aLanguages.size(); n++ ) | |||
| 144 | { | |||
| 145 | sCur = aLanguages[ n ]; | |||
| 146 | pXMLElement = (*pElem)[ sCur ]; | |||
| 147 | ||||
| 148 | if( pXMLElement != NULL__null ) | |||
| 149 | { | |||
| 150 | OUString data( | |||
| 151 | pXMLElement->ToOUString(). | |||
| 152 | replaceAll( | |||
| 153 | rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("\n")(&("\n")[0]), ((sal_Int32)((sizeof ("\n") / sizeof (("\n" )[0]))-1)), (((rtl_TextEncoding) 11))), | |||
| 154 | rtl::OUString()). | |||
| 155 | replaceAll( | |||
| 156 | rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("\t")(&("\t")[0]), ((sal_Int32)((sizeof ("\t") / sizeof (("\t" )[0]))-1)), (((rtl_TextEncoding) 11))), | |||
| 157 | rtl::OUString())); | |||
| 158 | sBuffer.append( sOUPrj ); | |||
| 159 | sBuffer.append('\t'); | |||
| 160 | if ( !rRoot_in.isEmpty()) | |||
| 161 | sBuffer.append( sOUActFileName ); | |||
| 162 | sBuffer.appendAscii(RTL_CONSTASCII_STRINGPARAM("\t0\t")(&("\t0\t")[0]), ((sal_Int32)(sizeof ("\t0\t") / sizeof ( ("\t0\t")[0]))-1)); | |||
| 163 | sBuffer.append( sOUGsi1 ); //"help"; | |||
| 164 | sBuffer.append('\t'); | |||
| 165 | rtl::OString sID = posm->first; // ID | |||
| 166 | sBuffer.append( rtl::OStringToOUString( sID, RTL_TEXTENCODING_UTF8(((rtl_TextEncoding) 76)) ) ); | |||
| 167 | sBuffer.append('\t'); | |||
| 168 | rtl::OString sOldRef = pXMLElement->GetOldref(); // oldref | |||
| 169 | sBuffer.append( rtl::OStringToOUString(sOldRef, RTL_TEXTENCODING_UTF8(((rtl_TextEncoding) 76)) ) ); | |||
| 170 | sBuffer.appendAscii(RTL_CONSTASCII_STRINGPARAM("\t\t\t0\t")(&("\t\t\t0\t")[0]), ((sal_Int32)(sizeof ("\t\t\t0\t") / sizeof (("\t\t\t0\t")[0]))-1)); | |||
| 171 | sBuffer.append( rtl::OStringToOUString( sCur, RTL_TEXTENCODING_UTF8(((rtl_TextEncoding) 76)) ) ); | |||
| 172 | sBuffer.append('\t'); | |||
| 173 | sBuffer.append( data ); | |||
| 174 | sBuffer.appendAscii(RTL_CONSTASCII_STRINGPARAM("\t\t\t\t")(&("\t\t\t\t")[0]), ((sal_Int32)(sizeof ("\t\t\t\t") / sizeof (("\t\t\t\t")[0]))-1)); | |||
| 175 | rtl::OString sOut(rtl::OUStringToOString(sBuffer.makeStringAndClear().getStr() , RTL_TEXTENCODING_UTF8(((rtl_TextEncoding) 76)))); | |||
| 176 | if( !data.isEmpty() ) | |||
| 177 | aSDFStream << sOut.getStr() << '\n'; | |||
| 178 | pXMLElement=NULL__null; | |||
| 179 | } | |||
| 180 | else | |||
| 181 | { | |||
| 182 | fprintf(stdoutstdout,"\nDBG: NullPointer in HelpParser::CreateSDF, Language %s, File %s\n", sCur.getStr(), sHelpFile.getStr()); | |||
| 183 | } | |||
| 184 | } | |||
| 185 | } | |||
| 186 | aSDFStream.close(); | |||
| 187 | ||||
| 188 | return sal_True((sal_Bool)1); | |||
| 189 | } | |||
| 190 | ||||
| 191 | bool HelpParser::Merge( const rtl::OString &rSDFFile, const rtl::OString &rDestinationFile , | |||
| 192 | const rtl::OString& rLanguage , MergeDataFile& aMergeDataFile ) | |||
| 193 | { | |||
| 194 | ||||
| 195 | (void) rSDFFile; | |||
| 196 | bool hasNoError = true; | |||
| 197 | ||||
| 198 | SimpleXMLParser aParser; | |||
| 199 | ||||
| 200 | rtl::OUString sXmlFile( | |||
| 201 | rtl::OStringToOUString(sHelpFile, RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)))); | |||
| 202 | //TODO: explicit BOM handling? | |||
| 203 | ||||
| 204 | XMLFile* xmlfile = ( aParser.Execute( sXmlFile, new XMLFile( rtl::OUString('0') ) ) ); | |||
| 205 | hasNoError = MergeSingleFile( xmlfile , aMergeDataFile , rLanguage , rDestinationFile ); | |||
| 206 | delete xmlfile; | |||
| 207 | return hasNoError; | |||
| 208 | } | |||
| 209 | ||||
| 210 | void HelpParser::parse_languages( std::vector<rtl::OString>& aLanguages , MergeDataFile& aMergeDataFile ){ | |||
| 211 | std::vector<rtl::OString> aTmp; | |||
| 212 | ||||
| 213 | Export::InitLanguages( false ); | |||
| 214 | ||||
| 215 | if (Export::sLanguages.equalsIgnoreAsciiCaseL(RTL_CONSTASCII_STRINGPARAM("ALL")(&("ALL")[0]), ((sal_Int32)(sizeof ("ALL") / sizeof (("ALL" )[0]))-1))) | |||
| 216 | { | |||
| 217 | aLanguages = aMergeDataFile.GetLanguages(); | |||
| 218 | aLanguages.push_back(rtl::OString(RTL_CONSTASCII_STRINGPARAM("en-US")(&("en-US")[0]), ((sal_Int32)(sizeof ("en-US") / sizeof ( ("en-US")[0]))-1))); | |||
| 219 | ||||
| 220 | if( !Export::sForcedLanguages.isEmpty() ) | |||
| 221 | { | |||
| 222 | std::vector<rtl::OString> aFL = Export::GetForcedLanguages(); | |||
| 223 | std::copy( aFL.begin() , | |||
| 224 | aFL.end() , | |||
| 225 | back_inserter( aLanguages ) | |||
| 226 | ); | |||
| 227 | std::sort( aLanguages.begin() , aLanguages.end() , std::less< rtl::OString >() ); | |||
| 228 | std::vector<rtl::OString>::iterator unique_iter = std::unique( aLanguages.begin() , aLanguages.end() , std::equal_to< rtl::OString >() ); | |||
| 229 | std::copy( aLanguages.begin() , unique_iter , back_inserter( aTmp ) ); | |||
| 230 | aLanguages = aTmp; | |||
| 231 | } | |||
| 232 | } | |||
| 233 | else{ | |||
| 234 | aLanguages = Export::GetLanguages(); | |||
| 235 | } | |||
| 236 | ||||
| 237 | } | |||
| 238 | ||||
| 239 | bool HelpParser::Merge( | |||
| 240 | const rtl::OString &rSDFFile, const rtl::OString &rPathX , const rtl::OString &rPathY , bool bISO , | |||
| 241 | const std::vector<rtl::OString>& aLanguages , MergeDataFile& aMergeDataFile , bool bCreateDir ) | |||
| 242 | { | |||
| 243 | ||||
| 244 | ||||
| 245 | (void) rSDFFile ; | |||
| 246 | bool hasNoError = true; | |||
| 247 | SimpleXMLParser aParser; | |||
| 248 | rtl::OUString sXmlFile( | |||
| 249 | rtl::OStringToOUString(sHelpFile, RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)))); | |||
| 250 | //TODO: explicit BOM handling? | |||
| 251 | ||||
| 252 | XMLFile* xmlfile = aParser.Execute( sXmlFile, new XMLFile( rtl::OUString('0') ) ); | |||
| 253 | ||||
| 254 | if( xmlfile == NULL__null) | |||
| 255 | { | |||
| 256 | printf("%s\n", rtl::OUStringToOString(aParser.GetError().sMessage, RTL_TEXTENCODING_UTF8(((rtl_TextEncoding) 76))).getStr()); | |||
| 257 | exit(-1); | |||
| 258 | } | |||
| 259 | ||||
| 260 | xmlfile->Extract(); | |||
| 261 | ||||
| 262 | rtl::OString sCur; | |||
| 263 | for( unsigned int n = 0; n < aLanguages.size(); n++ ){ | |||
| 264 | sCur = aLanguages[ n ]; | |||
| 265 | ||||
| 266 | rtl::OString sFilepath; | |||
| 267 | if( bISO ) sFilepath = GetOutpath( rPathX , sCur , rPathY ); | |||
| 268 | else sFilepath = rPathX; | |||
| 269 | if( bCreateDir ) | |||
| 270 | MakeDir(sFilepath); | |||
| 271 | ||||
| 272 | XMLFile* file = new XMLFile( *xmlfile ); | |||
| 273 | sFilepath += sHelpFile; | |||
| 274 | hasNoError = MergeSingleFile( file , aMergeDataFile , sCur , sFilepath ); | |||
| 275 | delete file; | |||
| 276 | ||||
| 277 | if( !hasNoError ) return false; // Stop on error | |||
| 278 | } | |||
| 279 | ||||
| 280 | delete xmlfile; | |||
| 281 | return hasNoError; | |||
| 282 | } | |||
| 283 | ||||
| 284 | bool HelpParser::MergeSingleFile( XMLFile* file , MergeDataFile& aMergeDataFile , const rtl::OString& sLanguage , | |||
| 285 | rtl::OString const & sPath ) | |||
| 286 | { | |||
| 287 | file->Extract(); | |||
| 288 | ||||
| 289 | XMLHashMap* aXMLStrHM = file->GetStrings(); | |||
| 290 | LangHashMap* aLangHM; | |||
| 291 | static ResData pResData( "","",""); | |||
| 292 | pResData.sResTyp = "help"; | |||
| 293 | ||||
| 294 | for(XMLHashMap::iterator pos=aXMLStrHM->begin();pos!=aXMLStrHM->end();++pos) // Merge every l10n related string | |||
| 295 | { | |||
| 296 | ||||
| 297 | aLangHM = pos->second; | |||
| 298 | #if OSL_DEBUG_LEVEL1 > 2 | |||
| 299 | printf("*********************DUMPING HASHMAP***************************************"); | |||
| 300 | Dump(aXMLStrHM); | |||
| 301 | printf("DBG: sHelpFile = %s\n",sHelpFile.getStr() ); | |||
| 302 | #endif | |||
| 303 | ||||
| 304 | pResData.sGId = pos->first; | |||
| 305 | pResData.sFilename = sHelpFile; | |||
| 306 | ||||
| 307 | ProcessHelp( aLangHM , sLanguage, &pResData , aMergeDataFile ); | |||
| 308 | } | |||
| 309 | ||||
| 310 | file->Write(sPath); | |||
| 311 | return true; | |||
| 312 | } | |||
| 313 | ||||
| 314 | rtl::OString HelpParser::GetOutpath( const rtl::OString& rPathX , const rtl::OString& sCur , const rtl::OString& rPathY ) | |||
| 315 | { | |||
| 316 | rtl::OString testpath = rPathX; | |||
| 317 | if (!testpath.endsWithL(RTL_CONSTASCII_STRINGPARAM("/")(&("/")[0]), ((sal_Int32)(sizeof ("/") / sizeof (("/")[0] ))-1))) { | |||
| 318 | testpath += "/"; | |||
| 319 | } | |||
| 320 | testpath += sCur; | |||
| 321 | testpath += "/"; | |||
| 322 | rtl::OString sRelativePath( rPathY ); | |||
| 323 | if (sRelativePath.matchL(RTL_CONSTASCII_STRINGPARAM("/")(&("/")[0]), ((sal_Int32)(sizeof ("/") / sizeof (("/")[0] ))-1))) { | |||
| 324 | sRelativePath = sRelativePath.copy(1); | |||
| 325 | } | |||
| 326 | testpath += sRelativePath; | |||
| 327 | testpath += "/"; | |||
| 328 | return testpath; | |||
| 329 | } | |||
| 330 | ||||
| 331 | void HelpParser::MakeDir(const rtl::OString& rPath) | |||
| 332 | { | |||
| 333 | rtl::OString sTPath(rPath.replaceAll("\\", "/")); | |||
| 334 | sal_Int32 cnt = helper::countOccurrences(sTPath, '/'); | |||
| 335 | rtl::OStringBuffer sCreateDir; | |||
| 336 | for (sal_uInt16 i = 0; i <= cnt; ++i) | |||
| 337 | { | |||
| 338 | sCreateDir.append(sTPath.getToken(i , '/')); | |||
| 339 | sCreateDir.append('/'); | |||
| 340 | #ifdef WNT | |||
| 341 | _mkdir( sCreateDir.getStr() ); | |||
| 342 | #else | |||
| 343 | mkdir( sCreateDir.getStr() , S_IRWXU(0400|0200|0100) | S_IRWXG((0400|0200|0100) >> 3) | S_IROTH((0400 >> 3) >> 3) | S_IXOTH((0100 >> 3) >> 3) ); | |||
| 344 | #endif | |||
| 345 | } | |||
| 346 | } | |||
| 347 | ||||
| 348 | ||||
| 349 | /* ProcessHelp Methode: search for en-US entry and replace it with the current language*/ | |||
| 350 | void HelpParser::ProcessHelp( LangHashMap* aLangHM , const rtl::OString& sCur , ResData *pResData , MergeDataFile& aMergeDataFile ){ | |||
| 351 | ||||
| 352 | XMLElement* pXMLElement = NULL__null; | |||
| 353 | PFormEntrys *pEntrys = NULL__null; | |||
| 354 | XMLData *data = NULL__null; | |||
| 355 | ||||
| 356 | rtl::OString sLId; | |||
| 357 | rtl::OString sGId; | |||
| 358 | ||||
| 359 | pEntrys = NULL__null; | |||
| 360 | ||||
| 361 | if( !sCur.equalsIgnoreAsciiCaseL(RTL_CONSTASCII_STRINGPARAM("en-US")(&("en-US")[0]), ((sal_Int32)(sizeof ("en-US") / sizeof ( ("en-US")[0]))-1)) ){ | |||
| ||||
| 362 | pXMLElement = (*aLangHM)[ "en-US" ]; | |||
| 363 | if( pXMLElement == NULL__null ) | |||
| 364 | { | |||
| 365 | printf("Error: Can't find en-US entry\n"); | |||
| 366 | } | |||
| 367 | if( pXMLElement != NULL__null ) | |||
| 368 | { | |||
| 369 | sLId = pXMLElement->GetOldref(); | |||
| 370 | pResData->sId = sLId; | |||
| 371 | ||||
| 372 | pEntrys = aMergeDataFile.GetPFormEntrys( pResData ); | |||
| 373 | if( pEntrys != NULL__null) | |||
| 374 | { | |||
| 375 | rtl::OString sNewText; | |||
| 376 | pEntrys->GetText( sNewText, STRING_TYP_TEXT0x0010, sCur , true ); | |||
| 377 | rtl::OUString sNewdata( | |||
| 378 | rtl::OStringToOUString(sNewText, RTL_TEXTENCODING_UTF8(((rtl_TextEncoding) 76)))); | |||
| 379 | if (!sNewdata.isEmpty()) | |||
| 380 | { | |||
| 381 | if( pXMLElement != NULL__null ) | |||
| 382 | { | |||
| 383 | data = new XMLData( sNewdata , NULL__null , true ); // Add new one | |||
| 384 | pXMLElement->RemoveAndDeleteAllChildren(); | |||
| 385 | pXMLElement->AddChild( data ); | |||
| 386 | aLangHM->erase( sCur ); | |||
| 387 | } | |||
| 388 | } | |||
| 389 | } | |||
| 390 | else if( pResData == NULL__null ) | |||
| 391 | { | |||
| 392 | fprintf(stdoutstdout,"Can't find GID=%s LID=%s TYP=%s\n", | |||
| 393 | pResData->sGId.getStr(), pResData->sId.getStr(), | |||
| 394 | pResData->sResTyp.getStr()); | |||
| ||||
| 395 | } | |||
| 396 | pXMLElement->ChangeLanguageTag( | |||
| 397 | rtl::OStringToOUString(sCur, RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)))); | |||
| 398 | } | |||
| 399 | ||||
| 400 | } | |||
| 401 | } | |||
| 402 | ||||
| 403 | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |