Question

Trying to learn how to use XLS/XLST for importing data into Filemaker. I have the very basics down, but now I need optimization.

Currently I have about 11 sets of data splintered into siblings I need to combine into a single record during the import process. Right now, I use 11 separate xsl files to import the data in 11 separate imports with each file focusing on a specific set of data (fire count, hit count, score, etc). This is terribly inefficient, but I lack the know-how to work in more detail with XLST and combine the fragmented data into one record.

An example of the data I'm working with is as follows:

<character_list limit="0" returned="2" milliseconds="0">
<character id="5428010618020696081" active_profile_id="15" faction_id="3" head_id="1" title_id="0">
<stats>
<weapon_stat_list>
<weapon_stat item_id="25003" last_save="1354544215" last_save_date="2012-12-03 14:16:55.0" stat_name="weapon_fire_count" value="108" vehicle_id="0"/>
<weapon_stat item_id="25003" last_save="1354544215" last_save_date="2012-12-03 14:16:55.0" stat_name="weapon_hit_count" value="60" vehicle_id="0"/>
<weapon_stat item_id="125" last_save="1354564613" last_save_date="2012-12-03 19:56:53.0" stat_name="weapon_fire_count" value="7708" vehicle_id="0"/>
<weapon_stat item_id="125" last_save="1354564613" last_save_date="2012-12-03 19:56:53.0" stat_name="weapon_play_time" value="13406" vehicle_id="0"/>
<weapon_stat item_id="125" last_save="1354564613" last_save_date="2012-12-03 19:56:53.0" stat_name="weapon_score" value="62012" vehicle_id="0"/>
<weapon_stat item_id="1259" last_save="1354573520" last_save_date="2012-12-03 22:25:20.211913" stat_name="weapon_fire_count" value="22" vehicle_id="0"/>
</weapon_stat_list>
</stats>
</character>
<character id="5428010618040144225" active_profile_id="6" faction_id="2" head_id="1" title_id="80">
<stats>
<weapon_stat_list>
<weapon_stat item_id="126" last_save="1353442416" last_save_date="2012-11-20 20:13:36.0" stat_name="weapon_fire_count" value="130" vehicle_id="0"/>
<weapon_stat item_id="126" last_save="1353442416" last_save_date="2012-11-20 20:13:36.0" stat_name="weapon_play_time" value="336" vehicle_id="0"/>
<weapon_stat item_id="126" last_save="1353442416" last_save_date="2012-11-20 20:13:36.0" stat_name="weapon_score" value="570" vehicle_id="0"/>
<weapon_stat item_id="1260" last_save="1353442416" last_save_date="2012-11-20 20:13:36.099928" stat_name="weapon_fire_count" value="181" vehicle_id="0"/>
<weapon_stat item_id="1260" last_save="1353442416" last_save_date="2012-11-20 20:13:36.100053" stat_name="weapon_play_time" value="471" vehicle_id="0"/>
<weapon_stat item_id="1260" last_save="1353442416" last_save_date="2012-11-20 20:13:36.100189" stat_name="weapon_score" value="635" vehicle_id="0"/>
<weapon_stat item_id="1261" last_save="1353447137" last_save_date="2012-11-20 21:32:17.0" stat_name="weapon_fire_count" value="321" vehicle_id="0"/>
<weapon_stat item_id="1261" last_save="1353447137" last_save_date="2012-11-20 21:32:17.0" stat_name="weapon_play_time" value="442" vehicle_id="0"/>
<weapon_stat item_id="1261" last_save="1353447137" last_save_date="2012-11-20 21:32:17.0" stat_name="weapon_score" value="1228" vehicle_id="0"/>
</weapon_stat_list>
</stats>
</character>
</character_list>

Unique identifiers for each character weapon data record would be the combination of the Character ID, item ID, and Vehicle ID. The type of data in the fragment is determined by the "stat_name" attribute.

The gist of the output I need is:

<ROW MODID="1" RECORDID="1" >
    <COL><DATA>5428010618020696081</DATA></COL>
    <COL><DATA>25003</DATA></COL>
    <COL><DATA>0</DATA></COL>
    <COL><DATA>108</DATA></COL>
    <COL><DATA>60</DATA></COL>
    <COL><DATA></DATA></COL>
</ROW>
<ROW MODID="2" RECORDID="2" >
    <COL><DATA>5428010618020696081</DATA></COL>
    <COL><DATA>125</DATA></COL>
    <COL><DATA>0</DATA></COL>
    <COL><DATA>7708</DATA></COL>
    <COL><DATA></DATA></COL>
    <COL><DATA>13406</DATA></COL>
    <COL><DATA>62012</DATA></COL>
</ROW>
<ROW MODID="3" RECORDID="3" >
    <COL><DATA>5428010618020696081</DATA></COL>
    <COL><DATA>1259</DATA></COL>
    <COL><DATA>0</DATA></COL>
    <COL><DATA>22</DATA></COL>
    <COL><DATA></DATA></COL>
    <COL><DATA></DATA></COL>
    <COL><DATA></DATA></COL>
</ROW>
<ROW MODID="4" RECORDID="4" >
    <COL><DATA>5428010618040144225</DATA></COL>
    <COL><DATA>126</DATA></COL>
    <COL><DATA>0</DATA></COL>
    <COL><DATA>130</DATA></COL>
    <COL><DATA></DATA></COL>
    <COL><DATA>336</DATA></COL>
    <COL><DATA>570</DATA></COL>
</ROW>
<ROW MODID="5" RECORDID="5" >
    <COL><DATA>5428010618040144225</DATA></COL>
    <COL><DATA>1260</DATA></COL>
    <COL><DATA>0</DATA></COL>
    <COL><DATA>181</DATA></COL>
    <COL><DATA></DATA></COL>
    <COL><DATA>471</DATA></COL>
    <COL><DATA>635</DATA></COL>
</ROW>
<ROW MODID="6" RECORDID="6" >
    <COL><DATA>5428010618040144225</DATA></COL>
    <COL><DATA>1261</DATA></COL>
    <COL><DATA>0</DATA></COL>
    <COL><DATA>321</DATA></COL>
    <COL><DATA></DATA></COL>
    <COL><DATA>442</DATA></COL>
    <COL><DATA>1228</DATA></COL>
</ROW>

There is of course more columns than that, but are omitted for simplicity. The output example has the "fields" of Character ID, Item ID, Vehicle ID, Fire Count, Hit Count, Play Time, Score in their respective order.

This is a copy of the XSL file that I am currently using to import hit count

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://tempuri.org/test.xsd">
        <xsl:template match="/character_list">
                <FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
                        <ERRORCODE>0</ERRORCODE>
                        <PRODUCT BUILD="" NAME="SOE API XML Import" VERSION="2.0" />
                        <DATABASE DATEFORMAT="yyyy.MM.dd" LAYOUT="" NAME="" RECORDS="" TIMEFORMAT="k:mm:ss" />
                        <METADATA>
                                <FIELD EMPTYOK="NO" MAXREPEAT="1" NAME="Character_ID" TYPE="TEXT" />
                                <FIELD EMPTYOK="NO" MAXREPEAT="1" NAME="Item_ID" TYPE="TEXT" />
                                <FIELD EMPTYOK="NO" MAXREPEAT="1" NAME="Vehicle_ID" TYPE="TEXT" />
                                <FIELD EMPTYOK="NO" MAXREPEAT="1" NAME="Value" TYPE="TEXT" />
                        </METADATA>
                        <RESULTSET>
                        <xsl:for-each select="character" >
                                <xsl:variable name="cid">
                                        <xsl:value-of select="@id" />
                                </xsl:variable>
                                <xsl:for-each select="stats/weapon_stat_list/weapon_stat[@stat_name='weapon_hit_count']" >
                                        <xsl:variable name="c">
                                                <xsl:value-of select="position()" />
                                        </xsl:variable>
                                        <ROW MODID="$c" RECORDID="$c" >
                                                <COL><DATA><xsl:value-of select="$cid" /></DATA></COL>
                                                <COL><DATA><xsl:value-of select="@item_id" /></DATA></COL>
                                                <COL><DATA><xsl:value-of select="@vehicle_id" /></DATA></COL>
                                                <COL><DATA><xsl:value-of select="@value" /></DATA></COL>
                                        </ROW>
                                </xsl:for-each>
                        </xsl:for-each>
                        </RESULTSET>
                </FMPXMLRESULT>
    </xsl:template>
</xsl:stylesheet>

In reading around it seems that I might need to use a for-each-group to group info together. I'll continue to trying and figure out what I need to do to make this work, but I'm hoping that someone can save me some time. I also don't know what version of XLST Filemaker 12 supports. I'll keep this updated if I find out more.

Was it helpful?

Solution

If you can iterate over all keys of the stat entries (from your comments, the keys are all item_id+vehicle_id values pairs), it should be pretty easy to aggregate all the attributes that belong to each key in one go.

From your expected output, it appears that for every key, there is exactly one <weapon_stat> element with stat_name='weapon_fire_count'. Given that this is correct, you'll easily get all item_id values by iterating over these <weapon_stat> elements:

<xsl:for-each select="stats/weapon_stat_list/weapon_stat[@stat_name='weapon_fire_count']" >

From these elements, it is straightforward to generate the output fields Item ID, Vehicle ID, and Fire Count. For the other three fields, you can select the value from the appropriate sibling with the following (a bit more advanced) XPath expression:

<xsl:value-of select="../weapon_stat[@stat_name='weapon_hit_count' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" />

The trick is to compare the @item_id and @vehicle_id of the respective context nodes (these are all siblings: ../weapon_stat) with the @item_id and @vehicle_id of the current node (which is the node selected by the for-each above).

All in all, you want to replace the inner for-each of your XSL file with this:

<xsl:for-each select="stats/weapon_stat_list/weapon_stat[@stat_name='weapon_fire_count']" >
    <ROW>
        <COL><DATA><xsl:value-of select="$cid" /></DATA></COL>
        <COL><DATA><xsl:value-of select="@item_id" /></DATA></COL>
        <COL><DATA><xsl:value-of select="@vehicle_id" /></DATA></COL>
        <COL><DATA><xsl:value-of select="@value" /></DATA></COL>
        <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_hit_count' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
        <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_play_time' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
        <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_score' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
    </ROW>
</xsl:for-each>

Note: I omitted the MODID and RECORDID counters because there is no efficient solution to add them in this transformation. However, it is straightforward to take the result of this transformation and add the counters through a second XSL transformation.


Update: In the pastebin example you gave in a comment, it seems that not every key has a weapon_fire_count stat. So instead of iterating only over these stat entries, you need to iterate over all stats, and omit the output if there was already a preceding sibling with the same key:

<xsl:for-each select="stats/weapon_stat_list/weapon_stat" >
    <xsl:if test="not(boolean(preceding-sibling::weapon_stat[@item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]))">
        <ROW >
            <COL><DATA><xsl:value-of select="$cid" /></DATA></COL>
            <COL><DATA><xsl:value-of select="@item_id" /></DATA></COL>
            <COL><DATA><xsl:value-of select="@vehicle_id" /></DATA></COL>
            <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_fire_count' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
            <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_hit_count' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
            <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_play_time' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
            <COL><DATA><xsl:value-of select="../weapon_stat[@stat_name='weapon_score' and @item_id=current()/@item_id and @vehicle_id=current()/@vehicle_id]/@value" /></DATA></COL>
        </ROW>
    </xsl:if>
</xsl:for-each> 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top