/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #undef SC_DLLIMPLEMENTATION #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! TODO make dynamic const SCSIZE ASCIIDLG_MAXROWS = MAXROWCOUNT; // Maximum number of source lines to concatenate while generating the preview // for one logical line. This may result in a wrong preview if the actual // number of embedded line feeds is greater, but a number too high would take // too much time (loop excessively if unlimited and large data) if none of the // selected separators are actually used in data but a field at start of line // is quoted. constexpr sal_uInt32 kMaxEmbeddedLinefeeds = 500; using namespace com::sun::star::uno; namespace { // Defines - CSV Import Preserve Options // For usage of index order see lcl_CreatePropertiesNames() below. enum CSVImportOptionsIndex { CSVIO_MergeDelimiters = 0, CSVIO_Separators, CSVIO_TextSeparators, CSVIO_FixedWidth, CSVIO_RemoveSpace, CSVIO_EvaluateFormulas, CSVIO_SeparatorType, // Settings for *all* dialog invocations above. // Settings not for SC_TEXTTOCOLUMNS below. CSVIO_FromRow, CSVIO_Text2ColSkipEmptyCells = CSVIO_FromRow, CSVIO_CharSet, CSVIO_QuotedAsText, CSVIO_DetectSpecialNum, CSVIO_DetectScientificNum, CSVIO_Language, // Plus one not for SC_IMPORTFILE. CSVIO_PasteSkipEmptyCells }; enum SeparatorType { FIXED, SEPARATOR, DETECT_SEPARATOR }; } // Config items for all three paths are defined in // officecfg/registry/schema/org/openoffice/Office/Calc.xcs // If not, options are neither loaded nor saved. const ::std::vector CSVImportOptionNames = { u"MergeDelimiters"_ustr, u"Separators"_ustr, u"TextSeparators"_ustr, u"FixedWidth"_ustr, u"RemoveSpace"_ustr, u"EvaluateFormulas"_ustr, u"SeparatorType"_ustr, u"FromRow"_ustr, u"CharSet"_ustr, u"QuotedFieldAsText"_ustr, u"DetectSpecialNumbers"_ustr, u"DetectScientificNumbers"_ustr, u"Language"_ustr, u"SkipEmptyCells"_ustr }; constexpr OUStringLiteral aSep_Path = u"Office.Calc/Dialogs/CSVImport"; constexpr OUStringLiteral aSep_Path_Clpbrd = u"Office.Calc/Dialogs/ClipboardTextImport"; constexpr OUStringLiteral aSep_Path_Text2Col = u"Office.Calc/Dialogs/TextToColumnsImport"; namespace { CSVImportOptionsIndex getSkipEmptyCellsIndex( ScImportAsciiCall eCall ) { return eCall == SC_TEXTTOCOLUMNS ? CSVIO_Text2ColSkipEmptyCells : CSVIO_PasteSkipEmptyCells; } } static void lcl_FillCombo(weld::ComboBox& rCombo, std::u16string_view rList, sal_Unicode cSelect) { OUString aStr; if (!rList.empty()) { sal_Int32 nIdx {0}; do { const OUString sEntry {o3tl::getToken(rList, 0, '\t', nIdx)}; rCombo.append_text(sEntry); if (nIdx>0 && static_cast(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nIdx))) == cSelect) aStr = sEntry; } while (nIdx>0); } if ( cSelect ) { if (aStr.isEmpty()) aStr = OUString(cSelect); // Ascii rCombo.set_entry_text(aStr); } } static sal_Unicode lcl_CharFromCombo(const weld::ComboBox& rCombo, std::u16string_view rList) { sal_Unicode c = 0; OUString aStr = rCombo.get_active_text(); if ( !aStr.isEmpty() && !rList.empty() ) { sal_Int32 nIdx {0}; OUString sToken {o3tl::getToken(rList, 0, '\t', nIdx)}; while (nIdx>0) { if ( ScGlobal::GetTransliteration().isEqual( aStr, sToken ) ) { sal_Int32 nTmpIdx {nIdx}; c = static_cast(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nTmpIdx))); } // Skip to next token at even position sToken = o3tl::getToken(rList, 1, '\t', nIdx); } if (!c) { sal_Unicode cFirst = aStr[0]; // #i24235# first try the first character of the string directly if( (aStr.getLength() == 1) || (cFirst < '0') || (cFirst > '9') ) c = cFirst; else // keep old behaviour for compatibility (i.e. "39" -> "'") c = static_cast(aStr.toInt32()); // Ascii } } return c; } static void lcl_CreatePropertiesNames ( OUString& rSepPath, Sequence& rNames, ScImportAsciiCall eCall ) { sal_Int32 nProperties = 0; switch(eCall) { case SC_IMPORTFILE: rSepPath = aSep_Path; nProperties = 13; break; case SC_PASTETEXT: rSepPath = aSep_Path_Clpbrd; nProperties = 14; break; case SC_TEXTTOCOLUMNS: default: rSepPath = aSep_Path_Text2Col; nProperties = 8; break; } rNames.realloc( nProperties ); OUString* pNames = rNames.getArray(); pNames[ CSVIO_MergeDelimiters ] = CSVImportOptionNames[ CSVIO_MergeDelimiters ]; pNames[ CSVIO_Separators ] = CSVImportOptionNames[ CSVIO_Separators ]; pNames[ CSVIO_TextSeparators ] = CSVImportOptionNames[ CSVIO_TextSeparators ]; pNames[ CSVIO_FixedWidth ] = CSVImportOptionNames[ CSVIO_FixedWidth ]; pNames[ CSVIO_RemoveSpace ] = CSVImportOptionNames[ CSVIO_RemoveSpace ]; pNames[ CSVIO_EvaluateFormulas ] = CSVImportOptionNames[ CSVIO_EvaluateFormulas ]; pNames[ CSVIO_SeparatorType ] = CSVImportOptionNames[ CSVIO_SeparatorType ]; if (eCall != SC_TEXTTOCOLUMNS) { pNames[ CSVIO_FromRow ] = CSVImportOptionNames[ CSVIO_FromRow ]; pNames[ CSVIO_CharSet ] = CSVImportOptionNames[ CSVIO_CharSet ]; pNames[ CSVIO_QuotedAsText ] = CSVImportOptionNames[ CSVIO_QuotedAsText ]; pNames[ CSVIO_DetectSpecialNum ] = CSVImportOptionNames[ CSVIO_DetectSpecialNum ]; pNames[ CSVIO_DetectScientificNum ] = CSVImportOptionNames[ CSVIO_DetectScientificNum ]; pNames[ CSVIO_Language ] = CSVImportOptionNames[ CSVIO_Language ]; } if (eCall != SC_IMPORTFILE) { const sal_Int32 nSkipEmptyCells = getSkipEmptyCellsIndex(eCall); assert( nSkipEmptyCells < rNames.getLength()); pNames[ nSkipEmptyCells ] = CSVImportOptionNames[ CSVIO_PasteSkipEmptyCells ]; } } static void lcl_LoadSeparators( OUString& rFieldSeparators, OUString& rTextSeparators, bool& rMergeDelimiters, bool& rQuotedAsText, bool& rDetectSpecialNum, bool& rDetectScientificNum, SeparatorType& rSepType, sal_Int32& rFromRow, sal_Int32& rCharSet, sal_Int32& rLanguage, bool& rSkipEmptyCells, bool& rRemoveSpace, bool& rEvaluateFormulas, ScImportAsciiCall eCall, bool& rBeforeDetection ) { SequenceaValues; const Any *pProperties; Sequence aNames; OUString aSepPath; lcl_CreatePropertiesNames ( aSepPath, aNames, eCall); ScLinkConfigItem aItem( aSepPath ); aValues = aItem.GetProperties( aNames ); pProperties = aValues.getConstArray(); if( pProperties[ CSVIO_MergeDelimiters ].hasValue() ) rMergeDelimiters = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_MergeDelimiters ] ); if( pProperties[ CSVIO_RemoveSpace ].hasValue() ) rRemoveSpace = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_RemoveSpace ] ); if( pProperties[ CSVIO_Separators ].hasValue() ) pProperties[ CSVIO_Separators ] >>= rFieldSeparators; if( pProperties[ CSVIO_TextSeparators ].hasValue() ) pProperties[ CSVIO_TextSeparators ] >>= rTextSeparators; rBeforeDetection = true; if( pProperties[ CSVIO_SeparatorType ].hasValue() ) { rBeforeDetection = false; rSepType = static_cast(ScUnoHelpFunctions::GetInt16FromAny( pProperties[ CSVIO_SeparatorType ] )); } else if( pProperties[ CSVIO_FixedWidth ].hasValue() ) rSepType = (ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_FixedWidth ] ) ? SeparatorType::FIXED : SeparatorType::DETECT_SEPARATOR); if( pProperties[ CSVIO_EvaluateFormulas ].hasValue() ) rEvaluateFormulas = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_EvaluateFormulas ] ); if (eCall != SC_TEXTTOCOLUMNS) { if( pProperties[ CSVIO_FromRow ].hasValue() ) pProperties[ CSVIO_FromRow ] >>= rFromRow; if( pProperties[ CSVIO_CharSet ].hasValue() ) pProperties[ CSVIO_CharSet ] >>= rCharSet; if ( pProperties[ CSVIO_QuotedAsText ].hasValue() ) pProperties[ CSVIO_QuotedAsText ] >>= rQuotedAsText; if ( pProperties[ CSVIO_DetectSpecialNum ].hasValue() ) pProperties[ CSVIO_DetectSpecialNum ] >>= rDetectSpecialNum; if ( pProperties[ CSVIO_DetectScientificNum ].hasValue() ) pProperties[ CSVIO_DetectScientificNum ] >>= rDetectScientificNum; if ( pProperties[ CSVIO_Language ].hasValue() ) pProperties[ CSVIO_Language ] >>= rLanguage; } if (eCall != SC_IMPORTFILE) { const sal_Int32 nSkipEmptyCells = getSkipEmptyCellsIndex(eCall); assert( nSkipEmptyCells < aValues.getLength()); if ( pProperties[nSkipEmptyCells].hasValue() ) rSkipEmptyCells = ScUnoHelpFunctions::GetBoolFromAny( pProperties[nSkipEmptyCells] ); } } static void lcl_SaveSeparators( const OUString& sFieldSeparators, const OUString& sTextSeparators, bool bMergeDelimiters, bool bQuotedAsText, bool bDetectSpecialNum, bool bDetectScientificNum, SeparatorType rSepType, sal_Int32 nFromRow, sal_Int32 nCharSet, sal_Int32 nLanguage, bool bSkipEmptyCells, bool bRemoveSpace, bool bEvaluateFormulas, ScImportAsciiCall eCall ) { Sequence aValues; Any *pProperties; Sequence aNames; OUString aSepPath; lcl_CreatePropertiesNames ( aSepPath, aNames, eCall ); ScLinkConfigItem aItem( aSepPath ); aValues = aItem.GetProperties( aNames ); pProperties = aValues.getArray(); pProperties[ CSVIO_MergeDelimiters ] <<= bMergeDelimiters; pProperties[ CSVIO_RemoveSpace ] <<= bRemoveSpace; pProperties[ CSVIO_Separators ] <<= sFieldSeparators; pProperties[ CSVIO_TextSeparators ] <<= sTextSeparators; pProperties[ CSVIO_EvaluateFormulas ] <<= bEvaluateFormulas; pProperties[ CSVIO_SeparatorType ] <<= static_cast(rSepType); if (eCall != SC_TEXTTOCOLUMNS) { pProperties[ CSVIO_FromRow ] <<= nFromRow; pProperties[ CSVIO_CharSet ] <<= nCharSet; pProperties[ CSVIO_QuotedAsText ] <<= bQuotedAsText; pProperties[ CSVIO_DetectSpecialNum ] <<= bDetectSpecialNum; pProperties[ CSVIO_DetectScientificNum ] <<= bDetectScientificNum; pProperties[ CSVIO_Language ] <<= nLanguage; } if (eCall != SC_IMPORTFILE) { const sal_Int32 nSkipEmptyCells = getSkipEmptyCellsIndex(eCall); assert( nSkipEmptyCells < aValues.getLength()); pProperties[ nSkipEmptyCells ] <<= bSkipEmptyCells; } aItem.PutProperties(aNames, aValues); } ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aDatName, SvStream* pInStream, ScImportAsciiCall eCall) : GenericDialogController(pParent, u"modules/scalc/ui/textimportcsv.ui"_ustr, u"TextImportCsvDialog"_ustr) , mpDatStream(pInStream) , mnStreamPos(pInStream ? pInStream->Tell() : 0) , mnStreamInitPos(mnStreamPos) , mnRowPosCount(0) , mcTextSep(ScAsciiOptions::cDefaultTextSep) , meDetectedCharSet(RTL_TEXTENCODING_DONTKNOW) , mbCharSetDetect(true) , meCall(eCall) , mxFtCharSet(m_xBuilder->weld_label(u"textcharset"_ustr)) , mxLbCharSet(new SvxTextEncodingBox(m_xBuilder->weld_combo_box(u"charset"_ustr))) , mxFtDetectedCharSet(m_xBuilder->weld_label(u"textdetectedcharset"_ustr)) , mxFtCustomLang(m_xBuilder->weld_label(u"textlanguage"_ustr)) , mxLbCustomLang(new SvxLanguageBox(m_xBuilder->weld_combo_box(u"language"_ustr))) , mxFtRow(m_xBuilder->weld_label(u"textfromrow"_ustr)) , mxNfRow(m_xBuilder->weld_spin_button(u"fromrow"_ustr)) , mxRbDetectSep(m_xBuilder->weld_radio_button(u"todetectseparator"_ustr)) , mxRbFixed(m_xBuilder->weld_radio_button(u"tofixedwidth"_ustr)) , mxRbSeparated(m_xBuilder->weld_radio_button(u"toseparatedby"_ustr)) , mxCkbTab(m_xBuilder->weld_check_button(u"tab"_ustr)) , mxCkbSemicolon(m_xBuilder->weld_check_button(u"semicolon"_ustr)) , mxCkbComma(m_xBuilder->weld_check_button(u"comma"_ustr)) , mxCkbRemoveSpace(m_xBuilder->weld_check_button(u"removespace"_ustr)) , mxCkbSpace(m_xBuilder->weld_check_button(u"space"_ustr)) , mxCkbOther(m_xBuilder->weld_check_button(u"other"_ustr)) , mxEdOther(m_xBuilder->weld_entry(u"inputother"_ustr)) , mxCkbAsOnce(m_xBuilder->weld_check_button(u"mergedelimiters"_ustr)) , mxFtTextSep(m_xBuilder->weld_label(u"texttextdelimiter"_ustr)) , mxCbTextSep(m_xBuilder->weld_combo_box(u"textdelimiter"_ustr)) , mxCkbQuotedAsText(m_xBuilder->weld_check_button(u"quotedfieldastext"_ustr)) , mxCkbDetectNumber(m_xBuilder->weld_check_button(u"detectspecialnumbers"_ustr)) , mxCkbDetectScientificNumber(m_xBuilder->weld_check_button(u"detectscientificnumbers"_ustr)) , mxCkbEvaluateFormulas(m_xBuilder->weld_check_button(u"evaluateformulas"_ustr)) , mxCkbSkipEmptyCells(m_xBuilder->weld_check_button(u"skipemptycells"_ustr)) , mxLbType(m_xBuilder->weld_combo_box(u"columntype"_ustr)) , mxAltTitle(m_xBuilder->weld_label(u"textalttitle"_ustr)) , mxTableBox(new ScCsvTableBox(*m_xBuilder)) , mxCkbAlwaysShowOnImport(m_xBuilder->weld_check_button(u"alwaysshowonimport"_ustr)) { SvtViewOptions aDlgOpt(EViewType::Dialog, "TextImportCsvDialog"); if (aDlgOpt.Exists()) m_xDialog->set_window_state(aDlgOpt.GetWindowState()); OUString aName = m_xDialog->get_title(); switch (meCall) { case SC_TEXTTOCOLUMNS: m_xDialog->set_title(mxAltTitle->get_label()); break; case SC_IMPORTFILE: if (!comphelper::LibreOfficeKit::isActive()) { aName += OUString::Concat(" - [") + aDatName + "]"; m_xDialog->set_title(aName); } mxCkbAlwaysShowOnImport->show(); mxCkbAlwaysShowOnImport->set_active( utl::isShowFilterOptionsDialog(SC_TEXT_CSV_FILTER_NAME)); break; default: break; } // To be able to prefill the correct values based on the file extension bool bIsTSV = (o3tl::endsWithIgnoreAsciiCase(aDatName, ".tsv") || o3tl::endsWithIgnoreAsciiCase(aDatName, ".tab")); // Default options are set in officecfg/registry/schema/org/openoffice/Office/Calc.xcs OUString sFieldSeparators(u",;\t"_ustr); OUString sTextSeparators(mcTextSep); bool bMergeDelimiters = false; SeparatorType eSepType = DETECT_SEPARATOR; bool bQuotedFieldAsText = false; bool bDetectSpecialNum = true; bool bDetectScientificNum = true; bool bEvaluateFormulas = (meCall != SC_IMPORTFILE); bool bSkipEmptyCells = true; bool bRemoveSpace = false; bool bBeforeDetection = false; sal_Int32 nFromRow = 1; sal_Int32 nCharSet = -1; sal_Int32 nLanguage = 0; lcl_LoadSeparators (sFieldSeparators, sTextSeparators, bMergeDelimiters, bQuotedFieldAsText, bDetectSpecialNum, bDetectScientificNum, eSepType, nFromRow, nCharSet, nLanguage, bSkipEmptyCells, bRemoveSpace, bEvaluateFormulas, meCall, bBeforeDetection); maFieldSeparators = sFieldSeparators; if( bMergeDelimiters && !bIsTSV ) mxCkbAsOnce->set_active(true); if (bQuotedFieldAsText) mxCkbQuotedAsText->set_active(true); if (bRemoveSpace) mxCkbRemoveSpace->set_active(true); if (bDetectSpecialNum) { mxCkbDetectNumber->set_active(true); bDetectScientificNum = true; mxCkbDetectScientificNumber->set_sensitive(false); } if (bDetectScientificNum) mxCkbDetectScientificNumber->set_active(true); if (bEvaluateFormulas) mxCkbEvaluateFormulas->set_active(true); if (bSkipEmptyCells) mxCkbSkipEmptyCells->set_active(true); if (eSepType == SeparatorType::FIXED) { if (bIsTSV) { eSepType = SeparatorType::SEPARATOR; mxRbSeparated->set_active(true); } else mxRbFixed->set_active(true); } else if (eSepType == SeparatorType::SEPARATOR) mxRbSeparated->set_active(true); else mxRbDetectSep->set_active(true); // Clipboard is always Unicode, else rely on default/config. rtl_TextEncoding ePreselectUnicode = (meCall == SC_IMPORTFILE ? RTL_TEXTENCODING_DONTKNOW : RTL_TEXTENCODING_UNICODE); // Detect character set only once and then use it for "Detect" option. SvStreamEndian eEndian; SfxObjectShell::DetectCharSet(*mpDatStream, meDetectedCharSet, eEndian); if (meDetectedCharSet == RTL_TEXTENCODING_UNICODE) mpDatStream->SetEndian(eEndian); else if ( meDetectedCharSet == RTL_TEXTENCODING_DONTKNOW ) { meDetectedCharSet = osl_getThreadTextEncoding(); // Prefer UTF-8, as UTF-16 would have already been detected from the stream. // This gives a better chance that the file is going to be opened correctly. if ( meDetectedCharSet == RTL_TEXTENCODING_UNICODE && mpDatStream ) meDetectedCharSet = RTL_TEXTENCODING_UTF8; } if (bIsTSV) SetSeparators('\t'); else SetSeparators(0); // Get Separators from the dialog (empty are set from default) maFieldSeparators = GetActiveSeparators(); // *** Separator characters *** lcl_FillCombo( *mxCbTextSep, SCSTR_TEXTSEP, mcTextSep ); mxCbTextSep->set_entry_text(sTextSeparators); // tdf#69207 - use selected text delimiter to parse the provided data mcTextSep = lcl_CharFromCombo(*mxCbTextSep, SCSTR_TEXTSEP); Link aSeparatorClickHdl =LINK( this, ScImportAsciiDlg, SeparatorClickHdl ); Link aOtherOptionsClickHdl =LINK( this, ScImportAsciiDlg, OtherOptionsClickHdl ); mxCbTextSep->connect_changed( LINK( this, ScImportAsciiDlg, SeparatorComboBoxHdl ) ); mxCkbTab->connect_toggled( aSeparatorClickHdl ); mxCkbSemicolon->connect_toggled( aSeparatorClickHdl ); mxCkbComma->connect_toggled( aSeparatorClickHdl ); mxCkbAsOnce->connect_toggled( aSeparatorClickHdl ); mxCkbSpace->connect_toggled( aSeparatorClickHdl ); mxCkbRemoveSpace->connect_toggled( aSeparatorClickHdl ); mxCkbOther->connect_toggled( aSeparatorClickHdl ); mxEdOther->connect_changed(LINK(this, ScImportAsciiDlg, SeparatorEditHdl)); mxCkbQuotedAsText->connect_toggled( aOtherOptionsClickHdl ); mxCkbDetectNumber->connect_toggled( aOtherOptionsClickHdl ); mxCkbDetectScientificNumber->connect_toggled( aOtherOptionsClickHdl ); mxCkbEvaluateFormulas->connect_toggled( aOtherOptionsClickHdl ); mxCkbSkipEmptyCells->connect_toggled( aOtherOptionsClickHdl ); // *** text encoding ListBox *** // all encodings allowed, including Unicode, but subsets are excluded mxLbCharSet->FillFromTextEncodingTable( true ); // Insert one "SYSTEM" entry for compatibility in AsciiOptions and system // independent document linkage. mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_DONTKNOW, ScResId( SCSTR_CHARSET_USER ) ); // Insert one for detecting charset. mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_USER_DETECTED, "- " + ScResId( SCSTR_AUTOMATIC ) + " -" ); if (ePreselectUnicode != RTL_TEXTENCODING_DONTKNOW) mxLbCharSet->SelectTextEncoding( ePreselectUnicode ); else if (nCharSet >= 0 && !bBeforeDetection) mxLbCharSet->set_active(nCharSet); else mxLbCharSet->SelectTextEncoding(RTL_TEXTENCODING_USER_DETECTED); SetSelectedCharSet(); mxLbCharSet->connect_changed( LINK( this, ScImportAsciiDlg, CharSetHdl ) ); mxLbCustomLang->SetLanguageList( SvxLanguageListFlags::ALL | SvxLanguageListFlags::ONLY_KNOWN, false, false); mxLbCustomLang->InsertLanguage(LANGUAGE_SYSTEM); mxLbCustomLang->set_active_id(static_cast(nLanguage)); // *** column type ListBox *** OUString aColumnUser( ScResId( SCSTR_COLUMN_USER ) ); for (sal_Int32 nIdx {0}; nIdx>=0; ) { mxLbType->append_text(aColumnUser.getToken(0, ';', nIdx)); } mxLbType->connect_changed( LINK( this, ScImportAsciiDlg, LbColTypeHdl ) ); mxLbType->set_sensitive(false); // *** table box preview *** mxTableBox->Init(); mxTableBox->SetUpdateTextHdl( LINK( this, ScImportAsciiDlg, UpdateTextHdl ) ); mxTableBox->InitTypes( *mxLbType ); mxTableBox->SetColTypeHdl( LINK( this, ScImportAsciiDlg, ColTypeHdl ) ); mxRbDetectSep->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); mxRbSeparated->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); mxRbFixed->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); RbSepFix(); UpdateVertical(); mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); if (nFromRow != 1) { mxNfRow->set_value(nFromRow); // tdf#163638 - show visual indicator for from rows mxTableBox->GetGrid().Execute(CSVCMD_SETFIRSTIMPORTLINE, nFromRow - 1); } mxNfRow->connect_value_changed(LINK(this, ScImportAsciiDlg, FirstRowHdl)); if (meCall == SC_TEXTTOCOLUMNS) { mxFtCharSet->set_sensitive(false); mxLbCharSet->set_sensitive(false); mxFtCustomLang->set_sensitive(false); mxLbCustomLang->set_active_id(LANGUAGE_SYSTEM); mxLbCustomLang->set_sensitive(false); mxFtRow->set_sensitive(false); mxNfRow->set_sensitive(false); // Quoted field as text option is not used for text-to-columns mode. mxCkbQuotedAsText->set_active(false); mxCkbQuotedAsText->set_sensitive(false); // Always detect special numbers for text-to-columns mode. mxCkbDetectNumber->set_active(true); mxCkbDetectNumber->set_sensitive(false); mxCkbDetectScientificNumber->set_active(true); mxCkbDetectScientificNumber->set_sensitive(false); } if (meCall == SC_IMPORTFILE) { //Empty cells in imported file are empty mxCkbSkipEmptyCells->set_active(false); mxCkbSkipEmptyCells->hide(); } if (comphelper::LibreOfficeKit::isActive()) m_xBuilder->weld_button(u"cancel"_ustr)->hide(); m_xDialog->SetInstallLOKNotifierHdl(LINK(this, ScImportAsciiDlg, InstallLOKNotifierHdl)); } IMPL_STATIC_LINK_NOARG(ScImportAsciiDlg, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*) { return GetpApp(); } ScImportAsciiDlg::~ScImportAsciiDlg() { SvtViewOptions aDlgOpt(EViewType::Dialog, "TextImportCsvDialog"); aDlgOpt.SetWindowState(m_xDialog->get_window_state(vcl::WindowDataMask::PosSize)); } bool ScImportAsciiDlg::GetLine( sal_uLong nLine, OUString &rText, sal_Unicode& rcDetectSep ) { if (nLine >= ASCIIDLG_MAXROWS || !mpDatStream) return false; bool bRet = true; bool bFixed = mxRbFixed->get_active(); if (!mpRowPosArray) mpRowPosArray.reset( new sal_uLong[ASCIIDLG_MAXROWS + 2] ); if (!mnRowPosCount) // complete re-fresh { memset( mpRowPosArray.get(), 0, sizeof(mpRowPosArray[0]) * (ASCIIDLG_MAXROWS+2)); Seek(0); mpDatStream->StartReadingUnicodeText( mpDatStream->GetStreamCharSet() ); mnStreamPos = mpDatStream->Tell(); mpRowPosArray[mnRowPosCount] = mnStreamPos; } if (nLine >= mnRowPosCount) { // need to work out some more line information do { if (!Seek(mpRowPosArray[mnRowPosCount]) || !mpDatStream->good()) { bRet = false; break; } rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators, mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds); mnStreamPos = mpDatStream->Tell(); mpRowPosArray[++mnRowPosCount] = mnStreamPos; } while (nLine >= mnRowPosCount && mpDatStream->good()); if (mpDatStream->eof() && mnRowPosCount && mnStreamPos == mpRowPosArray[mnRowPosCount-1]) { // the very end, not even an empty line read bRet = false; --mnRowPosCount; } } else { Seek( mpRowPosArray[nLine]); rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators, mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds); mnStreamPos = mpDatStream->Tell(); } // If the file content isn't unicode, ReadUniStringLine // may try to seek beyond the file's end and cause a CANTSEEK error // (depending on the stream type). The error code has to be cleared, // or further read operations (including non-unicode) will fail. if ( mpDatStream->GetError() == ERRCODE_IO_CANTSEEK ) mpDatStream->ResetError(); ScImportExport::EmbeddedNullTreatment( rText); return bRet; } void ScImportAsciiDlg::GetOptions( ScAsciiOptions& rOpt ) { rOpt.SetCharSet( meCharSet ); rOpt.SetCharSetSystem( mbCharSetSystem ); rOpt.SetLanguage(mxLbCustomLang->get_active_id()); rOpt.SetFixedLen( mxRbFixed->get_active() ); rOpt.SetStartRow( mxNfRow->get_value() ); mxTableBox->FillColumnData( rOpt ); if( mxRbSeparated->get_active() || mxRbDetectSep->get_active()) { rOpt.SetFieldSeps( GetActiveSeparators() ); rOpt.SetMergeSeps( mxCkbAsOnce->get_active() ); rOpt.SetRemoveSpace( mxCkbRemoveSpace->get_active() ); rOpt.SetTextSep( lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ) ); } rOpt.SetQuotedAsText(mxCkbQuotedAsText->get_active()); rOpt.SetDetectSpecialNumber(mxCkbDetectNumber->get_active()); rOpt.SetDetectScientificNumber(mxCkbDetectScientificNumber->get_active()); rOpt.SetEvaluateFormulas(mxCkbEvaluateFormulas->get_active()); rOpt.SetSkipEmptyCells(mxCkbSkipEmptyCells->get_active()); } void ScImportAsciiDlg::SaveParameters() { if (mxCkbAlwaysShowOnImport->get_visible()) { bool value(mxCkbAlwaysShowOnImport->get_active()); if (value != utl::isShowFilterOptionsDialog(SC_TEXT_CSV_FILTER_NAME)) { auto pChange(comphelper::ConfigurationChanges::create()); auto xFilterDialogSettings( utl::getSettingsForFilterOptions(SC_TEXT_CSV_FILTER_NAME, pChange)); xFilterDialogSettings->setPropertyValue(u"show"_ustr, Any(value)); pChange->commit(); } } lcl_SaveSeparators( GetSeparators(), mxCbTextSep->get_active_text(), mxCkbAsOnce->get_active(), mxCkbQuotedAsText->get_active(), mxCkbDetectNumber->get_active(), mxCkbDetectScientificNumber->get_active(), mxRbFixed->get_active() ? FIXED : (mxRbDetectSep->get_active() ? DETECT_SEPARATOR : SEPARATOR), mxNfRow->get_value(), mxLbCharSet->get_active(), static_cast(mxLbCustomLang->get_active_id()), mxCkbSkipEmptyCells->get_active(), mxCkbRemoveSpace->get_active(), mxCkbEvaluateFormulas->get_active(), meCall ); } void ScImportAsciiDlg::SetSeparators( sal_Unicode cSep ) { if (cSep) { // Exclusively set a separator, maFieldSeparators needs not be // modified, it's obtained by GetSeparators() after this call. static constexpr sal_Unicode aSeps[] = { '\t', ';', ',', ' ' }; for (const sal_Unicode c : aSeps) { const bool bSet = (c == cSep); switch (c) { case '\t': mxCkbTab->set_active(bSet); break; case ';': mxCkbSemicolon->set_active(bSet); break; case ',': mxCkbComma->set_active(bSet); break; case ' ': mxCkbSpace->set_active(bSet); break; } if (bSet) cSep = 0; } if (cSep) { mxCkbOther->set_active(true); mxEdOther->set_text(OUStringChar(cSep)); } } else { for (sal_Int32 i = 0; i < maFieldSeparators.getLength(); ++i) { switch (maFieldSeparators[i]) { case '\t': mxCkbTab->set_active(true); break; case ';': mxCkbSemicolon->set_active(true); break; case ',': mxCkbComma->set_active(true); break; case ' ': mxCkbSpace->set_active(true); break; default: mxCkbOther->set_active(true); mxEdOther->set_text(mxEdOther->get_text() + OUStringChar(maFieldSeparators[i])); } } } } void ScImportAsciiDlg::SetSelectedCharSet() { rtl_TextEncoding eOldCharSet = meCharSet; meCharSet = mxLbCharSet->GetSelectTextEncoding(); mbCharSetDetect = (meCharSet == RTL_TEXTENCODING_USER_DETECTED); mbCharSetSystem = (meCharSet == RTL_TEXTENCODING_DONTKNOW); if (mbCharSetDetect) { meCharSet = meDetectedCharSet; mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet)); } else if( mbCharSetSystem ) { meCharSet = osl_getThreadTextEncoding(); mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet)); } else mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet)); if (eOldCharSet != meCharSet) DetectCsvSeparators(); RbSepFix(); } OUString ScImportAsciiDlg::GetSeparators() const { OUString aSepChars; if( mxCkbTab->get_active() ) aSepChars += "\t"; if( mxCkbSemicolon->get_active() ) aSepChars += ";"; if( mxCkbComma->get_active() ) aSepChars += ","; if( mxCkbSpace->get_active() ) aSepChars += " "; if( mxCkbOther->get_active() ) aSepChars += mxEdOther->get_text(); return aSepChars; } OUString ScImportAsciiDlg::GetActiveSeparators() const { if (mxRbSeparated->get_active()) return GetSeparators(); if (mxRbDetectSep->get_active()) return maDetectedFieldSeps; return OUString(); } void ScImportAsciiDlg::SetupSeparatorCtrls() { bool bEnable = mxRbSeparated->get_active(); mxCkbTab->set_sensitive( bEnable ); mxCkbSemicolon->set_sensitive( bEnable ); mxCkbComma->set_sensitive( bEnable ); mxCkbSpace->set_sensitive( bEnable ); mxCkbOther->set_sensitive( bEnable ); mxEdOther->set_sensitive( bEnable ); bEnable = bEnable || mxRbDetectSep->get_active(); mxCkbRemoveSpace->set_sensitive( bEnable ); mxCkbAsOnce->set_sensitive( bEnable ); mxFtTextSep->set_sensitive( bEnable ); mxCbTextSep->set_sensitive( bEnable ); OUString aSepName; if (maDetectedFieldSeps.isEmpty()) aSepName += ScResId(SCSTR_NONE); else { for (int idx = 0; idx < maDetectedFieldSeps.getLength(); idx ++) { if (idx > 0) aSepName += u" "; if (maDetectedFieldSeps[idx] == u' ') aSepName += ScResId(SCSTR_FIELDSEP_SPACE); else if (maDetectedFieldSeps[idx] == u'\t') aSepName += ScResId(SCSTR_FIELDSEP_TAB); else aSepName += OUStringChar(maDetectedFieldSeps[idx]); } } mxRbDetectSep->set_label(ScResId(SCSTR_DETECTED).replaceFirst( "%1", aSepName)); } void ScImportAsciiDlg::DetectCsvSeparators() { mpDatStream->Seek(mnStreamInitPos); SfxObjectShell::DetectCsvSeparators(*mpDatStream, meCharSet, maDetectedFieldSeps, mcTextSep); mpDatStream->Seek(mnStreamPos); } void ScImportAsciiDlg::UpdateVertical() { mnRowPosCount = 0; if (mpDatStream) mpDatStream->SetStreamCharSet(meCharSet); } void ScImportAsciiDlg::RbSepFix() { weld::WaitObject aWaitObj(m_xDialog.get()); if (mxRbSeparated->get_active() || mxRbDetectSep->get_active()) { maFieldSeparators = GetActiveSeparators(); if (mxTableBox->IsFixedWidthMode()) mxTableBox->SetSeparatorsMode(); else mxTableBox->Refresh(); } else mxTableBox->SetFixedWidthMode(); SetupSeparatorCtrls(); } IMPL_LINK(ScImportAsciiDlg, RbSepFixHdl, weld::Toggleable&, rButton, void) { if (!rButton.get_active()) return; RbSepFix(); } IMPL_LINK(ScImportAsciiDlg, SeparatorClickHdl, weld::Toggleable&, rCtrl, void) { SeparatorHdl(&rCtrl); } IMPL_LINK( ScImportAsciiDlg, SeparatorComboBoxHdl, weld::ComboBox&, rCtrl, void ) { SeparatorHdl(&rCtrl); } IMPL_LINK( ScImportAsciiDlg, SeparatorEditHdl, weld::Entry&, rEdit, void ) { SeparatorHdl(&rEdit); } IMPL_LINK(ScImportAsciiDlg, OtherOptionsClickHdl, weld::Toggleable&, rCtrl, void) { if (&rCtrl == mxCkbDetectNumber.get()) { if (mxCkbDetectNumber->get_active()) { mxCkbDetectScientificNumber->set_active(true); mxCkbDetectScientificNumber->set_sensitive(false); } else mxCkbDetectScientificNumber->set_sensitive(true); return; } } void ScImportAsciiDlg::SeparatorHdl(const weld::Widget* pCtrl) { OSL_ENSURE( pCtrl, "ScImportAsciiDlg::SeparatorHdl - missing sender" ); OSL_ENSURE( !mxRbFixed->get_active(), "ScImportAsciiDlg::SeparatorHdl - not allowed in fixed width" ); /* #i41550# First update state of the controls. The GetSeparators() function needs final state of the check boxes. */ if (pCtrl == mxCkbOther.get() && mxCkbOther->get_active()) mxEdOther->grab_focus(); else if (pCtrl == mxEdOther.get()) mxCkbOther->set_active(!mxEdOther->get_text().isEmpty()); OUString aOldFldSeps( maFieldSeparators); sal_Unicode cOldSep = mcTextSep; mcTextSep = lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ); // Any separator changed may result in completely different lines due to // embedded line breaks. if (cOldSep != mcTextSep) { DetectCsvSeparators(); SetupSeparatorCtrls(); maFieldSeparators = GetActiveSeparators(); if (aOldFldSeps != maFieldSeparators) { UpdateVertical(); mxTableBox->Refresh(); return; } } else maFieldSeparators = GetActiveSeparators(); mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); } IMPL_LINK_NOARG(ScImportAsciiDlg, CharSetHdl, weld::ComboBox&, void) { if (mxLbCharSet->get_active() != -1) { weld::WaitObject aWaitObj(m_xDialog.get()); rtl_TextEncoding eOldCharSet = meCharSet; SetSelectedCharSet(); // switching char-set invalidates 8bit -> String conversions if (eOldCharSet != meCharSet) UpdateVertical(); mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); } } IMPL_LINK(ScImportAsciiDlg, FirstRowHdl, weld::SpinButton&, rNumField, void) { mxTableBox->GetGrid().Execute( CSVCMD_SETFIRSTIMPORTLINE, rNumField.get_value() - 1); } IMPL_LINK(ScImportAsciiDlg, LbColTypeHdl, weld::ComboBox&, rListBox, void) { if (&rListBox == mxLbType.get()) mxTableBox->GetGrid().Execute(CSVCMD_SETCOLUMNTYPE, rListBox.get_active()); } IMPL_LINK_NOARG(ScImportAsciiDlg, UpdateTextHdl, ScCsvTableBox&, void) { sal_Unicode cDetectSep = 0xffff; sal_Int32 nBaseLine = mxTableBox->GetGrid().GetFirstVisLine(); sal_Int32 nRead = mxTableBox->GetGrid().GetVisLineCount(); // If mnRowPosCount==0, this is an initializing call, read ahead for row // count and resulting scroll bar size and position to be able to scroll at // all. When adding lines, read only the amount of next lines to be // displayed. if (!mnRowPosCount || nRead > CSV_PREVIEW_LINES) nRead = CSV_PREVIEW_LINES; sal_Int32 i; for (i = 0; i < nRead; i++) { if (!GetLine( nBaseLine + i, maPreviewLine[i], cDetectSep)) break; } for (; i < CSV_PREVIEW_LINES; i++) maPreviewLine[i].clear(); mxTableBox->GetGrid().Execute( CSVCMD_SETLINECOUNT, mnRowPosCount); bool bMergeSep = mxCkbAsOnce->get_active(); bool bRemoveSpace = mxCkbRemoveSpace->get_active(); mxTableBox->SetUniStrings( maPreviewLine, maFieldSeparators, mcTextSep, bMergeSep, bRemoveSpace ); } IMPL_LINK( ScImportAsciiDlg, ColTypeHdl, ScCsvTableBox&, rTableBox, void ) { sal_Int32 nType = rTableBox.GetSelColumnType(); sal_Int32 nTypeCount = mxLbType->get_count(); bool bEmpty = (nType == CSV_TYPE_MULTI); bool bEnable = ((0 <= nType) && (nType < nTypeCount)) || bEmpty; mxLbType->set_sensitive( bEnable ); if (bEmpty) mxLbType->set_active(-1); else if (bEnable) mxLbType->set_active(nType); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */