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 #include <svl/zforlist.hxx>
21 #include <tools/stream.hxx>
22 #include <osl/diagnose.h>
23 #include <dif.hxx>
24 #include <docpool.hxx>
25 #include <document.hxx>
26 #include <fprogressbar.hxx>
27 #include <ftools.hxx>
28 #include <patattr.hxx>
29 #include <scerrors.hxx>
30 #include <scitems.hxx>
31 #include <stringutil.hxx>
32 #include <table.hxx>
33 #include <memory>
34
35 const sal_Unicode pKeyTABLE[] = { 'T', 'A', 'B', 'L', 'E', 0 };
36 const sal_Unicode pKeyVECTORS[] = { 'V', 'E', 'C', 'T', 'O', 'R', 'S', 0 };
37 const sal_Unicode pKeyTUPLES[] = { 'T', 'U', 'P', 'L', 'E', 'S', 0 };
38 const sal_Unicode pKeyDATA[] = { 'D', 'A', 'T', 'A', 0 };
39 const sal_Unicode pKeyBOT[] = { 'B', 'O', 'T', 0 };
40 const sal_Unicode pKeyEOD[] = { 'E', 'O', 'D', 0 };
41 const sal_Unicode pKeyERROR[] = { 'E', 'R', 'R', 'O', 'R', 0 };
42 const sal_Unicode pKeyTRUE[] = { 'T', 'R', 'U', 'E', 0 };
43 const sal_Unicode pKeyFALSE[] = { 'F', 'A', 'L', 'S', 'E', 0 };
44 const sal_Unicode pKeyNA[] = { 'N', 'A', 0 };
45 const sal_Unicode pKeyV[] = { 'V', 0 };
46 const sal_Unicode pKey1_0[] = { '1', ',', '0', 0 };
47
ScImportDif(SvStream & rIn,ScDocument * pDoc,const ScAddress & rInsPos,const rtl_TextEncoding eVon)48 ErrCode ScFormatFilterPluginImpl::ScImportDif(SvStream& rIn, ScDocument* pDoc, const ScAddress& rInsPos,
49 const rtl_TextEncoding eVon )
50 {
51 DifParser aDifParser( rIn, *pDoc, eVon );
52
53 SCTAB nBaseTab = rInsPos.Tab();
54
55 TOPIC eTopic = T_UNKNOWN;
56 bool bSyntErrWarn = false;
57 bool bOverflowWarn = false;
58
59 OUStringBuffer& rData = aDifParser.m_aData;
60
61 rIn.Seek( 0 );
62
63 ScfStreamProgressBar aPrgrsBar( rIn, pDoc->GetDocumentShell() );
64
65 while( eTopic != T_DATA && eTopic != T_END )
66 {
67 eTopic = aDifParser.GetNextTopic();
68
69 aPrgrsBar.Progress();
70
71 const bool bData = !rData.isEmpty();
72
73 switch( eTopic )
74 {
75 case T_TABLE:
76 {
77 if( aDifParser.nVector != 0 || aDifParser.nVal != 1 )
78 bSyntErrWarn = true;
79 if( bData )
80 pDoc->RenameTab(nBaseTab, rData.toString());
81 }
82 break;
83 case T_VECTORS:
84 {
85 if( aDifParser.nVector != 0 )
86 bSyntErrWarn = true;
87 }
88 break;
89 case T_TUPLES:
90 {
91 if( aDifParser.nVector != 0 )
92 bSyntErrWarn = true;
93 }
94 break;
95 case T_DATA:
96 {
97 if( aDifParser.nVector != 0 || aDifParser.nVal != 0 )
98 bSyntErrWarn = true;
99 }
100 break;
101 case T_LABEL:
102 case T_COMMENT:
103 case T_SIZE:
104 case T_PERIODICITY:
105 case T_MAJORSTART:
106 case T_MINORSTART:
107 case T_TRUELENGTH:
108 case T_UINITS:
109 case T_DISPLAYUNITS:
110 case T_END:
111 case T_UNKNOWN:
112 break;
113 default:
114 OSL_FAIL( "ScImportDif - missing enum" );
115 }
116
117 }
118
119 if( eTopic == T_DATA )
120 { // data starts here
121 SCCOL nBaseCol = rInsPos.Col();
122
123 SCCOL nColCnt = SCCOL_MAX;
124 SCROW nRowCnt = rInsPos.Row();
125 DifAttrCache aAttrCache;
126
127 DATASET eCurrent = D_UNKNOWN;
128
129 ScSetStringParam aStrParam; // used to set string value without number detection.
130 aStrParam.setTextInput();
131
132 while( eCurrent != D_EOD )
133 {
134 eCurrent = aDifParser.GetNextDataset();
135
136 aPrgrsBar.Progress();
137 ScAddress aPos(nColCnt, nRowCnt, nBaseTab);
138
139 OUString aData = rData.toString();
140
141 switch( eCurrent )
142 {
143 case D_BOT:
144 if( nColCnt < SCCOL_MAX )
145 nRowCnt++;
146 nColCnt = nBaseCol;
147 break;
148 case D_EOD:
149 break;
150 case D_NUMERIC: // Number cell
151 if( nColCnt == SCCOL_MAX )
152 nColCnt = nBaseCol;
153
154 if( pDoc->ValidCol(nColCnt) && pDoc->ValidRow(nRowCnt) )
155 {
156 pDoc->EnsureTable(nBaseTab);
157
158 if( DifParser::IsV( aData.getStr() ) )
159 {
160 pDoc->SetValue(aPos, aDifParser.fVal);
161 aAttrCache.SetNumFormat( pDoc, nColCnt, nRowCnt,
162 aDifParser.nNumFormat );
163 }
164 else if( aData == pKeyTRUE || aData == pKeyFALSE )
165 {
166 pDoc->SetValue(aPos, aDifParser.fVal);
167 aAttrCache.SetNumFormat( pDoc, nColCnt, nRowCnt,
168 aDifParser.nNumFormat );
169 }
170 else if( aData == pKeyNA || aData == pKeyERROR )
171 {
172 pDoc->SetString(aPos, aData, &aStrParam);
173 }
174 else
175 {
176 OUString aTmp = "#IND:" + aData + "?";
177 pDoc->SetString(aPos, aTmp, &aStrParam);
178 }
179 }
180 else
181 bOverflowWarn = true;
182
183 nColCnt++;
184 break;
185 case D_STRING: // Text cell
186 if( nColCnt == SCCOL_MAX )
187 nColCnt = nBaseCol;
188
189 if( pDoc->ValidCol(nColCnt) && pDoc->ValidRow(nRowCnt) )
190 {
191 if (!aData.isEmpty())
192 {
193 pDoc->EnsureTable(nBaseTab);
194 pDoc->SetTextCell(aPos, aData);
195 }
196 }
197 else
198 bOverflowWarn = true;
199
200 nColCnt++;
201 break;
202 case D_UNKNOWN:
203 break;
204 case D_SYNT_ERROR:
205 break;
206 default:
207 OSL_FAIL( "ScImportDif - missing enum" );
208 }
209 }
210
211 aAttrCache.Apply( *pDoc, nBaseTab );
212 }
213 else
214 return SCERR_IMPORT_FORMAT;
215
216 if( bSyntErrWarn )
217
218 // FIXME: Add proper warning!
219 return SCWARN_IMPORT_RANGE_OVERFLOW;
220
221 else if( bOverflowWarn )
222 return SCWARN_IMPORT_RANGE_OVERFLOW;
223 else
224 return ERRCODE_NONE;
225 }
226
DifParser(SvStream & rNewIn,const ScDocument & rDoc,rtl_TextEncoding eCharSet)227 DifParser::DifParser( SvStream& rNewIn, const ScDocument& rDoc, rtl_TextEncoding eCharSet )
228 : fVal(0.0)
229 , nVector(0)
230 , nVal(0)
231 , nNumFormat(0)
232 , pNumFormatter(rDoc.GetFormatTable())
233 , rIn(rNewIn)
234 {
235 if ( rIn.GetStreamCharSet() != eCharSet )
236 {
237 OSL_FAIL( "CharSet passed overrides and modifies StreamCharSet" );
238 rIn.SetStreamCharSet( eCharSet );
239 }
240 rIn.StartReadingUnicodeText( eCharSet );
241 }
242
GetNextTopic()243 TOPIC DifParser::GetNextTopic()
244 {
245 enum STATE { S_VectorVal, S_Data, S_END, S_START, S_UNKNOWN, S_ERROR_L2 };
246
247 static const sal_Unicode pKeyLABEL[] = { 'L', 'A', 'B', 'E', 'L', 0 };
248 static const sal_Unicode pKeyCOMMENT[] = { 'C', 'O', 'M', 'M', 'E', 'N', 'T', 0 };
249 static const sal_Unicode pKeySIZE[] = { 'S', 'I', 'Z', 'E', 0 };
250 static const sal_Unicode pKeyPERIODICITY[] = { 'P', 'E', 'R', 'I', 'O', 'D', 'I', 'C', 'I', 'T', 'Y', 0 };
251 static const sal_Unicode pKeyMAJORSTART[] = { 'M', 'A', 'J', 'O', 'R', 'S', 'T', 'A', 'R', 'T', 0 };
252 static const sal_Unicode pKeyMINORSTART[] = { 'M', 'I', 'N', 'O', 'R', 'S', 'T', 'A', 'R', 'T', 0 };
253 static const sal_Unicode pKeyTRUELENGTH[] = { 'T', 'R', 'U', 'E', 'L', 'E', 'N', 'G', 'T', 'H', 0 };
254 static const sal_Unicode pKeyUINITS[] = { 'U', 'I', 'N', 'I', 'T', 'S', 0 };
255 static const sal_Unicode pKeyDISPLAYUNITS[] = { 'D', 'I', 'S', 'P', 'L', 'A', 'Y', 'U', 'N', 'I', 'T', 'S', 0 };
256 static const sal_Unicode pKeyUNKNOWN[] = { 0 };
257
258 static const sal_Unicode* ppKeys[] =
259 {
260 pKeyTABLE, // 0
261 pKeyVECTORS,
262 pKeyTUPLES,
263 pKeyDATA,
264 pKeyLABEL,
265 pKeyCOMMENT, // 5
266 pKeySIZE,
267 pKeyPERIODICITY,
268 pKeyMAJORSTART,
269 pKeyMINORSTART,
270 pKeyTRUELENGTH, // 10
271 pKeyUINITS,
272 pKeyDISPLAYUNITS,
273 pKeyUNKNOWN // 13
274 };
275
276 static const TOPIC pTopics[] =
277 {
278 T_TABLE, // 0
279 T_VECTORS,
280 T_TUPLES,
281 T_DATA,
282 T_LABEL,
283 T_COMMENT, // 5
284 T_SIZE,
285 T_PERIODICITY,
286 T_MAJORSTART,
287 T_MINORSTART,
288 T_TRUELENGTH, // 10
289 T_UINITS,
290 T_DISPLAYUNITS,
291 T_UNKNOWN // 13
292 };
293
294 STATE eS = S_START;
295 OUString aLine;
296
297 nVector = 0;
298 nVal = 0;
299 TOPIC eRet = T_UNKNOWN;
300
301 while( eS != S_END )
302 {
303 if( !ReadNextLine( aLine ) )
304 {
305 eS = S_END;
306 eRet = T_END;
307 }
308
309 switch( eS )
310 {
311 case S_START:
312 {
313 const sal_Unicode* pRef;
314 sal_uInt16 nCnt = 0;
315 bool bSearch = true;
316
317 pRef = ppKeys[ nCnt ];
318
319 while( bSearch )
320 {
321 if( aLine == pRef )
322 {
323 eRet = pTopics[ nCnt ];
324 bSearch = false;
325 }
326 else
327 {
328 nCnt++;
329 pRef = ppKeys[ nCnt ];
330 if( !*pRef )
331 bSearch = false;
332 }
333 }
334
335 if( *pRef )
336 eS = S_VectorVal;
337 else
338 eS = S_UNKNOWN;
339 }
340 break;
341 case S_VectorVal:
342 {
343 const sal_Unicode* pCur = aLine.getStr();
344
345 pCur = ScanIntVal( pCur, nVector );
346
347 if( pCur && *pCur == ',' )
348 {
349 pCur++;
350 ScanIntVal( pCur, nVal );
351 eS = S_Data;
352 }
353 else
354 eS = S_ERROR_L2;
355 }
356 break;
357 case S_Data:
358 OSL_ENSURE( aLine.getLength() >= 2,
359 "+GetNextTopic(): <String> is too short!" );
360 if( aLine.getLength() > 2 )
361 m_aData.append(aLine.subView(1, aLine.getLength() - 2));
362 else
363 m_aData.truncate();
364 eS = S_END;
365 break;
366 case S_END:
367 OSL_FAIL( "DifParser::GetNextTopic - unexpected state" );
368 break;
369 case S_UNKNOWN:
370 // skip 2 lines
371 ReadNextLine( aLine );
372 [[fallthrough]];
373 case S_ERROR_L2: // error happened in line 2
374 // skip 1 line
375 ReadNextLine( aLine );
376 eS = S_END;
377 break;
378 default:
379 OSL_FAIL( "DifParser::GetNextTopic - missing enum" );
380 }
381 }
382
383 return eRet;
384 }
385
lcl_DeEscapeQuotesDif(OUStringBuffer & rString)386 static void lcl_DeEscapeQuotesDif(OUStringBuffer& rString)
387 {
388 // Special handling for DIF import: Escaped (duplicated) quotes are resolved.
389 // Single quote characters are left in place because older versions didn't
390 // escape quotes in strings (and Excel doesn't when using the clipboard).
391 // The quotes around the string are removed before this function is called.
392
393 rString = rString.toString().replaceAll("\"\"", "\"");
394 }
395
396 // Determine if passed in string is numeric data and set fVal/nNumFormat if so
GetNumberDataset(const sal_Unicode * pPossibleNumericData)397 DATASET DifParser::GetNumberDataset( const sal_Unicode* pPossibleNumericData )
398 {
399 DATASET eRet = D_SYNT_ERROR;
400
401 OSL_ENSURE( pNumFormatter, "-DifParser::GetNumberDataset(): No Formatter, more fun!" );
402 OUString aTestVal( pPossibleNumericData );
403 sal_uInt32 nFormat = 0;
404 double fTmpVal;
405 if( pNumFormatter->IsNumberFormat( aTestVal, nFormat, fTmpVal ) )
406 {
407 fVal = fTmpVal;
408 nNumFormat = nFormat;
409 eRet = D_NUMERIC;
410 }
411 else
412 eRet = D_SYNT_ERROR;
413
414 return eRet;
415 }
416
ReadNextLine(OUString & rStr)417 bool DifParser::ReadNextLine( OUString& rStr )
418 {
419 if( aLookAheadLine.isEmpty() )
420 {
421 return rIn.ReadUniOrByteStringLine( rStr, rIn.GetStreamCharSet() );
422 }
423 else
424 {
425 rStr = aLookAheadLine;
426 aLookAheadLine.clear();
427 return true;
428 }
429 }
430
431 // Look ahead in the stream to determine if the next line is the first line of
432 // a valid data record structure
LookAhead()433 bool DifParser::LookAhead()
434 {
435 const sal_Unicode* pCurrentBuffer;
436 bool bValidStructure = false;
437
438 OSL_ENSURE( aLookAheadLine.isEmpty(), "*DifParser::LookAhead(): LookAhead called twice in a row" );
439 rIn.ReadUniOrByteStringLine( aLookAheadLine, rIn.GetStreamCharSet() );
440
441 pCurrentBuffer = aLookAheadLine.getStr();
442
443 switch( *pCurrentBuffer )
444 {
445 case '-': // Special Datatype
446 pCurrentBuffer++;
447
448 if( Is1_0( pCurrentBuffer ) )
449 {
450 bValidStructure = true;
451 }
452 break;
453 case '0': // Numeric Data
454 pCurrentBuffer++;
455 if( *pCurrentBuffer == ',' )
456 {
457 pCurrentBuffer++;
458 bValidStructure = ( GetNumberDataset(pCurrentBuffer) != D_SYNT_ERROR );
459 }
460 break;
461 case '1': // String Data
462 if( Is1_0( aLookAheadLine.getStr() ) )
463 {
464 bValidStructure = true;
465 }
466 break;
467 }
468 return bValidStructure;
469 }
470
GetNextDataset()471 DATASET DifParser::GetNextDataset()
472 {
473 DATASET eRet = D_UNKNOWN;
474 OUString aLine;
475 const sal_Unicode* pCurrentBuffer;
476
477 ReadNextLine( aLine );
478
479 pCurrentBuffer = aLine.getStr();
480
481 switch( *pCurrentBuffer )
482 {
483 case '-': // Special Datatype
484 pCurrentBuffer++;
485
486 if( Is1_0( pCurrentBuffer ) )
487 {
488 ReadNextLine( aLine );
489 if( IsBOT( aLine.getStr() ) )
490 eRet = D_BOT;
491 else if( IsEOD( aLine.getStr() ) )
492 eRet = D_EOD;
493 }
494 break;
495 case '0': // Numeric Data
496 pCurrentBuffer++; // value in fVal, 2. line in m_aData
497 if( *pCurrentBuffer == ',' )
498 {
499 pCurrentBuffer++;
500 eRet = GetNumberDataset(pCurrentBuffer);
501 OUString aTmpLine;
502 ReadNextLine( aTmpLine );
503 if ( eRet == D_SYNT_ERROR )
504 { // for broken records write "#ERR: data" to cell
505 m_aData = OUString::Concat("#ERR: ") + pCurrentBuffer + " (" + aTmpLine + ")";
506 eRet = D_STRING;
507 }
508 else
509 {
510 m_aData = aTmpLine;
511 }
512 }
513 break;
514 case '1': // String Data
515 if( Is1_0( aLine.getStr() ) )
516 {
517 ReadNextLine( aLine );
518 sal_Int32 nLineLength = aLine.getLength();
519 const sal_Unicode* pLine = aLine.getStr();
520
521 if( nLineLength >= 1 && *pLine == '"' )
522 {
523 // Quotes are not always escaped (duplicated), see lcl_DeEscapeQuotesDif
524 // A look ahead into the next line is needed in order to deal with
525 // multiline strings containing quotes
526 if( LookAhead() )
527 {
528 // Single line string
529 if( nLineLength >= 2 && pLine[nLineLength - 1] == '"' )
530 {
531 m_aData = aLine.subView( 1, nLineLength - 2 );
532 lcl_DeEscapeQuotesDif(m_aData);
533 eRet = D_STRING;
534 }
535 }
536 else
537 {
538 // Multiline string
539 m_aData = aLine.subView( 1 );
540 bool bContinue = true;
541 while ( bContinue )
542 {
543 m_aData.append("\n");
544 bContinue = !rIn.eof() && ReadNextLine( aLine );
545 if( bContinue )
546 {
547 nLineLength = aLine.getLength();
548 if( nLineLength >= 1 )
549 {
550 pLine = aLine.getStr();
551 bContinue = !LookAhead();
552 if( bContinue )
553 {
554 m_aData.append(aLine);
555 }
556 else if( pLine[nLineLength - 1] == '"' )
557 {
558 m_aData.append(aLine.subView(0, nLineLength -1));
559 lcl_DeEscapeQuotesDif(m_aData);
560 eRet = D_STRING;
561 }
562 }
563 }
564 }
565 }
566 }
567 }
568 break;
569 }
570
571 if( eRet == D_UNKNOWN )
572 ReadNextLine( aLine );
573
574 if( rIn.eof() )
575 eRet = D_EOD;
576
577 return eRet;
578 }
579
ScanIntVal(const sal_Unicode * pStart,sal_uInt32 & rRet)580 const sal_Unicode* DifParser::ScanIntVal( const sal_Unicode* pStart, sal_uInt32& rRet )
581 {
582 // eat leading whitespace, not specified, but seen in the wild
583 while (*pStart == ' ' || *pStart == '\t')
584 ++pStart;
585
586 sal_Unicode cCurrent = *pStart;
587
588 if( IsNumber( cCurrent ) )
589 rRet = static_cast<sal_uInt32>( cCurrent - '0' );
590 else
591 return nullptr;
592
593 pStart++;
594 cCurrent = *pStart;
595
596 while( IsNumber( cCurrent ) && rRet < ( 0xFFFFFFFF / 10 ) )
597 {
598 rRet *= 10;
599 rRet += static_cast<sal_uInt32>( cCurrent - '0' );
600
601 pStart++;
602 cCurrent = *pStart;
603 }
604
605 return pStart;
606 }
607
DifColumn()608 DifColumn::DifColumn ()
609 : mpCurrent(nullptr)
610 {
611 }
612
SetNumFormat(const ScDocument * pDoc,SCROW nRow,const sal_uInt32 nNumFormat)613 void DifColumn::SetNumFormat( const ScDocument* pDoc, SCROW nRow, const sal_uInt32 nNumFormat )
614 {
615 OSL_ENSURE( pDoc->ValidRow(nRow), "*DifColumn::SetNumFormat(): Row too big!" );
616
617 if( nNumFormat > 0 )
618 {
619 if(mpCurrent)
620 {
621 OSL_ENSURE( nRow > 0,
622 "*DifColumn::SetNumFormat(): more cannot be zero!" );
623 OSL_ENSURE( nRow > mpCurrent->nEnd,
624 "*DifColumn::SetNumFormat(): start from scratch?" );
625
626 if( mpCurrent->nNumFormat == nNumFormat && mpCurrent->nEnd == nRow - 1 )
627 mpCurrent->nEnd = nRow;
628 else
629 NewEntry( nRow, nNumFormat );
630 }
631 else
632 NewEntry(nRow,nNumFormat );
633 }
634 else
635 mpCurrent = nullptr;
636 }
637
NewEntry(const SCROW nPos,const sal_uInt32 nNumFormat)638 void DifColumn::NewEntry( const SCROW nPos, const sal_uInt32 nNumFormat )
639 {
640 maEntries.emplace_back();
641 mpCurrent = &maEntries.back();
642 mpCurrent->nStart = mpCurrent->nEnd = nPos;
643 mpCurrent->nNumFormat = nNumFormat;
644
645 }
646
Apply(ScDocument & rDoc,const SCCOL nCol,const SCTAB nTab)647 void DifColumn::Apply( ScDocument& rDoc, const SCCOL nCol, const SCTAB nTab )
648 {
649 ScPatternAttr aAttr( rDoc.GetPool() );
650 SfxItemSet &rItemSet = aAttr.GetItemSet();
651
652 for (const auto& rEntry : maEntries)
653 {
654 OSL_ENSURE( rEntry.nNumFormat > 0,
655 "+DifColumn::Apply(): Number format must not be 0!" );
656
657 rItemSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, rEntry.nNumFormat ) );
658
659 rDoc.ApplyPatternAreaTab( nCol, rEntry.nStart, nCol, rEntry.nEnd, nTab, aAttr );
660
661 rItemSet.ClearItem();
662 }
663 }
664
DifAttrCache()665 DifAttrCache::DifAttrCache()
666 {
667 }
668
~DifAttrCache()669 DifAttrCache::~DifAttrCache()
670 {
671 }
672
SetNumFormat(const ScDocument * pDoc,const SCCOL nCol,const SCROW nRow,const sal_uInt32 nNumFormat)673 void DifAttrCache::SetNumFormat( const ScDocument* pDoc, const SCCOL nCol, const SCROW nRow, const sal_uInt32 nNumFormat )
674 {
675 OSL_ENSURE( pDoc->ValidCol(nCol), "-DifAttrCache::SetNumFormat(): Col too big!" );
676
677 if( !maColMap.count(nCol) )
678 maColMap[ nCol ].reset( new DifColumn );
679
680 maColMap[ nCol ]->SetNumFormat( pDoc, nRow, nNumFormat );
681 }
682
Apply(ScDocument & rDoc,SCTAB nTab)683 void DifAttrCache::Apply( ScDocument& rDoc, SCTAB nTab )
684 {
685 for( SCCOL nCol : rDoc.GetColumnsRange(nTab, 0, rDoc.MaxCol()) )
686 {
687 if( maColMap.count(nCol) )
688 maColMap[ nCol ]->Apply( rDoc, nCol, nTab );
689 }
690 }
691
692 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
693