Batch Insert Element from XML with Shared Filename

2020-02-07 12:43发布

问题:

I'm trying to insert 800 unique <REMARK>this is a remark</REMARK> elements into an existing set of 800 XML files. I generated 800 documents with only the <REMARK> element I want inserted into each XML. The thought being, I could insert this element into the corresponding XML document, based on a shared filename. For example, I have an XML called WNYC-SCHK-2004-02-20-37540.xml in one folder with only this information in it <REMARK>Think of Spanish classical music and the name Manuel de Falla naturally comes to mind.</REMARK> And I want to insert the element into a file also named WNYC-SCHK-2004-02-20-37540.xml just below the <CHANGETIME> element below:

<?xml version="1.0" encoding="ISO-8859-1"?>
<ENTRIES>
 <ENTRY>
  <NUMBER>622</NUMBER>
  <CLASS>Audio</CLASS>
  <TITLE>WNYC-SCHK-2004-02-20-37540</TITLE>
  <GENERATOR>DBM</GENERATOR>
  <CREATOR>JPASSMOR</CREATOR>
  <DATE>2015-01-06</DATE>
  <DATUM>2015-01-06</DATUM>
  <TIME>11:48:59</TIME>
  <TIMESTAMP>2015-01-06 11:48:59</TIMESTAMP>
  <LENGTH>00:58:53.920</LENGTH>
  <DURATION>3533920</DURATION>
  <SOFTDELETED>0</SOFTDELETED>
  <NODELETE>0</NODELETE>
  <READY>0</READY>
  <PERFECT>0</PERFECT>
  <FORARCHIVE>0</FORARCHIVE>
  <ARCHIVING>0</ARCHIVING>
  <ARCHIVED>0</ARCHIVED>
  <GROWING>0</GROWING>
  <NEW>0</NEW>
  <INVALID>0</INVALID>
  <LOWRESEXISTS>0</LOWRESEXISTS>
  <KEYFRAMEEXISTS>0</KEYFRAMEEXISTS>
  <VSAT>0</VSAT>
  <LOOP>0</LOOP>
  <INVISIBLE>0</INVISIBLE>
  <SHAREDAUDIO>0</SHAREDAUDIO>
  <TRANSMITTED>0</TRANSMITTED>
  <ROYALTIES>0</ROYALTIES>
  <WITHTEXTFILE>0</WITHTEXTFILE>
  <INDEXED>0</INDEXED>
  <PERSONALRADIO>0</PERSONALRADIO>
  <REQUESTDEARCHIVE>0</REQUESTDEARCHIVE>
  <REPLFLAGS>0</REPLFLAGS>
  <STATE>Existing</STATE>
  <AUTHOR>ARCHIVES</AUTHOR>
  <EDITOR>JPASSMOR</EDITOR>
  <CHANGEUSER>JPASSMOR</CHANGEUSER>
  <CHANGEDATE>2015-01-26</CHANGEDATE>
  <CHANGETIME>09:33:07</CHANGETIME>
  <FILESIZE>628255824</FILESIZE>
  <AUDIOFORMAT>Wave,BWF,RIFF</AUDIOFORMAT>
  <AUDIOMODE>Stereo</AUDIOMODE>
  <SAMPLERATE>44100</SAMPLERATE>
  <BITRATE>0</BITRATE>
  <TEXTLENGTH>00:00:00.000</TEXTLENGTH>
  <TEXTDURATION>0</TEXTDURATION>
  <BROADCASTINGS>0</BROADCASTINGS>
  <MARKIN>00:00:00.000</MARKIN>
  <MARKOUT>00:58:53.920</MARKOUT>
 </ENTRY>
</ENTRIES>

so that it looks like this

<?xml version="1.0" encoding="ISO-8859-1"?>
    <ENTRIES>
     <ENTRY>
      <NUMBER>622</NUMBER>
      <CLASS>Audio</CLASS>
      <TITLE>WNYC-SCHK-2004-02-20-37540</TITLE>
      <GENERATOR>DBM</GENERATOR>
      <CREATOR>JPASSMOR</CREATOR>
      <DATE>2015-01-06</DATE>
      <DATUM>2015-01-06</DATUM>
      <TIME>11:48:59</TIME>
      <TIMESTAMP>2015-01-06 11:48:59</TIMESTAMP>
      <LENGTH>00:58:53.920</LENGTH>
      <DURATION>3533920</DURATION>
      <SOFTDELETED>0</SOFTDELETED>
      <NODELETE>0</NODELETE>
      <READY>0</READY>
      <PERFECT>0</PERFECT>
      <FORARCHIVE>0</FORARCHIVE>
      <ARCHIVING>0</ARCHIVING>
      <ARCHIVED>0</ARCHIVED>
      <GROWING>0</GROWING>
      <NEW>0</NEW>
      <INVALID>0</INVALID>
      <LOWRESEXISTS>0</LOWRESEXISTS>
      <KEYFRAMEEXISTS>0</KEYFRAMEEXISTS>
      <VSAT>0</VSAT>
      <LOOP>0</LOOP>
      <INVISIBLE>0</INVISIBLE>
      <SHAREDAUDIO>0</SHAREDAUDIO>
      <TRANSMITTED>0</TRANSMITTED>
      <ROYALTIES>0</ROYALTIES>
      <WITHTEXTFILE>0</WITHTEXTFILE>
      <INDEXED>0</INDEXED>
      <PERSONALRADIO>0</PERSONALRADIO>
      <REQUESTDEARCHIVE>0</REQUESTDEARCHIVE>
      <REPLFLAGS>0</REPLFLAGS>
      <STATE>Existing</STATE>
      <AUTHOR>ARCHIVES</AUTHOR>
      <EDITOR>JPASSMOR</EDITOR>
      <CHANGEUSER>JPASSMOR</CHANGEUSER>
      <CHANGEDATE>2015-01-26</CHANGEDATE>
      <CHANGETIME>09:33:07</CHANGETIME>
      <REMARK>Think of Spanish classical music and the name Manuel de Falla naturally comes to mind.</REMARK>
      <FILESIZE>628255824</FILESIZE>
      <AUDIOFORMAT>Wave,BWF,RIFF</AUDIOFORMAT>
      <AUDIOMODE>Stereo</AUDIOMODE>
      <SAMPLERATE>44100</SAMPLERATE>
      <BITRATE>0</BITRATE>
      <TEXTLENGTH>00:00:00.000</TEXTLENGTH>
      <TEXTDURATION>0</TEXTDURATION>
      <BROADCASTINGS>0</BROADCASTINGS>
      <MARKIN>00:00:00.000</MARKIN>
      <MARKOUT>00:58:53.920</MARKOUT>
     </ENTRY>
    </ENTRIES>

I thought that there might be a way to use xsl to copy the remark from one xml doc into the other based on a shared filename in a batch. Or might there be an easier way to do it?

回答1:

This problem have an interesting aspect, so I used it to test a different method to process files.

@echo off
setlocal EnableDelayedExpansion

rem Process all .xml files in current directory
for %%a in (*.xml) do (

   rem Locate the line numbers where "CHANGETIME" and "/ENTRIES" appears
   set "insertLine="
   for /F "delims=:" %%b in ('findstr /N "CHANGETIME /ENTRIES" "%%a"') do (
      if not defined insertLine (
         set "insertLine=%%b"
      ) else (
         set "lastLine=%%b"
      )
   )

   rem Block used to read-input-file/create-output-file
   < "%%a" (

           rem Read the first line from input file
           set /P "line="

           rem Copy lines up to the insertion point
           for /L %%i in (1,1,!insertLine!) do set /P "line=!line!" & echo/

           rem Insert the corresponding REMARK file
           type "RemarksFolder\%%a"

           rem Copy the rest of lines
           set /A insertLine+=1
           for /L %%i in (!insertLine!,1,!lastLine!) do set /P "line=!line!" & echo/

           ) > "output.tmp"
   rem Block-end

   rem Replace input file with created output file
   move /Y "output.tmp" "%%a" > NUL

)

This program should run faster than other methods that compare line by line; however, it has the disadvantage that leading spaces are removed from all lines. Although additional code may be inserted in order to fix this point, doing that will slow down the process...



回答2:

The batch environment isn't terribly strongly suited for manipulating XML as XML. There's probably a way using Windows Script Host (VBScript or JScript) to evaluate the XML DOM, but in this situation it's probably easier just to use for loops and echos.

Read the remarks in the following example script for a full explanation of how it works.

@echo off
setlocal

set "remarkDir=remarks\"
set "xmlDir=xml\"

rem // for all files in xmlDir\*.xml
for %%I in ("%xmlDir%\*.xml") do (

    rem // echo filename without line break...
    set /P "=Processing %%~nxI... "<NUL

    rem // Read corresponding remark file into variable
    set /P "remark=" <"%remarkDir%\%%~nxI"

    rem // for each line in xmlDir\file.xml
    for /f "usebackq delims=" %%X in ("%%~fI") do (

        rem // append the line to a new file
        >>"%%~dpnI.new" echo/%%X

        rem // check whether the line contains /CHANGETIME
        set "line=%%X"
        setlocal enabledelayedexpansion
        if not "%%X"=="!line:/CHANGETIME=!" (

            rem // Line contains /CHANGETIME.  Append remark.
            >>"%%~dpnI.new" echo/!remark!
        )
        endlocal

    )

    rem // End of xml file.  Replace old with new.
    move /y "%%~dpnI.new" "%%~fI" >NUL
    echo Done.
)

note: StackOverflow isn't intended to be a free coding service, but I have sympathy for you. It sounds like you've put a lot of effort into painting yourself into this corner. Therefore, I hope this helps you out.



回答3:

Excuse me. In my first answer I said I wanted to use this problem as a test because its interesting aspect. Some time ago I wrote FilePointer.exe auxiliary program that allows to move the file pointer of a redirected file via its standard handle. That program may be used to solve this problem in a very simple way (and also any other problem with similar structure) because the former method of copy several lines via FOR command may be changed by a direct file pointer movement to a certain file position, or by a simple FINDSTR command to copy the rest of lines. Here it is:

@echo off
setlocal EnableDelayedExpansion

rem Example of use of FilePointer.exe auxiliary program
rem Antonio Perez Ayala

rem Process all .xml files in current directory
for %%a in (*.xml) do (

   rem Locate the insertion offset where "FILESIZE" line starts
   for /F "delims=:" %%b in ('findstr /O "FILESIZE" "%%a"') do set "insertPoint=%%b"

   rem Block used to edit the file via redirected Stdin and Stdout
   < "%%a" (

      rem Set Stdin file pointer at the insertion point
      FilePointer 0 !insertPoint!

      rem Copy the rest of lines to an auxiliary file
      findstr "^" > auxiliary.tmp 

      rem "FIND and MORE works different than FINDSTR."
      rem "FIND and MORE first resets the file position variable and then read the complete file to the EOF,"
      rem "If you use FINDSTR it simply reads the next data from current position, ..."
      rem http://www.dostips.com/forum/viewtopic.php?f=3&t=2128&p=9720#p9720

      rem Set Stdout file pointer at the insertion point
      FilePointer 1 !insertPoint!

      rem Insert the corresponding REMARK file
      type "RemarksFolder\%%a"

      rem And add the rest of lines
      type auxiliary.tmp

   ) >> "%%a"
   rem Block-end

)

del auxiliary.tmp

This method have several advantages over the former one. It run faster and the leading spaces are preserved. The first part of the file is kept in the same file, that is, it does not need to copy it into a temporary file. In this problem it is necessary to copy the lines from the insertion point to the EOF in a temporary file in order to make room for the inserted text, but in another problem that just needs to replace a text by other one of the same size, the change is immediate with no further processing no matter the size of the file! If the new text would be shorter than the original one it would be necessary to "compact" the data after the replacement point and then truncate the remaining data, that may be done with Truncate.exe (another one of my auxiliary programs).

You may download FilePointer.exe auxiliary program from this site.