<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
                xml:lang="en"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:local="http://www.w3.org/2005/xquery-local-functions"
                xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:variable name="className" as="xs:string" select="tokenAutomaton/@className"/>
    <xsl:variable name="defaultToken" as="xs:string" select="tokenAutomaton/@defaultToken"/>
    <xsl:variable name="tokens" as="element(token)+" select="tokenAutomaton/tokens/token"/>
    <xsl:variable name="tokenEnum" as="xs:string" select="tokenAutomaton/@tokenEnum"/>

    <xsl:variable name="warningGenerated" as="xs:string">/* NOTE: This file is AUTO GENERATED by qautomaton2cpp.xsl. */&#xA;</xsl:variable>

    <xsl:template match="tokenAutomaton">

        <xsl:variable name="uniqueLengths" as="xs:integer+" select="distinct-values(tokens/token/string-length())"/>

        <xsl:result-document method="text" href="{@headerFile}">

            <xsl:variable name="includeGuardName" select="string(@includeGuardName)"/>

            <xsl:value-of select="boilerplate/prolog"/>

            <xsl:value-of select="$warningGenerated"/>

            <xsl:text>&#xA;#ifndef </xsl:text>
            <xsl:value-of select="$includeGuardName"/>
            <xsl:text>&#xA;#define </xsl:text>
            <xsl:value-of select="$includeGuardName"/>
            <xsl:text>&#xA;&#xA;</xsl:text>

            <xsl:text>#include &lt;QtCore/QString>&#xA;</xsl:text>
            <xsl:text>&#xA;</xsl:text>
            <xsl:text>QT_BEGIN_NAMESPACE&#xA;</xsl:text>
            <xsl:text>&#xA;</xsl:text>

            <xsl:if test="@namespace">
                <xsl:text>namespace </xsl:text>
                <xsl:value-of select="@namespace"/>
                {
            </xsl:if>

            <xsl:text>class </xsl:text>
            <xsl:value-of select="@className"/>
                {
                <xsl:value-of select="@scope"/>:
                <xsl:text>enum </xsl:text>
                <xsl:value-of select="$tokenEnum"/>
                <xsl:text>&#xA;</xsl:text>
                {
                <xsl:value-of separator=",&#xA;">
                    <xsl:sequence select="@defaultToken"/>
                    <xsl:perform-sort select="tokens/token/local:tokenToEnumName(.)">
                        <xsl:sort select="."/>
                    </xsl:perform-sort>
                </xsl:value-of>
                };

                <xsl:text>static inline </xsl:text>
                <xsl:value-of select="$tokenEnum"/>
                <xsl:text> toToken(const QString &amp;value);&#xA;</xsl:text>
                <xsl:text>static inline </xsl:text>
                <xsl:value-of select="$tokenEnum"/>
                <xsl:text> toToken(const QStringRef &amp;value);&#xA;</xsl:text>
                <xsl:text>static </xsl:text>
                <xsl:value-of select="$tokenEnum"/>
                <xsl:text> toToken(const QChar *data, int length);&#xA;</xsl:text>
                    <xsl:if test="xs:boolean(@hasToString)">
                        <xsl:text>static QString toString(</xsl:text>
                        <xsl:value-of select="$tokenEnum"/>
                        <xsl:text> token);&#xA;</xsl:text>
                    </xsl:if>

                private:
                    <xsl:for-each select="$uniqueLengths">
                        <xsl:sort select="."/>
                        <xsl:text>static inline </xsl:text>
                        <xsl:value-of select="$tokenEnum"/>
                        <xsl:text> classifier</xsl:text>
                        <xsl:value-of select="."/>
                        <xsl:text>(const QChar *data);&#xA;</xsl:text>
                    </xsl:for-each>
                };

                <xsl:text>inline </xsl:text>
                <xsl:value-of select="@className"/>::<xsl:value-of select="$tokenEnum"/>
                <xsl:text> </xsl:text>
                <xsl:value-of select="@className"/>::toToken(const QString &amp;value)
                {
                    return toToken(value.constData(), value.length());
                }

                <xsl:text>inline </xsl:text>
                <xsl:value-of select="@className"/>::<xsl:value-of select="$tokenEnum"/>
                <xsl:text> </xsl:text>
                <xsl:value-of select="@className"/>::toToken(const QStringRef &amp;value)
                {
                    return toToken(value.constData(), value.length());
                }

                <xsl:if test="@namespace">
                    <xsl:text>}&#xA;</xsl:text>
                </xsl:if>

                <xsl:text>&#xA;</xsl:text>
                <xsl:text>QT_END_NAMESPACE&#xA;</xsl:text>
                <xsl:text>&#xA;</xsl:text>

                <xsl:text>#endif&#xA;</xsl:text>
        </xsl:result-document>

        <xsl:result-document method="text" href="{@sourceFile}">
            <xsl:value-of select="boilerplate/prolog"/>

            <xsl:value-of select="$warningGenerated"/>

            <xsl:text>&#xA;#include "</xsl:text>
            <xsl:value-of select="@headerFile"/>
            <xsl:text>"&#xA;</xsl:text>
            <xsl:text>&#xA;</xsl:text>
            <xsl:text>QT_BEGIN_NAMESPACE&#xA;</xsl:text>

            <xsl:if test="@namespace">
                <xsl:text>&#xA;</xsl:text>
                <xsl:text>using namespace </xsl:text>
                <xsl:value-of select="@namespace"/>
                <xsl:text>;&#xA;</xsl:text>
            </xsl:if>

            <xsl:text>&#xA;</xsl:text>
            <xsl:variable name="tokens" select="tokens/token"/>

            <xsl:for-each select="$uniqueLengths">
                <xsl:sort select="."/>
                <xsl:call-template name="generate-classifier">
                    <xsl:with-param name="strings" select="$tokens[string-length() eq current()]"/>
                </xsl:call-template>
            </xsl:for-each>

            <xsl:value-of select="@className"/>::<xsl:value-of select="$tokenEnum"/>
            <xsl:text> </xsl:text>
            <xsl:value-of select="@className"/>::toToken(const QChar *data, int length)
            {
                switch(length)
                {
                    <xsl:for-each select="$uniqueLengths">
                        <xsl:sort data-type="number" select="."/>
                        case <xsl:value-of select="."/>:
                            return classifier<xsl:value-of select="."/>(data);

                    </xsl:for-each>
                        default:
                            return <xsl:value-of select="@defaultToken"/>;
                }
            }

            <xsl:if test="xs:boolean(@hasToString)">
                QString <xsl:value-of select="@className"/>::toString(<xsl:value-of select="$tokenEnum"/> token)
                {
                    const unsigned short *data = 0;
                    int length = 0;

                    switch(token)
                    {
                    <xsl:for-each select="tokens/token">
                        <xsl:sort select="local:tokenToEnumName(.)"/>
                        case <xsl:sequence select="local:tokenToEnumName(.)"/>:
                        {<!-- Without these braces, the code doesn't compile on MSVC 2008. -->
                            static const unsigned short staticallyStored<xsl:value-of select="local:tokenToEnumName(.)"/>[] =
                            {
                            <xsl:value-of separator=", " select="string-to-codepoints(.), 0"/>
                            };
                            data = staticallyStored<xsl:value-of select="local:tokenToEnumName(.)"/>;
                            length = <xsl:value-of select="string-length(.)"/>;
                            break;
                        }
                    </xsl:for-each>
                        default:
                            /* It's either the default token, or an undefined enum
                             * value. We silence a compiler warning, and return the
                             * empty string. */
                            ;
                    }

                    union
                    {
                        const unsigned short *data;
                        const QChar *asQChar;
                    } converter;
                    converter.data = data;

                    return QString::fromRawData(converter.asQChar, length);
                }
            </xsl:if>

            <xsl:text>&#xA;</xsl:text>
            <xsl:text>QT_END_NAMESPACE&#xA;</xsl:text>
            <xsl:text>&#xA;</xsl:text>
        </xsl:result-document>

    </xsl:template>

    <xsl:template name="generate-classifier">
        <xsl:param name="strings" as="xs:string+"/>

        <xsl:value-of select="$className"/>::<xsl:value-of select="$tokenEnum"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="$className"/>::classifier<xsl:value-of select="."/>(const QChar *data)

        {
            <xsl:sequence select="local:generateBranching($strings, 1, 1)"/>

            return <xsl:value-of select="$defaultToken"/>;
        }
    </xsl:template>

    <xsl:function name="local:generateBranching" ><!--as="xs:string+">-->
        <xsl:param name="strings" as="xs:string+"/>
        <xsl:param name="depth" as="xs:integer"/>
        <xsl:param name="currentPos" as="xs:integer"/>

        <xsl:choose>
            <xsl:when test="count($strings) eq 1">
                <xsl:variable name="remainingLength" as="xs:integer" select="(string-length($strings) - $currentPos) + 1"/>
                <xsl:variable name="toMatch" as="xs:integer+" select="string-to-codepoints(substring($strings, $currentPos))"/>

                <xsl:if test="$remainingLength ne 0">
                    <xsl:choose>
                        <xsl:when test="$remainingLength eq 1">
                            if(data[<xsl:sequence select="$depth - 1"/>] == <xsl:sequence select="$toMatch"/>)
                        </xsl:when>
                        <xsl:when test="$remainingLength &gt; 1">
                            static const unsigned short string[] =
                            {
                                <xsl:value-of separator=", " select="string-to-codepoints(substring($strings, $currentPos))"/>
                            };
                            if(memcmp(&amp;data[<xsl:sequence select="$depth - 1"/>], &amp;string, sizeof(QChar) * <xsl:value-of select="$remainingLength"/>) == 0)
                        </xsl:when>
                    </xsl:choose>
                </xsl:if>

                return <xsl:value-of select="local:tokenToEnumName($strings)"/>;
            </xsl:when>
            <xsl:otherwise>
                <xsl:for-each select="distinct-values(for $i in $strings return substring($i, $currentPos, 1))">
                    <xsl:if test="position() &gt; 1">
                        <xsl:text>else </xsl:text>
                    </xsl:if>

                    <xsl:text>if (data[</xsl:text>
                    <xsl:sequence select="string($depth - 1)"/>
                    <xsl:text>] == </xsl:text>
                    <xsl:sequence select="string-to-codepoints(.)"/>
                    <xsl:text>)&#xA;</xsl:text>

                    {
                    <xsl:sequence select="local:generateBranching($strings[substring(., $currentPos, 1) eq current()], $depth + 1, $currentPos + 1)"/>
                    }

                </xsl:for-each>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>

    <xsl:function name="local:toCamelCase" as="xs:string">
        <xsl:param name="arg" as="xs:string"/>

        <xsl:sequence select="string-join((for $word in tokenize($arg,'[:-]+')
                                           return concat(upper-case(substring($word,1,1)),
                                                         substring($word, 2))) ,'')"/>

    </xsl:function>

    <xsl:function name="local:tokenToEnumName" as="xs:string">
        <xsl:param name="string" as="xs:string"/>

        <xsl:variable name="token" select="$tokens[. eq $string]"/>

        <xsl:choose>
            <xsl:when test="$token/@name">
                <xsl:sequence select="$token/@name"/>
            </xsl:when>
            <xsl:otherwise>
                <!-- We take the token's string value, and coerces into a C++
                     name. So get rid of invalid characters. Also do basic camel casing. -->
                <xsl:variable name="normalized" select="translate($string, 'ABCDEFGHIJKLMNOPQRSTYXZabcdefghijklmnopqrstyxz1234567890_', 'ABCDEFGHIJKLMNOPQRSTYXZabcdefghijklmnopqrstyxz1234567890_')"/>
                <xsl:value-of select="local:toCamelCase($normalized)"/>
            </xsl:otherwise>
        </xsl:choose>

    </xsl:function>

</xsl:stylesheet>

<!--
vim: et:ts=4:sw=4:sts=4
-->
