9.14. XML 函数

本节中描述的函数和类似函数的表达式对xml类型的值进行操作。有关xml类型的信息,请参见Section 8.13。用于在类型xml之间进行转换的类似函数的表达式xmlparsexmlserialize不在此处记录。

要使用大多数这些功能,需要 PostgreSQL 使用configure --with-libxml构建。

9 .14.1. 产生 XML 内容

一组函数和类函数表达式可用于从 SQL 数据生成 XML 内容。这样,它们特别适合将查询结果格式化为 XML 文档,以便在 Client 端应用程序中进行处理。

9.14.1.1. xmlcomment

xmlcomment(text)

函数xmlcomment创建一个 XML 值,其中包含带有以指定文本为内容的 XMLComments。文本不能包含“ --”或以“ -”结尾,因此生成的构造是有效的 XMLComments。如果参数为 null,则结果为 null。

Example:

SELECT xmlcomment('hello');

  xmlcomment
--------------
 <!--hello-->

9.14.1.2. xmlconcat

xmlconcat(xml[, ...])

函数xmlconcat串联单个 XML 值的列表,以创建包含 XML 内容片段的单个值。空值被省略;仅当没有非空参数时,结果才为空。

Example:

SELECT xmlconcat('<abc/>', '<bar>foo</bar>');

      xmlconcat
----------------------
 <abc/><bar>foo</bar>

XML 声明(如果存在)按以下方式组合。如果所有参数值都具有相同的 XML 版本声明,则在结果中使用该版本,否则不使用任何版本。如果所有参数值都具有独立的声明值“ yes”,则在结果中使用该值。如果所有参数值都有一个独立的声明值,并且至少有一个为“ no”,则将在结果中使用该值。否则结果将没有独立的声明。如果确定结果需要独立声明但不要求版本声明,则将使用 1.0 版的版本声明,因为 XML 要求 XML 声明包含一个版本声明。在所有情况下,都会忽略并删除编码声明。

Example:

SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone="no"?><bar/>');

             xmlconcat
-----------------------------------
 <?xml version="1.1"?><foo/><bar/>

9.14.1.3. xmlelement

xmlelement(name name [, xmlattributes(value [AS attname] [, ... ])] [, content, ...])

xmlelement表达式产生具有给定名称,属性和内容的 XML 元素。

Examples:

SELECT xmlelement(name foo);

 xmlelement
------------
 <foo/>

SELECT xmlelement(name foo, xmlattributes('xyz' as bar));

    xmlelement
------------------
 <foo bar="xyz"/>

SELECT xmlelement(name foo, xmlattributes(current_date as bar), 'cont', 'ent');

             xmlelement
-------------------------------------
 <foo bar="2007-01-26">content</foo>

无效的 XML 名称的元素和属性名称通过使用序列_xHHHH_替换有问题的字符来进行转义,其中* HHHH *是字符的 Unicode 代码点(以十六进制表示)。例如:

SELECT xmlelement(name "foo$bar", xmlattributes('xyz' as "a&b"));

            xmlelement
----------------------------------
 <foo_x0024_bar a_x0026_b="xyz"/>

如果属性值是列引用,则无需指定显式属性名称,在这种情况下,默认情况下,列名称将用作属性名称。在其他情况下,必须为属性指定一个明确的名称。所以这个例子是有效的:

CREATE TABLE test (a xml, b xml);
SELECT xmlelement(name test, xmlattributes(a, b)) FROM test;

但是这些不是:

SELECT xmlelement(name test, xmlattributes('constant'), a, b) FROM test;
SELECT xmlelement(name test, xmlattributes(func(a, b))) FROM test;

元素内容(如果指定)将根据其数据类型进行格式化。如果内容本身是xml类型,则可以构造复杂的 XML 文档。例如:

SELECT xmlelement(name foo, xmlattributes('xyz' as bar),
                            xmlelement(name abc),
                            xmlcomment('test'),
                            xmlelement(name xyz));

                  xmlelement
----------------------------------------------
 <foo bar="xyz"><abc/><!--test--><xyz/></foo>

其他类型的内容将被格式化为有效的 XML 字符数据。特别是这意味着字符\ <, >和&将转换为实体。二进制数据(数据类型bytea)将以 base64 或十六进制编码表示,具体取决于配置参数xmlbinary的设置。如Section D.3.1.3所讨论,为了使 PostgreSQLMap 与 SQL:2006 及更高版本中指定的 Map 对齐,预期会改变单个数据类型的特定行为。

9.14.1.4. xmlforest

xmlforest(content [AS name] [, ...])

xmlforest表达式使用给定的名称和内容生成元素的 XML 林(序列)。

Examples:

SELECT xmlforest('abc' AS foo, 123 AS bar);

          xmlforest
------------------------------
 <foo>abc</foo><bar>123</bar>

SELECT xmlforest(table_name, column_name)
FROM information_schema.columns
WHERE table_schema = 'pg_catalog';

                                         xmlforest
-------------------------------------------------------------------------------------------
 <table_name>pg_authid</table_name><column_name>rolname</column_name>
 <table_name>pg_authid</table_name><column_name>rolsuper</column_name>
 ...

如第二个示例所示,如果内容值是列引用,则可以省略元素名称,在这种情况下,默认情况下使用列名称。否则,必须指定一个名称。

如上面的xmlelement所示,将无效的 XML 名称的元素名称转义。类似地,内容数据将转义以生成有效的 XML 内容,除非它已经是xml类型。

请注意,如果 XML 林包含多个元素,则它们不是有效的 XML 文档,因此将xmlforest表达式包装在xmlelement中可能很有用。

9.14.1.5. xmlpi

xmlpi(name target [, content])

xmlpi表达式创建 XML 处理指令。内容(如果存在)不得包含字符序列?>

Example:

SELECT xmlpi(name php, 'echo "hello world";');

            xmlpi
-----------------------------
 <?php echo "hello world";?>

9.14.1.6. xmlroot

xmlroot(xml, version text | no value [, standalone yes|no|no value])

xmlroot表达式更改 XML 值的根节点的属性。如果指定了版本,它将替换根节点的版本声明中的值。如果指定了独立设置,它将替换根节点的独立声明中的值。

SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
               version '1.0', standalone yes);

                xmlroot
----------------------------------------
 <?xml version="1.0" standalone="yes"?>
 <content>abc</content>

9.14.1.7. xmlagg

xmlagg(xml)

与此处描述的其他函数不同,函数xmlagg是集合函数。它像xmlconcat一样将 Importing 值连接到聚合函数调用,不同之处在于,连接是在行之间而不是在单个行中的表达式之间进行。有关聚合函数的其他信息,请参见Section 9.20

Example:

CREATE TABLE test (y int, x xml);
INSERT INTO test VALUES (1, '<foo>abc</foo>');
INSERT INTO test VALUES (2, '<bar/>');
SELECT xmlagg(x) FROM test;
        xmlagg
----------------------
 <foo>abc</foo><bar/>

为了确定连接的 Sequences,可以将ORDER BY子句添加到集合调用中,如Section 4.2.7中所述。例如:

SELECT xmlagg(x ORDER BY y DESC) FROM test;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

在以前的版本中曾经建议使用以下非标准方法,在某些情况下可能仍然有用:

SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

9 .14.2. XML 谓词

本节中描述的表达式检查xml值的属性。

9 .14.2.1. 是文件

xml IS DOCUMENT

如果参数 XML 值是正确的 XML 文档,则表达式IS DOCUMENT返回 true,否则返回 false(即,它是内容片段),如果参数为 null,则返回 NULL。有关文档和内容片段之间的区别,请参见Section 8.13

9 .14.2.2. 不是文件

xml IS NOT DOCUMENT

如果参数 XML 值是正确的 XML 文档,则表达式IS NOT DOCUMENT返回 false;否则返回 true(即它是内容片段);如果参数为 null,则返回 NULL。

9.14.2.3. XMLEXISTS

XMLEXISTS(text PASSING [BY REF] xml [BY REF])

函数xmlexists评估 XPath 1.0 表达式(第一个参数),并将传递的 XML 值作为其上下文项。如果该评估的结果产生一个空节点集,则该函数返回 false;如果产生任何其他值,则返回 true。如果任何参数为 null,则该函数返回 null。作为上下文项传递的非空值必须是 XML 文档,而不是内容片段或任何非 XML 值。

Example:

SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Toronto</town><town>Ottawa</town></towns>');

 xmlexists
------------
 t
(1 row)

BY REF子句在 PostgreSQL 中被接受,但被忽略,如Section D.3.2中所述。在 SQL 标准中,xmlexists函数以 XML Query 语言评估表达式,但是 PostgreSQL 仅允许 XPath 1.0 表达式,如Section D.3.1所述。

9.14.2.4. xml_is_well_formed

xml_is_well_formed(text)
xml_is_well_formed_document(text)
xml_is_well_formed_content(text)

这些函数检查text字符串是否为格式正确的 XML,并返回布尔结果。 xml_is_well_formed_document检查格式正确的文档,而xml_is_well_formed_content检查格式正确的内容。如果将xmloption配置参数设置为DOCUMENT,则xml_is_well_formed执行前者;如果将其设置为CONTENT,则进行后者。这意味着xml_is_well_formed可用于查看对类型xml的简单强制转换是否将成功,而其他两个功能对于查看XMLPARSE的相应变体是否将成功将非常有用。

Examples:

SET xmloption TO DOCUMENT;
SELECT xml_is_well_formed('<>');
 xml_is_well_formed 
--------------------
 f
(1 row)

SELECT xml_is_well_formed('<abc/>');
 xml_is_well_formed 
--------------------
 t
(1 row)

SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc');
 xml_is_well_formed 
--------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
 xml_is_well_formed_document 
-----------------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
 xml_is_well_formed_document 
-----------------------------
 f
(1 row)

最后一个示例显示检查包括名称空间是否正确匹配。

9 .14.3. 处理 XML

为了处理数据类型xml的值,PostgreSQL 提供了xpathxpath_exists函数(用于评估 XPath 1.0 表达式)和XMLTABLE表函数。

9.14.3.1. xpath

xpath(xpath, xml [, nsarray])

函数xpath根据 XML 值* xml 评估 XPath 1.0 表达式 xpath *(一个text值)。它返回与 XPath 表达式生成的节点集相对应的 XML 值数组。如果 XPath 表达式返回标量值而不是节点集,则返回单元素数组。

第二个参数必须是格式正确的 XML 文档。特别是,它必须具有单个根节点元素。

该函数的第三个可选参数是名称空间 Map 的数组。此数组应为二维text数组,第二条轴的长度等于 2(即,它应为数组的数组,每个数组都由 2 个元素组成)。每个数组条目的第一个元素是名称空间名称(别名),第二个元素是名称空间 URI。不需要此数组中提供的别名与 XML 文档本身中使用的别名相同(换句话说,在 XML 文档和xpath函数上下文中,别名都是* local *)。

Example:

SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
             ARRAY[ARRAY['my', 'http://example.com']]);

 xpath  
--------
 {test}
(1 row)

要处理默认(匿名)名称空间,请执行以下操作:

SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
             ARRAY[ARRAY['mydefns', 'http://example.com']]);

 xpath
--------
 {test}
(1 row)

9.14.3.2. xpath_exists

xpath_exists(xpath, xml [, nsarray])

函数xpath_existsxpath函数的一种特殊形式。该函数返回一个布尔值,该布尔值指示查询是否得到满足(具体来说,查询是否产生除空节点集以外的任何值),而不是返回满足 XPath 1.0 表达式的单个 XML 值。此函数等效于XMLEXISTS谓词,除了它还支持名称空间 Map 参数。

Example:

SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                     ARRAY[ARRAY['my', 'http://example.com']]);

 xpath_exists  
--------------
 t
(1 row)

9.14.3.3. xmltable

xmltable( [XMLNAMESPACES(namespace uri AS namespace name[, ...]), ]
          row_expression PASSING [BY REF] document_expression [BY REF]
          COLUMNS name { type [PATH column_expression] [DEFAULT default_expression] [NOT NULL | NULL]
                        | FOR ORDINALITY }
                   [, ...]
)

xmltable函数根据给定的 XML 值,用于提取行的 XPath 过滤器以及一组列定义生成一个表。

可选的XMLNAMESPACES子句是以逗号分隔的名称空间列表。它指定了文档中使用的 XML 名称空间及其别名。当前不支持默认名称空间规范。

必需的* row_expression 参数是一个 XPath 1.0 表达式,该表达式经过评估,将 document_expression 作为其上下文项,以获取一组 XML 节点。这些节点是xmltable转换为输出行的对象。如果 document_expression 为空,或者 row_expression *不会产生空节点集或除节点集以外的任何值,则不会产生任何行。

  • document_expression 提供 row_expression *的上下文项。它必须是格式正确的 XML 文档;片段/森林不被接受。 BY REF子句被接受但被忽略,如Section D.3.2中所述。在 SQL 标准中,xmltable函数以 XML Query 语言评估表达式,但是 PostgreSQL 仅允许 XPath 1.0 表达式,如Section D.3.1所述。

强制性COLUMNS子句指定输出表中的列列表。每个条目描述一栏。有关格式,请参见上面的语法摘要。列名和类型是必需的; path,default 和 nullability 子句是可选的。

row_expression *的结果节点集中检索到的节点 Sequences 将以行号(从 1 开始)填充标记为FOR ORDINALITY的列。最多可以将一列标记为FOR ORDINALITY

Note

XPath 1.0 没有为节点集中的节点指定 Sequences,因此依赖于结果的特定 Sequences 的代码将取决于实现。详细信息可以在Section D.3.1.2中找到。

列的column_expression1 是针对每个行的 XPath 1.0 表达式,以row_expression结果的当前节点作为上下文项,以查找该列的值。如果没有给出* column_expression *,那么列名将用作隐式路径。

如果列的 XPath 表达式返回非 XML 值(在 XPath 1.0 中限制为字符串,布尔值或双精度),并且该列具有xml以外的 PostgreSQL 类型,则该列的设置方式就像是通过将值的字符串表示形式分配给 PostgreSQL 类型。在此发行版中,必须将 XPath 布尔值或 double 结果显式转换为字符串(即,环绕原始列表达式的 XPath 1.0 string函数); PostgreSQL 然后可以成功地将字符串分配给 Boolean 或双精度型的 SQL 结果列。这些转换规则与Section D.3.1.3中讨论的 SQL 标准的转换规则不同。

在此版本中,xml类型的 SQL 结果列或评估为 XML 类型的 XPath 列表达式,无论输出列 SQL 类型如何,均按照Section D.3.2中的描述进行处理。在 PostgreSQL 12 中,行为发生了重大变化。

如果路径表达式返回给定行的空节点集(通常,当它不匹配时),则除非指定* default_expression *,否则该列将被设置为NULL;否则,列将被设置为NULL。然后使用通过评估该表达式得出的值。

列可能标记为NOT NULL。如果NOT NULL列的* column_expression 不匹配任何内容,并且没有DEFAULT default_expression *也计算为 null,则报告错误。

每次需要该列的默认值时,都会评估一个* default_expression *,而不是在调用xmltable时立即对其进行评估。如果表达式符合稳定或不变的条件,则可以跳过重复评估。这意味着您可以在default_expression *中有用地使用nextval之类的易失函数。

Examples:

CREATE TABLE xmldata AS SELECT
xml $$
<ROWS>
  <ROW id="1">
    <COUNTRY_ID>AU</COUNTRY_ID>
    <COUNTRY_NAME>Australia</COUNTRY_NAME>
  </ROW>
  <ROW id="5">
    <COUNTRY_ID>JP</COUNTRY_ID>
    <COUNTRY_NAME>Japan</COUNTRY_NAME>
    <PREMIER_NAME>Shinzo Abe</PREMIER_NAME>
    <SIZE unit="sq_mi">145935</SIZE>
  </ROW>
  <ROW id="6">
    <COUNTRY_ID>SG</COUNTRY_ID>
    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
    <SIZE unit="sq_km">697</SIZE>
  </ROW>
</ROWS>
$$ AS data;

SELECT xmltable.*
  FROM xmldata,
       XMLTABLE('//ROWS/ROW'
                PASSING data
                COLUMNS id int PATH '@id',
                        ordinality FOR ORDINALITY,
                        "COUNTRY_NAME" text,
                        country_id text PATH 'COUNTRY_ID',
                        size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
                        size_other text PATH
                             'concat(SIZE[@unit!="sq_km"], " ", SIZE[@unit!="sq_km"]/@unit)',
                        premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified') ;

 id | ordinality | COUNTRY_NAME | country_id | size_sq_km |  size_other  | premier_name  
----+------------+--------------+------------+------------+--------------+---------------
  1 |          1 | Australia    | AU         |            |              | not specified
  5 |          2 | Japan        | JP         |            | 145935 sq_mi | Shinzo Abe
  6 |          3 | Singapore    | SG         |        697 |              | not specified

以下示例显示了多个 text()节点的串联,将列名用作 XPath 过滤器以及对空格,XMLComments 和处理指令的处理:

CREATE TABLE xmlelements AS SELECT
xml $$
  <root>
   <element>  Hello<!-- xyxxz -->2a2<?aaaaa?> <!--x-->  bbb<x>xxx</x>CC  </element>
  </root>
$$ AS data;

SELECT xmltable.*
  FROM xmlelements, XMLTABLE('/root' PASSING data COLUMNS element text);
       element        
----------------------
   Hello2a2   bbbCC

下面的示例说明了如何使用XMLNAMESPACES子句来指定 XML 文档以及 XPath 表达式中使用的名称空间列表:

WITH xmldata(data) AS (VALUES ('
<example xmlns="http://example.com/myns" xmlns:B="http://example.com/b">
 <item foo="1" B:bar="2"/>
 <item foo="3" B:bar="4"/>
 <item foo="4" B:bar="5"/>
</example>'::xml)
)
SELECT xmltable.*
  FROM XMLTABLE(XMLNAMESPACES('http://example.com/myns' AS x,
                              'http://example.com/b' AS "B"),
             '/x:example/x:item'
                PASSING (SELECT data FROM xmldata)
                COLUMNS foo int PATH '@foo',
                  bar int PATH '@B:bar');
 foo | bar
-----+-----
   1 |   2
   3 |   4
   4 |   5
(3 rows)

9 .14.4. 将表 Map 到 XML

以下函数将关系表的内容 Map 到 XML 值。可以将它们视为 XML 导出功能:

table_to_xml(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xml(query text, nulls boolean, tableforest boolean, targetns text)
cursor_to_xml(cursor refcursor, count int, nulls boolean,
              tableforest boolean, targetns text)

每个函数的返回类型为xml

table_to_xmlMap 作为参数* tbl 传递的命名表的内容。 regclass类型接受使用通常的符号标识表的字符串,包括可选的模式限定符和双引号。 query_to_xml执行查询,其文本作为参数 query 传递并 Map 结果集。 cursor_to_xml从参数 cursor *指定的游标中获取指示的行数。如果必须 Map 大表,则建议使用此变体,因为每个函数的结果值都在内存中构建。

如果* tableforest *为 false,则生成的 XML 文档如下所示:

<tablename>
  <row>
    <columnname1>data</columnname1>
    <columnname2>data</columnname2>
  </row>

  <row>
    ...
  </row>

  ...
</tablename>

如果* tableforest *为 true,则结果是一个 XML 内容片段,如下所示:

<tablename>
  <columnname1>data</columnname1>
  <columnname2>data</columnname2>
</tablename>

<tablename>
  ...
</tablename>

...

如果没有可用的表名,也就是说,在 Map 查询或游标时,字符串table以第一种格式使用,row以第二种格式使用。

这些格式之间的选择取决于用户。第一种格式是正确的 XML 文档,这在许多应用程序中将非常重要。如果以后要将结果值重新组合到一个文档中,则第二种格式在cursor_to_xml函数中会更有用。上面讨论的用于生成 XML 内容的功能(尤其是xmlelement)可用于更改结果以使其具有品味。

数据值的 Map 方式与上面针对函数xmlelement所述的方式相同。

参数* nulls *确定是否应在输出中包含空值。如果为 true,则列中的空值表示为:

<columnname xsi:nil="true"/>

其中xsi是 XML 模式实例的 XML 名称空间前缀。适当的名称空间声明将添加到结果值中。如果为 false,则将从输出中省略包含空值的列。

参数* targetns *指定结果的所需 XML 名称空间。如果不需要特定的名称空间,则应传递一个空字符串。

以下函数返回描述上述相应函数执行的 Map 的 XML Schema 文档:

table_to_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xmlschema(query text, nulls boolean, tableforest boolean, targetns text)
cursor_to_xmlschema(cursor refcursor, nulls boolean, tableforest boolean, targetns text)

必须传递相同的参数,以获取匹配的 XML 数据 Map 和 XML Schema 文档。

以下功能在链接在一起的一个文档(或林)中生成 XML 数据 Map 和相应的 XML Schema。当需要自成体系和自我描述的结果时,它们将非常有用:

table_to_xml_and_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xml_and_xmlschema(query text, nulls boolean, tableforest boolean, targetns text)

此外,以下功能可用于生成整个模式或整个当前数据库的类似 Map:

schema_to_xml(schema name, nulls boolean, tableforest boolean, targetns text)
schema_to_xmlschema(schema name, nulls boolean, tableforest boolean, targetns text)
schema_to_xml_and_xmlschema(schema name, nulls boolean, tableforest boolean, targetns text)

database_to_xml(nulls boolean, tableforest boolean, targetns text)
database_to_xmlschema(nulls boolean, tableforest boolean, targetns text)
database_to_xml_and_xmlschema(nulls boolean, tableforest boolean, targetns text)

请注意,这些可能会产生大量数据,需要在内存中构建这些数据。当请求大型模式或数据库的内容 Map 时,可能值得考虑单独 Map 表,甚至可能通过游标来 Map。

模式内容 Map 的结果如下所示:

<schemaname>

table1-mapping

table2-mapping

...

</schemaname>

表 Map 的格式取决于* tableforest *参数,如上所述。

数据库内容 Map 的结果如下所示:

<dbname>

<schema1name>
  ...
</schema1name>

<schema2name>
  ...
</schema2name>

...

</dbname>

模式 Map 如上。

作为使用这些函数产生的输出的示例,Example 9.1显示了一个 XSLT 样式表,该样式表将table_to_xml_and_xmlschema的输出转换为包含表数据表格格式表示的 HTML 文档。以类似的方式,这些函数的结果可以转换为其他基于 XML 的格式。

实施例 9.1. XSLT 样式表,用于将 SQL/XML 输出转换为 HTML

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.w3.org/1999/xhtml"
>

  <xsl:output method="xml"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
      doctype-public="-//W3C/DTD XHTML 1.0 Strict//EN"
      indent="yes"/>

  <xsl:template match="/*">
    <xsl:variable name="schema" select="//xsd:schema"/>
    <xsl:variable name="tabletypename"
                  select="$schema/xsd:element[@name=name(current())]/@type"/>
    <xsl:variable name="rowtypename"
                  select="$schema/xsd:complexType[@name=$tabletypename]/xsd:sequence/xsd:element[@name='row']/@type"/>

    <html>
      <head>
        <title><xsl:value-of select="name(current())"/></title>
      </head>
      <body>
        <table>
          <tr>
            <xsl:for-each select="$schema/xsd:complexType[@name=$rowtypename]/xsd:sequence/xsd:element/@name">
              <th><xsl:value-of select="."/></th>
            </xsl:for-each>
          </tr>

          <xsl:for-each select="row">
            <tr>
              <xsl:for-each select="*">
                <td><xsl:value-of select="."/></td>
              </xsl:for-each>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

</xsl:stylesheet>