Wednesday 6 February 2013

Using DBMS_XMLGEN for XML from PL/SQL


I started looking into the DBMS_XMLGEN package when we had a need to do character escaping of some XML text. Basically we needed to convert all the < and > characters to &lt; and &gt; in PL/SQL.
Here is a quick script to demonstrate using DBMS_XMLGEN.CONVERT to do this.

To convert to a simple string ‘<>CAT<>’

SELECT DBMS_XMLGEN.CONVERT(xmldata=>'<>CAT<>', flag=>0) FROM dual

results in the string

&lt;&gt;CAT&lt;&gt;

Then to convert back the other way.

SELECT DBMS_XMLGEN.CONVERT(xmldata=>'&lt;&gt;CAT&lt;&gt;', flag=>1) FROM dual

results in the original string

<>CAT<>

With the flags instead of using 0 or 1 you should be able to use DBMS_XMLGEN.ENCODE and DBMS_XMLGEN.DECODE but I have not been able to get these to be recognised which might be an environmental issue in this database.

Looking further into the DBMS_XMLGEN package I found that is can do a great deal more with XML processing. Here is a basic example of running a query and having it formatted as XML then using dbms_output.put_line to display the CLOB.
DECLARE
    v_ctxhandle     DBMS_XMLGEN.ctxHandle;
    v_clob              CLOB;
    v_offset           NUMBER := 1;           
BEGIN
    v_ctxhandle := DBMS_XMLGEN.NEWCONTEXT (
                             'SELECT employee_id ID,
                                 last_name,
                                 to_char(hire_date, ''DD/MM/YYYY'') Hire
                             FROM employees
                             WHERE employee_id < 105');
    v_clob := DBMS_XMLGEN.getXML(v_ctxhandle);
    loop exit when v_offset > dbms_lob.getlength(v_clob);
         dbms_output.put_line(dbms_lob.substr( v_clob, 32767, v_offset));
         v_offset := v_offset + 32767;
    end loop;
    DBMS_LOB.FREETEMPORARY(v_clob);
    DBMS_XMLGEN.CLOSECONTEXT(v_ctxhandle);
END;

This results in this XML
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <ID>100</ID>
  <LAST_NAME>King</LAST_NAME>
  <HIRE>17/06/1987</HIRE>
 </ROW>
 <ROW>
  <ID>101</ID>
  <LAST_NAME>Kochhar</LAST_NAME>
  <HIRE>21/09/1989</HIRE>
 </ROW>
 <ROW>
  <ID>102</ID>
  <LAST_NAME>De Haan</LAST_NAME>
  <HIRE>13/01/1993</HIRE>
 </ROW>
 <ROW>
  <ID>103</ID>
  <LAST_NAME>Hunold</LAST_NAME>
  <HIRE>03/01/1990</HIRE>
 </ROW>
 <ROW>
  <ID>104</ID>
  <LAST_NAME>Ernst</LAST_NAME>
  <HIRE>21/05/1991</HIRE>
 </ROW>
</ROWSET>

Now to further expand the example we can include some options to change the way the XML is handled.

DECLARE
    v_ctxhandle        DBMS_XMLGEN.ctxHandle;
    v_clob                CLOB;
    v_offset        NUMBER := 1;        
BEGIN
    v_ctxhandle := DBMS_XMLGEN.NEWCONTEXT (
                                'SELECT employee_id ID,
                                        last_name,
                                        to_char(hire_date, ''DD/MM/YYYY'') Hire
                                FROM employees
                                WHERE employee_id < 105');
    -- Pretty print will do the nice indentation.
    DBMS_XMLGEN.setPrettyPrinting(v_ctxhandle, TRUE);
    -- The indentation width can be set.
    DBMS_XMLGEN.setIndentationWidth(v_ctxhandle, 2);
    -- Set the tag for the rowset
    DBMS_XMLGEN.setrowsettag(v_ctxhandle, 'PEOPLE');
    -- Set the tag for the row.
    DBMS_XMLGEN.setrowtag(v_ctxhandle, 'PERSON');
    -- when we retrieve row from this query only get two at a time, this can let 
    -- you do things like control the amount of data you show on one page.
    DBMS_XMLGEN.setmaxrows(v_ctxhandle, 2);
    -- Now do the query as before
    v_clob := DBMS_XMLGEN.getXML(v_ctxhandle);
    loop exit when v_offset > dbms_lob.getlength(v_clob);
        dbms_output.put_line(dbms_lob.substr( v_clob, 32767, v_offset));
        v_offset := v_offset + 32767;
    end loop;

    -- so this has displayed two rows and we can do some other processing then
    -- process two more rows.
    dbms_output.put_line('Can do some new page processing here');
    DBMS_LOB.FREETEMPORARY(v_clob);
    v_offset := 1;

    v_clob := DBMS_XMLGEN.getXML(v_ctxhandle);
    loop exit when v_offset > dbms_lob.getlength(v_clob);
        dbms_output.put_line(dbms_lob.substr( v_clob, 32767, v_offset));
        v_offset := v_offset + 32767;
    end loop;
    DBMS_LOB.FREETEMPORARY(v_clob);
    DBMS_XMLGEN.CLOSECONTEXT(v_ctxhandle);
END;

Gives us XML like this. Notice the text 'Can do some new page processing here' is just to show where you could do further processing between section of XML output.

<?xml version="1.0"?>
<PEOPLE>
  <PERSON>
    <ID>100</ID>
    <LAST_NAME>King</LAST_NAME>
    <HIRE>17/06/1987</HIRE>
  </PERSON>
  <PERSON>
    <ID>101</ID>
    <LAST_NAME>Kochhar</LAST_NAME>
    <HIRE>21/09/1989</HIRE>
  </PERSON>
</PEOPLE>
Can do some new page processing here
<?xml version="1.0"?>
<PEOPLE>
  <PERSON>
    <ID>102</ID>
    <LAST_NAME>De Haan</LAST_NAME>
    <HIRE>13/01/1993</HIRE>
  </PERSON>
  <PERSON>
    <ID>103</ID>
    <LAST_NAME>Hunold</LAST_NAME>
    <HIRE>03/01/1990</HIRE>
  </PERSON>
</PEOPLE>

There are lots of ways of doing XML processing from PL/SQL and DBMS_XMLGEN seems to offer many options that can be explored further.