Processing XML
We have covered the basic techniques of generating XML from SQL data in object-relational tables and XMLTypes. In this section, we discuss how you can process the XML documents before passing them to applications. Specifically, we discuss how to convert XML fragments into XML documents and how to use DBMS_XMLDOM to create a DOM during XML document generation. PL/SQL transformations are discussed in Chapter 26.
Dealing with XML Fragments
XMLType operations may result in creating XMLTypes containing XML fragments instead of XML documents. Since you cannot insert an XML fragment into XMLType columns or tables, you need to use the following function to check whether or not the current XML type is an XML fragment:
SQL> INSERT INTO temp_emp
SELECT XMLCONCAT(XMLELEMENT("Email",email),
XMLELEMENT("Name", first_name||' '||last_name))
FROM employees
WHERE ROWNUM<3;
insert into temp_emp
*
ERROR at line 1:
ORA-19010: Cannot insert XML fragments
The following query checks if the result of the SQL/XML function is an XML document fragment:
SQL> SELECT XMLCONCAT(XMLELEMENT("Email",email),
2 XMLELEMENT("Name", first_name||' '||last_name)).isFragment()
3 FROM employees
4 WHERE ROWNUM<2;
XMLCONCAT(XMLELEMENT("EMAIL",EMAIL),
XMLELEMENT("NAME",FIRST_NAME||''||LAST_NAME)
-----------------------------------------------------------------------
1
The result of the preceding query shows that the XMLType is an XML fragment. Normally, such XML fragments do not meet the single root element requirement for XML documents. Therefore, you need to create a well-formed XML document using XMLELEMENT() before instering it into XMLTypes as follows:
INSERT INTO temp_emp
SELECT XMLELEMENT"RESULT",
XMLCONCAT(XMLELEMENT("Email",email),
XMLELEMENT("Name", first_name||' '||last_name)))
FROM employees
WHERE ROWNUM<2;
DOM Editing
As discussed in Chapter 2, the Document Object Model (DOM) is a set of standard APIs dealing with XML as a tree-type memory object. You can use the DOM APIs to delete, insert, and update XML elements, nodes, and attributes. In Oracle Database 10g, you can use the DBMS_XMLDOM package to perform these functions. From Chapter 2, you should be already familiar with the DOM operations, so the following examples show how these are performed from PL/SQL.
Operating on XMLType DOM
In Oracle Database 10g, you do not need to reparse an XML document if it is stored as an XMLType. There are two functions in the DBMS_XMLDOM package that directly support XMLType DOM operations:
FUNCTION NEWDOMDOCUMENT(xmldoc IN SYS.XMLType) RETURN DOMDOCUMENT;
FUNCTION GETXMLTYPE(doc IN DOMDOCUMENT) RETURN SYS.XMLType;
As a result, you can call DOM functions by creating an XMLType, using its construction function, and then passing it to DBMS_XMLDOM.NEWDOMDOCUMENT() as an input. After DOM processing, you can get the result in an XMLType by using the DBMS_XMLDOM.GETXMLTYPE() function. Here is an example of an update operation:
CREATE OR REPLACE PROCEDURE updateXMLElement(p_doc IN OUT XMLTYPE,
p_ename IN VARCHAR2,
p_content IN VARCHAR2) AS
v_item NUMBER;
v_doc DBMS_XMLDOM.DOMDOCUMENT;
v_ndoc DBMS_XMLDOM.DOMNODE;
v_nlist DBMS_XMLDOM.DOMNODELIST;
v_node DBMS_XMLDOM.DOMNODE;
v_elem DBMS_XMLDOM.DOMELEMENT;
v_nelem DBMS_XMLDOM.DOMNODE;
v_text DBMS_XMLDOM.DOMTEXT;
v_ntext DBMS_XMLDOM.DOMNODE;
BEGIN
-- Create DOM Object
v_doc := DBMS_XMLDOM.NEWDOMDOCUMENT(p_doc);
v_ndoc :=DBMS_XMLDOM.MAKENODE(DBMS_XMLDOM.GETDOCUMENTELEMENT(v_doc));
-- Select the DOM Nodes
v_nlist:= DBMS_XMLDOM.GETELEMENTSBYTAGNAME(v_doc,p_ename);
IF DBMS_XMLDOM.GETLENGTH(v_nlist) > 0 THEN
DBMS_OUTPUT.PUT_LINE('Update the: '||p_ename||' elements.');
FOR v_item IN 0..DBMS_XMLDOM.GETLENGTH(v_nlist) LOOP
v_node := DBMS_XMLDOM.ITEM(v_nlist, v_item);
v_ntext := DBMS_XMLDOM.GETFIRSTCHILD(v_node);
DBMS_OUTPUT.PUT_LINE(DBMS_XMLDOM.GETNODEVALUE(v_ntext));
DBMS_XMLDOM.SETNODEVALUE(v_ntext, p_content);
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('No '||p_ename||' element in the current
document.');
END IF;
-- Free resources
DBMS_XMLDOM.FREEDOCUMENT(v_doc);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Exceptions during the process');
END;
In the example, the DBMS_XMLDOM.DOMDOCUMENT() creates the DOMDOCUMENT from the XMLType input p_doc. Then, a set of DOM operations is performed, which include using the DBMS_XMLDOM.GETELEMENTBYTAGNAME() function to get the XML elements with name p_ename for the updates and iterating each XML element item to update the content using the DBMS_XMLDOM.SETNODEVALUE().Next, the created procedure can be used to update the telephone number for an employee with EMPLOYEE_ID of 199. The original document contains the following:
<ROWSET>
<ROW>
<EMPLOYEE_ID>199</EMPLOYEE_ID>
…
<PHONE_NUMBER>650.507.9844</PHONE_NUMBER>
…
</ROW>
Then, run the updateXMLElement() function:
DECLARE
v_doc XMLType;
BEGIN
-- Create Sample XML Document
SELECT XMLTYPE(DBMS_XMLGEN.GETXML('SELECT * FROM employees
WHERE employee_id=199'))
INTO v_doc FROM dual;
-- Update the Element
updateXMLElement(v_doc,'PHONE_NUMBER','650.506.9181');
-- Print out the content
SELECT v_doc.getClobVal() INTO :result FROM dual;
END;
The result is the XML document containing the updated phone number:
<ROWSET>
<ROW>
<EMPLOYEE_ID>199</EMPLOYEE_ID>
…
<PHONE_NUMBER>650.506.9181</PHONE_NUMBER>
…
</ROW>
</ROWSET>
If multiple employees were selected, all of them would be updated. You can try the sample yourself by updating the WHERE clause in the SQL statement.
Appending XML Document Fragments
If you have two XML documents and want to merge them together, you can use the following functions, which use DBMS_XMLDOM:
FUNCTION importNode(doc DOMDOCUMENT, importednode DOMNODE,
deep BOOLEAN) RETURN DOMNODE;
FUNCTION appendChild(n DOMNODE, newChild IN DOMNODE) RETURN DOMNODE;
For example, if you create one XML document containing the department information and want to merge this document with one containing the employees of this department, you could use the following code:
SQL> SET AUTOPRINT ON
SQL> VAR RESULT CLOB
SQL> DECLARE
v_tdoc XMLType;
v_tsubdoc XMLType;
v_result CLOB;
v_doc DBMS_XMLDOM.DOMDOCUMENT;
v_subdoc DBMS_XMLDOM.DOMDOCUMENT;
v_doc_elem DBMS_XMLDOM.DOMELEMENT;
v_subdoc_elem DBMS_XMLDOM.DOMELEMENT;
v_node DBMS_XMLDOM.DOMNODE;
v_impnode DBMS_XMLDOM.DOMNODE;
BEGIN
-- Create the Main XML document
SELECT XMLType(DBMS_XMLGEN.GETXML('SELECT * FROM departments
WHERE department_id =20'))
INTO v_tdoc FROM dual;
-- Create the Sub XML document
SELECT XMLType(DBMS_XMLGEN.GETXML('SELECT * FROM employees
WHERE department_id = 20'))
INTO v_tsubdoc from dual;
-- Merge the documents
v_doc := DBMS_XMLDOM.NEWDOMDOCUMENT(v_tdoc);
v_subdoc :=DBMS_XMLDOM.NEWDOMDOCUMENT(v_tsubdoc);
v_doc_elem := DBMS_XMLDOM.GETDOCUMENTELEMENT(v_doc);
v_subdoc_elem := DBMS_XMLDOM.GETDOCUMENTELEMENT(v_subdoc);
v_impnode := DBMS_XMLDOM.IMPORTNODE(v_doc,
DBMS_XMLDOM.MAKENODE(v_subdoc_elem), true) ;
v_node := DBMS_XMLDOM.APPENDCHILD(
DBMS_XMLDOM.MAKENODE(v_doc_elem), v_impnode);
--v_node := DBMS_XMLDOM.APPENDCHILD(
-- DBMS_XMLDOM.MAKENODE(v_doc_elem),
-- DBMS_XMLDOM.MAKENODE(v_subdoc_elem));
DBMS_LOB.CREATETEMPORARY(v_result,true,DBMS_LOB.SESSION);
DBMS_XMLDOM.WRITETOCLOB(v_doc,v_result);
SELECT v_result INTO :result FROM dual;
DBMS_LOB.FREETEMPORARY(v_result);
END;
This example uses the DBMS_XMLDOM.IMPORTNODE() and DBMS_XMLDOM.APPENDCHILD() functions to create the merged document. This is the suggested and standards-compliant way to add content from one document to another via the DOM. We explore the DBMS_XMLDOM package further in Chapter 26.