Update .properties file with values from .xml file

2019-08-04 17:22发布

问题:

The following xmlstarlet command:

./xmlstarlet-1.5.0/xml.exe fo --dropdtd $filename | ./xmlstarlet-1.5.0/xml.exe sel -t -m "//DOC//PTXT" -v "concat(./@ID,' ', .)"

returns multiple results, because my file contains a lot of doc/ptxt. I need to do something with each output, more explicitly I need to do something with each ptxt value. How can I loop through all results of XMLstarlet?

My output looks something like:

my.id.one Text I need 
my.id.two Text I also need
my.id.three Surprisingly I need this text too

and what I need is to have each id-text pair in maybe two variables id and text, because I have another file containing id-value pairs and I need to match those pairs.

Update 1:

A concrete example of what I need to do: I have file X.xml and file Y.properties File X.xml has the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE DOC SYSTEM "ts.dtd">
<?xml-stylesheet type="text/css" href="ts.css"?>
<DOC LOCALE="en-US"> 
    <PTXT ID="text.something">Open door</PTXT> 
    <PTXT ID="text.something.else">Open another door</PTXT>
    <PTXT ID="text.whatever">Close all</PTXT>
</DOC>

File Y.properties has the following structure:

text.something=Open window
text.something.else=Open another door

The result that I expect is Y.properties with this content:

text.something=Open door
text.something.else=Open another door
text.whatever=Close all

To do:

  • If value from id in X.xml is different than value from key in Y.properties, the value from key in Y.properties should be updated.
  • If value from id in X.xml is the same as value from key in Y.properties, nothing should be done
  • If id from X.xml is not present as key in Y.properties, it should be added with its value from X.xml to Y.properties

My current shell code is:

for filename in C:/Temp/XLocation/*.xml; do
        ./xmlstarlet-1.5.0/xml.exe fo --dropdtd $filename | ./xmlstarlet-1.5.0/xml.exe sel -t -m "//DOC//PTXT" -v "concat(./@ID,' ', .)"
done

which currently only takes every id/value from X.xml and prints to std output. The for is there, as you probably suspect, because I have multiple X.xml and Y.properties on which I must execute this code.

Problem 1: @janos I have the following problem and I really don't understand why. It all works ok, but few of them don't. Example: X.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE DOC SYSTEM "ts.dtd">
<?xml-stylesheet type="text/css" href="ts.css"?>
<DOC> 
<PTXT ID="a.b.c.d" CONTEXT="label"><NTI>Text</NTI></PTXT>
</DOC>

Y.properties:

a.b.c.d=Text

and my output is:

a.b.c.d=
    Text=
=

Can you please help me as I really don't understand what's going on.

Problem 2: Having the following input: X.xml

my.id = \u00D6ffnen Express WebTools

and Y.properties

<PTXT ID="my.id" CONTEXT="">Öffnen <NTI>Express WebTools</NTI></PTXT> 

results in: out.properties

my.id=Öffnen Express WebTools
my.id=\u00D6ffnen Express WebTools

instead of

my.id=Öffnen Express WebTools

回答1:

First generate the data to match the format in Y.properties:

xmlstarlet fo --dropdtd a.xml | \
  xmlstarlet sel -t -m "//DOC//PTXT" -v $'concat(@ID, "=", ., "\n")'

For your example this produces:

text.something=Open door
text.something.else=Open another door
text.whatever=Close all

Then you can use Awk to perform the following logic:

  • Use the output of the previous command as the first file with -
  • Use Y.properties as the second file
  • Process line by line, using = as a separator, and building up a map of key-value pairs, using a simple logic: if the key is not in the map, add the key-value pair. Since we will get the lines of the xmlstarlet output first, the above logic will add all those values to the mapping. As we process the lines of the second file (Y.properties), lines whose key is already in the map will be ignored, but new key-value pairs will be added.

Like this:

xmlstarlet fo --dropdtd a.xml | \
  xmlstarlet sel -t -m "//DOC//PTXT" -v $'concat(@ID, "=", ., "\n")' | \
  awk -F= '!($1 in m) { m[$1] = $2 }
           END { for (key in m) { print key "=" m[key] } }' - Y.properties

You can redirect the output to the desired target file.

To perform the above to multiple files, you can wrap the above code in a function, with appropriate parameters. For example:

mergeprops() {
    local filename=$1
    local propsfile=$2
    local out=$3

    xmlstarlet fo --dropdtd "$filename" | \
      xmlstarlet sel -t -m "//DOC//PTXT" -v $'concat(@ID, "=", ., "\n")' | \
      awk -F= '!($1 in m) { m[$1] = $2 }
               END { for (key in m) { print key "=" m[key] } }' - "$propsfile" | sed 's/\s*=\s*/=/g' > "$out"
}

for filename in /c/Temp/XLocation/*.xml; do
    mergeprops "$filename" Y.properties "$filename.out"
done

Let me know if you need more help.