0

first post here.

I’m trying to update (or add) strings to a plain text file, using strings from another text file.

Example:

File1.txt (Reference File)

<add key="1" value="False" />
<add key="2" value="C:\Temp" />
<add key="3" value="True" />
<add key="4" value="True" />
<add key="5" value="False" />
<add key="6" value="False" />
<add key="7" value="False" />
<add key="8" value="False" />
<add key="9" value="False" />
<add key="10" value="newkey" />
<add key="11" value="False" />
<add key="12" value="127.0.0.1" />
<add key="13" value="True" />
<add key="14" value="True" />
<add key="15" value="False" />
<add key="16" value="False" />
<add key="17" value="False" />
<add key="18" value="True" />
<add key="19" value="True" />
<add key="20" value="True" />
<add key="21" value="True" />
<add key="22" value="True" />

File2.txt (Target File)

  <150 strings>
  </150 strings>

  <appSettings>
    <add key="1" value="False" />
    <add key="2" value="False" />
    <add key="3" value="False" />
    <add key="4" value="False" />
    <add key="5" value="False" />
    <add key="6" value="False" />
    <add key="7" value="False" />
    <add key="8" value="False" />
    <add key="9" value="False" />
    <add key="10" value="False" />
    <add key="11" value="False" />
    <add key="12" value="False" />
    <add key="13" value="False" />
    <add key="14" value="False" />
    <add key="15" value="False" />
    <add key="16" value="False" />
    <add key="17" value="False" />
    <add key="18" value="False" />
    <add key="19" value="False" />
    <add key="20" value="False" />
    <add key="21" value="False" />
    <add key="22" value="False" />
  </appSettings>
    <startup>
        <supportedRuntime/>
    </startup>
</configuration>

Requirements:

  1. If file2.txt already contains one or all keys (i.e. 1 through 22), I want to replace the keys with file1.txt's keys.

  2. If file2.txt is missing any keys that are present in file1.txt, they need to be added. As long as they appear within <AppSettings>, the order does not matter.

  3. If file2.txt contains keys that are not present in file1.txt, they should be left alone.

  4. Anything outside of <AppSettings> should be left untouched.

After briefly testing Dbenham's last script, it looks like it accomplishes everything, but the lines above and below are moved around (my fault for not providing full examples).

Here is the current output from Dbenham's script:

  <150 strings>
  </150 strings>

  <appSettings>
  </appSettings>
    <startup>
        <supportedRuntime/>
    </startup>
</configuration>
    <add key="1" value="False" />
    <add key="2" value="C:\Temp" />
    <add key="3" value="True" />
    <add key="4" value="True" />
    <add key="5" value="False" />
    <add key="6" value="False" />
    <add key="7" value="False" />
    <add key="8" value="False" />
    <add key="9" value="False" />
    <add key="10" value="newkey" />
    <add key="11" value="False" />
    <add key="12" value="127.0.0.1" />
    <add key="13" value="True" />
    <add key="14" value="True" />
    <add key="15" value="False" />
    <add key="16" value="False" />
    <add key="17" value="False" />
    <add key="18" value="True" />
    <add key="19" value="True" />
    <add key="20" value="True" />
    <add key="21" value="True" />
    <add key="22" value="True" />
  • What have you already attempted before posting here? Can you provide examples? – unclemeat Feb 12 '14 at 00:09
  • I used findstr to pull strings from other files to create 'file1.txt', but need some direction on where to start with accomplishing the above. Being very new to scripting, I was unable to make sense of the scripts on some slightly similar SO scenarios/questions. Thanks – user3299447 Feb 12 '14 at 01:04
  • If I correctly understood your explanation, at end the File2.txt will contain _the same contents_ of File1.txt, so a simple `copy File1.txt File2.txt /y` would solve this problem! Have both files another lines different than "``" ones? If so, it is _very important_ that you list a _real_ small segment of both files... – Aacini Feb 12 '14 at 01:25
  • Left unsaid, I presume you want to preserve existing key values from file2 that do not exist in file1. This gets to the same point that @Aacini is making. – dbenham Feb 12 '14 at 12:41
  • dbenham, you're correct. There are values in file2 that don't exist in file1, otherwise a copy would work... Just working on getting some 'real' samples uploaded now. – user3299447 Feb 12 '14 at 22:04

2 Answers2

1
@ECHO OFF
SETLOCAL
:: remove variables starting $ or #
For %%b IN ($ #) DO FOR  /F "delims==" %%a In ('set %%b 2^>Nul') DO SET "%%a="

FOR /f "delims=" %%a IN (reference.txt) DO FOR /f "tokens=3delims== " %%h IN ("%%a") DO (
 SET "$%%~h=%%a"
)
FOR /f "delims=" %%a IN (target.txt) DO FOR /f "tokens=3delims== " %%h IN ("%%a") DO (
 SET "#%%~h=%%a"
)
(
 FOR  /F "tokens=1*delims=$=" %%c In ('set $ 2^>Nul') DO (
  ECHO(%%d
  SET "#%%c="
 )
 FOR  /F "tokens=1*delims==" %%c In ('set # 2^>Nul') DO (
  ECHO(%%d
 )
)>newfile.txt

GOTO :EOF

Your source data conains "smart quotes" - I've assumed you've actually used quotes.

Essentailly, set $keyname=line from reference;#keyname=line from target

Then output all $keyname and clear any #keyname with the same keyname.

Finally, output all remaining #keynames

result appears in newfile.txt

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • +1, Interesting approach. Two limitations - 1) the key values must be case insensitive. [My answer](http://stackoverflow.com/a/21728203/1012053) suffers the same problem. 2) Your keys must not contain `=`. You can eliminate the # variables if you process the target file first and and have both files define $ variables. The reference will overwrite the target $ variables. Then you only need a single FOR /F loop to produce the output. – dbenham Feb 12 '14 at 13:52
0

Here is a new answer based on updated question

You really should be using a tool designed to edit XML. There are many ways the configuration file (file2.txt) could be modified in a way that would still be valid, but that would break any pure batch solution. But, assuming the layout does not change from what you are showing...

If all you need to do is replace the appSettings within file2.txt with the content of file1.txt, then:

@echo off
setlocal enableDelayedExpansion
set "replace="
>file2.txt.new (
  for /f "delims=" %%A in (file2.txt) do (
    set "ln=%%A"
    if "!ln:</appSettings>=!" neq "!ln!" (
      type file1.txt
      set "replace="
    )
    if not defined replace echo(!ln!
    if "!ln:<appSettings>=!" neq "!ln!" set replace=1
  )
)
move /y file2.txt.new file2.txt >nul

The above will not preserve any keys found in file2.txt that do not exist in file1.txt.

If you need to preserve keys from file2.txt that do not exist in file1.txt, then a modification of Magoo's answer is probably the simplest solution:

@echo off
setlocal enableDelayedExpansion

for /f "delims==" %%A in ('set $ 2^>nul') do set "%%A="
set "replace="
>file2.txt.new (
  for /f "delims=" %%A in (file2.txt) do (
    set "ln=%%A"
    if not defined replace (
      echo(!ln!
      if "!ln:<appSettings>=!" neq "!ln!" set replace=1
    ) else if "!ln:</appSettings>=!" equ "!ln!" (
      for /f tokens^=2^ delims^=^" %%K in ("%%A") do set "$%%K=%%A"
    ) else (
      for /f "delims=" %%B in (file1.txt) do for /f tokens^=2^ delims^=^" %%K in ("%%B") do set "$%%K=%%B"
      for /f "tokens=1* delims==" %%B in ('set $') do echo(%%C
      echo(!ln!
      set "replace="
    )
  )
)
move /y file2.txt.new file2.txt >nul

Both solutions above will corrupt content that contains !. There are changes that can be made if ! needs to be supported. The solutions also assume the keys are not case sensitive.

Also, both solutions will strip out empty lines from file2.txt. But that should not matter with an XML document. A bit of extra code could be added to preserve empty lines.

Update

Here is a modified version of the 2nd solution that preserves ! characters in the content:

@echo off
setlocal disableDelayedExpansion

for /f "delims==" %%A in ('set $ 2^>nul') do set "%%A="
set "replace="
set "endReplace="
>file2.txt.new (
  for /f "delims=" %%A in (file2.txt) do (
    set "ln=%%A"
    if not defined replace (
      echo(%%A
      setlocal enableDelayedExpansion
      if "!ln:<appSettings>=!" neq "!ln!" (
        endlocal
        set replace=1
      ) else endlocal
    ) else (
      setlocal enableDelayedExpansion
      if "!ln:</appSettings>=!" equ "!ln!" (
        endlocal
        for /f tokens^=2^ delims^=^" %%K in ("%%A") do set "$%%K=%%A"
      ) else (
        endlocal
        for /f "delims=" %%B in (file1.txt) do for /f tokens^=2^ delims^=^" %%K in ("%%B") do set "$%%K=%%B"
        for /f "tokens=1* delims==" %%B in ('set $') do echo(%%C
        echo(%%A
        set "replace="
      )
    )
  )
)
move /y file2.txt.new file2.txt >nul

Below is the original answer based on these file1.txt and file2.txt specs from the original question:

File1.txt (Reference File)

<add key="abc" value="123" />
<add key="def" value="456" />
<add key="ghi" value="789" />

File2.txt (Target File)

<add key="def" value="blank" />
<add key="ghi" value="blank" />

I am assuming that all lines in your files match the template that you have shown (except that normal double quotes are used instead of the smart quotes, as Magoo has pointed out). I also assume that the order of the lines in the final output does not matter, and the keys are NOT case sensitive.

Assuming that the keys do not contain spaces. tabs, or backslashes, then:

@echo off
>file1.txt.screen (for /f "tokens=2" %%A in (file1.txt) do echo( %%A )
>file2.txt.new (
  findstr /livg:file1.txt.screen file2.txt
  type file1.txt
)
del file1.txt.screen
move /y file2.txt.new file2.txt >nul

If the keys may contain spaces or tabs, but do not contain backslashes, then:

@echo off
>file1.txt.screen (for /f tokens^=2^ delims^=^" %%A in (file1.txt) do echo( key="%%A" )
>file2.txt.new (
  findstr /livg:file1.txt.screen file2.txt
  type file1.txt
)
del file1.txt.screen
move /y file2.txt.new file2.txt >nul

If the keys may contain spaces or tabs or backslashes, then:

@echo off
setlocal disableDelayedExpansion
>file1.txt.screen (
  for /f tokens^=2^ delims^=^" %%A in (file1.txt) do (
    set "key=%%A"
    setlocal enableDelayedExpansion
    (echo( key="!key:\=\\!" )
    endlocal
  )
)
>file2.txt.new (
  findstr /livg:file1.txt.screen file2.txt
  type file1.txt
)
del file1.txt.screen
move /y file2.txt.new file2.txt >nul

All the solutions above must do a case insensitive search when looking for keys because of this FINDSTR bug: Why doesn't this FINDSTR example with multiple literal search strings find a match?

Update

Below is a case insensitive solution that eliminates all of the restrictions.

@echo off
setlocal disableDelayedExpansion
for /f tokens^=2^ delims^=^" %%A in (file1.txt) do (
  set "search= key="%%A" "
  setlocal enableDelayedExpansion
  (echo !search:\=\\!) >file2.txt.screen
  findstr /vlg:file2.txt.screen file2.txt >file2.txt.new
  move /y file2.txt.new file2.txt >nul
  endlocal
)
type file1.txt >>file2.txt
del file2.txt.screen
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Dbenham - this is very close to exactly what's needed, only I failed to mention that there are strings (which should be left untouched) above and below the keys which are being modified/added. I'm updating my original post with these full examples now. Much Thanks! – user3299447 Feb 12 '14 at 22:08
  • "Both solutions above will corrupt content that contains !. There are changes that can be made if ! needs to be supported." I just realized that the ! character is used, for commenting out lines in file2.txt (these need to remain). Simple change? – user3299447 Feb 18 '14 at 20:13
  • After some further research, disabling DelayedExpansion is what's stripping the ! character. Looks like it's a pretty involved script change to accommodate this character... Any suggestions are appreciated, Thanks! – user3299447 Feb 19 '14 at 17:46
  • Didn't realize that comments dont notify users that have commented, just used a @ to notify; thanks dbenham – user3299447 Feb 19 '14 at 21:59
  • In your "Here is a modified version of the 2nd solution that preserves ! characters in the content" script, for the line `if "!ln:=!" neq "!ln!" (`, is there a way to have spaces in the string we're searching for? i.e. `if "!ln: workflow=!" neq "!ln!" (`. I've tried encasing it in double quotes – user3299447 Oct 27 '14 at 23:44