Sorting bom2csv.xsl Output

The EESchema default XSL processor is xsltproc. I have dealt with libxml in the past, and I found it really hard to work with on Windows. Based on Dolganoff’s posting “BOM generation in “new” Kicad (Windows)”, I installed saxon9he on my PC instead of xsltproc. However, when I downloaded the latest version, there was only a saxon9he.jar file. No problem, since I already had Java installed on my PC. Based on installing Saxon in the obvious location, the command line becomes:

java -jar c:\saxon\saxon9he.jar -t “%I” -xsl:“C:\Program Files\KiCad\bin\scripting\plugins\bom2csv.xsl” -o:"%O.csv"

Saxon9he has worked flawlessly for me, generating both BOM’s and netlists. However the output is not sorted, so it’s difficult to work with. Using the standard XLS version 1.0 sorting function to sort components by reference is also unsatisfactory, since it is only possible to order components alphabetically, so that they are still not in proper order (e.g. J18, J19, J2, J20…). Maybe there is another way, but XSL version 2.0 added regular expression matching, which will work just fine to sort references properly. Saxon9he is fully XSL version 2.0 compliant.

Rather than tackle regular expressions directly, I downloaded the functx library from “http://www.xsltfunctions.com/xsl/download.html”. I stored the “functx-1.0-doc-2007-01.xsl” file in the same directory as bom2csv.xsl, then I made the obvious changes to bom2csv.xsl to import the functx library and sort the components. A copy of my modified bom2csv.xsl file appears below.

Note that you will need Administrator privilege on Windows to install Saxon in a privileged location such as c:\saxon. You will need Administrator privilege to replace bom2csv.xsl or to modify it in situ. (I used Notepad++ and ran it with Administrator privilege.) You’ll also need Administrator privilege to copy the functx library to that same directory. The approved approach would be to put those files in some subdirectory off of your user directory. (Modify the above EESchema command line accordingly.)

Without having tested, I can’t say if the following script will work with xsltproc. However since saxon9he is available in Java, it should be possible to run the script with saxon9he on both Linux and OS X.

<!--XSL style sheet to convert EESCHEMA XML Partlist Format to CSV BOM Format
    Based on bom2csv.xsl, which is:
    Copyright (C) 2013, Stefan Helmert.
    GPL v2.

    Functionality:
        Generate csv table with table head of all existing field names
        and assigned field values entries.

    How to use this is explained in eeschema.pdf chapter 14.  You enter a command line into the
    BOM exporter using a new (custom) tab in the BOM export dialog.  The command is
    similar to
        on Windows:
            xsltproc -o "%O.csv" "C:\Program Files (x86)\KiCad\bin\plugins\bom2csv.xsl" "%I"
        on Linux:
            xsltproc -o "%O.csv" /usr/local/lib/kicad/plugins/bom2csv.xsl "%I"

    Instead of "%O.csv" you can alternatively use "%O" if you will supply your own file extension when
    prompted in the UI.  The double quotes are there to account for the possibility of space(s)
    in the filename.
-->

<!--
    @package
    Generate a BOM as a comma separated list (csv file type).
    One component per line
    Fields are
    Reference, Symbol, Value, Footprint, Datasheet, [additional defined fields...]
-->

<!DOCTYPE xsl:stylesheet [
  <!ENTITY nl  "&#xd;&#xa;">    <!--new line: remove &#xd; for linux or os-x -->
]>


<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:functx="http://www.functx.com">

    <xsl:import href="functx-1.0-doc-2007-01.xsl"/>
    <xsl:output method="text"/>

    <!-- for table head and empty table fields-->
    <xsl:key name="headentr" match="field" use="@name"/>

    <!-- main part -->
    <xsl:template match="/export">
        <xsl:text>Reference, Symbol, Value, Footprint, Datasheet</xsl:text>

            <!-- find all existing table head entries and list each one once -->
            <xsl:for-each select="components/comp/fields/field[generate-id(.) = generate-id(key('headentr',@name)[1])]">
                <xsl:text>, </xsl:text>
                <xsl:value-of select="@name"/>
            </xsl:for-each>
            <xsl:text>&nl;</xsl:text>

        <!-- all table entries -->
        <xsl:apply-templates select="components/comp">
            <xsl:sort select="functx:substring-before-match(@ref,'[0-9]')"/>
            <xsl:sort select="functx:substring-after-last-match(@ref,'[a-zA-Z]')" data-type="number"/>
        </xsl:apply-templates>
    </xsl:template>

    <!-- the table entries -->
    <xsl:template match="components/comp">
        <xsl:value-of select="@ref"/><xsl:text>,</xsl:text>
        <xsl:value-of select="libsource/@lib"/><xsl:text>:</xsl:text><xsl:value-of select="libsource/@part"/><xsl:text>,</xsl:text>
        <xsl:value-of select="value"/><xsl:text>,</xsl:text>
        <xsl:value-of select="footprint"/><xsl:text>,</xsl:text>
        <xsl:value-of select="datasheet"/>
        <xsl:apply-templates select="fields"/>
        <xsl:text>&nl;</xsl:text>
    </xsl:template>


    <!-- table entries with dynamic table head -->
    <xsl:template match="fields">

        <!-- remember current fields section -->
        <xsl:variable name="fieldvar" select="field"/>

        <!-- for all existing head entries -->
        <xsl:for-each select="/export/components/comp/fields/field[generate-id(.) = generate-id(key('headentr',@name)[1])]">
            <xsl:variable name="allnames" select="@name"/>
            <xsl:text>,</xsl:text>

            <!-- for all field entries in the remembered fields section -->
            <xsl:for-each select="$fieldvar">

                <!-- only if this field entry exists in this fields section -->
                <xsl:if test="@name=$allnames">
                    <!-- content of the field -->
                    <xsl:value-of select="."/>
                </xsl:if>
                <!--
                    If it does not exist, use an empty cell in output for this row.
                    Every non-blank entry is assigned to its proper column.
                -->
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>

 </xsl:stylesheet>
1 Like

From time to time we need to refer to a netlist for checking things like footprint assignments and changes to nets. Most of the netlists available from Eeschema are simply too verbose for manual use. The Pads-PCB format is more Spartan than the rest, but it is hardly human friendly.

Starting with the “netlist_form_pads-pcb.xsl” style sheet, I applied the above techniques to generate a more useful netlist for manual operations. In addition to sorting the lists, I added some board information from the schematic title blocks and I set the script up to list up to 10 pins/pads per line of the net list.

A typical command line to invoke the script from the netlist facility inside Eeschema would be:

java -jar c:\saxon\saxon9he.jar -t “%I” -xsl:“C:\Program Files\KiCad\bin\scripting\plugins\netlist2txt.xsl” -o:"%O.txt"

The script (netlist2txt.xsl) follows:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--XSL style sheet to generate a human readable netlist.
    Based on netlist_form_pads-pcb.xsl, which is:
    Copyright (C) 2010, SoftPLC Corporation.
    GPL v2.

    How to use:
        see eeschema.pdf, chapter 14
-->

<!--
    @package
    Generate a netlist in a simplified human readable format.
    The components are listed with their corresponding footprints, one component per line.
    Nets are listed with their associated pads.
-->

<!DOCTYPE xsl:stylesheet [
  <!ENTITY nl  "&#xd;&#xa;">    <!--new line: remove &#xd; for linux or os-x -->
]>

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:functx="http://www.functx.com">

<xsl:import href="functx-1.0-doc-2007-01.xsl"/>

<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>

<xsl:template match="/export">
    <xsl:text>Kicad Netlist&nl;</xsl:text>
    <xsl:text>&nl;Title:     </xsl:text>
    <xsl:value-of select="design/sheet[1]/title_block/title"/>
    <xsl:text>&nl;Company:   </xsl:text>
    <xsl:value-of select="design/sheet[1]/title_block/company"/>
    <xsl:text>&nl;Revision:  </xsl:text>
    <xsl:value-of select="design/sheet[1]/title_block/rev"/>
    <xsl:text>&nl;Date:      </xsl:text>
    <xsl:value-of select="design/date"/>
    <xsl:text>&nl;&nl;</xsl:text>
    <xsl:text>Component List:&nl;</xsl:text>
    <xsl:apply-templates select="components/comp">
        <xsl:sort select="functx:substring-before-match(@ref,'[0-9]')"/>
        <xsl:sort select="functx:substring-after-last-match(@ref,'[a-zA-Z]')" data-type="number"/>
    </xsl:apply-templates>
    <xsl:text>&nl;Net List:&nl;</xsl:text>
    <xsl:apply-templates select="nets/net">
        <xsl:sort select="@name"/>
    </xsl:apply-templates>
</xsl:template>

<!-- for each component -->
<xsl:template match="comp">
    <xsl:text> </xsl:text>
    <xsl:value-of select="@ref"/>
    <xsl:text> </xsl:text>
    <xsl:choose>
        <xsl:when test = "footprint != '' ">
            <xsl:apply-templates select="footprint"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:text>unknown</xsl:text>
        </xsl:otherwise>
    </xsl:choose>
    <xsl:text>&nl;</xsl:text>
</xsl:template>

<!-- for each net -->
<xsl:template match="net">
    <!-- nets are output only if there is more than one pin in net -->
    <xsl:if test="count(node)>1">
        <xsl:text>Net </xsl:text>
        <xsl:value-of select="@name"/>
        <xsl:text>&nl;</xsl:text>
        <xsl:apply-templates select="node">
            <xsl:sort select="functx:substring-before-match(@ref,'[0-9]')"/>
            <xsl:sort select="functx:substring-after-last-match(@ref,'[a-zA-Z]')" data-type="number"/>
            <xsl:sort select="functx:substring-before-match(@pin,'[0-9]')"/>
            <xsl:sort select="functx:substring-after-last-match(@pin,'[a-zA-Z]')" data-type="number"/>
        </xsl:apply-templates>
        <xsl:if test="count(node) mod 10 != 0">
            <xsl:text>&nl;</xsl:text>
        </xsl:if>
    </xsl:if>
</xsl:template>

<!-- for each node -->
<xsl:template match="node">
    <xsl:text> </xsl:text>
    <xsl:value-of select="@ref"/>
    <xsl:text>.</xsl:text>
    <xsl:value-of select="@pin"/>
    <xsl:if test="position() mod 10 = 0">
        <xsl:text>&nl;</xsl:text>
    </xsl:if>
</xsl:template>
</xsl:stylesheet>