9.14. Fonctions XML

Les fonctions et expressions décrites dans cette section opèrent sur des valeurs de type xml. Lire la Section 8.13, « Type XML » pour des informations sur le type xml. Les expressions xmlparse et xmlserialize permettant de convertir vers ou à partir du type xml ne sont pas reprises ici. L'utilisation d'un grand nombre de ces fonctions nécessite que l'installation soit construite avec configure --with-libxml.

9.14.1. Produire un contenu XML

Un ensemble de fonctions et expressions de type fonction est disponible pour produire du contenu XML à partir de données SQL. En tant que telles, elles conviennent particulièrement bien pour formater les résultats de requêtes en XML à traiter dans les applications clientes.

9.14.1.1. xmlcomment

xmlcomment(text)
          

La fonction xmlcomment crée une valeur XML contenant un commentaire XML avec, comme contenu, le texte indiqué. Le texte ne peut pas contenir « -- » ou se terminer par un « - » de sorte que la construction résultante représente un commentaire XML valide. Si l'argument est NULL, le résultat est NULL.

Exemple :

SELECT xmlcomment('bonjour');

  xmlcomment
--------------
 <!--bonjour-->
           

9.14.1.2. xmlconcat

 xmlconcat(xml[, ...])
         

La fonction xmlconcat concatène une liste de valeurs XML individuelles pour créer une valeur simple contenant un fragment de contenu XML. Les valeurs NULL sont omises ; le résultat est NULL seulement s'il n'y a pas d'arguments non NULL.

Exemple :

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

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

Les déclarations XML, si elles sont présentes, sont combinées comme suit. Si toutes les valeurs en argument ont la même déclaration de version XML, cette version est utilisée dans le résultat. Sinon aucune version n'est utilisée. Si toutes les valeurs en argument ont la valeur de déclaration « standalone » à « yes », alors cette valeur est utilisée dans le résultat. Si toutes les valeurs en argument ont une valeur de déclaration « standalone » et qu'au moins l'une d'entre elles est « no », alors cette valeur est utilisée dans le résultat. Sinon le résultat n'a aucune déclaration « standalone ». Si le résultat nécessite une déclaration « standalone » sans déclaration de version, une déclaration de version 1.0 est utilisée car le standard XML impose qu'une déclaration XML contienne une déclaration de version. Les déclarations d'encodage sont ignorées et supprimées dans tous les cas.

Exemple :

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 nom [, xmlattributes(valeur [AS nom_attribut] [, ... ])] [, contenu, ...])
          

L'expression xmlelement produit un élément XML avec le nom, les attributs et le contenu donnés.

Exemples :

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>

Les noms d'élément et d'attribut qui ne sont pas des noms XML valides sont modifiés en remplaçant les caractères indésirables par une séquence _xHHHH_, où HHHH est le codage Unicode du caractère en notation hexadécimal. Par exemple :

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

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

Un nom explicite d'attribut n'a pas besoin d'être indiqué si la valeur de l'attribut est la référence d'une colonne, auquel cas le nom de la colonne est utilisé comme nom de l'attribut par défaut. Dans tous les autres cas, l'attribut doit avoir un nom explicite. Donc, cet exemple est valide :

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

Mais ceux-ci ne le sont pas :

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

Si le contenu de l'élément est précisé, il est formaté en fonction du type de données. Si le contenu est lui-même de type xml, des documents XML complexes peuvent être construits. Par exemple :

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

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

Le contenu des autres types est formaté avec des données XML valides. Cela signifie en particulier que les caractères <, >, et & sont convertis en entités. Les données binaires (type bytea) sont représentées dans un encodage base64 ou hexadécimal, suivant la configuration du paramètre xmlbinary. Le comportement particulier pour les types de données individuels devrait évoluer pour aligner les types de données SQL et PostgreSQL avec la spécification de XML Schema, auquel cas une description plus précise sera ajoutée.

9.14.1.4. xmlforest

 xmlforest(contenu [AS nom] [, ...])
        

L'expression xmlforest produit un arbre XML (autrement dit une séquence) d'éléments utilisant les noms et le contenu donnés.

Exemples :

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>
 ...

Comme indiqué dans le second exemple, le nom de l'élément peut être omis si la valeur du contenu est une référence de colonne, auquel cas le nom de la colonne est utilisé par défaut. Sinon, un nom doit être indiqué.

Les noms d'éléments qui ne sont pas des noms XML valides sont échappés comme indiqué pour xmlelement ci-dessus. De façon similaire, les données de contenu sont échappées pour rendre le contenu XML valide sauf s'il est déjà de type xml.

Les arbres XML ne sont pas des documents XML valides s'ils sont constitués de plus d'un élément. Il peut donc s'avérer utile d'emballer les expressions xmlforest dans xmlelement.

9.14.1.5. xmlpi

 xmlpi(name target [, content])
         

L'expression xmlpi crée une instruction de traitement XML. Le contenu, si présent, ne doit pas contenir la séquence de caractères ?>.

Exemple :

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])
          

L'expression xmlroot modifie les propriétés du nœud racine d'une valeur XML. Si une version est indiquée, elle remplace la valeur dans la déclaration de version du nœud racine. Si un paramètre « standalone » est spécifié, il remplace la valeur dans la déclaration « standalone » du nœud racine.

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)
           

La fonction xmlagg est, à la différence des fonctions décrites ici, une fonction d'aggrégat. Elle concatène les valeurs en entrée pour les passer en argument à la fonction d'aggrégat, comme le fait la fonction xmlconcat, sauf que la concaténation survient entre les lignes plutôt qu'entre les expressions d'une même ligne. Voir Section 9.20, « Fonctions d'agrégat » pour plus d'informations sur les fonctions d'agrégat.

Exemple :

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/>

Pour déterminer l'ordre de la concaténation, une clause ORDER BY peut être ajoutée à l'appel de l'agrégat comme décrit dans Section 4.2.7, « Expressions d'agrégat ». Par exemple :

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

L'approche non standard suivante était recommendée dans les versions précédentes et peut toujours être utiles dans certains cas particuliers :

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

9.14.2. Prédicats XML

Les expressions décrites dans cette section vérifient les propriétés de valeurs du type xml.

9.14.2.1. IS DOCUMENT

xml IS DOCUMENT
            

L'expression IS DOCUMENT renvoie true si la valeur de l'argument XML est un document XML correct, false dans le cas contraire (c'est-à-dire qu'il s'agit d'un fragment de document) ou NULL si l'argument est NULL. Voir la Section 8.13, « Type XML » pour les différences entre documents et fragments de contenu.

9.14.2.2. XMLEXISTS

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

La fonction xmlexists renvoie true si l'expression XPath dans le premier argument renvoie des nœuds. Elle renvoie faux sinon. (Si un des arguments est NULL, le résultat est NULL.)

Exemple :

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

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

Les clauses BY REF n'ont pas d'effet dans PostgreSQL mais sont autorisées pour se conformer au standard SQL et pour la compatibilité avec les autres implémentations. D'après le standard SQL, le premier BY REF est requis, le second est optionel. De plus, notez que le standard SQL spécifie que la construction xmlexists prend une expression XQuery en premier argument mais PostgreSQL supporte actuellement seulement XPath, qui est un sous-ensemble de XQuery.

9.14.2.3. xml_is_well_formed

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

Ces fonctions vérifient si la chaîne text est du XML bien formé et renvoient un résultat booléen. xml_is_well_formed_document vérifie si le document est bien formé alors que xml_is_well_formed_content vérifie si le contenu est bien formé. xml_is_well_formed est équivalent à xml_is_well_formed_document si le paramètre de configuration xmloption vaut DOCUMENT et est équivalent à xml_is_well_formed_content si le paramètre vaut CONTENT. Cela signifie que xml_is_well_formed est utile pour savoir si une conversion au type xml va réussir alors que les deux autres sont utiles pour savoir si les variantes correspondantes de XMLPARSE vont réussir.

Exemples :

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)

Le dernier exemple monte que les vérifications incluent les correspondances d'espace de noms.

9.14.3. Traiter du XML

Pour traiter les valeurs du type xml, PostgreSQL fournit les fonctions xpath et xpath_exists, qui évaluent les expressions XPath 1.0.

xpath(xpath, xml [, nsarray])
              

La fonction xpath évalue l'expression XPath xpath (une valeur de type text) avec la valeur XML xml. Elle renvoie un tableau de valeurs XML correspondant à l'ensemble de nœuds produit par une expression XPath. Si l'expression XPath renvoit une valeur scalaire à la place d'un ensemble de nœuds, un tableau à un seul élément est renvoyé.

Le second argument doit être un document XML bien formé. En particulier, il doit avoir un seul élément de nœud racine.

Le troisième argument (optionnel) de la fonction est un tableau de correspondances de namespace. Ce tableau text doit avoir deux dimensions dont la seconde a une longueur 2 (en fait, c'est un tableau de tableaux à exactement deux éléments). Le premier élément de chaque entrée du tableau est le nom du namespace (alias), le second étant l'URI du namespace. Il n'est pas requis que les alias fournis dans ce tableau soient les mêmes que ceux utilisés dans le document XML (autrement dit, que ce soit dans le contexte du document XML ou dans celui de la fonction xpath, les alias ont une vue locale).

Exemple :

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)

Pour gérer des namespaces par défaut (anonymes), faites ainsi :

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

 xpath
--------
 {test}
(1 row)
xpath_exists(xpath, xml [, nsarray])
               

La fonction xpath_exists est une forme spécialisée de la fonction xpath. Au lieu de renvoyer les valeurs XML individuelles qui satisfont XPath, cette fonction renvoie un booléen indiquant si la requête a été satisfaite ou non. Cette fonction est équivalent au prédicat standard XMLEXISTS, sauf qu'il fonctionne aussi avec un argument de correspondance d'espace de nom.

Exemple :

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.4. Transformer les tables en XML

Les fonctions suivantes transforment le contenu de tables relationnelles en valeurs XML. Il s'agit en quelque sorte d'un export 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)

Le type en retour de ces fonctions est xml.

table_to_xml transforme le contenu de la table passée en argument (paramètre tbl). regclass accepte des chaînes identifiant les tables en utilisant la notation habituelle, incluant les qualifications possibles du schéma et les guillemets doubles. query_to_xml exécute la requête dont le texte est passé par le paramètre query et transforme le résultat. cursor_to_xml récupère le nombre indiqué de lignes à partir du curseur indiqué par le paramètre cursor. Cette variante est recommandée si la transformation se fait sur de grosses tables car la valeur en résultat est construite en mémoire pour chaque fonction.

Si tableforest vaut false, alors le document XML résultant ressemble à ceci :

<tablename>
  <row>
    <columnname1>donnees</columnname1>
    <columnname2>donnees</columnname2>
  </row>

  <row>
    ...
  </row>

  ...
</tablename>

Si tableforest vaut true, le résultat est un fragment XML qui ressemble à ceci :

<tablename>
  <columnname1>donnees</columnname1>
  <columnname2>donnees</columnname2>
</tablename>

<tablename>
  ...
</tablename>

...

Si aucune table n'est disponible, c'est-à-dire lors d'une transformation à partir d'une requête ou d'un curseur, la chaîne table est utilisée dans le premier format, et la chaîne row dans le second.

Le choix entre ces formats dépend de l'utilisateur. Le premier format est un document XML correct, ce qui est important dans beaucoup d'applications. Le second format tend à être plus utile dans la fonction cursor_to_xml si les valeurs du résultat sont à rassembler plus tard dans un document. Les fonctions pour produire du contenu XML discutées ci-dessus, en particulier xmlelement, peuvent être utilisées pour modifier les résultats.

Les valeurs des données sont transformées de la même façon que ce qui est décrit ci-dessus pour la fonction xmlelement.

Le paramètre nulls détermine si les valeurs NULL doivent être incluses en sortie. À true, les valeurs NULL dans les colonnes sont représentées ainsi :

<columnname xsi:nil="true"/>

xsi est le préfixe de l'espace de noms XML pour l'instance XML Schema. Une déclaration appropriée d'un espace de noms est ajoutée à la valeur du résultat. À false, les colonnes contenant des valeurs NULL sont simplement omises de la sortie.

Le paramètre targetns indique l'espace de noms souhaité pour le résultat. Si aucun espace de nom particulier n'est demandé, une chaîne vide doit être passée.

Les fonctions suivantes renvoient des documents XML Schema décrivant la transformation réalisée par les fonctions ci-dessus.

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)
                

Il est essentiel que les mêmes paramètres soient passés pour obtenir les bonnes transformations de données XML et des documents XML Schema.

Les fonctions suivantes réalisent la transformation des données XML et du XML Schema correspondant en un seul document (ou arbre), liés ensemble. Elles sont utiles lorsque les résultats doivent être auto-contenus et auto-descriptifs.

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)
                

De plus, les fonctions suivantes sont disponibles pour produire des transformations analogues de schémas complets ou de bases de données complètes.

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)
                

Elles peuvent produire beaucoup de données, qui sont construites en mémoire. Lors de transformations de gros schémas ou de grosses bases, il peut être utile de considérer la transformation séparée des tables, parfois même via un curseur.

Le résultat de la transformation du contenu d'un schéma ressemble à ceci :

<nomschema>

transformation-table1

transformation-table2

...

</nomschema>

où le format de transformation d'une table dépend du paramètre tableforest comme expliqué ci-dessus.

Le résultat de la transformation du contenu d'une base ressemble à ceci :

<nombase>

<nomschema1>
  ...
</nomschema1>

<nomschema2>
  ...
</nomschema2>

...

</nombase>

avec une transformation du schéma identique à celle indiquée ci-dessus.

En exemple de l'utilisation de la sortie produite par ces fonctions, la Figure 9.1, « Feuille de style XSLT pour convertir du SQL/XML en HTML » montre une feuille de style XSLT qui convertit la sortie de table_to_xml_and_xmlschema en un document HTML contenant un affichage en tableau des données de la table. D'une façon similaire, les données en résultat de ces fonctions peuvent être converties dans d'autres formats basés sur le XML.

Figure 9.1. Feuille de style XSLT pour convertir du SQL/XML en 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>