Simple example of how to generate an XML file in Oracle

Would someone have a simple example of a PLSQL Procedure (or documentation location) to generate an XML file based on a Schema (xsd) , the data would be generated from a SQL query. In reality archives of the RPS of the ANS (National Health Agency BR).

Author: Motta, 2016-02-03

2 answers

There are some ways to generate a XML in PL-SQL. But in the case of XSD, I imagine that you should do a validation of Schema. So I will divide the answer into two parts, first in examples of generating XML and finally the validation of Schema.

Options to generate the XML:

1. DBMS_XSLPROCESSOR

This example uses CLOB to store the stream that will be the file XML and then it is created and saved in the indicated folder using the package DBMS_XSLPROCESSOR.

DECLARE
 varClob     CLOB;
 varString   VARCHAR2(4000);

BEGIN
 -- Abre a instancia do CLOB e o coloca em modo de escrita
 dbms_lob.createtemporary(varClob, TRUE);
 dbms_lob.open(varClob, dbms_lob.lob_readwrite);

 -- Variável string para armazenar parte do XML
 varString := '<?xml version="1.0" encoding="utf-8"?>' ||
              '<root>' ||
              '<teste>Este é um teste</teste>' ||
              '</root>';

 -- Cria um XML simples de testes, pode ser dado quantos APPEND quiser e
 -- ir montando o XML durante sua rotina PL/SQL
 dbms_lob.writeappend(varClob, LENGTH(varString), varString);

 -- Aqui irá de fato gerar o arquivo físico do XML
 DBMS_XSLPROCESSOR.CLOB2FILE(varClob, '/minha/pasta/', 'teste.xml', NLS_CHARSET_ID('UTF8'));

 -- Liberar dados do CLOB da memória
 dbms_lob.close(varClob);
 dbms_lob.freetemporary(varClob);
END;

Note that in this example there is no parse, that is, the XML is generated raw and any abnormality will not be validated and consequently an invalid XML, a more refined solution would be to perform the parse of the CLOB to prevent some failure in the XML compromising a later process. It will be seen in item 4.

2. Package xmlgen

Another very simple way to generate a XML document is to use the XMLGEN package. The function getXml() takes the parameters for the query SQL and the metaData type (such as DTD) and returns a CLOB containing the document XML.

SELECT xmlgen.getXml(
  'SELECT empno "EMP_NO"
        , ename "NAME"
        , deptno "DEPT_NO"
     FROM emp
    WHERE deptno = 10'
  , 0
  ) FROM dual;

Result:

<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <EMP_NO>7782</EMP_NO>
      <NAME>CLARK</NAME>
      <DEPT_NO>10</DEPT_NO>
   </ROW>
   <ROW num="2">
      <EMP_NO>7839</EMP_NO>
      <NAME>KING</NAME>
      <DEPT_NO>10</DEPT_NO>
   </ROW>
   <ROW num="3">
      <EMP_NO>7934</EMP_NO>
      <NAME>MILLER</NAME>
      <DEPT_NO>10</DEPT_NO>
   </ROW>
</ROWSET>

The result is a root element called ROWSET, which contains a list of ROW elements. Each row element has the row number as an attribute, and each row element contains the EMP_NO, NAME, and DEPT_NO elements.

3. Using XML-SQL Utility (XSU)

XML-SQL Utility (XSU) provides a simple way to achieve transformation of data by mapping any query to XML and vice versa. XSU provides the basic functionality for getting and placing data to and from a database.

DBMS_XMLQUERY and DBMS_XMLSAVE are two packages that support XML generation and storage through XML. Here we will focus on the generation of XML.

Generating XML by calling the function getXML() results in a CLOB containing the document XML. A context identifier necessary in most subsequent calls is created in the first step.

DECLARE
  queryCtx dbms_xmlquery.ctxType;
  result CLOB;
BEGIN
  -- set up the query context
  queryCtx := dbms_xmlquery.newContext(
    'SELECT empno "EMP_NO"
          , ename "NAME"
          , deptno "DEPT_NO"
       FROM emp
      WHERE deptno = 5'
  );

If the DTD schema definition or XML explicitly defined tag names other than column names, you can change row and ROWSET tag names easily:

dbms_xmlquery.setRowTag(
      queryCtx
    , 'EMP'
  );
dbms_xmlquery.setRowSetTag(
      queryCtx
    , 'EMPSET'
  );

Ok, now you are ready to run the query and generate the result XML as CLOB. A simple procedure printClobOut() supports printing a CLOB on the screen. Finally, the query identifier should be closed to release the resources.

result := dbms_xmlquery.getXml(queryCtx);
printClobOut(result);
dbms_xmlquery.closeContext(queryCtx);

The result is something like:

<?xml version = '1.0'?>
<EMPSET>
   <EMP num="1">
      <EMP_NO>7782</EMP_NO>
      <NAME>CLARK</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
   <EMP num="2">
      <EMP_NO>7839</EMP_NO>
      <NAME>KING</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
   <EMP num="3">
      <EMP_NO>7934</EMP_NO>
      <NAME>MILLER</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
</EMPSET>

4. Package xmldom

The XMLDOM package implements the Document Object Model Interface (Dom) interface, as defined by the W3C XML recommendations. let's follow a simple example to discuss the use of the XMLDOM package.

In the Declaration section, you need a set of DOM references. The identifier DOMDocument is the most important. It will be used in most calls subsequent. In addition, you need different DOM node identifiers to reference the parent node, root node, user node, and item node for each element. The cursor selects the data that will be exposed.

DECLARE
  doc xmldom.DOMDocument;
  main_node xmldom.DOMNode;
  root_node xmldom.DOMNode;
  user_node xmldom.DOMNode;
  item_node xmldom.DOMNode;
  root_elmt xmldom.DOMElement;
  item_elmt xmldom.DOMElement;
  item_text xmldom.DOMText;
  CURSOR get_users(p_deptno NUMBER) IS
    SELECT empno
         , ename
         , deptno
         , rownum
      FROM emp
     WHERE deptno = p_deptno;

First, you create a new document identifier. Next, you create the main node for this document. The root element is called EMPSET and attached as a child node to the parent node. The returned identifier is used as the root node for calls subsequent.

BEGIN
  doc := xmldom.newDOMDocument;
  main_node := xmldom.makeNode(doc);
  root_elmt := xmldom.createElement(
      doc
    , 'EMPSET'
  );
  root_node := xmldom.appendChild(
      main_node
    , xmldom.makeNode(root_elmt)
  );

For each record found in the query, a new element called EMP is created. The row number is added as an attribute to the element. This element is attached as a child node to the root node. The returned identifier is used as the user node for subsequent calls.

FOR get_users_rec IN get_users(10) LOOP
  item_elmt := xmldom.createElement(
      doc
    , 'EMP'
  );
  xmldom.setAttribute(
      item_elmt
    , 'num'
    , get_users_rec.rownum
  );
  user_node := xmldom.appendChild(
      root_node
    , xmldom.makeNode(item_elmt)
  );

Text elements can now be added to the DOM document. In the first step, a new element named EMP_NO is created. This element is attached as a child node to the node user. At the second stage, a text node is created that contains the registration data, in this case the number of the employee. This text node is attached as a child node to the item node.

item_elmt := xmldom.createElement(
    doc
  , 'EMP_NO'
);
item_node := xmldom.appendChild(
    user_node
  , xmldom.makeNode(item_elmt)
);
item_text := xmldom.createTextNode(
    doc
  , get_users_rec.empno
);
item_node := xmldom.appendChild(
    item_node
  , xmldom.makeNode(item_text)
);

The same can be done with the NAME and DEPT_NO text elements.

After all records have been processed and all data has been loaded into the DOM document, they can be, for example, archived and their resources released:

  END LOOP;
  xmldom.writeToFile(
      doc
    , '/tmp/xml/docSample.xml'
  );
  xmldom.freeDocument(doc);
END;

Note that the package XMLDOM you can write the XML file to all locations accessible by the Oracle OS user, regardless of the current UTL_FILE_DIR boot parameter. The resulting file contains the following lines:

<?xml version = '1.0' encoding = 'UTF-8'?>
<EMPSET>
   <EMP num="1">
      <EMP_NO>7782</EMP_NO>
      <NAME>CLARK</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
   <EMP num="2">
      <EMP_NO>7839</EMP_NO>
      <NAME>KING</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
   <EMP num="3">
      <EMP_NO>7934</EMP_NO>
      <NAME>MILLER</NAME>
      <DEPT_NO>10</DEPT_NO>
   </EMP>
</EMPSET>

XSD validation

To validate XML against a XSD, you first need to register the XSD.

In this example, let's use the following XML:

<order>
  <orderId> 1 </orderId>
  <customerId> 123 </customerId>
</order>

And the check conforms to the following XSD:

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="order">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:byte" name="orderId"/>
        <xs:element type="xs:byte" name="customerId"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

(generated using http://www.freeformatter.com/xsd-generator.html#ad-output )

You register it by calling DBMS_XMLschema.registerSchema:

begin
  dbms_xmlschema.registerSchema('order.xsd',
'<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="order">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:byte" name="orderId"/>
        <xs:element type="xs:byte" name="customerId"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>');
end;
/

You can then verify that the XMLType instances are compliant using isSchemaValid. If it conforms, then returns 1, otherwise returns 0:

declare
  xml xmltype;
begin
  xml := xmltype('<order>
  <orderId>1</orderId>
  <customerId>123</customerId>
</order>');

  dbms_output.put_line(xml.isSchemaValid('order.xsd'));
end;
/

But that doesn't give much information! You can get more details on why a document does not comply by calling XMLType.schemaValidate. To use this, your XML must have the correct namespace information.

To do this, add:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="order.xsd"

Do this and you will now receive an exception if XML does not comply:

declare
  xml xmltype;
begin
  xml := xmltype('<order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="order.xsd">
  <orderId>1</orderId>
</order>');

  xmltype.schemaValidate(xml);
end;
/

ORA-31154: invalid XML document
ORA-19202: Error occurred in XML processing
LSX-00213: only 0 occurrences of particle "customerId", minimum is 1
ORA-06512: at "SYS.XMLTYPE", line 354
ORA-06512: at line 8
31154. 00000 -  "invalid XML document" 
*Cause:    The XML document is invalid with respect to its XML Schema.
*Action:   Fix the errors identified and try again.

With this we can create a procedure to validate the XML XSD Schema:

CREATE OR REPLACE PROCEDURE VALIDADOR_XML(pr_xml IN xmltype) IS
/*
 * Procedure valida o xml de acordo com um schema xsd predefinido
**/
BEGIN
  dbms_xmlschema.registerSchema('order.xsd',
        '<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
          <xs:element name="order">
            <xs:complexType>
              <xs:sequence>
                <xs:element type="xs:byte" name="orderId"/>
                <xs:element type="xs:byte" name="customerId"/>
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:schema>');

  IF pr_xml.isSchemaValid('order.xsd') THEN
    dbms_output.put_line ('XML VALIDO');
  ELSE
    dbms_output.put_line ('XML INVALIDO');
    /*xmltype.schemaValidate(pr_xml);*/
  END IF;
END;

References

Creating XML Documents with PL / SQL

Creating an XML file with Oracle PL / SQL

Oracle® XML DB developer's Guide

Validate XML documents with XML schema in Oracle PL / SQL?

Transforming and validating xmltype data

Ask TOM - how to use isSchemaValid() in oracle 11gr2 to validate XML against the XSD scheme?

Validate XML in ORACLE against an XSD using DBMS_XMLSCHEMA and XMLTYPE.SCHEMAVALIDATE

 1
Author: Darlei Fernando Zillmer, 2019-11-08 04:39:58

I just left a project that generated an xml based on a base in Oracle. For this I used SQLX -- > https://oracle-base.com/articles/misc/sqlxml-sqlx-generating-xml-content-using-sql. the rules of xsd that I had to abide by, I did everything in hand.

 0
Author: Edvaldo Costa, 2016-02-03 21:06:03