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 #undef SC_DLLIMPLEMENTATION
21
22 #include <svx/txencbox.hxx>
23
24 #include <global.hxx>
25 #include <scresid.hxx>
26 #include <impex.hxx>
27 #include <scuiasciiopt.hxx>
28 #include <strings.hrc>
29 #include <strings.hxx>
30 #include <csvtablebox.hxx>
31 #include <osl/thread.h>
32 #include <unotools/transliterationwrapper.hxx>
33
34 #include <optutil.hxx>
35 #include <com/sun/star/uno/Any.hxx>
36 #include <com/sun/star/uno/Sequence.hxx>
37 #include <miscuno.hxx>
38 #include <osl/diagnose.h>
39 #include <vcl/svapp.hxx>
40 #include <comphelper/lok.hxx>
41 #include <comphelper/sequence.hxx>
42 #include <o3tl/string_view.hxx>
43
44 #include <unicode/ucsdet.h>
45 #include <sfx2/objsh.hxx>
46 #include <svx/txenctab.hxx>
47 #include <unotools/filteroptions_settings.hxx>
48 #include <unotools/viewoptions.hxx>
49
50 //! TODO make dynamic
51 const SCSIZE ASCIIDLG_MAXROWS = MAXROWCOUNT;
52
53 // Maximum number of source lines to concatenate while generating the preview
54 // for one logical line. This may result in a wrong preview if the actual
55 // number of embedded line feeds is greater, but a number too high would take
56 // too much time (loop excessively if unlimited and large data) if none of the
57 // selected separators are actually used in data but a field at start of line
58 // is quoted.
59 constexpr sal_uInt32 kMaxEmbeddedLinefeeds = 500;
60
61 using namespace com::sun::star::uno;
62
63 namespace {
64 // The values of this enum are stored in config's "SeparatorType" node
65 enum SeparatorType
66 {
67 FIXED = 0,
68 SEPARATOR = 1,
69 DETECT_SEPARATOR = 2,
70 };
71
72 }
73
74 // Config items for all three paths are defined in
75 // officecfg/registry/schema/org/openoffice/Office/Calc.xcs
76 // If not, options are neither loaded nor saved.
77 constexpr OUString CSVIO_MergeDelimiters = u"MergeDelimiters"_ustr;
78 constexpr OUString CSVIO_Separators = u"Separators"_ustr;
79 constexpr OUString CSVIO_TextSeparators = u"TextSeparators"_ustr;
80 constexpr OUString CSVIO_RemoveSpace = u"RemoveSpace"_ustr;
81 constexpr OUString CSVIO_EvaluateFormulas = u"EvaluateFormulas"_ustr;
82 constexpr OUString CSVIO_SeparatorType = u"SeparatorType"_ustr;
83 constexpr OUString CSVIO_FromRow = u"FromRow"_ustr;
84 constexpr OUString CSVIO_Encoding = u"Encoding"_ustr;
85 constexpr OUString CSVIO_QuotedAsText = u"QuotedFieldAsText"_ustr;
86 constexpr OUString CSVIO_DetectSpecialNum = u"DetectSpecialNumbers"_ustr;
87 constexpr OUString CSVIO_DetectScientificNum = u"DetectScientificNumbers"_ustr;
88 constexpr OUString CSVIO_Language = u"Language"_ustr;
89 constexpr OUString CSVIO_SkipEmptyCells = u"SkipEmptyCells"_ustr;
90
lcl_FillCombo(weld::ComboBox & rCombo,std::u16string_view rList,sal_Unicode cSelect)91 static void lcl_FillCombo(weld::ComboBox& rCombo, std::u16string_view rList, sal_Unicode cSelect)
92 {
93 OUString aStr;
94 if (!rList.empty())
95 {
96 sal_Int32 nIdx {0};
97 do
98 {
99 const OUString sEntry {o3tl::getToken(rList, 0, '\t', nIdx)};
100 rCombo.append_text(sEntry);
101 if (nIdx>0 && static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nIdx))) == cSelect)
102 aStr = sEntry;
103 }
104 while (nIdx>0);
105 }
106
107 if ( cSelect )
108 {
109 if (aStr.isEmpty())
110 aStr = OUString(cSelect); // Ascii
111
112 rCombo.set_entry_text(aStr);
113 }
114 }
115
lcl_CharFromCombo(const weld::ComboBox & rCombo,std::u16string_view rList)116 static sal_Unicode lcl_CharFromCombo(const weld::ComboBox& rCombo, std::u16string_view rList)
117 {
118 sal_Unicode c = 0;
119 OUString aStr = rCombo.get_active_text();
120 if ( !aStr.isEmpty() && !rList.empty() )
121 {
122 sal_Int32 nIdx {0};
123 OUString sToken {o3tl::getToken(rList, 0, '\t', nIdx)};
124 while (nIdx>0)
125 {
126 if ( ScGlobal::GetTransliteration().isEqual( aStr, sToken ) )
127 {
128 sal_Int32 nTmpIdx {nIdx};
129 c = static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nTmpIdx)));
130 }
131 // Skip to next token at even position
132 sToken = o3tl::getToken(rList, 1, '\t', nIdx);
133 }
134 if (!c)
135 {
136 sal_Unicode cFirst = aStr[0];
137 // #i24235# first try the first character of the string directly
138 if( (aStr.getLength() == 1) || (cFirst < '0') || (cFirst > '9') )
139 c = cFirst;
140 else // keep old behaviour for compatibility (i.e. "39" -> "'")
141 c = static_cast<sal_Unicode>(aStr.toInt32()); // Ascii
142 }
143 }
144 return c;
145 }
146
lcl_GetConfigPath(ScImportAsciiCall eCall)147 static OUString lcl_GetConfigPath(ScImportAsciiCall eCall)
148 {
149 switch(eCall)
150 {
151 case SC_IMPORTFILE:
152 return u"Office.Calc/Dialogs/CSVImport"_ustr;
153 case SC_PASTETEXT:
154 return u"Office.Calc/Dialogs/ClipboardTextImport"_ustr;
155 case SC_TEXTTOCOLUMNS:
156 default:
157 return u"Office.Calc/Dialogs/TextToColumnsImport"_ustr;
158 }
159 }
160
lcl_LoadSeparators(ScImportAsciiCall eCall,OUString & rFieldSeparators,OUString & rTextSeparators,bool & rMergeDelimiters,bool & rQuotedAsText,bool & rDetectSpecialNum,bool & rDetectScientificNum,SeparatorType & rSepType,sal_Int32 & rFromRow,rtl_TextEncoding & rEncoding,sal_Int32 & rLanguage,bool & rSkipEmptyCells,bool & rRemoveSpace,bool & rEvaluateFormulas)161 static void lcl_LoadSeparators(ScImportAsciiCall eCall, OUString& rFieldSeparators, OUString& rTextSeparators,
162 bool& rMergeDelimiters, bool& rQuotedAsText, bool& rDetectSpecialNum, bool& rDetectScientificNum,
163 SeparatorType& rSepType, sal_Int32& rFromRow, rtl_TextEncoding& rEncoding,
164 sal_Int32& rLanguage, bool& rSkipEmptyCells, bool& rRemoveSpace,
165 bool& rEvaluateFormulas)
166 {
167 ScLinkConfigItem aItem(lcl_GetConfigPath(eCall));
168 const Sequence<OUString> aNames = aItem.GetNodeNames({});
169 const Sequence<Any> aValues = aItem.GetProperties(aNames);
170
171 rEncoding = RTL_TEXTENCODING_DONTKNOW;
172
173 for (sal_Int32 i = 0; i < aNames.getLength(); ++i)
174 {
175 const OUString& name = aNames[i];
176 const Any& value = aValues[i];
177 if (!value.hasValue())
178 continue;
179 if (name == CSVIO_MergeDelimiters)
180 rMergeDelimiters = ScUnoHelpFunctions::GetBoolFromAny(value);
181 else if (name == CSVIO_RemoveSpace)
182 rRemoveSpace = ScUnoHelpFunctions::GetBoolFromAny(value);
183 else if (name == CSVIO_Separators)
184 value >>= rFieldSeparators;
185 else if (name == CSVIO_TextSeparators)
186 value >>= rTextSeparators;
187 else if (name == CSVIO_SeparatorType)
188 rSepType = static_cast<SeparatorType>(ScUnoHelpFunctions::GetInt16FromAny(value));
189 else if (name == CSVIO_EvaluateFormulas)
190 rEvaluateFormulas = ScUnoHelpFunctions::GetBoolFromAny(value);
191 else if (name == CSVIO_FromRow)
192 value >>= rFromRow;
193 else if (name == CSVIO_Encoding)
194 value >>= rEncoding;
195 else if (name == CSVIO_QuotedAsText)
196 value >>= rQuotedAsText;
197 else if (name == CSVIO_DetectSpecialNum)
198 value >>= rDetectSpecialNum;
199 else if (name == CSVIO_DetectScientificNum)
200 value >>= rDetectScientificNum;
201 else if (name == CSVIO_Language)
202 value >>= rLanguage;
203 else if (name == CSVIO_SkipEmptyCells)
204 rSkipEmptyCells = ScUnoHelpFunctions::GetBoolFromAny(value);
205 }
206 }
207
lcl_SaveSeparators(ScImportAsciiCall eCall,const OUString & sFieldSeparators,const OUString & sTextSeparators,bool bMergeDelimiters,bool bQuotedAsText,bool bDetectSpecialNum,bool bDetectScientificNum,SeparatorType rSepType,sal_Int32 nFromRow,rtl_TextEncoding eEncoding,sal_Int32 nLanguage,bool bSkipEmptyCells,bool bRemoveSpace,bool bEvaluateFormulas)208 static void lcl_SaveSeparators(ScImportAsciiCall eCall,
209 const OUString& sFieldSeparators, const OUString& sTextSeparators, bool bMergeDelimiters, bool bQuotedAsText,
210 bool bDetectSpecialNum, bool bDetectScientificNum, SeparatorType rSepType, sal_Int32 nFromRow,
211 rtl_TextEncoding eEncoding, sal_Int32 nLanguage, bool bSkipEmptyCells, bool bRemoveSpace, bool bEvaluateFormulas)
212 {
213 ScLinkConfigItem aItem(lcl_GetConfigPath(eCall));
214 std::unordered_map<OUString, Any> properties;
215
216 for (const OUString& name : aItem.GetNodeNames({}))
217 {
218 if (name == CSVIO_MergeDelimiters)
219 properties[name] <<= bMergeDelimiters;
220 else if (name == CSVIO_RemoveSpace)
221 properties[name] <<= bRemoveSpace;
222 else if (name == CSVIO_Separators)
223 properties[name] <<= sFieldSeparators;
224 else if (name == CSVIO_TextSeparators)
225 properties[name] <<= sTextSeparators;
226 else if (name == CSVIO_EvaluateFormulas)
227 properties[name] <<= bEvaluateFormulas;
228 else if (name == CSVIO_SeparatorType)
229 properties[name] <<= static_cast<sal_Int16>(rSepType);
230 else if (name == CSVIO_FromRow)
231 properties[name] <<= nFromRow;
232 else if (name == CSVIO_Encoding)
233 properties[name] <<= eEncoding;
234 else if (name == CSVIO_QuotedAsText)
235 properties[name] <<= bQuotedAsText;
236 else if (name == CSVIO_DetectSpecialNum)
237 properties[name] <<= bDetectSpecialNum;
238 else if (name == CSVIO_DetectScientificNum)
239 properties[name] <<= bDetectScientificNum;
240 else if (name == CSVIO_Language)
241 properties[name] <<= nLanguage;
242 else if (name == CSVIO_SkipEmptyCells)
243 properties[name] <<= bSkipEmptyCells;
244 }
245
246 aItem.PutProperties(comphelper::mapKeysToSequence(properties),
247 comphelper::mapValuesToSequence(properties));
248 }
249
ScImportAsciiDlg(weld::Window * pParent,std::u16string_view aDatName,SvStream * pInStream,ScImportAsciiCall eCall)250 ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aDatName,
251 SvStream* pInStream, ScImportAsciiCall eCall)
252 : GenericDialogController(pParent, u"modules/scalc/ui/textimportcsv.ui"_ustr, u"TextImportCsvDialog"_ustr)
253 , mpDatStream(pInStream)
254 , mnStreamPos(pInStream ? pInStream->Tell() : 0)
255 , mnStreamInitPos(mnStreamPos)
256 , mnRowPosCount(0)
257 , mcTextSep(ScAsciiOptions::cDefaultTextSep)
258 , meDetectedCharSet(RTL_TEXTENCODING_DONTKNOW)
259 , mbCharSetDetect(true)
260 , meCall(eCall)
261 , mxFtCharSet(m_xBuilder->weld_label(u"textcharset"_ustr))
262 , mxLbCharSet(new SvxTextEncodingBox(m_xBuilder->weld_combo_box(u"charset"_ustr)))
263 , mxFtDetectedCharSet(m_xBuilder->weld_label(u"textdetectedcharset"_ustr))
264 , mxFtCustomLang(m_xBuilder->weld_label(u"textlanguage"_ustr))
265 , mxLbCustomLang(new SvxLanguageBox(m_xBuilder->weld_combo_box(u"language"_ustr)))
266 , mxFtRow(m_xBuilder->weld_label(u"textfromrow"_ustr))
267 , mxNfRow(m_xBuilder->weld_spin_button(u"fromrow"_ustr))
268 , mxRbDetectSep(m_xBuilder->weld_radio_button(u"todetectseparator"_ustr))
269 , mxRbFixed(m_xBuilder->weld_radio_button(u"tofixedwidth"_ustr))
270 , mxRbSeparated(m_xBuilder->weld_radio_button(u"toseparatedby"_ustr))
271 , mxCkbTab(m_xBuilder->weld_check_button(u"tab"_ustr))
272 , mxCkbSemicolon(m_xBuilder->weld_check_button(u"semicolon"_ustr))
273 , mxCkbComma(m_xBuilder->weld_check_button(u"comma"_ustr))
274 , mxCkbRemoveSpace(m_xBuilder->weld_check_button(u"removespace"_ustr))
275 , mxCkbSpace(m_xBuilder->weld_check_button(u"space"_ustr))
276 , mxCkbOther(m_xBuilder->weld_check_button(u"other"_ustr))
277 , mxEdOther(m_xBuilder->weld_entry(u"inputother"_ustr))
278 , mxCkbMergeDelimiters(m_xBuilder->weld_check_button(u"mergedelimiters"_ustr))
279 , mxFtTextSep(m_xBuilder->weld_label(u"texttextdelimiter"_ustr))
280 , mxCbTextSep(m_xBuilder->weld_combo_box(u"textdelimiter"_ustr))
281 , mxCkbQuotedAsText(m_xBuilder->weld_check_button(u"quotedfieldastext"_ustr))
282 , mxCkbDetectNumber(m_xBuilder->weld_check_button(u"detectspecialnumbers"_ustr))
283 , mxCkbDetectScientificNumber(m_xBuilder->weld_check_button(u"detectscientificnumbers"_ustr))
284 , mxCkbEvaluateFormulas(m_xBuilder->weld_check_button(u"evaluateformulas"_ustr))
285 , mxCkbSkipEmptyCells(m_xBuilder->weld_check_button(u"skipemptycells"_ustr))
286 , mxLbType(m_xBuilder->weld_combo_box(u"columntype"_ustr))
287 , mxAltTitle(m_xBuilder->weld_label(u"textalttitle"_ustr))
288 , mxTableBox(new ScCsvTableBox(*m_xBuilder))
289 , mxCkbAlwaysShowOnImport(m_xBuilder->weld_check_button(u"alwaysshowonimport"_ustr))
290 {
291 SvtViewOptions aDlgOpt(EViewType::Dialog, "TextImportCsvDialog");
292 if (aDlgOpt.Exists())
293 m_xDialog->set_window_state(aDlgOpt.GetWindowState());
294
295 OUString aName = m_xDialog->get_title();
296 switch (meCall)
297 {
298 case SC_TEXTTOCOLUMNS:
299 m_xDialog->set_title(mxAltTitle->get_label());
300 break;
301 case SC_IMPORTFILE:
302 if (!comphelper::LibreOfficeKit::isActive())
303 {
304 aName += OUString::Concat(" - [") + aDatName + "]";
305 m_xDialog->set_title(aName);
306 }
307 mxCkbAlwaysShowOnImport->show();
308 mxCkbAlwaysShowOnImport->set_active(
309 utl::isShowFilterOptionsDialog(SC_TEXT_CSV_FILTER_NAME));
310 break;
311 default:
312 break;
313 }
314
315 // To be able to prefill the correct values based on the file extension
316 bool bIsTSV = (o3tl::endsWithIgnoreAsciiCase(aDatName, ".tsv") || o3tl::endsWithIgnoreAsciiCase(aDatName, ".tab"));
317
318 // Default options are set in officecfg/registry/schema/org/openoffice/Office/Calc.xcs
319 OUString sFieldSeparators(u",;\t"_ustr);
320 OUString sTextSeparators(mcTextSep);
321 bool bMergeDelimiters = false;
322 SeparatorType eSepType = DETECT_SEPARATOR;
323 bool bQuotedFieldAsText = false;
324 bool bDetectSpecialNum = true;
325 bool bDetectScientificNum = true;
326 bool bEvaluateFormulas = (meCall != SC_IMPORTFILE);
327 bool bSkipEmptyCells = true;
328 bool bRemoveSpace = false;
329 sal_Int32 nFromRow = 1;
330 rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW;
331 sal_Int32 nLanguage = 0;
332
333 lcl_LoadSeparators ( meCall, sFieldSeparators, sTextSeparators, bMergeDelimiters,
334 bQuotedFieldAsText, bDetectSpecialNum, bDetectScientificNum, eSepType, nFromRow,
335 eEncoding, nLanguage, bSkipEmptyCells, bRemoveSpace, bEvaluateFormulas);
336
337 maFieldSeparators = sFieldSeparators;
338
339 if( bMergeDelimiters && !bIsTSV )
340 mxCkbMergeDelimiters->set_active(true);
341 if (bQuotedFieldAsText)
342 mxCkbQuotedAsText->set_active(true);
343 if (bRemoveSpace)
344 mxCkbRemoveSpace->set_active(true);
345 if (bDetectSpecialNum)
346 {
347 mxCkbDetectNumber->set_active(true);
348 bDetectScientificNum = true;
349 mxCkbDetectScientificNumber->set_sensitive(false);
350 }
351 if (bDetectScientificNum)
352 mxCkbDetectScientificNumber->set_active(true);
353 if (bEvaluateFormulas)
354 mxCkbEvaluateFormulas->set_active(true);
355 if (bSkipEmptyCells)
356 mxCkbSkipEmptyCells->set_active(true);
357 if (eSepType == SeparatorType::FIXED)
358 {
359 if (bIsTSV)
360 {
361 eSepType = SeparatorType::SEPARATOR;
362 mxRbSeparated->set_active(true);
363 }
364 else
365 mxRbFixed->set_active(true);
366 }
367 else if (eSepType == SeparatorType::SEPARATOR)
368 mxRbSeparated->set_active(true);
369 else
370 mxRbDetectSep->set_active(true);
371
372 // Detect character set only once and then use it for "Detect" option.
373 SvStreamEndian eEndian;
374 SfxObjectShell::DetectCharSet(*mpDatStream, meDetectedCharSet, eEndian);
375 if (meDetectedCharSet == RTL_TEXTENCODING_UNICODE)
376 mpDatStream->SetEndian(eEndian);
377 else if ( meDetectedCharSet == RTL_TEXTENCODING_DONTKNOW )
378 {
379 meDetectedCharSet = osl_getThreadTextEncoding();
380 // Prefer UTF-8, as UTF-16 would have already been detected from the stream.
381 // This gives a better chance that the file is going to be opened correctly.
382 if ( meDetectedCharSet == RTL_TEXTENCODING_UNICODE && mpDatStream )
383 meDetectedCharSet = RTL_TEXTENCODING_UTF8;
384 }
385
386 if (bIsTSV)
387 SetSeparators('\t');
388 else
389 SetSeparators(0);
390
391 // Get Separators from the dialog (empty are set from default)
392 maFieldSeparators = GetActiveSeparators();
393
394 // *** Separator characters ***
395 lcl_FillCombo( *mxCbTextSep, SCSTR_TEXTSEP, mcTextSep );
396 mxCbTextSep->set_entry_text(sTextSeparators);
397 // tdf#69207 - use selected text delimiter to parse the provided data
398 mcTextSep = lcl_CharFromCombo(*mxCbTextSep, SCSTR_TEXTSEP);
399
400 Link<weld::Toggleable&,void> aSeparatorClickHdl =LINK( this, ScImportAsciiDlg, SeparatorClickHdl );
401 Link<weld::Toggleable&,void> aOtherOptionsClickHdl =LINK( this, ScImportAsciiDlg, OtherOptionsClickHdl );
402 mxCbTextSep->connect_changed( LINK( this, ScImportAsciiDlg, SeparatorComboBoxHdl ) );
403 mxCkbTab->connect_toggled( aSeparatorClickHdl );
404 mxCkbSemicolon->connect_toggled( aSeparatorClickHdl );
405 mxCkbComma->connect_toggled( aSeparatorClickHdl );
406 mxCkbMergeDelimiters->connect_toggled( aSeparatorClickHdl );
407 mxCkbSpace->connect_toggled( aSeparatorClickHdl );
408 mxCkbRemoveSpace->connect_toggled( aSeparatorClickHdl );
409 mxCkbOther->connect_toggled( aSeparatorClickHdl );
410 mxEdOther->connect_changed(LINK(this, ScImportAsciiDlg, SeparatorEditHdl));
411 mxCkbQuotedAsText->connect_toggled( aOtherOptionsClickHdl );
412 mxCkbDetectNumber->connect_toggled( aOtherOptionsClickHdl );
413 mxCkbDetectScientificNumber->connect_toggled( aOtherOptionsClickHdl );
414 mxCkbEvaluateFormulas->connect_toggled( aOtherOptionsClickHdl );
415 mxCkbSkipEmptyCells->connect_toggled( aOtherOptionsClickHdl );
416
417 // *** text encoding ListBox ***
418 // all encodings allowed, including Unicode, but subsets are excluded
419 mxLbCharSet->FillFromTextEncodingTable( true );
420 // Insert one "SYSTEM" entry for compatibility in AsciiOptions and system
421 // independent document linkage.
422 mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_DONTKNOW, ScResId( SCSTR_CHARSET_USER ) );
423 // Insert one for detecting charset.
424 mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_USER_DETECTED, "- " + ScResId( SCSTR_AUTOMATIC ) + " -" );
425
426 // Clipboard is always Unicode, and TextToColumns doesn't use encoding.
427 if (meCall != SC_IMPORTFILE)
428 eEncoding = RTL_TEXTENCODING_UNICODE;
429 else if (eEncoding == RTL_TEXTENCODING_DONTKNOW)
430 eEncoding = RTL_TEXTENCODING_USER_DETECTED;
431
432 mxLbCharSet->SelectTextEncoding(eEncoding);
433
434 SetSelectedCharSet();
435 mxLbCharSet->connect_changed( LINK( this, ScImportAsciiDlg, CharSetHdl ) );
436
437 mxLbCustomLang->SetLanguageList(
438 SvxLanguageListFlags::ALL | SvxLanguageListFlags::ONLY_KNOWN, false, false);
439 mxLbCustomLang->InsertLanguage(LANGUAGE_SYSTEM);
440 mxLbCustomLang->set_active_id(static_cast<LanguageType>(nLanguage));
441
442 // *** column type ListBox ***
443 OUString aColumnUser( ScResId( SCSTR_COLUMN_USER ) );
444 for (sal_Int32 nIdx {0}; nIdx>=0; )
445 {
446 mxLbType->append_text(aColumnUser.getToken(0, ';', nIdx));
447 }
448
449 mxLbType->connect_changed( LINK( this, ScImportAsciiDlg, LbColTypeHdl ) );
450 mxLbType->set_sensitive(false);
451
452 // *** table box preview ***
453 mxTableBox->Init();
454 mxTableBox->SetUpdateTextHdl( LINK( this, ScImportAsciiDlg, UpdateTextHdl ) );
455 mxTableBox->InitTypes( *mxLbType );
456 mxTableBox->SetColTypeHdl( LINK( this, ScImportAsciiDlg, ColTypeHdl ) );
457
458 mxRbDetectSep->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) );
459 mxRbSeparated->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) );
460 mxRbFixed->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) );
461
462 RbSepFix();
463
464 UpdateVertical();
465
466 mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS );
467
468 if (nFromRow != 1)
469 {
470 mxNfRow->set_value(nFromRow);
471 // tdf#163638 - show visual indicator for from rows
472 mxTableBox->GetGrid().Execute(CSVCMD_SETFIRSTIMPORTLINE, nFromRow - 1);
473 }
474 mxNfRow->connect_value_changed(LINK(this, ScImportAsciiDlg, FirstRowHdl));
475
476 if (meCall == SC_TEXTTOCOLUMNS)
477 {
478 m_xBuilder->weld_frame(u"frame1"_ustr)->hide(); // the whole "Import" section
479
480 mxFtCharSet->set_sensitive(false);
481 mxLbCharSet->set_sensitive(false);
482 mxFtCustomLang->set_sensitive(false);
483 mxLbCustomLang->set_active_id(LANGUAGE_SYSTEM);
484 mxLbCustomLang->set_sensitive(false);
485 mxFtRow->set_sensitive(false);
486 mxNfRow->set_sensitive(false);
487
488 // Quoted field as text option is not used for text-to-columns mode.
489 mxCkbQuotedAsText->set_active(false);
490 mxCkbQuotedAsText->set_sensitive(false);
491
492 // Always detect special numbers for text-to-columns mode.
493 mxCkbDetectNumber->set_active(true);
494 mxCkbDetectNumber->set_sensitive(false);
495 mxCkbDetectScientificNumber->set_active(true);
496 mxCkbDetectScientificNumber->set_sensitive(false);
497 }
498 if (meCall == SC_IMPORTFILE)
499 {
500 //Empty cells in imported file are empty
501 mxCkbSkipEmptyCells->set_active(false);
502 mxCkbSkipEmptyCells->hide();
503 }
504
505 if (comphelper::LibreOfficeKit::isActive())
506 m_xBuilder->weld_button(u"cancel"_ustr)->hide();
507 m_xDialog->SetInstallLOKNotifierHdl(LINK(this, ScImportAsciiDlg, InstallLOKNotifierHdl));
508 }
509
IMPL_STATIC_LINK_NOARG(ScImportAsciiDlg,InstallLOKNotifierHdl,void *,vcl::ILibreOfficeKitNotifier *)510 IMPL_STATIC_LINK_NOARG(ScImportAsciiDlg, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*)
511 {
512 return GetpApp();
513 }
514
~ScImportAsciiDlg()515 ScImportAsciiDlg::~ScImportAsciiDlg()
516 {
517 SvtViewOptions aDlgOpt(EViewType::Dialog, "TextImportCsvDialog");
518 aDlgOpt.SetWindowState(m_xDialog->get_window_state(vcl::WindowDataMask::PosSize));
519 }
520
GetLine(sal_uLong nLine,OUString & rText,sal_Unicode & rcDetectSep)521 bool ScImportAsciiDlg::GetLine( sal_uLong nLine, OUString &rText, sal_Unicode& rcDetectSep )
522 {
523 if (nLine >= ASCIIDLG_MAXROWS || !mpDatStream)
524 return false;
525
526 bool bRet = true;
527 bool bFixed = mxRbFixed->get_active();
528
529 if (!mpRowPosArray)
530 mpRowPosArray.reset( new sal_uLong[ASCIIDLG_MAXROWS + 2] );
531
532 if (!mnRowPosCount) // complete re-fresh
533 {
534 memset( mpRowPosArray.get(), 0, sizeof(mpRowPosArray[0]) * (ASCIIDLG_MAXROWS+2));
535
536 Seek(0);
537 mpDatStream->StartReadingUnicodeText( mpDatStream->GetStreamCharSet() );
538
539 mnStreamPos = mpDatStream->Tell();
540 mpRowPosArray[mnRowPosCount] = mnStreamPos;
541 }
542
543 if (nLine >= mnRowPosCount)
544 {
545 // need to work out some more line information
546 do
547 {
548 if (!Seek(mpRowPosArray[mnRowPosCount]) || !mpDatStream->good())
549 {
550 bRet = false;
551 break;
552 }
553 rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators,
554 mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds);
555 mnStreamPos = mpDatStream->Tell();
556 mpRowPosArray[++mnRowPosCount] = mnStreamPos;
557 } while (nLine >= mnRowPosCount && mpDatStream->good());
558 if (mpDatStream->eof() && mnRowPosCount &&
559 mnStreamPos == mpRowPosArray[mnRowPosCount-1])
560 {
561 // the very end, not even an empty line read
562 bRet = false;
563 --mnRowPosCount;
564 }
565 }
566 else
567 {
568 Seek( mpRowPosArray[nLine]);
569 rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators, mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds);
570 mnStreamPos = mpDatStream->Tell();
571 }
572
573 // If the file content isn't unicode, ReadUniStringLine
574 // may try to seek beyond the file's end and cause a CANTSEEK error
575 // (depending on the stream type). The error code has to be cleared,
576 // or further read operations (including non-unicode) will fail.
577 if ( mpDatStream->GetError() == ERRCODE_IO_CANTSEEK )
578 mpDatStream->ResetError();
579
580 ScImportExport::EmbeddedNullTreatment( rText);
581
582 return bRet;
583 }
584
GetOptions(ScAsciiOptions & rOpt)585 void ScImportAsciiDlg::GetOptions( ScAsciiOptions& rOpt )
586 {
587 rOpt.SetCharSet( meCharSet );
588 rOpt.SetCharSetSystem( mbCharSetSystem );
589 rOpt.SetLanguage(mxLbCustomLang->get_active_id());
590 rOpt.SetFixedLen( mxRbFixed->get_active() );
591 rOpt.SetStartRow( mxNfRow->get_value() );
592 mxTableBox->FillColumnData( rOpt );
593 if( mxRbSeparated->get_active() || mxRbDetectSep->get_active())
594 {
595 rOpt.SetFieldSeps( GetActiveSeparators() );
596 rOpt.SetMergeSeps( mxCkbMergeDelimiters->get_active() );
597 rOpt.SetRemoveSpace( mxCkbRemoveSpace->get_active() );
598 rOpt.SetTextSep( lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ) );
599 }
600
601 rOpt.SetQuotedAsText(mxCkbQuotedAsText->get_active());
602 rOpt.SetDetectSpecialNumber(mxCkbDetectNumber->get_active());
603 rOpt.SetDetectScientificNumber(mxCkbDetectScientificNumber->get_active());
604 rOpt.SetEvaluateFormulas(mxCkbEvaluateFormulas->get_active());
605 rOpt.SetSkipEmptyCells(mxCkbSkipEmptyCells->get_active());
606 }
607
SaveParameters()608 void ScImportAsciiDlg::SaveParameters()
609 {
610 if (mxCkbAlwaysShowOnImport->get_visible())
611 {
612 bool value(mxCkbAlwaysShowOnImport->get_active());
613 if (value != utl::isShowFilterOptionsDialog(SC_TEXT_CSV_FILTER_NAME))
614 {
615 auto pChange(comphelper::ConfigurationChanges::create());
616 auto xFilterDialogSettings(
617 utl::getSettingsForFilterOptions(SC_TEXT_CSV_FILTER_NAME, pChange));
618 xFilterDialogSettings->setPropertyValue(u"show"_ustr, Any(value));
619 pChange->commit();
620 }
621 }
622 lcl_SaveSeparators(meCall, GetSeparators(), mxCbTextSep->get_active_text(), mxCkbMergeDelimiters->get_active(),
623 mxCkbQuotedAsText->get_active(), mxCkbDetectNumber->get_active(), mxCkbDetectScientificNumber->get_active(),
624 mxRbFixed->get_active() ? FIXED : (mxRbDetectSep->get_active() ? DETECT_SEPARATOR : SEPARATOR),
625 mxNfRow->get_value(),
626 mxLbCharSet->GetSelectTextEncoding(),
627 static_cast<sal_uInt16>(mxLbCustomLang->get_active_id()),
628 mxCkbSkipEmptyCells->get_active(), mxCkbRemoveSpace->get_active(),
629 mxCkbEvaluateFormulas->get_active());
630 }
631
SetSeparators(sal_Unicode cSep)632 void ScImportAsciiDlg::SetSeparators( sal_Unicode cSep )
633 {
634 if (cSep)
635 {
636 // Exclusively set a separator, maFieldSeparators needs not be
637 // modified, it's obtained by GetSeparators() after this call.
638 static constexpr sal_Unicode aSeps[] = { '\t', ';', ',', ' ' };
639 for (const sal_Unicode c : aSeps)
640 {
641 const bool bSet = (c == cSep);
642 switch (c)
643 {
644 case '\t': mxCkbTab->set_active(bSet); break;
645 case ';': mxCkbSemicolon->set_active(bSet); break;
646 case ',': mxCkbComma->set_active(bSet); break;
647 case ' ': mxCkbSpace->set_active(bSet); break;
648 }
649 if (bSet)
650 cSep = 0;
651 }
652 if (cSep)
653 {
654 mxCkbOther->set_active(true);
655 mxEdOther->set_text(OUStringChar(cSep));
656 }
657 }
658 else
659 {
660 for (sal_Int32 i = 0; i < maFieldSeparators.getLength(); ++i)
661 {
662 switch (maFieldSeparators[i])
663 {
664 case '\t': mxCkbTab->set_active(true); break;
665 case ';': mxCkbSemicolon->set_active(true); break;
666 case ',': mxCkbComma->set_active(true); break;
667 case ' ': mxCkbSpace->set_active(true); break;
668 default:
669 mxCkbOther->set_active(true);
670 mxEdOther->set_text(mxEdOther->get_text() + OUStringChar(maFieldSeparators[i]));
671 }
672 }
673 }
674 }
675
SetSelectedCharSet()676 void ScImportAsciiDlg::SetSelectedCharSet()
677 {
678 rtl_TextEncoding eOldCharSet = meCharSet;
679 meCharSet = mxLbCharSet->GetSelectTextEncoding();
680 mbCharSetDetect = (meCharSet == RTL_TEXTENCODING_USER_DETECTED);
681 mbCharSetSystem = (meCharSet == RTL_TEXTENCODING_DONTKNOW);
682 if (mbCharSetDetect)
683 {
684 meCharSet = meDetectedCharSet;
685 mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet));
686 }
687 else if( mbCharSetSystem )
688 {
689 meCharSet = osl_getThreadTextEncoding();
690 mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet));
691 }
692 else
693 mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet));
694
695 if (eOldCharSet != meCharSet)
696 DetectCsvSeparators();
697
698 RbSepFix();
699 }
700
GetSeparators() const701 OUString ScImportAsciiDlg::GetSeparators() const
702 {
703 OUString aSepChars;
704 if( mxCkbTab->get_active() )
705 aSepChars += "\t";
706 if( mxCkbSemicolon->get_active() )
707 aSepChars += ";";
708 if( mxCkbComma->get_active() )
709 aSepChars += ",";
710 if( mxCkbSpace->get_active() )
711 aSepChars += " ";
712 if( mxCkbOther->get_active() )
713 aSepChars += mxEdOther->get_text();
714 return aSepChars;
715 }
716
GetActiveSeparators() const717 OUString ScImportAsciiDlg::GetActiveSeparators() const
718 {
719 if (mxRbSeparated->get_active())
720 return GetSeparators();
721
722 if (mxRbDetectSep->get_active())
723 return maDetectedFieldSeps;
724
725 return OUString();
726 }
727
SetupSeparatorCtrls()728 void ScImportAsciiDlg::SetupSeparatorCtrls()
729 {
730 bool bEnable = mxRbSeparated->get_active();
731 mxCkbTab->set_sensitive( bEnable );
732 mxCkbSemicolon->set_sensitive( bEnable );
733 mxCkbComma->set_sensitive( bEnable );
734 mxCkbSpace->set_sensitive( bEnable );
735 mxCkbOther->set_sensitive( bEnable );
736 mxEdOther->set_sensitive( bEnable );
737
738 bEnable = bEnable || mxRbDetectSep->get_active();
739 mxCkbRemoveSpace->set_sensitive( bEnable );
740 mxCkbMergeDelimiters->set_sensitive( bEnable );
741 mxFtTextSep->set_sensitive( bEnable );
742 mxCbTextSep->set_sensitive( bEnable );
743
744 OUString aSepName;
745 if (maDetectedFieldSeps.isEmpty())
746 aSepName += ScResId(SCSTR_NONE);
747 else
748 {
749 for (int idx = 0; idx < maDetectedFieldSeps.getLength(); idx ++)
750 {
751 if (idx > 0)
752 aSepName += u" ";
753
754 if (maDetectedFieldSeps[idx] == u' ')
755 aSepName += ScResId(SCSTR_FIELDSEP_SPACE);
756 else if (maDetectedFieldSeps[idx] == u'\t')
757 aSepName += ScResId(SCSTR_FIELDSEP_TAB);
758 else
759 aSepName += OUStringChar(maDetectedFieldSeps[idx]);
760 }
761 }
762 mxRbDetectSep->set_label(ScResId(SCSTR_DETECTED).replaceFirst( "%1", aSepName));
763 }
764
DetectCsvSeparators()765 void ScImportAsciiDlg::DetectCsvSeparators()
766 {
767 mpDatStream->Seek(mnStreamInitPos);
768 SfxObjectShell::DetectCsvSeparators(*mpDatStream, meCharSet, maDetectedFieldSeps, mcTextSep);
769 mpDatStream->Seek(mnStreamPos);
770 }
771
UpdateVertical()772 void ScImportAsciiDlg::UpdateVertical()
773 {
774 mnRowPosCount = 0;
775 if (mpDatStream)
776 mpDatStream->SetStreamCharSet(meCharSet);
777 }
778
RbSepFix()779 void ScImportAsciiDlg::RbSepFix()
780 {
781 weld::WaitObject aWaitObj(m_xDialog.get());
782 if (mxRbSeparated->get_active() || mxRbDetectSep->get_active())
783 {
784 maFieldSeparators = GetActiveSeparators();
785 if (mxTableBox->IsFixedWidthMode())
786 mxTableBox->SetSeparatorsMode();
787 else
788 mxTableBox->Refresh();
789 }
790 else
791 mxTableBox->SetFixedWidthMode();
792
793 SetupSeparatorCtrls();
794 }
795
IMPL_LINK(ScImportAsciiDlg,RbSepFixHdl,weld::Toggleable &,rButton,void)796 IMPL_LINK(ScImportAsciiDlg, RbSepFixHdl, weld::Toggleable&, rButton, void)
797 {
798 if (!rButton.get_active())
799 return;
800 RbSepFix();
801 }
802
IMPL_LINK(ScImportAsciiDlg,SeparatorClickHdl,weld::Toggleable &,rCtrl,void)803 IMPL_LINK(ScImportAsciiDlg, SeparatorClickHdl, weld::Toggleable&, rCtrl, void)
804 {
805 SeparatorHdl(&rCtrl);
806 }
807
IMPL_LINK(ScImportAsciiDlg,SeparatorComboBoxHdl,weld::ComboBox &,rCtrl,void)808 IMPL_LINK( ScImportAsciiDlg, SeparatorComboBoxHdl, weld::ComboBox&, rCtrl, void )
809 {
810 SeparatorHdl(&rCtrl);
811 }
812
IMPL_LINK(ScImportAsciiDlg,SeparatorEditHdl,weld::Entry &,rEdit,void)813 IMPL_LINK( ScImportAsciiDlg, SeparatorEditHdl, weld::Entry&, rEdit, void )
814 {
815 SeparatorHdl(&rEdit);
816 }
817
IMPL_LINK(ScImportAsciiDlg,OtherOptionsClickHdl,weld::Toggleable &,rCtrl,void)818 IMPL_LINK(ScImportAsciiDlg, OtherOptionsClickHdl, weld::Toggleable&, rCtrl, void)
819 {
820 if (&rCtrl == mxCkbDetectNumber.get())
821 {
822 if (mxCkbDetectNumber->get_active())
823 {
824 mxCkbDetectScientificNumber->set_active(true);
825 mxCkbDetectScientificNumber->set_sensitive(false);
826 }
827 else
828 mxCkbDetectScientificNumber->set_sensitive(true);
829 return;
830 }
831 }
832
SeparatorHdl(const weld::Widget * pCtrl)833 void ScImportAsciiDlg::SeparatorHdl(const weld::Widget* pCtrl)
834 {
835 OSL_ENSURE( pCtrl, "ScImportAsciiDlg::SeparatorHdl - missing sender" );
836 OSL_ENSURE( !mxRbFixed->get_active(), "ScImportAsciiDlg::SeparatorHdl - not allowed in fixed width" );
837
838 /* #i41550# First update state of the controls. The GetSeparators()
839 function needs final state of the check boxes. */
840 if (pCtrl == mxCkbOther.get() && mxCkbOther->get_active())
841 mxEdOther->grab_focus();
842 else if (pCtrl == mxEdOther.get())
843 mxCkbOther->set_active(!mxEdOther->get_text().isEmpty());
844
845 OUString aOldFldSeps( maFieldSeparators);
846 sal_Unicode cOldSep = mcTextSep;
847 mcTextSep = lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP );
848 // Any separator changed may result in completely different lines due to
849 // embedded line breaks.
850 if (cOldSep != mcTextSep)
851 {
852 DetectCsvSeparators();
853
854 SetupSeparatorCtrls();
855
856 maFieldSeparators = GetActiveSeparators();
857 if (aOldFldSeps != maFieldSeparators)
858 {
859 UpdateVertical();
860 mxTableBox->Refresh();
861 return;
862 }
863 }
864 else
865 maFieldSeparators = GetActiveSeparators();
866
867 mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS );
868 }
869
IMPL_LINK_NOARG(ScImportAsciiDlg,CharSetHdl,weld::ComboBox &,void)870 IMPL_LINK_NOARG(ScImportAsciiDlg, CharSetHdl, weld::ComboBox&, void)
871 {
872 if (mxLbCharSet->get_active() != -1)
873 {
874 weld::WaitObject aWaitObj(m_xDialog.get());
875 rtl_TextEncoding eOldCharSet = meCharSet;
876 SetSelectedCharSet();
877 // switching char-set invalidates 8bit -> String conversions
878 if (eOldCharSet != meCharSet)
879 UpdateVertical();
880
881 mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS );
882 }
883 }
884
IMPL_LINK(ScImportAsciiDlg,FirstRowHdl,weld::SpinButton &,rNumField,void)885 IMPL_LINK(ScImportAsciiDlg, FirstRowHdl, weld::SpinButton&, rNumField, void)
886 {
887 mxTableBox->GetGrid().Execute( CSVCMD_SETFIRSTIMPORTLINE, rNumField.get_value() - 1);
888 }
889
IMPL_LINK(ScImportAsciiDlg,LbColTypeHdl,weld::ComboBox &,rListBox,void)890 IMPL_LINK(ScImportAsciiDlg, LbColTypeHdl, weld::ComboBox&, rListBox, void)
891 {
892 if (&rListBox == mxLbType.get())
893 mxTableBox->GetGrid().Execute(CSVCMD_SETCOLUMNTYPE, rListBox.get_active());
894 }
895
IMPL_LINK_NOARG(ScImportAsciiDlg,UpdateTextHdl,ScCsvTableBox &,void)896 IMPL_LINK_NOARG(ScImportAsciiDlg, UpdateTextHdl, ScCsvTableBox&, void)
897 {
898 sal_Unicode cDetectSep = 0xffff;
899
900 sal_Int32 nBaseLine = mxTableBox->GetGrid().GetFirstVisLine();
901 sal_Int32 nRead = mxTableBox->GetGrid().GetVisLineCount();
902 // If mnRowPosCount==0, this is an initializing call, read ahead for row
903 // count and resulting scroll bar size and position to be able to scroll at
904 // all. When adding lines, read only the amount of next lines to be
905 // displayed.
906 if (!mnRowPosCount || nRead > CSV_PREVIEW_LINES)
907 nRead = CSV_PREVIEW_LINES;
908
909 sal_Int32 i;
910 for (i = 0; i < nRead; i++)
911 {
912 if (!GetLine( nBaseLine + i, maPreviewLine[i], cDetectSep))
913 break;
914 }
915 for (; i < CSV_PREVIEW_LINES; i++)
916 maPreviewLine[i].clear();
917
918
919 mxTableBox->GetGrid().Execute( CSVCMD_SETLINECOUNT, mnRowPosCount);
920 bool bMergeSep = mxCkbMergeDelimiters->get_active();
921 bool bRemoveSpace = mxCkbRemoveSpace->get_active();
922 mxTableBox->SetUniStrings( maPreviewLine, maFieldSeparators, mcTextSep, bMergeSep, bRemoveSpace );
923 }
924
IMPL_LINK(ScImportAsciiDlg,ColTypeHdl,ScCsvTableBox &,rTableBox,void)925 IMPL_LINK( ScImportAsciiDlg, ColTypeHdl, ScCsvTableBox&, rTableBox, void )
926 {
927 sal_Int32 nType = rTableBox.GetSelColumnType();
928 sal_Int32 nTypeCount = mxLbType->get_count();
929 bool bEmpty = (nType == CSV_TYPE_MULTI);
930 bool bEnable = ((0 <= nType) && (nType < nTypeCount)) || bEmpty;
931
932 mxLbType->set_sensitive( bEnable );
933
934 if (bEmpty)
935 mxLbType->set_active(-1);
936 else if (bEnable)
937 mxLbType->set_active(nType);
938 }
939
940 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
941