Reputation: 123
I need to get values from multiple attributes from an XML file that is formatted on a single line.
I have checked many examples, but none of them really worked for me due to the single line file.
First of all I tried to use the findstr command, which does not really helps as it always returns whole line (everything in my case) and its apparently not possible to get values using REGEX - only to find the correct line. e.g. like
findstr /c:"testCase=" test_case_run_log_report.xml
Then I tried using the delims and tokens in the FOR command. This approach might work if its really exactly specified according to the input file, but I need a generic way as the xml file might contain more "testCaseRunLogTestStep" steps each time its started. This would be actually working solution at least in case the XML wont be stored on single line only. e.g. (the token numbers are not exact here, but as described this solution cannot be used as well)
for /F "tokens=4,6,81delims==" %%a IN (
test_case_run_log_report.xml
) do echo %%a
So my idea would be to split the file content by e.g. "/>", then maybe run the FOR command and do some magic around. But thats too much for me.
This is how the XML file might look like
<?xml version="1.0" encoding="UTF-8"?>
<con:testCases testCase="testCase1" timeTaken="201" status="FINISHED" timeStamp="2019-07-25 09:00:47" xmlns:con="http://xx/config"><con:testCaseRunLogTestStep name="testStep1" timeTaken="222" status="OK" timestamp="2019-07-25 09:00:45" httpStatus="200" contentLength="9" readTime="6" totalTime="216" dnsTime="0" connectTime="117" timeToFirstByte="93" httpMethod="GET" /> <con:testCaseRunLogTestStep name="testStep2" timeTaken="528" status="OK" timestamp="2019-07-25 09:00:46" httpStatus="200" contentLength="0" readTime="0" totalTime="529" dnsTime="0" connectTime="1" timeToFirstByte="528" httpMethod="GET"/></con:testCases>
I would expect results from the testCaseRunLogTestStep
nodes that can be shown and also evaluated.
<name attribute>=<status attribute>
For example:
testStep1=OK
testStep2=OK
I used to use PowerShell but then experienced issues with different PowerShell versions installed on different servers. So for compatibility reasons I switched to plain BATCH, which has been working quite fine for different tasks, until now.
I am developer myself, but this task I feel like Alice in wonderland.
Upvotes: 0
Views: 887
Reputation: 49086
Here is a batch file using only internal commands of Windows command processor cmd.exe
.
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "XmlFile=test_case_run_log_report.xml"
if not exist "%XmlFile%" (
echo ERROR: File "%XmlFile%" not found.
goto :EOF
)
rem Assign last non-empty line of specified XML file not starting
rem with a semicolon to environment variable XmlLine if not longer
rem than 8181 characters. Maximum length of "XmlLine=..." is 8191
rem characters and so maximum value length is 8181 characters.
set "XmlLine="
for /F "usebackq delims=" %%I in ("%XmlFile%") do (
set "XmlLine=%%I"
set "NewLine=!XmlLine:"=!"
if /I "!NewLine!" == "<?xml version=1.0 encoding=UTF-8?>" set "XmlLine="
)
if not defined XmlLine (
echo ERROR: File "%XmlFile%" is empty or contains too much data.
goto :EOF
)
for %%I in ("%XmlFile%") do set "ResultFile=%%~dpnI.txt"
del "%ResultFile%" 2>nul
rem Remove everything from beginning of line to end of the string between *
rem and = and assign this remaining part of the line to variable NewLine.
rem If nothing was removed, there is no more tag con:testCaseRunLogTestStep
rem with a space and attribute name in remaining line. Otherwise get values
rem of attribute name and status and output both into result file.
:GetNameStatus
set "NewLine=!XmlLine:*<con:testCaseRunLogTestStep name=!"
if not "!NewLine!" == "!XmlLine!" (
set "XmlLine=!NewLine!"
for /F "tokens=1,5 delims== " %%I in ("!XmlLine!") do echo %%~I=%%~J>>"%ResultFile%"
goto GetNameStatus
)
rem Delete result file if existing but file size is less or equal 2 bytes.
if exist "%ResultFile%" for %%I in ("%ResultFile%") do if %%~zI LEQ 2 del "%ResultFile%"
if not exist "%ResultFile%" (
echo ERROR: No element con:testCaseRunLogTestStep with attributes name and status
echo found in file "%XmlFile%".
)
endlocal
This batch file works only for XML files on which the last line with the data of interest is not longer than 8181 characters. See the Microsoft documentation page Command prompt (Cmd.exe) command-line string limitation. The maximum command line length is 8191 characters. "
, XmlLine
, =
and "
take already ten characters. So the string value read from file and assigned to the environment variable cannot be longer than 8181 characters.
It also works only as expected if the XML file does not contain exclamation marks.
This task can be also done using JREPL.BAT written by Dave Benham which is a batch file / JScript hybrid to run a regular expression replace on a file using JScript.
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "XmlFile=test_case_run_log_report.xml"
if not exist "%XmlFile%" (
echo ERROR: File "%XmlFile%" not found.
goto :EOF
)
if not exist "%~dp0jrepl.bat" (
echo ERROR: Batch file "%~dp0jrepl.bat" not found.
goto :EOF
)
for %%I in ("%XmlFile%") do (
set "ResultFile=%%~dpnI.txt"
set "XmlFileSize=%%~zI"
)
del "%ResultFile%" 2>nul
call "%~dp0jrepl.bat" "[\s\S]*?<con:testCaseRunLogTestStep[\s\S]+?name=\x22([^\x22]+)[^>]+?status=\x22([^\x22]+)[^>]+>(?:\s*</con:testCases>\s*)?" "$1=$2\r\n" /M /XSEQ /F "%XmlFile%" /O "%ResultFile%"
if exist "%ResultFile%" for %%I in ("%ResultFile%") do if %%~zI == %XmlFileSize% del "%ResultFile%"
if not exist "%ResultFile%" (
echo ERROR: No element con:testCaseRunLogTestStep with attributes name and status
echo found in file "%XmlFile%".
)
endlocal
This solution works for single-line as well as multi-line XML file without a line length limitation (other than available free RAM). It is also more flexible regarding to position of the attributes name
and status
within element con:testCaseRunLogTestStep
as long as this element contains first name
and next status
.
The case-sensitive JScript search regular expression executed by cscript.exe
means:
[\s\S]*?
... any whitespace or any non-whitespace character 0 or more times non-greedy. So this expression matches everything from beginning of file or end of previous match to <con:testCaseRunLogTestStep
.<con:testCaseRunLogTestStep
... this string must be found next for a positive match.[^>]+?
... any character not being >
one or more times non-greedy. So this expression matches everything after start tag to attribute name
within the tag.name=\x22
... the string name="
must be found next for a positive match. The double quote character is specified using its hexadecimal representation as an argument string usually cannot contain "
on Windows command line.(
...)
... first marking group. The string found by the expression inside this first marking group is back-referenced with $1
in replace string.[^\x22]+
... any character except "
one or more times. This expression matches the value of attribute name
.[^>]+?
... like before any character not being >
one or more times non-greedy. So this expression matches everything from "
after value of attribute name
to attribute status
within the tag.status=\x22
... the string status="
must be found next for a positive match.(
...)
... second marking group. The string found by the expression inside this second marking group is back-referenced with $2
in replace string.[^\x22]+
... like before any character except "
one or more times. This expression matches the value of attribute status
.[^>]+>
... any character not being >
one or more times and next >
. This expression matches everything from "
after value of attribute status
up to end of inline element con:testCaseRunLogTestStep
.(?:
...)?
... non-marking group to match optionally\s*</con:testCases>\s*
... any whitespace character 0 or more times and the string </con:testCases>
and once again any whitespace character 0 or more times. This optionally applied expression matches everything after last tag of con:testCaseRunLogTestStep
to end of file as long as the file ends with the end tag </con:testCases>
.Other solutions would be also possible using JREPL.BAT using JScript. This is just one solution which worked on my tests with provided example file content and variations of it.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
del /?
echo /?
endlocal /?
for /?
goto /?
if /?
rem /?
set /?
setlocal /?
jrepl.bat /?
Upvotes: 1