SQL Server Массовая вставка файла CSV с несогласованными кавычками - PullRequest
32 голосов
/ 23 апреля 2009

Можно ли BULK INSERT (SQL Server) CSV-файл, в котором поля только ПРОИЗОЛЬНО окружены кавычками? В частности, кавычки окружают только те поля, которые содержат ",".

Другими словами, у меня есть данные, которые выглядят так (первая строка содержит заголовки):

id, company, rep, employees
729216,INGRAM MICRO INC.,"Stuart, Becky",523
729235,"GREAT PLAINS ENERGY, INC.","Nelson, Beena",114
721177,GEORGE WESTON BAKERIES INC,"Hogan, Meg",253

Поскольку кавычки не согласованы, я не могу использовать «,» в качестве разделителя, и я не знаю, как создать файл формата, который учитывает это.

Я попытался использовать ',' в качестве разделителя и загрузить его во временную таблицу, где каждый столбец является varchar, а затем с помощью некоторой хитрой обработки, чтобы удалить кавычки, но это тоже не работает, потому что поля, содержащие ',' разделены на несколько столбцов.

К сожалению, у меня нет возможности манипулировать CSV-файлом заранее.

Это безнадежно?

Заранее большое спасибо за любые советы.

Кстати, я видел этот пост Массовый импорт SQL из CSV , но в этом случае КАЖДОЕ поле было последовательно заключено в кавычки. Таким образом, в этом случае он мог бы использовать ',' в качестве разделителя, а затем вычеркнуть кавычки.

Ответы [ 15 ]

19 голосов
/ 23 апреля 2009

Невозможно выполнить массовую вставку для этого файла из MSDN:

Чтобы использовать его как файл данных для массового импорта, файл CSV должен соответствовать следующим ограничениям:

  • Поля данных никогда не содержат терминатора поля.
  • Либо ни одно, ни все значения в поле данных заключены в кавычки ("").

(http://msdn.microsoft.com/en-us/library/ms188609.aspx)

Некоторая простая обработка текста должна быть всем, что требуется для подготовки файла к импорту. В качестве альтернативы вашим пользователям может потребоваться либо отформатировать файл в соответствии с указаниями se, либо использовать в качестве разделителя что-либо кроме запятой (например, |)

18 голосов
/ 23 января 2010

Вам нужно предварительно обработать файл, точка.

Если вам действительно нужно это сделать, вот код. Я написал это, потому что у меня абсолютно не было выбора. Это служебный код, и я не горжусь этим, но он работает. Подход заключается не в том, чтобы заставить SQL понимать поля в кавычках, а в том, чтобы манипулировать файлом, чтобы использовать совершенно другой разделитель.

РЕДАКТИРОВАТЬ: Вот код в репозитории GitHub. Он был улучшен и теперь поставляется с юнит-тестами! https://github.com/chrisclark/Redelim-it

Эта функция принимает входной файл и заменяет все запятые с разделителями полей (НЕ запятые внутри текстовых полей в кавычках, только фактические разделители) новым разделителем. Затем вы можете указать серверу sql использовать новый разделитель полей вместо запятой. В версии функции здесь заполнитель - <<em> TMP > (я уверен, что это не будет появляться в исходном csv - если это так, готовьтесь к взрывам).

Поэтому после запуска этой функции вы импортируете в sql, выполнив что-то вроде:

BULK INSERT MyTable
FROM 'C:\FileCreatedFromThisFunction.csv'
WITH
(
FIELDTERMINATOR = '<*TMP*>',
ROWTERMINATOR = '\n'
)

И без дальнейших церемоний, ужасная, ужасная функция, которую я заранее извиняюсь за то, что навязал вам (правка - я разместил рабочую программу, которая делает это вместо функции в моем блоге здесь ):

Private Function CsvToOtherDelimiter(ByVal InputFile As String, ByVal OutputFile As String) As Integer

        Dim PH1 As String = "<*TMP*>"

        Dim objReader As StreamReader = Nothing
        Dim count As Integer = 0 'This will also serve as a primary key'
        Dim sb As New System.Text.StringBuilder

        Try
            objReader = New StreamReader(File.OpenRead(InputFile), System.Text.Encoding.Default)
        Catch ex As Exception
            UpdateStatus(ex.Message)
        End Try

        If objReader Is Nothing Then
            UpdateStatus("Invalid file: " & InputFile)
            count = -1
            Exit Function
        End If

        'grab the first line
    Dim line = reader.ReadLine()
    'and advance to the next line b/c the first line is column headings
    If hasHeaders Then
        line = Trim(reader.ReadLine)
    End If

    While Not String.IsNullOrEmpty(line) 'loop through each line

        count += 1

        'Replace commas with our custom-made delimiter
        line = line.Replace(",", ph1)

        'Find a quoted part of the line, which could legitimately contain commas.
        'In that case we will need to identify the quoted section and swap commas back in for our custom placeholder.
        Dim starti = line.IndexOf(ph1 & """", 0)
        If line.IndexOf("""",0) = 0 then starti=0

        While starti > -1 'loop through quoted fields

            Dim FieldTerminatorFound As Boolean = False

            'Find end quote token (originally  a ",)
            Dim endi As Integer = line.IndexOf("""" & ph1, starti)

            If endi < 0 Then
                FieldTerminatorFound = True
                If endi < 0 Then endi = line.Length - 1
            End If

            While Not FieldTerminatorFound

                'Find any more quotes that are part of that sequence, if any
                Dim backChar As String = """" 'thats one quote
                Dim quoteCount = 0
                While backChar = """"
                    quoteCount += 1
                    backChar = line.Chars(endi - quoteCount)
                End While

                If quoteCount Mod 2 = 1 Then 'odd number of quotes. real field terminator
                    FieldTerminatorFound = True
                Else 'keep looking
                    endi = line.IndexOf("""" & ph1, endi + 1)
                End If
            End While

            'Grab the quoted field from the line, now that we have the start and ending indices
            Dim source = line.Substring(starti + ph1.Length, endi - starti - ph1.Length + 1)

            'And swap the commas back in
            line = line.Replace(source, source.Replace(ph1, ","))

            'Find the next quoted field
            '                If endi >= line.Length - 1 Then endi = line.Length 'During the swap, the length of line shrinks so an endi value at the end of the line will fail
            starti = line.IndexOf(ph1 & """", starti + ph1.Length)

        End While

            line = objReader.ReadLine

        End While

        objReader.Close()

        SaveTextToFile(sb.ToString, OutputFile)

        Return count

    End Function
8 голосов
/ 02 марта 2012

Мне очень помог ответ Криса, но я хотел запустить его из SQL Server, используя T-SQL (а не CLR), поэтому я преобразовал его код в код T-SQL. Но затем я сделал еще один шаг, обернув все в хранимую процедуру, которая сделала следующее:

  1. использовать массовую вставку для первоначального импорта файла CSV
  2. очистить строки, используя код Криса
  3. вернуть результаты в виде таблицы

Для своих нужд я дополнительно очистил строки, удалив кавычки вокруг значений и преобразовав две двойные кавычки в одну двойную кавычку (я думаю, это правильный метод).

CREATE PROCEDURE SSP_CSVToTable

-- Add the parameters for the stored procedure here
@InputFile nvarchar(4000)
, @FirstLine int

AS

BEGIN

-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

--convert the CSV file to a table
--clean up the lines so that commas are handles correctly

DECLARE @sql nvarchar(4000)
DECLARE @PH1 nvarchar(50)
DECLARE @LINECOUNT int -- This will also serve as a primary key
DECLARE @CURLINE int
DECLARE @Line nvarchar(4000)
DECLARE @starti int
DECLARE @endi int
DECLARE @FieldTerminatorFound bit
DECLARE @backChar nvarchar(4000)
DECLARE @quoteCount int
DECLARE @source nvarchar(4000)
DECLARE @COLCOUNT int
DECLARE @CURCOL int
DECLARE @ColVal nvarchar(4000)

-- new delimiter
SET @PH1 = '†'

-- create single column table to hold each line of file
CREATE TABLE [#CSVLine]([line] nvarchar(4000))

-- bulk insert into temp table
-- cannot use variable path with bulk insert
-- so we must run using dynamic sql
SET @Sql = 'BULK INSERT #CSVLine
FROM ''' + @InputFile + '''
WITH
(
FIRSTROW=' + CAST(@FirstLine as varchar) + ',
FIELDTERMINATOR = ''\n'',
ROWTERMINATOR = ''\n''
)'

-- run dynamic statement to populate temp table
EXEC(@sql)

-- get number of lines in table
SET @LINECOUNT = @@ROWCOUNT

-- add identity column to table so that we can loop through it
ALTER TABLE [#CSVLine] ADD [RowId] [int] IDENTITY(1,1) NOT NULL

IF @LINECOUNT > 0
BEGIN
    -- cycle through each line, cleaning each line
    SET @CURLINE = 1
    WHILE @CURLINE <= @LINECOUNT
    BEGIN
        -- get current line
        SELECT @line = line
          FROM #CSVLine
         WHERE [RowId] = @CURLINE

        -- Replace commas with our custom-made delimiter
        SET @Line = REPLACE(@Line, ',', @PH1)

        -- Find a quoted part of the line, which could legitimately contain commas.
        -- In that case we will need to identify the quoted section and swap commas back in for our custom placeholder.
        SET @starti = CHARINDEX(@PH1 + '"' ,@Line, 0)
        If CHARINDEX('"', @Line, 0) = 0 SET @starti = 0

        -- loop through quoted fields
        WHILE @starti > 0 
        BEGIN
            SET @FieldTerminatorFound = 0

            -- Find end quote token (originally  a ",)
            SET @endi = CHARINDEX('"' + @PH1, @Line, @starti)  -- sLine.IndexOf("""" & PH1, starti)

            IF @endi < 1
            BEGIN
                SET @FieldTerminatorFound = 1
                If @endi < 1 SET @endi = LEN(@Line) - 1
            END

            WHILE @FieldTerminatorFound = 0
            BEGIN
                -- Find any more quotes that are part of that sequence, if any
                SET @backChar = '"' -- thats one quote
                SET @quoteCount = 0

                WHILE @backChar = '"'
                BEGIN
                    SET @quoteCount = @quoteCount + 1
                    SET @backChar = SUBSTRING(@Line, @endi-@quoteCount, 1) -- sLine.Chars(endi - quoteCount)
                END

                IF (@quoteCount % 2) = 1
                BEGIN
                    -- odd number of quotes. real field terminator
                    SET @FieldTerminatorFound = 1
                END
                ELSE 
                BEGIN
                    -- keep looking
                    SET @endi = CHARINDEX('"' + @PH1, @Line, @endi + 1) -- sLine.IndexOf("""" & PH1, endi + 1)
                END

            END

            -- Grab the quoted field from the line, now that we have the start and ending indices
            SET @source = SUBSTRING(@Line, @starti + LEN(@PH1), @endi - @starti - LEN(@PH1) + 1) 
            -- sLine.Substring(starti + PH1.Length, endi - starti - PH1.Length + 1)

            -- And swap the commas back in
            SET @Line = REPLACE(@Line, @source, REPLACE(@source, @PH1, ','))
            --sLine.Replace(source, source.Replace(PH1, ","))

            -- Find the next quoted field
            -- If endi >= line.Length - 1 Then endi = line.Length 'During the swap, the length of line shrinks so an endi value at the end of the line will fail
            SET @starti = CHARINDEX(@PH1 + '"', @Line, @starti + LEN(@PH1))
            --sLine.IndexOf(PH1 & """", starti + PH1.Length)

        END

        -- get table based on current line
        IF OBJECT_ID('tempdb..#Line') IS NOT NULL
            DROP TABLE #Line

        -- converts a delimited list into a table
        SELECT *
        INTO #Line
        FROM dbo.iter_charlist_to_table(@Line,@PH1)

        -- get number of columns in line
        SET @COLCOUNT = @@ROWCOUNT

        -- dynamically create CSV temp table to hold CSV columns and lines
        -- only need to create once
        IF OBJECT_ID('tempdb..#CSV') IS NULL
        BEGIN
            -- create initial structure of CSV table
            CREATE TABLE [#CSV]([Col1] nvarchar(100))

            -- dynamically add a column for each column found in the first line
            SET @CURCOL = 1
            WHILE @CURCOL <= @COLCOUNT
            BEGIN
                -- first column already exists, don't need to add
                IF @CURCOL > 1 
                BEGIN
                    -- add field
                    SET @sql = 'ALTER TABLE [#CSV] ADD [Col' + Cast(@CURCOL as varchar) + '] nvarchar(100)'

                    --print @sql

                    -- this adds the fields to the temp table
                    EXEC(@sql)
                END

                -- go to next column
                SET @CURCOL = @CURCOL + 1
            END
        END

        -- build dynamic sql to insert current line into CSV table
        SET @sql = 'INSERT INTO [#CSV] VALUES('

        -- loop through line table, dynamically adding each column value
        SET @CURCOL = 1
        WHILE @CURCOL <= @COLCOUNT
        BEGIN
            -- get current column
            Select @ColVal = str 
              From #Line 
             Where listpos = @CURCOL

            IF LEN(@ColVal) > 0
            BEGIN
                -- remove quotes from beginning if exist
                IF LEFT(@ColVal,1) = '"'
                    SET @ColVal = RIGHT(@ColVal, LEN(@ColVal) - 1)

                -- remove quotes from end if exist
                IF RIGHT(@ColVal,1) = '"'
                    SET @ColVal = LEFT(@ColVal, LEN(@ColVal) - 1)
            END

            -- write column value
            -- make value sql safe by replacing single quotes with two single quotes
            -- also, replace two double quotes with a single double quote
            SET @sql = @sql + '''' + REPLACE(REPLACE(@ColVal, '''',''''''), '""', '"') + ''''

            -- add comma separater except for the last record
            IF @CURCOL <> @COLCOUNT
                SET @sql = @sql + ','

            -- go to next column
            SET @CURCOL = @CURCOL + 1
        END

        -- close sql statement
        SET @sql = @sql + ')'

        --print @sql

        -- run sql to add line to table
        EXEC(@sql)

        -- move to next line
        SET @CURLINE = @CURLINE + 1

    END

END

-- return CSV table
SELECT * FROM [#CSV]

END

GO

Хранимая процедура использует эту вспомогательную функцию, которая анализирует строку в таблице (спасибо Erland Sommarskog!):

CREATE FUNCTION [dbo].[iter_charlist_to_table]
                (@list      ntext,
                 @delimiter nchar(1) = N',')
     RETURNS @tbl TABLE (listpos int IDENTITY(1, 1) NOT NULL,
                         str     varchar(4000),
                         nstr    nvarchar(2000)) AS

BEGIN
  DECLARE @pos      int,
          @textpos  int,
          @chunklen smallint,
          @tmpstr   nvarchar(4000),
          @leftover nvarchar(4000),
          @tmpval   nvarchar(4000)

  SET @textpos = 1
  SET @leftover = ''
  WHILE @textpos <= datalength(@list) / 2
  BEGIN
     SET @chunklen = 4000 - datalength(@leftover) / 2
     SET @tmpstr = @leftover + substring(@list, @textpos, @chunklen)
     SET @textpos = @textpos + @chunklen

     SET @pos = charindex(@delimiter, @tmpstr)

     WHILE @pos > 0
     BEGIN
        SET @tmpval = ltrim(rtrim(left(@tmpstr, @pos - 1)))
        INSERT @tbl (str, nstr) VALUES(@tmpval, @tmpval)
        SET @tmpstr = substring(@tmpstr, @pos + 1, len(@tmpstr))
        SET @pos = charindex(@delimiter, @tmpstr)
     END

     SET @leftover = @tmpstr
  END

  INSERT @tbl(str, nstr) VALUES (ltrim(rtrim(@leftover)), ltrim(rtrim(@leftover)))

RETURN

END

Вот как я это называю из T-SQL. В этом случае я вставляю результаты в временную таблицу, поэтому сначала создаю временную таблицу:

    -- create temp table for file import
CREATE TABLE #temp
(
    CustomerCode nvarchar(100) NULL,
    Name nvarchar(100) NULL,
    [Address] nvarchar(100) NULL,
    City nvarchar(100) NULL,
    [State] nvarchar(100) NULL,
    Zip nvarchar(100) NULL,
    OrderNumber nvarchar(100) NULL,
    TimeWindow nvarchar(100) NULL,
    OrderType nvarchar(100) NULL,
    Duration nvarchar(100) NULL,
    [Weight] nvarchar(100) NULL,
    Volume nvarchar(100) NULL
)

-- convert the CSV file into a table
INSERT #temp
EXEC [dbo].[SSP_CSVToTable]
     @InputFile = @FileLocation
    ,@FirstLine = @FirstImportRow

Я не очень тестировал производительность, но она хорошо работает для того, что мне нужно - импортировать файлы CSV с менее чем 1000 строк. Тем не менее, он может подавиться очень большими файлами.

Надеюсь, кто-то еще найдет это полезным.

Ура!

5 голосов
/ 21 января 2011

Я также создал функцию для преобразования CSV в пригодный для использования формат для массовой вставки. Я использовал ответный пост Криса Кларка в качестве отправной точки для создания следующей функции C #.

Я использовал регулярное выражение для поиска полей. Затем я построчно воссоздал файл, записав его в новый файл по мере необходимости, чтобы избежать загрузки всего файла в память.

private void CsvToOtherDelimiter(string CSVFile, System.Data.Linq.Mapping.MetaTable tbl)
{
    char PH1 = '|';
    StringBuilder ln;

    //Confirm file exists. Else, throw exception
    if (File.Exists(CSVFile))
    {
        using (TextReader tr = new StreamReader(CSVFile))
        {
            //Use a temp file to store our conversion
            using (TextWriter tw = new StreamWriter(CSVFile + ".tmp"))
            {
                string line = tr.ReadLine();
                //If we have already converted, no need to reconvert.
                //NOTE: We make the assumption here that the input header file 
                //      doesn't have a PH1 value unless it's already been converted.
                if (line.IndexOf(PH1) >= 0)
                {
                    tw.Close();
                    tr.Close();
                    File.Delete(CSVFile + ".tmp");
                    return;
                }
                //Loop through input file
                while (!string.IsNullOrEmpty(line))
                {
                    ln = new StringBuilder();

                    //1. Use Regex expression to find comma separated values 
                    //using quotes as optional text qualifiers 
                    //(what MS EXCEL does when you import a csv file)
                    //2. Remove text qualifier quotes from data
                    //3. Replace any values of PH1 found in column data 
                    //with an equivalent character
                    //Regex:  \A[^,]*(?=,)|(?:[^",]*"[^"]*"[^",]*)+|[^",]*"[^"]*\Z|(?<=,)[^,]*(?=,)|(?<=,)[^,]*\Z|\A[^,]*\Z
                    List<string> fieldList = Regex.Matches(line, @"\A[^,]*(?=,)|(?:[^"",]*""[^""]*""[^"",]*)+|[^"",]*""[^""]*\Z|(?<=,)[^,]*(?=,)|(?<=,)[^,]*\Z|\A[^,]*\Z")
                            .Cast<Match>()
                            .Select(m => RemoveCSVQuotes(m.Value).Replace(PH1, '¦'))
                            .ToList<string>();

                    //Add the list of fields to ln, separated by PH1
                    fieldList.ToList().ForEach(m => ln.Append(m + PH1));

                    //Write to file. Don't include trailing PH1 value.
                    tw.WriteLine(ln.ToString().Substring(0, ln.ToString().LastIndexOf(PH1)));

                    line = tr.ReadLine();
                }


                tw.Close();
            }
            tr.Close();

            //Optional:  replace input file with output file
            File.Delete(CSVFile);
            File.Move(CSVFile + ".tmp", CSVFile);
        }
    }
    else
    {
        throw new ArgumentException(string.Format("Source file {0} not found", CSVFile));
    }
}
//The output file no longer needs quotes as a text qualifier, so remove them
private string RemoveCSVQuotes(string value)
{
    //if is empty string, then remove double quotes
    if (value == @"""""") value = "";
    //remove any double quotes, then any quotes on ends
    value = value.Replace(@"""""", @"""");
    if (value.Length >= 2)
        if (value.Substring(0, 1) == @"""")
            value = value.Substring(1, value.Length - 2);
    return value;
}
3 голосов
/ 22 ноября 2013

Чаще всего эта проблема вызвана тем, что пользователи экспортируют файл Excel в CSV.

Существует два способа решения этой проблемы:

  1. Экспорт из Excel с использованием макроса, согласно предложению Microsoft
  2. Или действительно простой способ:
    • Откройте CSV в Excel.
    • Сохранить как файл Excel. (.xls или .xlsx).
    • Импорт этого файла в SQL Server как файл Excel .
    • Посмеяться над собой, потому что вам не нужно было ничего кодировать, как в приведенных выше решениях ... мухахахаха

Import as Excel file

Вот некоторые SQL , если вы действительно хотите написать его (после сохранения CSV-файла в Excel):

select * 
into SQLServerTable FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0', 
    'Excel 8.0;Database=D:\testing.xls;HDR=YES', 
    'SELECT * FROM [Sheet1$]')
2 голосов
/ 20 октября 2012

Альтернативный метод - при условии, что у вас нет загрузки полей или вы ожидаете, что в самих данных появится кавычка, можно использовать функцию REPLACE.

UPDATE dbo.tablename 
        SET dbo.tablename.target_field = REPLACE(t.importedValue, '"', '')
FROM #tempTable t
WHERE dbo.tablename.target_id = t.importedID;

Я использовал это. Я не могу претендовать на производительность. Это просто быстрый и грязный способ обойти проблему.

2 голосов
/ 28 сентября 2009

Это может быть сложнее или сложнее, чем то, что вы готовы использовать, но ...

Если вы можете реализовать логику для анализа строк в полях в VB или C #, вы можете сделать это с помощью функции табличного значения CLR (TVF).

CLR TVF может быть хорошим способом считывания данных из внешнего источника, когда вы хотите, чтобы какой-нибудь код C # или VB разделял данные на столбцы и / или корректировал значения.

Вы должны быть готовы добавить сборку CLR в вашу базу данных (и такую, которая разрешает внешние или небезопасные операции, чтобы она могла открывать файлы). Это может быть немного сложнее или сложнее, но может стоить того, что вы получаете.

У меня было несколько больших файлов, которые нужно было регулярно загружать в таблицы как можно быстрее, но для некоторых столбцов нужно было выполнять определенные переводы кода, и требовалась специальная обработка для загрузки значений, которые в противном случае вызвали бы ошибки типа данных с простым основная вставка.

Короче говоря, CLR TVF позволяет вам запускать код C # или VB для каждой строки файла с массовой вставкой, такой как производительность (хотя вам может потребоваться беспокоиться о ведении журнала). Пример в документации по SQL Server позволяет создать TVF для чтения из журнала событий, который можно использовать в качестве отправной точки.

Обратите внимание, что код в TVR CLR может получить доступ к базе данных только на этапе инициализации до обработки первой строки (например, нет поиска для каждой строки - вы используете обычный TVF поверх этого, чтобы делать такие вещи). Похоже, вам это не нужно по вашему вопросу.

Также обратите внимание, что у каждого CLR TVF должны быть явно указаны выходные столбцы, поэтому вы не можете написать общий, который можно использовать повторно для каждого отдельного файла CSV, который у вас может быть.

Вы можете написать один CLR TVF для чтения целых строк из файла, возвращая набор результатов из одного столбца, а затем использовать обычные TVF для чтения из этого файла для каждого типа файла. Это требует, чтобы код анализировал каждую строку для написания на T-SQL, но избегает необходимости писать много TVR CLR.

1 голос
/ 11 декабря 2018

Требуется предварительная обработка.

Функция PowerShell Import-CSV поддерживает этот тип файла. Export-CSV затем инкапсулирует каждое значение в кавычках.

Один файл:

Import-Csv import.csv | Export-Csv -NoTypeInformation export.csv

Чтобы объединить множество файлов с путями C: \ year \ input_date.csv:

$inputPath = 'C:\????\input_????????.csv'
$outputPath = 'C:\merged.csv'
Get-ChildItem $inputPath |
  Select -ExpandProperty FullName |
  Import-CSV |
  Export-CSV -NoTypeInformation -Path $outputPath

PowerShell обычно можно запускать с агентом SQL Server с использованием учетной записи-посредника PowerShell.

Если разделители не обрабатываются должным образом, явно укажите другой разделитель.

 Export-CSV -NoTypeInformation -Delimiter ';' -Path $outputPath
1 голос
/ 12 ноября 2010

Я обнаружил несколько проблем, когда в наших полях, таких как Майк, было «,», 456 2nd St, Apt 5 ».

Решение этой проблемы: @ http://crazzycoding.blogspot.com/2010/11/import-csv-file-into-sql-server-using.html

Спасибо, - Ашиш

1 голос
/ 19 мая 2010

Крис, Спасибо большое за это! Ты спас мое печенье !! Я не мог поверить, что массовый погрузчик не справится с этим делом, когда XL делает такую ​​хорошую работу ... разве эти парни не видят друг друга в залах ??? В любом случае ... Мне нужна версия ConsoleApplication, так что вот что я взломал вместе. Это плохо и грязно, но работает как чемпион! Я жестко закодировал разделитель и закомментировал заголовок, так как он не нужен для моего приложения.

Хотел бы я тоже вставить сюда большое пиво для тебя.

Боже, я понятия не имею, почему Конечный Модуль и Открытый Класс находятся вне блока кода ... srry!

    Module Module1

    Sub Main()

        Dim arrArgs() As String = Command.Split(",")
        Dim i As Integer
        Dim obj As New ReDelimIt()

        Console.Write(vbNewLine & vbNewLine)

        If arrArgs(0) <> Nothing Then
            For i = LBound(arrArgs) To UBound(arrArgs)
                Console.Write("Parameter " & i & " is " & arrArgs(i) & vbNewLine)
            Next


            obj.ProcessFile(arrArgs(0), arrArgs(1))

        Else
            Console.Write("Usage Test1 <inputfile>,<outputfile>")
        End If

        Console.Write(vbNewLine & vbNewLine)
    End Sub

 End Module

 Public Class ReDelimIt

    Public Function ProcessFile(ByVal InputFile As String, ByVal OutputFile As String) As Integer

        Dim ph1 As String = "|"

        Dim objReader As System.IO.StreamReader = Nothing
        Dim count As Integer = 0 'This will also serve as a primary key
        Dim sb As New System.Text.StringBuilder

        Try
            objReader = New System.IO.StreamReader(System.IO.File.OpenRead(InputFile), System.Text.Encoding.Default)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

        If objReader Is Nothing Then
            MsgBox("Invalid file: " & InputFile)
            count = -1
            Exit Function
        End If

        'grab the first line
        Dim line = objReader.ReadLine()
        'and advance to the next line b/c the first line is column headings
        'Removed Check Headers can put in if needed.
        'If chkHeaders.Checked Then
        'line = objReader.ReadLine
        'End If

        While Not String.IsNullOrEmpty(line) 'loop through each line

            count += 1

            'Replace commas with our custom-made delimiter
            line = line.Replace(",", ph1)

            'Find a quoted part of the line, which could legitimately contain commas.
            'In that case we will need to identify the quoted section and swap commas back in for our custom placeholder.
            Dim starti = line.IndexOf(ph1 & """", 0)

            While starti > -1 'loop through quoted fields

                'Find end quote token (originally  a ",)
                Dim endi = line.IndexOf("""" & ph1, starti)

                'The end quote token could be a false positive because there could occur a ", sequence.
                'It would be double-quoted ("",) so check for that here
                Dim check1 = line.IndexOf("""""" & ph1, starti)

                'A """, sequence can occur if a quoted field ends in a quote.
                'In this case, the above check matches, but we actually SHOULD process this as an end quote token
                Dim check2 = line.IndexOf("""""""" & ph1, starti)

                'If we are in the check1 ("",) situation, keep searching for an end quote token
                'The +1 and +2 accounts for the extra length of the checked sequences
                While (endi = check1 + 1 AndAlso endi <> check2 + 2) 'loop through "false" tokens in the quoted fields
                    endi = line.IndexOf("""" & ph1, endi + 1)
                    check1 = line.IndexOf("""""" & ph1, check1 + 1)
                    check2 = line.IndexOf("""""""" & ph1, check2 + 1)
                End While

                'We have searched for an end token (",) but can't find one, so that means the line ends in a "
                If endi < 0 Then endi = line.Length - 1

                'Grab the quoted field from the line, now that we have the start and ending indices
                Dim source = line.Substring(starti + ph1.Length, endi - starti - ph1.Length + 1)

                'And swap the commas back in
                line = line.Replace(source, source.Replace(ph1, ","))

                'Find the next quoted field
                If endi >= line.Length - 1 Then endi = line.Length 'During the swap, the length of line shrinks so an endi value at the end of the line will fail
                starti = line.IndexOf(ph1 & """", starti + ph1.Length)

            End While

            'Add our primary key to the line
            ' Removed for now
            'If chkAddKey.Checked Then
            'line = String.Concat(count.ToString, ph1, line)
            ' End If

            sb.AppendLine(line)

            line = objReader.ReadLine

        End While

        objReader.Close()

        SaveTextToFile(sb.ToString, OutputFile)

        Return count

    End Function

    Public Function SaveTextToFile(ByVal strData As String, ByVal FullPath As String) As Boolean
        Dim bAns As Boolean = False
        Dim objReader As System.IO.StreamWriter
        Try
            objReader = New System.IO.StreamWriter(FullPath, False, System.Text.Encoding.Default)
            objReader.Write(strData)
            objReader.Close()
            bAns = True
        Catch Ex As Exception
            Throw Ex
        End Try
        Return bAns
    End Function

End Class
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...