openplanning

Phân tích XML trong Oracle PL/SQL

  1. Giới thiệu
  2. XML Parse API
  3. Các nguồn dữ liệu XML (XML Data Sources)
  4. Phân tích XML từ nguồn TEXT hoặc CLOB
  5. Phân tích file XML
  6. Phân tích XML sử dụng Dbms_Xslprocessor

1. Giới thiệu

Tài liệu được viết dựa trên:
  • Oracle 9i (10g,11g, 12c)

2. XML Parse API

Oracle cung cấp 2 API cơ bản để phân tích (parse) XML:
  • DOM (Document Object Model)
  • XSLT & XPath

3. Các nguồn dữ liệu XML (XML Data Sources)

Bạn có thể phân tích một tài liệu XML với nguồn dữ liệu từ:
  • Nguồn dữ liệu XML là một file.
  • Nguồn dữ liệu là văn bản (varchar2,..)
  • Nguồn dữ liệu là CLOB

4. Phân tích XML từ nguồn TEXT hoặc CLOB

Ví dụ phân tích một tài liệu XML đơn giản (Nguồn văn bản):
Parse_Xml_Example
Create Or Replace Procedure Parse_Xml_Example As
 p              Dbms_Xmlparser.Parser;
 v_Doc          Dbms_Xmldom.Domdocument;
 v_Root_Element Dbms_Xmldom.Domelement;
 v_Child_Nodes  Dbms_Xmldom.Domnodelist;
 v_Child_Node   Dbms_Xmldom.Domnode;
 v_Text_Node    Dbms_Xmldom.Domnode;
 v_Emp_Nodes    Dbms_Xmldom.Domnodelist;
 v_Emp_Node     Dbms_Xmldom.Domnode;
 ---
 v_Xml_Data Varchar2(4000);
 v_Deptno   Varchar2(30);
 v_Dname    Varchar2(100);
 v_Location Varchar2(255);
 v_Empno    Varchar2(30);
 v_Ename    Varchar2(100);
 v_Job      Varchar2(100);
 v_Hiredate Date;
 v_Mrg      Number;
 v_Sal      Number;
 --
 v_Attr_Nodes     Dbms_Xmldom.Domnamednodemap;
 v_Attr_Node      Dbms_Xmldom.Domnode;
 v_Attribute_Name Varchar2(50);
 v_Node_Name      Varchar2(50);
 v_Node_Value     Varchar2(100);
Begin
 -- Text không chứa <?xml version="1.0"?>
 v_Xml_Data := '<department deptno="10" dname="ACCOUNTING" location="NEW YORK">
                 <employee empno="7782" ename="CLARK">
                    <job>MANAGER</job>
                    <mrg>7839</mrg>
                    <hiredate>6/9/1981</hiredate>
                    <sal>2450.00</sal>
                 </employee>
                 <employee empno="7839" ename="KING">
                    <job>PRESIDENT</job>
                    <mrg></mrg>
                    <hiredate>11/17/1981</hiredate>
                    <sal>5000.00</sal>
                 </employee>
                 <employee empno="7934" ename="MILLER">
                    <job>CLERK</job>
                    <mrg>7782</mrg>
                    <hiredate>1/23/1982</hiredate>
                    <sal>1300.00</sal>
                 </employee>
              </department>';
 -- Tạo một bộ phân tích XML
 p := Dbms_Xmlparser.Newparser;
 Dbms_Xmlparser.Setvalidationmode(p
                                 ,False);
 -- Parse XML into DOM object                      
 Dbms_Xmlparser.Parsebuffer(p
                           ,v_Xml_Data);
 -- Lấy ra đối tượng Dbms_xmldom.Domdocument
 -- (Phần tử mô tả toàn bộ tài liệu)                    
 v_Doc := Dbms_Xmlparser.Getdocument(p);
 -- Phần tử gốc của tài liệu (<department>)
 v_Root_Element := Dbms_Xmldom.Getdocumentelement(v_Doc);
 -- Lấy giá trị của các thuộc tính
 v_Deptno   := Dbms_Xmldom.Getattribute(v_Root_Element
                                       ,'deptno');
 v_Dname    := Dbms_Xmldom.Getattribute(v_Root_Element
                                       ,'dname');
 v_Location := Dbms_Xmldom.Getattribute(v_Root_Element
                                       ,'location');
 ---------                              
 Dbms_Output.Put_Line('v_Deptno=' || v_Deptno);
 Dbms_Output.Put_Line('v_Dname=' || v_Dname);
 Dbms_Output.Put_Line('v_Location=' || v_Location);
 --------                              
 -- Danh sách các Node "employee" của v_Root_Element (Dbms_xmldom.Domnodelist)
 v_Emp_Nodes := Dbms_Xmldom.Getelementsbytagname(v_Root_Element
                                                ,'employee');
 For j In 0 .. Dbms_Xmldom.Getlength(v_Emp_Nodes) Loop
    v_Emp_Node := Dbms_Xmldom.Item(v_Emp_Nodes
                                  ,j);
    -- Attribute List (Dbms_xmldom.Domnamednodemap)                              
    v_Attr_Nodes := Dbms_Xmldom.Getattributes(v_Emp_Node);
    --
    If (Dbms_Xmldom.Isnull(v_Attr_Nodes) = False) Then
       For i In 0 .. Dbms_Xmldom.Getlength(v_Attr_Nodes) - 1 Loop
          v_Attr_Node := Dbms_Xmldom.Item(v_Attr_Nodes
                                         ,i);
          v_Node_Name := Dbms_Xmldom.Getnodename(v_Attr_Node);
          --
          If v_Node_Name = 'empno' Then
             v_Empno := Dbms_Xmldom.Getnodevalue(v_Attr_Node);
          Elsif v_Node_Name = 'ename' Then
             v_Ename := Dbms_Xmldom.Getnodevalue(v_Attr_Node);
          End If;
       End Loop;
       Dbms_Output.Put_Line('v_Empno=' || v_Empno);
       Dbms_Output.Put_Line('v_Ename=' || v_Ename);
    End If;
    ----
    -- Child nodes of employee node.
    --
    v_Child_Nodes := Dbms_Xmldom.Getchildnodes(v_Emp_Node);
    --
    For i In 0 .. Dbms_Xmldom.Getlength(v_Child_Nodes) - 1 Loop
       -- <job>,<mrg>,<hiredate>,<sal>
       v_Child_Node := Dbms_Xmldom.Item(v_Child_Nodes
                                       ,i);
       v_Node_Name  := Dbms_Xmldom.Getnodename(v_Child_Node);
       v_Text_Node  := Dbms_Xmldom.Getfirstchild(v_Child_Node);
      
       v_Node_Value := Dbms_Xmldom.Getnodevalue(v_Text_Node);
       --
       If v_Node_Name = 'job' Then
          v_Job := v_Node_Value;
       Elsif v_Node_Name = 'mrg' Then
          v_Mrg := To_Number(v_Node_Value);
       Elsif v_Node_Name = 'hiredate' Then
          v_Hiredate := To_Date(v_Node_Value
                               ,'MM/dd/yyyy');
       Elsif v_Node_Name = 'sal' Then
          v_Sal := To_Number(v_Node_Value);
       End If;
    End Loop;
    --
    Dbms_Output.Put_Line('v_Job=' || v_Job);
    Dbms_Output.Put_Line('v_Mrg=' || v_Mrg);
    Dbms_Output.Put_Line('v_Hiredate=' || v_Hiredate);
    Dbms_Output.Put_Line('v_Sal=' || v_Sal);
 End Loop;
End;
Chạy thủ tục:
begin
 -- Call the procedure
 parse_xml_example;
end;
Bạn cũng có thể phân tích XML từ nguồn CLOB.
Declare
  p               Dbms_Xmlparser.Parser;
  v_Xml_Clob      Clob;
  v_Doc           Dbms_Xmldom.Domdocument;
  v_Root_Element  Dbms_Xmldom.Domelement;
  v_Child_Nodes   Dbms_Xmldom.Domnodelist;
  v_Greeting_Node Dbms_Xmldom.Domnode;
  v_Text_Node     Dbms_Xmldom.Domnode;
  v_Text          Varchar2(100);
Begin
  -- Một dữ liệu CLOB (Có thể từ cột CLOB của bảng).
  v_Xml_Clob := '<data><greeting>Hello</greeting></data>';
 
  -- Tạo một bộ phân tích XML
  p := Dbms_Xmlparser.Newparser;
 
  -- Phân tích dữ liệu XML thành các đối tượng DOM.                          
  Dbms_Xmlparser.Parseclob(p
                          ,v_Xml_Clob);
  -- Lấy ra đối tượng Dbms_xmldom.Domdocument
  -- (Phần tử mô tả toàn bộ tài liệu)                    
  v_Doc := Dbms_Xmlparser.Getdocument(p);
 
  -- Phần tử gốc của tài liệu (<data>)
  v_Root_Element := Dbms_Xmldom.Getdocumentelement(v_Doc);
 
  -- Danh sách các node con 'greeting'.
  v_Child_Nodes := Dbms_Xmldom.Getchildrenbytagname(v_Root_Element
                                                   ,'greeting');
 
  -- Phần từ đầu tiên trong danh sách.
  v_Greeting_Node := Dbms_Xmldom.Item(v_Child_Nodes
                                     ,0);
  v_Text_Node     := Dbms_Xmldom.Getfirstchild(v_Greeting_Node);
  -- Hello
  v_Text := Dbms_Xmldom.Getnodevalue(v_Text_Node);
  --
  Dbms_Output.Put_Line('Greeting:' || v_Text);
End;

5. Phân tích file XML

Trước hết bạn cần tạo ra một thư mục ảo, và gán quyền truy cập vào file trên thư mục đó.
-- Tạo ra một thư mục ảo (Thư mục DBA)

Create Directory MY_XML_DIR as 'C:/TEMP';

-- Gán quyền đọc và ghi trên thư mục này cho user scott.

Grant Read,Write on Directory MY_XML_DIR to scott;
Ví dụ dưới đây phân tích một file xml:
C:/TEMP/company.xml
<company id="111" companyName="Microsoft">
<websites>
  <website>http://microsoft.com</website>
  <website>http://msn.com</website>
  <website>http://hotmail.com</website>
</websites>
<address>
  <street>1 Microsoft Way</street>
  <city>Redmond</city>
</address>
</company>
Parse_Xml_File_Example
Create Or Replace Procedure Parse_Xml_File_Example As
  v_Bfile    Bfile;
  v_Xml_Clob Clob;
  ---
  p              Dbms_Xmlparser.Parser;
  v_Doc          Dbms_Xmldom.Domdocument;
  v_Root_Element Dbms_Xmldom.Domelement;
  v_Child_Nodes  Dbms_Xmldom.Domnodelist;
  v_Child_Node   Dbms_Xmldom.Domnode;
  v_Text_Node    Dbms_Xmldom.Domnode;
  v_Text         Varchar2(100);
  ----
  v_Dest_Offset  Integer := 1;
  v_Src_Offset   Integer := 1;
  v_Lang_Context Number := Dbms_Lob.Default_Lang_Ctx;
  v_Warning      Integer;
  --
  v_Value Varchar2(255);
Begin
 
  -- Đối tượng đại diện cho file XML.
  v_Bfile := Bfilename('MY_XML_DIR'
                      ,'company.xml');
 
  -- Tạo dữ liệu CLOB rỗng
  Dbms_Lob.Createtemporary(v_Xml_Clob
                          ,Cache => False);
 
  -- Mở file XML                          
  Dbms_Lob.Open(v_Bfile
               ,Dbms_Lob.Lob_Readonly);
 
  -- Tải dữ liệu trong file vào v_Clob.
  Dbms_Lob.Loadclobfromfile(v_Xml_Clob -- Dest_Lob IN OUT  
                           ,v_Bfile -- Src_Lob In  
                           ,Dbms_Lob.Getlength(v_Bfile) -- Amount In  
                           ,v_Dest_Offset -- Dest_Offset IN OUT
                           ,v_Src_Offset -- Src_Offset In Out
                           ,Dbms_Lob.Default_Csid -- Bfile_Csid In  
                           ,v_Lang_Context -- Lang_Context In Out
                           ,v_Warning -- Warning OUT  
                            );
 
  -- Sau khi đọc xong, đóng lại.
  Dbms_Lob.Close(v_Bfile);
  --
 
  -- Tạo một bộ phân tích XML
  p := Dbms_Xmlparser.Newparser;
  --
  
  -- Phân tích dữ liệu XML thành các đối tượng DOM.                          
  Dbms_Xmlparser.Parseclob(p
                          ,v_Xml_Clob);
 
  -- Lấy ra đối tượng Dbms_xmldom.Domdocument
  -- (Phần tử mô tả toàn bộ tài liệu)                    
  v_Doc := Dbms_Xmlparser.Getdocument(p);
 
  -- Phần tử gốc của tài liệu (<company>)
  v_Root_Element := Dbms_Xmldom.Getdocumentelement(v_Doc);
  --
  v_Value := Dbms_Xmldom.Getattribute(v_Root_Element
                                     ,'id');
  Dbms_Output.Put_Line('id=' || v_Value);
  v_Value := Dbms_Xmldom.Getattribute(v_Root_Element
                                     ,'companyName');
  Dbms_Output.Put_Line('companyName=' || v_Value);
  --- return Dbms_Xmldom.Domnodelist
  v_Child_Nodes := Dbms_Xmldom.Getchildrenbytagname(v_Root_Element
                                                   ,'*');
  For i In 0 .. Dbms_Xmldom.Getlength(v_Child_Nodes) - 1 Loop
     v_Child_Node := Dbms_Xmldom.Item(v_Child_Nodes
                                     ,i);
     --
     If Dbms_Xmldom.Getnodename(v_Child_Node) = 'websites' Then
        Dbms_Output.Put_Line('Found websites');
        -- ...
     Elsif Dbms_Xmldom.Getnodename(v_Child_Node) = 'address' Then
        Dbms_Output.Put_Line('Found address');
        -- ...
     End If;
  End Loop;
Exception
  When Others Then
     Dbms_Output.Put_Line(Sqlerrm);
     Dbms_Lob.Freetemporary(v_Xml_Clob);
     Dbms_Xmlparser.Freeparser(p);
     Dbms_Xmldom.Freedocument(v_Doc);
End;
Chạy thủ tục:
begin
  -- Call the procedure
  parse_xml_file_example;
end;

6. Phân tích XML sử dụng Dbms_Xslprocessor

Oracle Cung cấp cho bạn package Dbms_Xslprocessor giúp bạn truy cập vào dữ liệu XML nhanh chóng và dễ dàng hơn . Hãy xem ví dụ sau:
Declare
   v_Xml_Clob Clob;
   ---
   p                Dbms_Xmlparser.Parser;
   v_Doc            Dbms_Xmldom.Domdocument;
   v_Root_Element   Dbms_Xmldom.Domelement;
   v_Child_Nodes    Dbms_Xmldom.Domnodelist;
   v_Current_Node   Dbms_Xmldom.Domnode;
   v_Websites_Nodes Dbms_Xmldom.Domnodelist;
   --
   v_Id           Number;
   v_Company_Name Varchar2(255);
   v_Street       Varchar2(255);
   v_City         Varchar2(50);
   v_Note         Varchar2(255);
Begin
   v_Xml_Clob := '<companies xmlns:my-ns="http://somedomain.com/abc">
                    <company id="111" companyName="Microsoft">
                       <websites>
                          <website>http://microsoft.com</website>
                          <website>http://msn.com</website>
                          <website>http://hotmail.com</website>
                       </websites>
                       <address>
                          <street>1 Microsoft Way</street>
                          <city>Redmond</city>
                       </address>
                       <my-ns:note>Microsoft Note</my-ns:note>
                    </company>
                    <company id="100" companyName="Apple">
                       <websites>
                          <website>http://applet.com</website>
                       </websites>
                       <address>
                          <street>1 Infinite Loop</street>
                          <city>Cupertino</city>
                       </address>
                       <my-ns:note>Apple Note</my-ns:note>
                    </company>
                 </companies>';
   --
   -- Tạo một bộ phân tích XML
   p := Dbms_Xmlparser.Newparser;
   -- 
   -- Phân tích dữ liệu XML thành các đối tượng DOM.                          
   Dbms_Xmlparser.Parseclob(p
                           ,v_Xml_Clob);
   -- Lấy ra đối tượng Dbms_xmldom.Domdocument
   -- (Phần tử mô tả toàn bộ tài liệu)                    
   v_Doc := Dbms_Xmlparser.Getdocument(p);
   -- Phần tử gốc của tài liệu (<companies>)
   v_Root_Element := Dbms_Xmldom.Getdocumentelement(v_Doc);
   --- return Dbms_Xmldom.Domnodelist
   v_Child_Nodes := Dbms_Xmldom.Getchildrenbytagname(v_Root_Element
                                                    ,'*');
   For i In 0 .. Dbms_Xmldom.Getlength(v_Child_Nodes) - 1 Loop
      -- <company> Node.
      v_Current_Node := Dbms_Xmldom.Item(v_Child_Nodes
                                        ,i);
      Dbms_Xslprocessor.Valueof(v_Current_Node
                               ,'@id'
                               ,v_Id -- OUT
                                );
      Dbms_Xslprocessor.Valueof(v_Current_Node
                               ,'@companyName'
                               ,v_Company_Name -- OUT
                                );
      Dbms_Xslprocessor.Valueof(v_Current_Node
                               ,'address/street/text()'
                               ,v_Street -- OUT
                                );
      Dbms_Xslprocessor.Valueof(v_Current_Node
                               ,'address/city/text()'
                               ,v_City -- OUT
                                );
                             
      -- Với trường hợp element có namespace
      -- Cần phải thêm tham số thứ 4.                     
      Dbms_Xslprocessor.Valueof(v_Current_Node
                               ,'my-ns:note/text()'
                               ,v_Note -- OUT
                               ,'xmlns:my-ns=http://somedomain.com/abc');
      Dbms_Output.Put_Line('v_Id=' || v_Id);
      Dbms_Output.Put_Line('v_company_Name=' || v_Company_Name);
      Dbms_Output.Put_Line(' - v_street=' || v_Street);
      Dbms_Output.Put_Line(' - v_city=' || v_City);
      Dbms_Output.Put_Line(' - v_Note=' || v_Note);
 
      -- Lựa chọn các Node phù hợp với đường dẫn.
      -- return Dbms_Xmldom.Domnodelist
      v_Websites_Nodes := Dbms_Xslprocessor.Selectnodes(v_Current_Node
                                                       ,'websites/website');
      For j In 0 .. Dbms_Xmldom.Getlength(v_Websites_Nodes) - 1 Loop
         v_Current_Node := Dbms_Xmldom.Item(v_Websites_Nodes
                                           ,j);
         Dbms_Output.Put_Line(' - website=' ||
                              Dbms_Xslprocessor.Valueof(v_Current_Node
                                                       ,'text()'));
      End Loop;
   End Loop;
Exception
   When Others Then
      Dbms_Output.Put_Line(Sqlerrm);
      Dbms_Lob.Freetemporary(v_Xml_Clob);
      Dbms_Xmlparser.Freeparser(p);
      Dbms_Xmldom.Freedocument(v_Doc);
End;
Chạy ví dụ:
Nếu phần tử (element) của bạn có sử dụng namespace:
<companies xmlns:my-ns="http://somedomain.com/abc">

    <company id="111" companyName="Microsoft">

       .....

       <my-ns:note>Microsoft Note</my-ns:note>

    </company>

    ....

 </companies>
Bạn cần phải thêm tham số chỉ định namespace:
Dbms_Xslprocessor.Valueof(v_Current_Node
                       ,'my-ns:note/text()'
                       ,v_Note -- OUT
                       ,'xmlns:my-ns=http://somedomain.com/abc');
Ngược lại bạn sẽ nhận được lỗi:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00601: Invalid token in: 'my-ns:note/text()'