Logo Oracle Deutschland   Application Express Community
Arbeiten mit XML in Application Express

Erscheinungsmonat APEX-Version Datenbankversion
Mai 2013 alle ab 10.2

Mit XML kommt man als APEX-Entwickler immer wieder in Kontakt. Und ob man XML erzeugen, zerlegen, prüfen oder anzeigen muss: Die Oracle-Datenbank bietet zahlreiche Funktionen, Packages und Werkzeuge zum Umgang mit XML an, die man aus APEX heraus auch sehr einfach nutzen kann. Dieser Community-Tipp widmet sich dem Thema und stellt vor, wie man verschiedenste Aufgabenstellungen rund um XML mit der Datenbank erledigen kann. Zur besseren Demonstration werden alle XML-Funktionen anhand von Formularen oder Berichten vorgestellt - natürlich ist die Anwendung in PL/SQL-Logik oder APEX-Prozessen ebenso möglich.

XML hochladen und in einer Tabelle speichern

Alles beginnt damit, dass ein XML-Dokument über eine APEX-Anwendung in eine Tabelle hochgeladen wird. Das interessante wird dabei sein, von einem BLOB (als solcher taucht eine hochgeladene Datei in APEX auf) zu einem XMLTYPE (dem Datentypen für XML) zu kommen. Erstellen Sie also zuerst eine Tabelle, in die die XML-Dateien gespeichert werden sollen. Dabei nehmen wir für die Dokumente selbst nicht etwa CLOB oder BLOB, sondern eben den Datentypen, den die Datenbank dafür vorsieht: XMLTYPE.

create table tab_xml_dokumente(
  id          number        not null primary key,
  dateiname   varchar2(500) not null,
  zeitstempel date          not null,
  xml         xmltype
)
xmltype column xml store as binary xml
allow nonschema
/

create sequence seq_xml_dokumente
/

create trigger tr_pk_xml_dokumente 
before insert on tab_xml_dokumente
for each row
begin
  :new.id := seq_xml_dokumente.nextval;
  :new.zeitstempel := sysdate;
end;
/

In der Oracle-Datenbank ist XML nicht gleich XML. Es wird zwar stets der Datentyp XMLTYPE verwendet, dieser bietet aber drei Varianten zur konkreten Speicherung an: Textbasiert, Objektrelational und (ab Oracle 11g) Binary XML. Eine genaue Betrachtung dieser Varianten würde den Rahmen dieses Artikels sprengen - wer interessiert ist, findet dazu hier ausführliche Informationen.

In diesem Tipp verwenden wir die flexibelste Variante Binary XML, die aber erst ab Oracle11g zur Verfügung steht. In Oracle10g müssen Sie das CREATE TABLE-Kommando durch das folgende ersetzen.

create table tab_xml_dokumente(
  id          number        not null primary key,
  dateiname   varchar2(500) not null,
  zeitstempel date          not null,
  xml         xmltype
)
/

Dann brauchen Sie eine APEX-Anwendung und darin eine Seite zum Hochladen von XML-Dateien. Stellen Sie die Sprache der Anwendung (Globalization) für diesen Tipp am besten auf Englisch ein; das macht den späteren Umgang mit den XML-Beispieldateien wegen des Zahlenformats leichter. Auf der Seite zum Hochladen erstellen Sie ein Element vom Typ File Browse, eine Schaltfläche zum Absenden und ein Textfeld für den Dokumentzeichensatz (daraus kann man natürlich auch eine Auswahlliste machen). Stellen Sie beim Element zum File-Upload ein, dass die Datei nach WWV_FLOW_FILES geladen werden soll (Abbildung 1) - ein direktes Laden in die XML-Tabelle ist nicht möglich. Die Seite sollte dann in etwa wie in Abbildung 2 aussehen ...

Die hochgeladenen XML-Dateien werden (zunächst) nach "WWV_FLOW_FILES" geladen
Abbildung 1: Die hochgeladenen XML-Dateien werden (zunächst) nach "WWV_FLOW_FILES" geladen
Anwendungsseite zum Upload von XML-Dokumenten
Abbildung 2: Anwendungsseite zum Upload von XML-Dokumenten

Als nächstes erstellen Sie den PL/SQL-Seitenprozess, der die XML-Datei von der Tabelle WWV_FLOW_FILES nach TAB_XML_DOKUMENTE kopiert und dabei aus dem BLOB einen XMLTYPE macht. Und für diesen Prozess benötigen wir das zusätzliche Formularelement für den Dokumentzeichensatz. Diese Information ist zwar eigentlich auch in der XML-Datei selbst vorhanden, aber in vielen Fällen ist der "XML-Prolog" falsch oder fehlt völlig. Erzeugen Sie also einen neuen onSubmit-Prozess, der bei Klick auf die Schaltfläche ausgeführt werden soll und folgenden PL/SQL-Code enthält.

declare
  l_xml       blob;
  l_filename  wwv_flow_files.filename%type;
begin
 begin
  select blob_content, filename 
   into l_xml, l_filename
  from wwv_flow_files 
  where name = :P2_XML;

  insert into tab_xml_dokumente(
    dateiname, xml
  ) values (
    l_filename, 
    -- Hier(!) wird der BLOB in einen XMLTYPE gewandelt.
    xmltype(l_xml, nls_charset_id(:P2_CHARSET))
  ); 
 exception
  when NO_DATA_FOUND then 
   -- Fehlerbehandlung hier - mindestens: Logging
   null;
 end;
 delete from wwv_flow_files where name = :P2_XML;
end;

Versehen Sie den Prozess dann noch mit Erfolgs- und Fehlermeldungen und sorgen Sie dafür, dass er nur dann ausgeführt wird, wenn die Schaltfläche UPLOAD geklickt wurde. Bevor Sie nun etwas hochladen, legen Sie noch einen Bericht an, der unterhalb des Upload-Formulars dargestellt werden soll - er soll eine Übersicht über die Inhalte der Tabelle geben. Nehmen Sie dazu folgendes Berichts-SQL.

select id, zeitstempel, dateiname
from tab_xml_dokumente

Damit ist die Upload-Seite fertig. Nehmen Sie nun also ein XML-Dokument (zum Beispiel ADAMS-20011127121040988PST.xml) und laden Sie es hoch (Abbildung 3). Tragen Sie AL32UTF8 als Encoding ein.

Ein XML-Dokument wurde hochgeladen
Abbildung 3: Ein XML-Dokument wurde hochgeladen

Nun befindet sich also ein XML-Dokument in der Tabelle. Der nächste Schritt wäre, die XML-Inhalte auf einer anderen APEX-Seite anzuzeigen. Den bereits vorhandenen Bericht können wir zur Auswahl des anzuzeigenden Dokuments verwenden; verlinken Sie dazu die Berichtsspalte ID mit der nächsten APEX-Seite, wie in Abbildung 4 dargestellt ...

Verlinkung der Berichtsspalte "ID" mit der APEX-Seite zum Darstellen des XML
Abbildung 4: Verlinkung der Berichtsspalte "ID" mit der APEX-Seite zum Darstellen des XML

Auf der APEX-Seite 3 legen Sie nun ein verstecktes Element (Hidden Item) namens P3_ID an, danach geht es an die Darstellung des XML-Dokumentes ...

XML-Dokumente auf APEX-Seiten anzeigen

Der erste Versuch, das XML in APEX zur Anzeige zu bringen, wäre ein recht einfacher Bericht mit folgender SQL-Abfrage:

select xml
from tab_xml_dokumente
where id = :P3_ID

Das Ergebnis kann, wie Abbildung 5 zeigt, nicht wirklich zufriedenstellen ...

XMLTYPE-Tabellenspalten können nicht direkt in einem APEX-Bericht angezeigt werden
Abbildung 5: XMLTYPE-Tabellenspalten können nicht direkt in einem APEX-Bericht angezeigt werden

Eine Tabellenspalte vom Typ XMLTYPE kann von einem APEX-Bericht nicht ohne weiteres angezeigt werden. Das XML muss zunächst in Zeichendaten (also in einen CLOB) umgewandelt werden. Im Internet findet man hierfür recht häufig die Funktion GETCLOBVAL(), diese ist aber veraltet - ab Oracle11g ist XMLSERIALIZE die richtige. Zunächst also das Berichts-SQL für Oracle10g ...

select x.xml.getclobval() as xml
from tab_xml_dokumente x
where id = :P3_ID

... und dann das für Oracle11g ...

select 
  xmlserialize(
    document xml as clob
    indent size = 2
  ) as xml
from tab_xml_dokumente
where id = :P3_ID

Man sieht der Syntax schon an, dass die XMLSERIALIZE-Funktion einige Möglichkeiten anbietet, die Ausgabe zu steuern - so kann man mit INDENT SIZE bzw. NO INDENT steuern, ob das XML mit Einrückungen oder ohne dargestellt werden soll (Pretty Printing), daneben können auch Parameter zum Steuern des sog. XML-Prologs verwendet werden.

Der XMLTYPE wird nun dargestellt
Abbildung 6: Der XMLTYPE wird nun dargestellt

Abbildung 6 zeigt, dass das XML nun dargestellt werden kann; für APEX ist es ja auch gar kein XML mehr, sondern ein CLOB - und den kann ein APEX-Bericht ja problemlos anzeigen. Während die XML-Tags korrekt mit ihren spitzen Klammern dargestellt werden, klappt das mit den Zeilenumbrüchen und den Einrückungen noch nicht so richtig - aber dies lässt sich leicht in den Griff bekommen. Navigieren Sie dazu zu den Eigenschaften der Berichtsspalte, dort zu HTML-Ausdruck und dort tragen Sie ein: <pre>#XML#</pre> (Abbildung 7). Damit wird das XML in ein HTML-Tag <pre> eingebaut - der Browser wird nun alle Zeilenumbrüche und Leerzeichen so darstellen, wie sie im XML-"Quellcode" vorkommen.

Eintragen eines "HTML-Ausdrucks" für die Berichtsspalte
Abbildung 7: Eintragen eines "HTML-Ausdrucks" für die Berichtsspalte

Bevor Sie sich den Bericht nochmals ansehen, stellen Sie außerdem noch Strip HTML im Bereich der Berichtsattribute auf No um (Abbildung 8), damit auch wirklich alle Tags dargestellt werden.

"Strip HTML" abschalten
Abbildung 8: "Strip HTML" abschalten

Nun können Sie das XML-Dokument im Browser betrachten ...

Darstellung des XML-Dokuments auf einer APEX-Seite
Abbildung 9: Darstellung des XML-Dokuments auf einer APEX-Seite

Einzelne XML-Tags extrahieren und als Bericht darstellen

In den meisten Fällen möchte man jedoch nicht den XML-Quelltext auf der APEX-Seite darstellen, sondern die Inhalte der XML-Tags aufbereiten - und auch hierfür bietet die Datenbank SQL-Funktionen an. Sie können zu jedem beliebigen XML-Tag navigieren (dabei sogar die XML-Hierarchie durchlaufen), den Inhalt "ausschneiden" und wie eine normale, relationale Tabelle darstellen.

Erzeugen Sie also nochmals eine neue APEX-Seite und auf dieser wiederum einen neuen Bericht. Zunächst sollen die XML "Kopfdaten" angezeigt werden, also die, die nur einmal pro Dokument vorkommen. Die SQL-Funktion XMLTABLE dient zum Extrahieren einzelner XML-Tags. Verwenden Sie die folgende SQL-Abfrage ...

select 
  t.zeitstempel,
  t.dateiname,
  x.username,
  x.costcenter,
  x.realname,
  x.address,
  x.telephone
from 
  tab_xml_dokumente t,
  xmltable(
    '/PurchaseOrder'
    passing xml 
    columns
      username   varchar2(30)  path '/PurchaseOrder/User/text()', 
      costcenter varchar2(3)   path '/PurchaseOrder/CostCenter/text()', 
      realname   varchar2(50)  path '/PurchaseOrder/ShippingInstructions/name/text()', 
      address    varchar2(100) path '/PurchaseOrder/ShippingInstructions/address/text()', 
      telephone  varchar2(100) path '/PurchaseOrder/ShippingInstructions/telephone/text()'
   ) x

Das "Mapping" der XML-Tags auf die Ergebnisspalten der Abfrage ist in der COLUMNS-Klausel sehr gut erkennbar. Abbildung 10 zeigt das Ergebnis des Berichts (nachdem noch zwei weitere XML-Dokumente, ALLEN-20011127121048799PST.xml oder BLAKE-20011127121045143PST.xml, hochgeladen wurden).

XML-Inhalte: aufbereitet als normaler Bericht
Abbildung 10: XML-Inhalte: aufbereitet als normaler Bericht

Auch die Inhalte des mehrfach vorkommenden XML-Tags LineItem können in einem APEX-Bericht aufbereitet werden - allerdings sind nun zwei, miteinander verbundene, XMLTABLE-Ausdrücke nötig ...

select 
  x.costcenter,
  x.realname,
  x.address,
  i.item_number,
  i.item_desc,
  i.item_unitprice,
  i.item_quantity,
  i.item_unitprice * i.item_quantity as item_total
from 
  tab_xml_dokumente t,
  xmltable(
    '/PurchaseOrder'
    passing xml 
    columns
      username   varchar2(30)  path '/PurchaseOrder/User/text()', 
      costcenter varchar2(3)   path '/PurchaseOrder/CostCenter/text()', 
      realname   varchar2(50)  path '/PurchaseOrder/ShippingInstructions/name/text()', 
      address    varchar2(100) path '/PurchaseOrder/ShippingInstructions/address/text()', 
      telephone  varchar2(100) path '/PurchaseOrder/ShippingInstructions/telephone/text()',
      lineitems  xmltype       path '/PurchaseOrder/LineItems'
   ) x,
   xmltable(
    '/LineItems/LineItem'
    passing x.lineitems
    columns
      item_number    number       path '/LineItem/@ItemNumber',
      item_desc      varchar2(90) path '/LineItem/Description/text()',
      item_partid    varchar2(20) path '/LineItem/Part/@Id',
      item_unitprice number       path '/LineItem/Part/@UnitPrice',
      item_quantity  number       path '/LineItem/Part/@Quantity'
   ) i

In der COLUMNS-Klausel des ersten XMLTABLE-Ausdrucks wird nun zusätzlich die Spalte LINEITEMS vom Typ XMLTYPE festgelegt. Diese enthält den gesamten XML-Abschnitt mit den (mehrfach vorkommenden) "LineItems". Dieser wird nun im zweiten XMLTABLE-Ausdruck als eigenes XML-Dokument aufgefasst, zerlegt und wiederum auf Ergebnisspalten abgebildet. Die SQL-Abfrage liefert nun auch nicht mehr eine Zeile pro XML-Dokument zurück, sondern eine pro "LineItem" - der Bericht enthält also wesentlich mehr Zeilen. Für diese Abfrage wäre ein interaktiver Bericht am besten geeignet (Abbildung 11).

Bericht auf Ebene der "LineItems": Das XML wurde noch weiter zerlegt
Abbildung 11: Bericht auf Ebene der "LineItems": Das XML wurde noch weiter zerlegt

Es ist einleuchtend, dass man nun, mit den Mitteln von SQL, beliebig auf den Daten des XML-Dokuments arbeiten kann. Dessen Inhalte lassen sich auffassen wie eine normale Tabelle - und damit bieten sich auch alle Möglichkeiten einer normalen Tabelle.

Stylesheet Transformationen: XSLT

Eine der Eigenschaften von XML ist, dass es sich durch Stylesheet Transformationen in andere Ausgabeformate umwandeln lässt - und diese Stylesheets werden wiederum in einer standardisierten XML-Notation kodiert. Häufig liegen bereits fertige XSLT-Stylesheets vor - wie auch für diesen Community-Tipp. Laden Sie also die Dateien render-html.xsl (funktioniert nur mit diesen "PurchaseOrder"-XML-Dokumenten) und render-xml.xsl (funktioniert mit allen XML-Dokumenten) zunächst auf Ihren Rechner herunter und speichern Sie diese dann in Ihrem APEX Workspace als Statische Datei ab.

XSLT-Stylesheets als "Statische Dateien" speichern
Abbildung 12: XSLT-Stylesheets als "Statische Dateien" speichern

Eine "Stylesheet-Transformation" können Sie in der Datenbank mit der SQL-Funktion XMLTRANSFORM ausführen. XMLTRANSFORM nimmt zwei Parameter entgegen: Im ersten Parameter wird das umzuwandelnde XML-Dokument, im zweiten das XSLT-Stylesheet (beide als XMLTYPE) übergeben. Zurückgegeben wird wiederum ein XMLTYPE. Mit dem bisher gelernten können wir also eine SQL-Abfrage als Berichtsquelle formulieren, welche beide Dokumente liest, die Stylesheet-Transformation durchführt und das Ergebnis anzeigt.

with tab_xsl as (
-- Die erste "Inline-View" liest das XSL-Stylesheet
-- aus WWV_FLOW_FILES ("Statische Dateien")
  select 
    xmltype(blob_content, nls_charset_id('AL32UTF8')
  ) as xsl_file
  from wwv_flow_files 
  where filename = :P5_XSL_FILE
),
-- Die zweite "Inline View" führt die Stylesheet-Transformation
-- aus. Verwendet werden die Tabelle TAB_XML_DOKUMENTE und die
-- erste "Inline View"
xsl_transform as (
  select 
    xmltransform(xm.xml, xs.xsl_file) as transformed_xml
  from tab_xml_dokumente xm, tab_xsl xs
  where xm.id = :P5_ID
)
-- Das "finale" SELECT wandelt den XMLTYPE der XSLT-Transformation
-- schließlich in einen CLOB um, welcher im Bericht dargestellt werden
-- kann.
select 
 xmlserialize(
  document transformed_xml as clob
  no indent
) as html_output
from xsl_transform

Ist Ihnen aufgefallen, dass wir den Namen des Stylesheets in der SQL-Abfrage als APEX-Item (P5_XSL_FILE) kodiert haben? Damit können wir das XML-Dokument auf der Anwendungsseite - einfach per Auswahlliste - unterschiedlich ausgeben lassen. Erzeugen Sie nun eine neue APEX-Anwendungsseite mit folgenden Komponenten:

  • Eine Region vom Typ HTML , welche die beiden folgenden Elemente aufnimmt.
  • Ein Element namens P5_ID als Auswahlliste: Nehmen Sie als Wertelisten-Abfrage: "select dateiname d, id r from tab_xml_dokumente".
  • Ein Element namens P5_XSL_FILE als Auswahlliste - tragen Sie als Werteliste ein: "select filename d, filename r from wwv_flow_files where filename like '%.xsl'".
  • Einen "klassischen" Bericht mit obiger SQL-Abfrage. Achten Sie darauf, dass Sie unter Page Items to Submit die beiden definierten Elemente P5_ID und P5_XSL_FILE eintragen. Stellen Sie außerdem sicher, dass Partial Page Refresh in den Berichtsattributen aktiviert ist.
  • Sorgen Sie dann noch mit einer dynamischen Aktion dafür, dass der Bericht bei Änderung an einer der beiden Wertelisten (onChange) aktualisiert wird (refresh).

Die Seite sollte dann in etwa so aussehen ...

APEX-Seite mit einem Bericht, der das Ergebnis einer XSLT-Transformation zeigt
Abbildung 13: APEX-Seite mit einem Bericht, der das Ergebnis einer XSLT-Transformation zeigt

Wie schon im zweiten Abschnitt, als wir XML in einem Bericht angezeigt haben, wird das XML so angezeigt, wie es ist - aber ohne Zeilenumbrüche und Einrückungen. Diesmal ist unser Ziel aber etwas anders: Denn das Ergebnis der XSLT-Transformation soll nicht mehr als XML dargestellt werden - der Browser soll es als HTML interpretieren. Und das ist bekanntlich ganz einfach: Stellen Sie die Anzeigeart der Berichtsspalte in den Spaltenattributen auf Standard Report Column um (Abbildung 14). Schalten Sie außerdem noch die Funktion Strip HTML in den Berichtsattributen ab.

Berichtsspalte umstellen auf "Standard Report Column"
Abbildung 14: Berichtsspalte umstellen auf "Standard Report Column"

Starten Sie die Seite danach nochmal. Sie können nun sehen, dass das Ergebnis der XSLT-Transformation nun vom Browser als HTML interpretiert wird. Und während das erste Stylesheet (render-html.xsl) versucht, ein "Formularlayout" für das XML zu realisieren (Abbildung 15) ...

"Formularlayout" per XSLT-Transformation
Abbildung 15: "Formularlayout" per XSLT-Transformation

... stellt render-xml.xsl das XML wieder so dar, wie es ist - nun aber mit Syntax Highlighting (Abbildung 16). Und dieses XSLT-Stylesheet funktioniert mit beliebigen XML-Dokumenten - die Darstellung funktioniert in allen Browsern.

"Syntax Highlighting" für XML mit der Datenbank und APEX
Abbildung 16: "Syntax Highlighting" für XML mit der Datenbank und APEX

Fazit

Die Oracle-Datenbank bietet umfangreiche Funktionen zum Umgang mit XML an - und als APEX-Entwickler können Sie all diese Funktionen ohne weiteres nutzen. Natürlich ist es nicht immer sinnvoll, Anwendungs-Oberflächen mit solchen XML-Funktionen zu gestalten, sehr oft wird es nötig sein, diese Funktionen in PL/SQL Paketen, Seitenprozessen oder Computations zu verwenden. Die gute Nachricht ist, dass Sie überall, wo SQL und PL/SQL verwendet werden können, auch die XML-Funktionen nutzen können. XML ist für APEX also überhaupt kein Problem.

Zurück zur Community-Seite