I'm developing a web application that leverages multiple programming languages. The data flow resembles:
- Browser » PHP » PL/SQL » XML » XSLT » XHTML + JavaScript » Browser
Using different languages makes it tempting to hard-code various constants within the different languages, which encourages inconsistencies. Without a single source, it is highly probable that "Your Name" will sometimes be "Your name" or "your name" or even "Username", depending on what language requires the value.
To avoid duplicating these values, a "configuration" table exists:
| code | label | languages | data_type | +-------------------+---------------------+------------+-----------+ | DEFAULT_USER_NAME | Your Name | xsl,js,php | string | | BASE_IMAGE | ${BASE_APP}images/ | xsl,js | string | | BASE_ICON | ${BASE_IMAGE}icons/ | xsl,js | string | | BASE_APP | /app/ | xsl,js,php | string |
The configuration table is then converted to XML, with the references expanded as expected:
BASE_ICON = /app/images/icons/
Once in XML, files for constants are generated for the appropriate syntax:
- constants.php
- constants.xsl
- constants.js
Since the constants are now defined in a single table, there is no more duplication.
Problem
I am concerned that the problem of multi-language constants has a technically better solution.
When a project uses multiple programming languages that tightly communicate, how would you prevent constant values from being repeated throughout different language-specific source files?
In other words, is there a better design for eliminating duplicate values across different programming languages?
Generate XML
The code to generate the XML resembles...
CREATE OR REPLACE FUNCTION generate_configuration_xml()
RETURNS text AS
$BODY$
DECLARE
v_result TEXT DEFAULT '<configuration/>';
BEGIN
SELECT
xmlroot (
xmlelement( name "configuration",
xmlagg(
xmlelement( name "constant",
xmlattributes(
c.code AS "name",
c.languages AS "languages"
),
xmlelement( name "value",
xmlattributes(
c.data_type AS "type"
),
recipe.get_configuration_value( c.code, 0 )
)
)
)
),
VERSION '1.0',
STANDALONE YES
)
INTO
v_result
FROM
configuration c;
RETURN v_result;
END;
Get Configuration Value
The code to retrieve a constant (as called above) resembles:
LOOP
SELECT substring( v_result from '\$\{.+?\}' ) INTO v_code_replace;
EXIT WHEN v_code_replace IS NULL;
p_code := substr( v_code_replace, 3, length( v_code_replace ) - 3 );
v_code_value := get_configuration_value( p_code, p_depth + 1 );
v_result := replace( v_result, v_code_replace, v_code_value );
END LOOP;
Generated XML
This produces an XML file similar to the following:
<?xml version="1.0" standalone="yes"?>
<configuration>
<constant name="BASE_IMAGE" languages="xsl.js">
<value type="string">/app/images/</value>
</constant>
<constant name="BASE_ICON" languages="xsl.js">
<value type="string">/app/images/icons/</value>
</constant>
<constant name="DEFAULT_USER_NAME" languages="xsl.js,php">
<value type="string">Your Name</value>
</constant>
</configuration>
Transformation
Using the above language-neutral XML document, XSL can generate constants in all programming languages used by the project. The template to transform the constants into various language-specific declarations resembles:
<xsl:template match="//configuration">
<xsl:choose>
<xsl:when test="$language = 'xsl'">
<xsl:call-template name="xsl-header" />
<xsl:text disable-output-escaping="yes"><![CDATA[<!-- ]]></xsl:text> <xsl:call-template name="comment" /> <xsl:text disable-output-escaping="yes"><![CDATA[ -->]]></xsl:text>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="constant[contains(@languages,'xsl')]" mode="xsl" />
<xsl:call-template name="xsl-footer" />
</xsl:when>
<xsl:when test="$language = 'php'">
<xsl:call-template name="php-header" />
<xsl:text>// </xsl:text><xsl:call-template name="comment" />
<xsl:text>
</xsl:text>
<xsl:apply-templates select="constant[contains(@languages,'php')]" mode="php" />
<xsl:call-template name="php-footer" />
</xsl:when>
<xsl:when test="$language = 'javascript'">
<xsl:text>// </xsl:text><xsl:call-template name="comment" />
<xsl:text>
</xsl:text>
<xsl:apply-templates select="constant[contains(@languages,'javascript')]" mode="javascript" />
</xsl:when>
</xsl:choose>
</xsl:template>
XSL
The XML can be transformed into XSL (e.g., constants.xsl):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="DEFAULT_USER_NAME">Your Name</xsl:variable>
</xsl:stylesheet>
JavaScript
The XML can be transformed into JavaScript (e.g., constants.js):
var DEFAULT_USER_NAME = "Your Name";
PHP
And the XML can be transformed into PHP (e.g., constants.php):
<?php
$DEFAULT_USER_NAME = 'Your Name';
?>
These files can then be included by their respective "main" entry points. Any time a new value is required (i.e., change "Your Name" to "Click 'Your Name' To Begin"), the constants can be auto-generated from the configuration table, enforcing consistency.
Execution
The constants are then generated as follows:
#!/bin/bash
# Fetch the most recent values for the constants.
psql schema -t -A -q -c 'select generate_configuration_xml();' -o constants.xml
# Transform the constants into a programming language variable.
xsltproc --stringparam language javascript generate.xsl constants.xml > constants.js
xsltproc --stringparam language xsl generate.xsl constants.xml > constants.xsl
xsltproc --stringparam language php generate.xsl constants.xml > constants.php
rm constants.xml
This produces a number of different files. Publishing the same constants in Java would require little more than the following:
xsltproc --stringparam language java generate.xsl constants.xml > Constants.java
Is there a better way?