[xsl] I love programs that output programs

Subject: [xsl] I love programs that output programs
From: "Roger L Costello costello@xxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Fri, 17 Jan 2025 20:21:58 -0000
Hi Folks,

Recently I was reading Brian Kernighan's new book on AWK, and it showed a
wicked cool AWK program that outputs an AWK program. Below is the AWK program,
followed by the equivalent XSLT program.

The task is to run some checks on a password file. Here is a sample password
file:

root:qyxRi2uVjrg:0:2::/:
jd:1L./v6iblzzNE:9:1:John Doe:/usr/jd:
jt:otxs1oToyvMQ:15:1:Jim Thomson:/usr/jt:
uucp:xutIBs2hKtcls:48:1:uucp daemon:/usr/lib/uucp:uucico
sm:xNqy//GDc8FFg:170:2:Sally Smith:/usr/sm:
a!f:aiopaIAjfaI:21:1:Fake Person:/usr/a!f:
jv::1:Jules Verne:/usr/jv:
ah:dsjkdAJ:34:1:Alexander Hamilton

Fields within each line are delimited by a colon.

The first field is the username. It should be alphanumeric. Check each line to
confirm that its username is alphanumeric.
The second field is an encrypted version of the password. The field should not
be empty. Check each line to confirm that it's not empty.
I won't discuss the other fields.
The third check confirms that each line has 7 fields.

-----------------------------------------------------------------------------
---------------
          AWK Version
-----------------------------------------------------------------------------
---------------
Here's an AWK expression to check whether a line does not have 7 fields:

NF != 7

[NF = Number of Fields, NF is a built-in function]

Here's an AWK expression to check whether the second field is empty:

$2 == ""

[$1 denotes the first field, $2 denotes the second field, etc.]

Here's an AWK expression to check whether the first field is not
alphanumeric:

$1 ~ /[^A-Za-z0-9]/

[tilda means "matches"; the stuff between slashes is a regex]

Put the expressions into a file, along with a description:

NF != 7			does not have 7 fields
$2 == ""		no password
$1 ~ /[^A-Za-z0-9]/	nonalphanumeric user id

The following AWK program inputs those expressions and outputs a program that
applies the expressions to each line of the password file:

BEGIN { FS = "\t+" }
{ printf("%s {\n\tprintf(\"line %%d, %s: %%s\\n\",NR,$0) }\n", $1, $2) }

[FS = Field Separator. NR = Row Number, $0 denotes the entire line. If you
know the C printf statement you can probably figure out what that printf is
doing--it is outputting a printf]

Run the AWK program; it generates this AWK program:

NF != 7 {
	printf("line %d, does not have 7 fields: %s\n",NR,$0) }
$2 == "" {
	printf("line %d, no password: %s\n",NR,$0) }
$1 ~ /[^A-Za-z0-9]/ {
	printf("line %d, nonalphanumeric user id: %s\n",NR,$0) }

Now run that generated AWK program against the password file. It reveals
errors in the password file:

line 6, nonalphanumeric user id: a!f:aiopaIAjfaI:21:1:Fake Person:/usr/a!f:
line 7, does not have 7 fields: jv::1:Jules Verne:/usr/jv:
line 7, no password: jv::1:Jules Verne:/usr/jv:
line 8, does not have 7 fields: ah:dsjkdAJ:34:1:Alexander Hamilton

-----------------------------------------------------------------------------
---------------
          XSLT/XPath Version
-----------------------------------------------------------------------------
---------------
Format the password file as XML:

<passwds>
    <passwd>root:qyxRi2uVjrg:0:2::/:</passwd>
    <passwd>jd:1L./v6iblzzNE:9:1:John Doe:/usr/jd:</passwd>
    <passwd>jt:otxs1oToyvMQ:15:1:Jim Thomson:/usr/jt:</passwd>
    <passwd>uucp:xutIBs2hKtcls:48:1:uucp daemon:/usr/lib/uucp:uucico</passwd>
    <passwd>sm:xNqy//GDc8FFg:170:2:Sally Smith:/usr/sm:</passwd>
    <passwd>a!f:aiopaIAjfaI:21:1:Fake Person:/usr/a!f:</passwd>
    <passwd>jv::1:Jules Verne:/usr/jv:</passwd>
    <passwd>ah:dsjkdAJ:34:1:Alexander Hamilton</passwd>
</passwds>

Create XPath expressions for the three checks and put the XPath expressions
into a file:

<validation_tests>
    <test>
        <xpath>count(tokenize(.,':')) ne 7</xpath>
        <message>does not have 7 fields</message>
    </test>
    <test>
        <xpath>tokenize(.,':')[2] eq ''</xpath>
        <message>no password</message>
    </test>
    <test>
        <xpath>string-length(translate(tokenize(., ':')[1],
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', '')) ne
0</xpath>
        <message>nonalphanumeric user id</message>
    </test>
</validation_tests>

This XSLT program inputs the XPath expressions file and outputs an XSLT
program that applies the XPath expressions:

<xsl:stylesheet 	xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
    		xmlns:axsl="http://www.w3.org/1999/XSL/Transform/alias";
    		version="3.0">

    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

    <xsl:template match="/">
        <axsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="3.0">
            <axsl:template match="/">
                <axsl:for-each select="/passwds/passwd">
                    <axsl:variable name="passwd" select="."/>
                    <xsl:for-each select="/validation_tests/test">
                        <axsl:choose>
                            <axsl:when test="{xpath}">
                                <axsl:message>line <axsl:value-of
select="position()"/>, <xsl:value-of select="message"/>: <axsl:value-of
select="$passwd"/></axsl:message>
                            </axsl:when>
                        </axsl:choose>
                    </xsl:for-each>
                </axsl:for-each>
            </axsl:template>
        </axsl:stylesheet>
    </xsl:template>
</xsl:stylesheet>

Here is the XSLT it generates:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="3.0">
    <xsl:template match="/">
        <xsl:for-each select="/passwds/passwd">
            <xsl:variable name="passwd" select="."/>
            <xsl:choose>
                <xsl:when test="count(tokenize(., ':')) ne 7">
                    <xsl:message>line <xsl:value-of select="position()"/>,
does not have 7 fields: <xsl:value-of select="$passwd"/></xsl:message>
                </xsl:when>
            </xsl:choose>
            <xsl:choose>
                <xsl:when test="tokenize(., ':')[2] eq ''">
                    <xsl:message>line <xsl:value-of select="position()"/>, no
password: <xsl:value-of select="$passwd"/></xsl:message>
                </xsl:when>
            </xsl:choose>
            <xsl:choose>
                <xsl:when test="string-length(translate(tokenize(., ':')[1],
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', '')) ne 0">
                    <xsl:message>line <xsl:value-of select="position()"/>,
nonalphanumeric user id: <xsl:value-of select="$passwd"/></xsl:message>
                </xsl:when>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Run the generated XSLT program against the password file; these messages are
displayed:

line 6, nonalphanumeric user id: a!f:aiopaIAjfaI:21:1:Fake Person:/usr/a!f:
line 7, does not have 7 fields: jv::1:Jules Verne:/usr/jv:
line 7, no password: jv::1:Jules Verne:/usr/jv:
line 8, does not have 7 fields: ah:dsjkdAJ:34:1:Alexander Hamilton

Current Thread