У меня есть тяжелый XML-файл 1Go, имеющий следующую структуру:

 <?xml version='1.0' encoding='windows-1252'?>
 <ext:BookingExtraction>
     <Booking><Code>2016Z00258</Code><Advertiser><Code>00123</Code<Name>LOUIS VUITTON</Name></Advertiser></Booking>
     <Booking><Code>2016Z00259</Code><Advertiser><Code>00124</Code<Name>Adidas</Name></Advertiser></Booking>
 </ext:BookingExtraction>

Поскольку структура действительно проста, моя цель - заставить 150 последних строк XML-файла скопировать их в новый файл и добавить открывающий тег в первую строку, чтобы получить правильно сформированный XML.

Алгоритм работает нормально, но некоторые строки, содержащие более 65 536 символов, разбиты на несколько строк. Я читал, что DOS ограничивает количество символов в строке 65 536. Вот почему он добавляет символ ввода каретки после этих 65 536 символов.

В результате окончательный XML-код сформирован неправильно из-за ввода каретки в середине строки. Например:

 <ext:BookingExtraction>
     <Booking><Code>2016Z00258</Code><Advertiser><Code>00123</Code><Name>LOUIS VUIT
TON</Name></Advertiser></Booking>
</ext:BookingExtraction>

Я попытался удалить символы ввода каретки, но это не сработало. Ты хоть представляешь, как я могу это исправить?

`@echo off
setLocal EnableDelayedExpansion

::Get XML file
for /r %%a in (extractedBookings_BookingWithoutUnitsContent_PRD_*.xml) do (
    ::echo "%%~dpa" and full path is "%%~nxa"
    set fileName="%%~nxa"
)


::Get the 150 last line of the file 
    echo File path: "%fileName%"    
    for /f %%i in ('find /v /c "" ^< "%fileName%"') do set /a lines=%%i
    echo nb lines: "%lines%"
    set /a startLine=%lines% - 150
    echo Start line "%startLine%"
    more /e +%startLine% "%fileName%" > extractedBookings_BookingWithoutUnitsContent_PRD.xml



::adding opening tag to the new file
    echo ^<?xml version='1.0' encoding='windows-1252'?^> > newFile.xml
    echo ^<ext:BookingExtraction^> >> newFile.xml

::Get the final file
   type extractedBookings_BookingWithoutUnitsContent_PRD.xml >> newFile.xml
   type newFile.xml > extractedBookings_BookingWithoutUnitsContent_PRD.xml`

Заранее спасибо

1
Bob 29 Фев 2016 в 18:08

2 ответа

Лучший ответ

Ваш вопрос сбивает с толку; фраза «ДОС ограничить количество строк 65 536 символов» неточна. Когда вывод команды more перенаправляется в файл на диске, он ожидает символа после 65536 строк , и такой символ вставляется в вывод. Кроме того, максимальная длина строки в команде FIND составляет 1070 символов (в соответствии с этим сайтом), поэтому я угадайте, что в вашем файле более короткие строки. Вам просто нужен метод, который может выводить более 64 КБ строк.

Приведенное ниже решение - это в основном ваш тот же код, но в нем используется комбинация команды set /P для пропуска первых строк и команды findstr для отображения остальных вместо вашей команды more +%startLine%.

@echo off
setLocal EnableDelayedExpansion

::Get XML file
for /r %%a in (extractedBookings_BookingWithoutUnitsContent_PRD_*.xml) do (
    ::echo "%%~dpa" and full path is "%%~nxa"
    set fileName="%%~nxa"
)


::Get the 150 last line of the file 
    echo File path: "%fileName%"    
    for /f %%i in ('find /v /c "" ^< "%fileName%"') do set /a lines=%%i
    echo nb lines: "%lines%"
    set /a startLine=%lines% - 150
    echo Start line "%startLine%"

    REM Use a code block to read from redirected input file (and write to output file)
    < "%fileName%" (

       rem adding opening tag to the new file
       echo ^<?xml version='1.0' encoding='windows-1252'?^>
       echo ^<ext:BookingExtraction^>

       REM Skip the first total-150 lines
       for /L %%i in (1,1,%startLine%) do set /P "="

       REM Copy the rest
       findstr "^"

    ) > extractedBookings_BookingWithoutUnitsContent_PRD.xml

Этот метод все равно может дать сбой, если длина строки ввода превышает 1023 символа, потому что это предел команды set /P.

1
Aacini 1 Мар 2016 в 01:36

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

Ваш пример XML по-прежнему немного неоднозначен, поэтому я предполагаю, что у вас есть один тег <ext:BookingExtraction> с тонной дочерних узлов <Booking>, которые вы хотите сократить до последних 150.

Однако, прежде чем ваш пример XML сможет быть проанализирован (помимо исправления отсутствующего > в </code>), нам нужно немного его обработать, определив пространство имен, которому принадлежит ext.

Перед:

<ext:BookingExtraction>

После:

<ext:BookingExtraction xmlns:ext="http://localhost">

Хотя, строго говоря, это, вероятно, поддельное пространство имен, тем не менее оно достаточно хорошо для того, чтобы сделать XML-анализируемым. Мы можем сделать это программно, прочитав XML в переменную и выполнив замену регулярного выражения. После этого достаточно просто удалить дочерние узлы в цикле while, пока вы не достигнете своей цели в 150 элементов.

Сохраните это с расширением .bat, замените test.xml на местоположение вашего файла XML и запустите его.

@if (@CodeSection == @Batch) @then
@echo off & setlocal
cscript /nologo /e:JScript "%~f0" "test.xml" "output.xml"
goto :EOF
@end // end Batch / begin JScript hybrid code

var args = { infile: WSH.Arguments(0), outfile: WSH.Arguments(1) },
    fso = WSH.CreateObject('Scripting.FileSystemObject'),
    file = fso.OpenTextFile(args.infile, 1),
    xml = file.ReadAll(),
    DOM = WSH.CreateObject('MSXML2.DOMDocument.6.0'),
    ns = 'xmlns:ext="http://localhost"',
    xpath = '/ext:BookingExtraction/Booking';

file.Close();
DOM.loadXML(xml.replace(
    /<(ext:BookingExtraction)>/i,
    function($0, $1) { return '<' + $1 + ' ' + ns + '>' }
));

if (DOM.parseError.errorCode) {
    var e = DOM.parseError;
    WSH.StdErr.WriteLine('Error in ' + args.infile + ' line ' + e.line + ' char '
        + e.linepos + ':\n' + e.reason + '\n' + e.srcText);
    WSH.Quit(1);
}

DOM.setProperty('SelectionNamespaces', ns);

while (DOM.selectNodes(xpath).length > 150) {
    var node = DOM.selectSingleNode(xpath)
    node.parentNode.removeChild(node)
}

DOM.save(args.outfile)

... Или может быть проще просто вырезать пространство имен ext: и заменить его позже. Вот пакетный гибридный скрипт + PowerShell, который демонстрирует. Это не так быстро, как гибрид пакетной + Jscript, и у него есть побочный эффект украшения всех тегов, независимо от того, хотите ли вы, чтобы они были с отступом или нет. Но у него есть преимущество простоты.

<# : batch portion
@echo off & setlocal

set "infile=test.xml"
set "outfile=out.xml"

powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF
: end batch / begin PowerShell hybrid #>

[xml]$xml = (gc $env:infile) -replace "ext:"
$xpath = "/BookingExtraction/Booking"
$deleted = 0

while ($xml.selectNodes($xpath).Count -gt 150) {
    $node = $xml.selectSingleNode($xpath)
    [void]$node.parentNode.removeChild($node)
    $deleted++
}

write-host "Removed $deleted ndoes" -f magenta

$xml.save($env:outfile)
(gc $env:outfile) -replace "BookingExtraction", "ext:BookingExtraction" | sc $env:outfile

Изменить: при работе с большими файлами (более 1 ГБ), может быть, на самом деле было бы лучше обрезать жир как плоский текст, а не манипулировать как данными структурированного объекта. Если вам нужны последние 150 строк, я думаю, будет более эффективным начать снизу и работать в обратном направлении, чем начинать сверху и пропускать миллионы строк. Открытие XML-файла с помощью методов .NET позволит вам почти мгновенно перейти к концу файла, а затем подойти к нему. Попробуйте этот пакет + сценарий PowerShell и посмотрите, работает ли он для вас более эффективно:

<# : batch portion
@echo off & setlocal

set "infile=test.xml"
set "outfile=out.xml"

powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF
: end batch / begin PowerShell hybrid #>

$lines = 150
$found = 0
$reader = new-object IO.StreamReader((gi $env:infile).FullName)
$stream = $reader.BaseStream
$xml = $reader.ReadLine(), $reader.ReadLine()

$pos = $stream.Seek(0, [IO.SeekOrigin]::End)

while ($found -le $lines) {

    $reader.DiscardBufferedData()
    $stream.Position = --$pos
    $char = $reader.Peek()

    if ($char -eq -1) { break }
    else { if ($char -eq 10) { $found++ } }
}

$reader.DiscardBufferedData()
$stream.Position = ++$pos

$xml += $reader.ReadToEnd()
$reader.Close()

$xml -join "`r`n" | out-file $env:outfile
0
rojo 3 Мар 2016 в 02:53