- note -

Batch

next:batでtee
prev:日付の操作
8/10

batでXMLをパース   - Last modified:2014/2/15

バッドノウハウの集大成と言えるようなソースになりました。
やっつけ … XMLの仕様を把握してないので、解析方法がいい加減。 ぽければいいのだ。 残念ながら途中で飽きてしまったのでかなりやっつけですが、
なんとなく動いてくれる可能性が一応あります。

parsexml.bat

@echo off
setlocal

if "%~1" == "" exit /b
if "%~1" == "selfcall" goto %2
set param2=%2
if defined param2 (
  set _rootname=%~2
) else (
  set _rootname=root
)

set C_cdata=[CDATA[
set C_cdata_end=]]^>
set C_comment_end=--^>
set C_node_delim=/

set dec=
set exc=
set lt=
set cur=
set comment=
set cdata=
set close=
set quote=
set equal=
set acn_first=1
set nodename=
set nodepath=%_rootname%

if not defined _rootname set _rootname=_

set filesize=%~z1
if not defined filesize (
  echo Can't open file [%1]
  rem '
  exit /b 1
)

set readsize=0
for /f delims^=^ eol^= %%I in (%1) do (
  set line=%%I 
  call :GetLength line
  call set /a length=%%errorlevel%%-1
  setlocal enabledelayedexpansion
  for /l %%K in (!length!,1,!length!) do (
    endlocal
    for /l %%J in (0,1,%%K) do (
      call :ParseNext %%J
      set /a readsize+=1
      call set /a per=%%readsize%%00/filesize
      call set /p _=%%per%%%%%%<nul
    )
  )
  set /a readsize+=1
) || exit /b 1
echo;100%%
for /f delims^=^ eol^= %%I in ('set %_rootnode%') do (
  if defined _rootnode (endlocal&set %_rootname%=%_rootnode%)
  set %%I
)
exit /b

:GetLength
setlocal enabledelayedexpansion
for /l %%I in (0,1,8192) do if !%1:~%%I^,1! == !empty! exit /b %%I
exit /b 0

:ParseNext
call :SubStr char line %1 1
setlocal enabledelayedexpansion
if !char! == ^	 (
  endlocal
  set char= 
  setlocal enabledelayedexpansion
)
if defined dec (
  if !char! == ^> (
    endlocal
    set dec=
  )
) else if defined exc (
  if defined exc- (
    if !char! == - (
      endlocal
      set exc=
      set exc-=
      set comment=1
    )
  ) else if defined exc[ (
    if !char! == !C_cdata:~%exc[%^,1! (
      if !C_cdata:~%exc[%^,1! == !C_cdata:~%exc[%^,2! (
        endlocal
        set exc=
        set exc[=
        set cdata=1
      ) else (
        endlocal
        set /a exc[+=1
      )
    )
  ) else (
    if !char! == - (
      endlocal
      set exc-=1
    ) else if !char! == [ (
      endlocal
      set exc[=1
    )
  )
) else if defined comment (
  endlocal
  call :CheckEnd comment C_comment_end
) else if defined cdata (
  endlocal
  call :CheckEnd cdata C_cdata_end
) else if defined close (
  endlocal
  call :CheckEnd close close_end
  if not defined close (
    call :ReturnParentNode
    set lt=
  )
) else if defined lt (
  if !lt! == 1 (
    if !char! == ^^^! (
      endlocal
      set exc=1
      set lt=
    ) else if !char! == ? (
      endlocal
      set dec=1
      set lt=
    ) else if !char! == / (
      endlocal
      set close=1
      set close_end=%nodename%^>
    ) else (
      endlocal
      call :ConcatStr cur char
      set lt=2
    )
  ) else if !lt! == 2 (
    if !char! == ^  (
      endlocal
      call :AddChildNode cur
      set lt=3
      set cur=
    ) else if !char! == / (
      endlocal
      call :AddChildNode cur
      set close=1
      set close_end=^>
      set cur=
    ) else if !char! == ^> (
      endlocal
      call :AddChildNode cur
      set cur=
      set lt=
    ) else (
      endlocal
      call :ConcatStr cur char
    )
  ) else if !lt! == 3 (
    if defined quote (
      if !char! == !quote! (
        endlocal
        call :SetStr %nodepath%.%attrname% cur
        set quote=
        set attrname=
        set cur=
      ) else (
        endlocal
        call :ConcatStr cur char
      )
    ) else if defined equal (
      if !char! == ^" (
        endlocal
        set quote=^"
        set equal=
      ) else if !char! == ^' (
        endlocal
        set quote=^'
        set equal=
      )
    ) else (
      if !char! == / (
        endlocal
        set close=1
        set close_end=^>
      ) else if !char! == ^> (
        endlocal
        set lt=
      ) else if !char! == ^= (
        endlocal
        set equal=1
        call :AddListElem %nodepath%.* cur
        call :SetStr attrname cur
        set cur=
      ) else if !char! == ^  (
        rem
      ) else (
        endlocal
        call :ConcatStr cur char
      )
    )
  )
) else (
  if !char! == ^< (
    endlocal
    if defined nodepath (
      call :SetStr %nodepath% cur
      call :CollectSpace %nodepath%
    )
    set lt=1
    set cur=
  ) else (
    endlocal
    call :ConcatStr cur char
  )
)
exit /b

:CheckEnd
setlocal enabledelayedexpansion
set /a check_index=check_index
if !char! == !%2:~%check_index%^,1! (
  if !%2:~%check_index%^,1! == !%2:~%check_index%^,2! (
    endlocal
    set %1=
    set check_index=
  ) else (
    endlocal
    set /a check_index+=1
  )
) else (
  endlocal
  set check_index=
)
exit /b

:ReplaceStr
if %1 == selfcall (
  setlocal enabledelayedexpansion
  echo;!%3:%4=%5!
  exit /b
)
for /f delims^=^ eol^= %%I in ('call %~f0 selfcall ReplaceStr %1 %2 %3') do (
  set %1=%%I
)
exit /b

:SubStr
if %1 == selfcall (
  setlocal enabledelayedexpansion
  echo;!%3:~%4,%5!
  exit /b
)
for /f delims^=^ eol^= %%I in ('call %~f0 selfcall SubStr %2 %3 %4') do (
  set %1=%%I
)
exit /b

:ConcatStr
if %1 == selfcall (
  setlocal enabledelayedexpansion
  echo;!%3!!%4!
  exit /b
)
for /f delims^=^ eol^= %%I in ('call %~f0 selfcall ConcatStr %1 %2') do (
  set %1=%%I
)
exit /b

:SetStr
if %1 == selfcall (
  setlocal enabledelayedexpansion
  echo;!%3!
  exit /b
)
for /f delims^=^ eol^= %%I in ('call %~f0 selfcall SetStr %2') do (
  set %1=%%I
)
exit /b

:CollectSpace
if not defined %1 exit /b
call set %1=%%%1:  = %%
setlocal enabledelayedexpansion
if not "!%1!" == "!%1:  = !" (
  endlocal
  goto CollectSpace
)
endlocal
exit /b

:AddChildNode
call set nodename=%%%1%%
if defined nodepath (
  if not defined acn_first (
    call :AddListElem %nodepath%/* nodename
  )
  call set nodepath=%%nodepath%%/%%nodename%%
) else (
  call set nodepath=%%nodename%%
)
if defined %nodepath%@ (
  call set /a count=%%%nodepath%@%%+1
  call set %nodepath%@=%%count%%
  call set nodepath=%nodepath%@%%count%%
) else if defined %nodepath%# (
  if defined acn_first (
    for /f "delims=^= tokens=1" %%I in ('set %nodepath%') do (
      set %%I=
    )
  ) else (
    call :CopyNode %nodepath% %nodepath%@1 1
    set %nodepath%@=2
    set nodepath=%nodepath%@2
  )
)
set acn_first=
exit /b

:CopyNode
for /f "delims=^= tokens=1,*" %%I in ('set %1') do (
  set srcname=%%I
  call set dstname=%%srcname:%1=%2%%
  call set %%dstname%%=%%J
  if "%3" == "1" set %%I=
)
exit /b

:AddListElem
if defined %1 (
  call set %1=%%%1%% %%%2%%
) else (
  call set %1=%%%2%%
)
exit /b

:ReturnParentNode
set _rootnode=%nodepath%
set count=0
call set nodelist=%%%nodepath%/*%%
if defined nodelist (
  for %%I in (%nodelist%) do (
    set /a count+=1
    set /a rpn_tmp_%%I+=1
  )
  set nodelist=
  for /f "delims=^= tokens=1,*" %%I in ('set rpn_tmp_') do (
    set rpn_name=%%I
    if %%J gtr 1 (
      call set nodelist=%%nodelist%% %%rpn_name:rpn_tmp_=%%@
    ) else (
      call set nodelist=%%nodelist%% %%rpn_name:rpn_tmp_=%%
    )
    set %%I=
  )
  call set %nodepath%/*=%%nodelist:~1%%
)
set %nodepath%#=%count%
set curpath=%nodepath%
set prevname=
set nodename=
set nodepath=
for %%I in (%curpath:/= %) do (
  if defined nodepath call set nodepath=%%nodepath%%/
  call set nodename=%%prevname%%
  call set nodepath=%%nodepath%%%%nodename%%
  set prevname=%%I
)
for /f "delims=@ tokens=1" %%I in ('echo;%nodename%') do (
  set nodename=%%I
)
exit /b

使用例

test.xml

<?xml version="1.0" encoding="shift-jis"?>
<root>
  <A a="hoge" z='17'>piyo</A>
  <B ddx="fuga">
    <foo>あああ</foo>
    <foo>456</foo>
    <bar></bar>
  </B>
</root>
上記のようなXMLがあったとき、下記のようにコマンドを打ちます。
>parsexml test.xml XML
実行後、環境変数に解析結果が入っているはずです。
>set XML
XML=XML/root
XML/root=
XML/root#=2
XML/root/*=A B
XML/root/A=piyo
XML/root/A#=0
XML/root/A.*=a z
XML/root/A.a=hoge
XML/root/A.z=17
XML/root/B=
XML/root/B#=3
XML/root/B.*=ddx
XML/root/B.ddx=fuga
XML/root/B/*=bar foo@
XML/root/B/bar#=0
XML/root/B/foo@=2
XML/root/B/foo@1=あああ
XML/root/B/foo@1#=0
XML/root/B/foo@2=456
XML/root/B/foo@2#=0
あとは思う儘にこの解析結果を使うだけです。 とっても簡単!
勿論、bat特有の感動的な処理速度も健在です。