1.3.3. fejezet, XSLT

A böngésző oldali XSL transzformációknak két változatáról írok. Ez lehet JavaScript alapú, és böngésző alapú. A böngésző alapú jobban támogatott, a mai modern böngészők (IE, FireFox, Opera, Safari, Chrome) mind alkalmazzák az xml fejlécként megadható transzformációt.

Az data.xml a következő lehet:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href='xsl/table.xsl' type='text/xsl'?>
<xml>
	<data>
		<rec>
			<field name="id">1</field>
			<field name="name">kategoria</field>
			<field name="descriptor">&#60;?xml version='1.0'
				encoding='utf-8'?>&#60;containerdescriptor
				classname='com.infokristaly.engine.containers.SQLContainer'>
				&#60;attribute name='pagesize'>15&#60;/attribute>&#60;attribute
				name='autoopen'>true&#60;/attribute>&#60;table>
				&#60;sqlstring>select id, megnevezes from
				demand_kategoria&#60;/sqlstring>
				&#60;fieldlist>
				&#60;field name='id' type='java.lang.Integer' order='1' primarykey='true'
				visible='false'>
				&#60;label context='HUN'>azonosító&#60;/label>
				&#60;/field>
				&#60;field name='megnevezes' type='java.lang.String' order='2' visible='true'>
				&#60;label context='HUN'>Kategória&#60;/label>
				&#60;/field>
				&#60;/fieldlist>
				&#60;/table>&#60;/containerdescriptor></field>
		</rec>
		<rec>
			<field name="id">2</field>
			<field name="name">arlista</field>
			<field name="descriptor">&#60;?xml version='1.0'
				encoding='utf-8'?>&#60;containerdescriptor
				classname='com.infokristaly.engine.containers.SQLContainer'>&#60;table>
				&#60;sqlstring>select kategoria_id, kod1, kod2, megnevezes, allapot,
				partner_netto, fogyaszto_netto, fogyaszto_brutto, validfrom from
				demand_arlista where kategoria_id = ?&#60;/sqlstring>
				&#60;fieldlist>
				&#60;field name='kategoria_id' type='java.lang.Integer' order='1'
				primarykey='true' visible='false'>
				&#60;label context='HUN'>Kategória azonosító&#60;/label>
				&#60;/field>
				&#60;field name='kod1' type='java.lang.String' order='2' visible='true'>
				&#60;label context='HUN'>Termékcsoport kód&#60;/label>
				&#60;/field>
				&#60;field name='kod2' type='java.lang.String' order='3' primarykey='true'
				visible='true'>
				&#60;label context='HUN'>Termék azonosító&#60;/label>
				&#60;/field>
				&#60;field name='megnevezes' type='java.lang.String' order='4' visible='true'>
				&#60;label context='HUN'>Megnevezés&#60;/label>
				&#60;/field>
				&#60;field name='allapot' type='java.lang.String' order='5' visible='true'>
				&#60;label context='HUN'>Állapot&#60;/label>
				&#60;/field>
				&#60;field name='partner_netto' type='java.lang.Integer' order='6'
				visible='true'>
				&#60;label context='HUN'>Partner ár (nettó HUF)&#60;/label>
				&#60;/field>
				&#60;field name='fogyaszto_netto' type='java.lang.Integer' order='7'
				visible='true'>
				&#60;label context='HUN'>Fogyasztói ár (nettó HUF)&#60;/label>
				&#60;/field>
				&#60;field name='fogyaszto_brutto' type='java.lang.Integer' order='8'
				visible='true'>
				&#60;label context='HUN'>Fogyasztói ár (bruttó HUF)&#60;/label>
				&#60;/field>
				&#60;field name='validfrom' type='java.util.Date'
				view='com.infokristaly.views.fields.SimpleDateFormatView' order='9'
				visible='true'>
				&#60;label context='HUN'>Érvényes (-tól)&#60;/label>
				&#60;/field>
				&#60;/fieldlist>
				&#60;references>&#60;container source='./kategoria' fieldname='id'/>&#60;/references>
				&#60;/table>&#60;/containerdescriptor></field>
		</rec>
		<rec>
			<field name="id">3</field>
			<field name="name">arlistaszerk</field>
			<field name="descriptor">&#60;?xml version='1.0'
				encoding='utf-8'?>&#60;containerdescriptor
				classname='com.infokristaly.engine.containers.MemoryContainer'>
				&#60;attribute name='autoopen'>true&#60;/attribute>&#60;table>
				&#60;fieldlist>
				&#60;field name='kategoria_id' type='java.lang.Integer' order='1'
				primarykey='true' visible='true'>
				&#60;label context='HUN'>Kategória azonosító&#60;/label>
				&#60;/field>
				&#60;field name='kod1' type='java.lang.String' order='2' visible='true'>
				&#60;label context='HUN'>Termékcsoport kód&#60;/label>
				&#60;/field>
				&#60;field name='kod2' type='java.lang.String' order='3' primarykey='true'
				visible='true'>
				&#60;label context='HUN'>Termék azonosító&#60;/label>
				&#60;/field>
				&#60;field name='megnevezes' type='java.lang.String' order='4' visible='true'
				editortype='textarea' editoroptions='rows=10 cols=80'>
				&#60;label context='HUN'>Megnevezés&#60;/label>
				&#60;/field>
				&#60;field name='allapot' type='java.lang.String' order='5' visible='true'>
				&#60;label context='HUN'>Állapot&#60;/label>
				&#60;/field>
				&#60;field name='partner_netto' type='java.lang.Integer' order='6'
				visible='true'>
				&#60;label context='HUN'>Partner ár (nettó HUF)&#60;/label>
				&#60;/field>
				&#60;field name='fogyaszto_netto' type='java.lang.Integer' order='7'
				visible='true'>
				&#60;label context='HUN'>Fogyasztói ár (nettó HUF)&#60;/label>
				&#60;/field>
				&#60;field name='fogyaszto_brutto' type='java.lang.Integer' order='8'
				visible='true'>
				&#60;label context='HUN'>Fogyasztói ár (bruttó HUF)&#60;/label>
				&#60;/field>
				&#60;field name='validfrom' type='java.lang.String' order='9' visible='true'>
				&#60;label context='HUN'>Érvényes (-tól)&#60;/label>
				&#60;/field>
				&#60;/fieldlist>
				&#60;/table>&#60;/containerdescriptor></field>
		</rec>
		<rec>
			<field name="id">4</field>
			<field name="name">kategoria</field>
			<field name="descriptor">&#60;?xml version='1.0'
				encoding='utf-8'?>&#60;containerdescriptor
				classname='com.infokristaly.engine.containers.SQLContainer'>
				&#60;attribute name='pagesize'>10&#60;/attribute>&#60;attribute
				name='autoopen'>true&#60;/attribute>&#60;table>
				&#60;sqlstring>select id, megnevezes from
				demand_kategoria&#60;/sqlstring>
				&#60;fieldlist>
				&#60;field name='id' type='java.lang.Integer' order='1' primarykey='true'
				visible='false'>
				&#60;label context='HUN'>azonosító&#60;/label>
				&#60;/field>
				&#60;field name='megnevezes' type='java.lang.String' order='2' visible='true'>
				&#60;label context='HUN'>Kategória&#60;/label>
				&#60;/field>
				&#60;/fieldlist>
				&#60;/table>&#60;/containerdescriptor></field>
		</rec>
		<rec>
			<field name="id">5</field>
			<field name="name">level_lista</field>
			<field name="descriptor">&#60;?xml version='1.0'
				encoding='utf-8'?>&#60;containerdescriptor
				classname='com.infokristaly.engine.containers.MemoryContainer'>
				&#60;attribute name='pagesize'>10&#60;/attribute>&#60;attribute
				name='autoopen'>true&#60;/attribute>&#60;table>
				&#60;fieldlist>
				&#60;field name='messagenumber' type='java.lang.Integer' order='1'
				visible='true'>
				&#60;label context='HUN'>Azonosító&#60;/label>
				&#60;/field>
				&#60;field name='date' type='java.util.Date' order='2' visible='true'>
				&#60;label context='HUN'>Dátum&#60;/label>
				&#60;/field>
				&#60;field name='from' type='java.lang.String' order='3' visible='true'>
				&#60;label context='HUN'>Feladó&#60;/label>
				&#60;/field>
				&#60;field name='subject' type='java.lang.String' order='4' visible='true'>
				&#60;label context='HUN'>Tárgy&#60;/label>
				&#60;/field>
				&#60;/fieldlist>
				&#60;/table>&#60;/containerdescriptor></field>
		</rec>
		<rec>
			<field name="id">6</field>
			<field name="name">dbfcontainer</field>
			<field name="descriptor">&#60;?xml version='1.0'
				encoding='utf-8'?>&#60;containerdescriptor
				classname='com.infokristaly.engine.containers.CachedReadOnlyDBFContainer'>
				&#60;table>
				&#60;fieldlist>
				&#60;field name='kulcs' type='java.lang.Integer' order='1' visible='true'>
				&#60;label context='HUN'>Kulcs&#60;/label>
				&#60;/field>
				&#60;field name='cim' type='java.lang.String' order='2' visible='true'>
				&#60;label context='HUN'>Cím&#60;/label>
				&#60;/field>
				&#60;field name='mutato' type='java.lang.Integer' order='3' visible='true'>
				&#60;label context='HUN'>Mutató&#60;/label>
				&#60;/field>
				&#60;/fieldlist>
				&#60;/table>&#60;/containerdescriptor>
			</field>
		</rec>
	</data>
</xml>

A fejlécben látszik a table.xsl, ami arra készteti a böngészőket, hogy a száraz tényeket tartalmazó xml-ből valami újat készítsen, mondjuk egy általánosan megjeleníthető HTML-t, de lehet ez akár egy CSV vagy egy Excel 2003 XLS xml. Maradjunk egy egyszerű HTML-nél. Ehhez használjuk a következő table.xsl-t:

<?xml version="1.0" encoding='utf-8'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	version="1.0">
	<xsl:output method="html" indent="yes" />
	<xsl:template match="/xml/data">
		<table width="100%">
			<thead>
				<tr>
					<td>id</td>
					<td>name</td>
					<td>description</td>
				</tr>
			</thead>
			<tbody>
				<xsl:for-each select="./rec">
					<xsl:element name="tr">
						<xsl:variable name="row" select="./field" />
						<td valign='top'>
							<xsl:value-of select="$row[@name='id']" />
						</td>
						<td valign='top'>
							<xsl:value-of select="$row[@name='name']" />
						</td>
						<td valign='top'>
							<xsl:value-of select="$row[@name='descriptor']" />
						</td>
					</xsl:element>
				</xsl:for-each>
			</tbody>
		</table>
	</xsl:template>
 
</xsl:stylesheet>

JavaScript

Böngésző oldali JavaScript-et használva az átalakításhoz rendelkezésünkre áll FireFox-ban egy XSLTProcessor nevű osztály. IE-ben ActiveXObject-ként elérhető a MSXML2.XSLTemplate kiegészítő. Ezekkel, és egy kis JQuery segítséggel (pl.: AJAX kérésekkel) egyszerűen leképezhető egy táblázat az XML-ből. Nézzünk egy példát FireFox-ra:

function doTransform() {
	var XMLpath = "XMLGenerator?xsl=jquery";
	var XSLpath = "xsl/table.xsl";
	var xslRef;
	var xmlRef;
	$.ajax({
		type : "GET",
		url : XSLpath,
		async : false,
		dataType : "xml",
		success : function(xsl) {
			xslRef = xsl;
		}
	});
 
	$.ajax({
		type : "GET",
		url : XMLpath,
		async : false,
		dataType : "xml",
		success : function(xml) {
			xmlRef = xml;
		}
	});
 
	if ((xmlRef != null) && (xslRef != null)) {
		containerDoc = document.implementation.createDocument('', "xml", null);
		$(xmlRef).find('>').each(function(index) {
			containerDoc.firstChild.appendChild(this);
		});
 
		var xsltProcessor = new XSLTProcessor();
		xsltProcessor.reset();
		xsltProcessor.importStylesheet(xslRef);
 
		var fragment = xsltProcessor.transformToFragment(containerDoc, document);
		$("#content").html(fragment);
	}
}

Az XML generátor Servlet egy JBoss 7 AS-en fut, amiben konfigurálva van a demandDS datasource. A servlet kódja:

package hu.infokristaly.homework.dynamicweb;
 
import hu.infokristaly.homework.dynamicweb.utils.StrTk;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Hashtable;
 
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
 
@WebServlet("/XMLGenerator")
public class XMLGenerator extends HttpServlet {
 
    private static final long serialVersionUID = -6851012218039126751L;
 
    public XMLGenerator() {
        super();
    }
 
    public static DataSource getDataSource() {
        DataSource result = null;
        try {
            Hashtable<String, String> env = new Hashtable<String, String>(11);
            InitialContext ctx = new InitialContext(env);
            Context envContext = (Context) ctx.lookup("java:jboss");
            result = (DataSource) envContext.lookup("datasources/demandDS");
        } catch (NamingException e) {
            e.printStackTrace();
        }
        return result;
    }
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ByteArrayOutputStream ostream = new ByteArrayOutputStream();
        OutputStreamWriter sout = new OutputStreamWriter(ostream, "utf-8");
        sout.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
        if (!"jquery".equals(request.getParameter("xsl"))) {
            sout.write("<?xml-stylesheet href='xsl/table.xsl' type='text/xsl'?>");
            sout.write("<xml>");
        } 
        sout.write("<data>");
        DataSource ds = getDataSource();
        try {
            Statement stmt = ds.getConnection().createStatement();
            if (stmt.execute("select id,name,descriptorXML from ContainerDescriptor")) {
                ResultSet rs = stmt.getResultSet();
                while (rs.next()) {
                    sout.write("<rec>");
                    sout.write("<field name=\"id\">" + rs.getLong(1) + "</field>");
                    sout.write("<field name=\"name\">" + StrTk.HTMLEnc(rs.getString(2)) + "</field>");
                    sout.write("<field name=\"descriptor\">" + StrTk.HTMLEnc(rs.getString(3)) + "</field>");
                    sout.write("</rec>");
                }
                rs.close();
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        sout.write("</data>");
        if (!"jquery".equals(request.getParameter("xsl"))) {
            sout.write("</xml>");
        }
        sout.flush();
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/xml");
        ServletOutputStream out = response.getOutputStream();
 
        ostream.writeTo(out);
        out.flush();
        out.close();
    }
 
}

Maga a test4transform.html pedig a következő:

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="js/jquery.js"></script>
        <script type="text/javascript" src="js/transform.js"></script>
        <meta charset="UTF-8">
        <title>Transform JQuery AJAX XML/XSLT</title>
    </head>
    <body>
	   <button onclick="doTransform()">Transform</button>
	   <p id="content"></p>
    </body>
</html>

Talán a jövőben lesznek olyan, általánosan elterjedt dinamikus oldalak, amik ki fogják használni ezt a technikát. Ahogy a belinkelt CSS és a JavaScript fájlok, az XSL-ek is a böngésző cache-be kerülnek, így még hatékonyabban csökkentik az adatforgalmat. Hátránya viszont van, amit főleg hibakeresésnél érzünk meg. Ugyanis a FireBug-hoz hasonló transzformáció követő még nincs a piacon, már pedig erre nagy szükség lenne. De bizakodjunk, hiszen a szabványos DOM és a JavaScript elterjedése is fokozatosan alakult ki. Talán ez a fejlesztési irány is kap akkora figyelmet, mint annak idején a szabványosított HTML4, és fokozatosan bekerül a böngészőkbe egy ilyen eszköz.