Archivo de la etiqueta: XML

Extraer datos del XML Facturae

Introducción

Oracle permite acceder a datos de un XML desde una sentencia SELECT. Para ello utiliza el estandar XPath descrito por la W3C. El acceso a los datos del XML se puede realizar mediante el uso de la función EXTRACT, que ha sido deprecada a partir de Oracle 11g Release 2, y la función XMLQuery que viene a reemplazarla y a ampliar sus posibilidades.

Un ejemplo muy sencillo podría ser el siguiente:

<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
    <node01 Attr01="Atribute 01">Node 01 text</node01>
    <node02>Node 02 text</node02>
</root>

Con la función EXTRACT de Oracle:

select dbms_xmlgen.convert(
         xmltype('<?xml version="1.0" encoding="ISO-8859-1"?>
                  <root>
                    <node01 Attr01="Atribute 01">Node 01 text</node01>
                    <node02>Node 02 text</node02>
                  </root>').extract( '/root/node01/text()' ).getstringval(), 1
       ) as "Extract node01"
from dual;
Extract node01
--------------
Node 01 text

Con la función XMLQuery de Oracle:

select xmlcast(
         xmlquery( '/root/node01/text()'
                   PASSING xmltype('<?xml version="1.0" encoding="ISO-8859-1"?>
                                    <root>
                                      <node01 Attr01="Atribute 01">Node 01 text</node01>
                                      <node02>Node 02 text</node02>
                                    </root>')
                   RETURNING CONTENT
                 ) as VARCHAR2(4000)
       ) as "XMLQuery node01"
from dual;
XMLQuery node01
---------------
Node 01 text

El modelo de factura XML en España: Facturae y FACe

Facturae es un esquema XML de factura electrónica diseñado en primer lugar por la Asociación CCI (Centro de Cooperación Interbancaria) y posteriormente asumido por la Agencia Tributaria española, con el objetivo de fijar un estándar nacional para el intercambio de facturas electrónicas. A día de hoy nos encontramos en la versión 3 del estándar, del que se han publicado varias revisiones. La más reciente es la revisión 3.2.1.

El pasado 15 de enero de 2015 se produjo un importante paso en la implantación de Facturae como modelo de referencia en España. Este día entró en vigor la obligatoriedad de enviar factura electrónica a la administración pública. Para ello, los distintos niveles de administración se han dotado de puntos generales de entrada de facturas como FACe, el punto de entrada de la administración central, y otros sistemas desarrollados por administraciones autonómicas o locales. Estos puntos de entrada admiten generalmente el modelo Facturae versión 3, con distintos grados de aceptación de las revisiones que se han liberado del mismo.

Otro factor importante que se ha introducido, junto a los puntos de entrada de facturas, es la designación del receptor de la factura mediante el sistema DIR3. Este sistema utiliza tres códigos para designar a todos los organismos que forman parte de la administración pública. Para informar los códigos DIR3 en la factura electrónica se han utilizado los nodos de centros administrativos del comprador disponibles en el modelo Facturae.

Oracle, SQL y el modelo XML Facturae

Como ya se ha comentado en la introducción, Oracle permite consultar datos de una estructura XML desde SQL mediante el estándar XPath. Los ejemplos que se muestran a continuación extraen los datos más relevantes del formato Facturae v3.x de cara a las integraciones con FACe y el uso de DIR3:

  • CIF del comprador
  • Nombre del comprador
  • Datos DIR3: Oficina Contable (OC), Órgano Gestor (OG) y Unidad Tramitadora (UT)
  • Número de la factura
  • Serie de la factura
  • Fecha de la factura
  • FileReference o Referencia de registro

Para estos ejemplos se ha utilizado el XML que propone la web oficial del estandar Facturae, incluyendo algunas modificaciones para poder mostrar el tratamiento de los centros administrativos que marca DIR3.

SELECT + EXTRACT

Ejemplo de select directa sobre una tabla con el XML en un campo Clob y la función EXTRACT:

select dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/TaxIdentification/TaxIdentificationNumber/text()').getstringval(), 1) as "CIF Comprador",
       decode( xmltype(data_clob).extract( '//Parties/BuyerParty/TaxIdentification/PersonTypeCode/text()').getstringval(),
               'J', dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/LegalEntity/CorporateName/text()').getstringval(), 1),
               dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/Individual/Name/text()').getstringval(), 1) || ' ' ||
               dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/Individual/FirstSurname/text()').getstringval(), 1)
             ) as "Nombre Comprador",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/AdministrativeCentres/AdministrativeCentre[RoleTypeCode = "01"]/CentreCode/text()').getstringval(), 1) as "Oficina Cont.",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/AdministrativeCentres/AdministrativeCentre[RoleTypeCode = "02"]/CentreCode/text()').getstringval(), 1) as "Órgano Ges.",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Parties/BuyerParty/AdministrativeCentres/AdministrativeCentre[RoleTypeCode = "03"]/CentreCode/text()').getstringval(), 1) as "Unidad Tr.",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Invoices/Invoice[1]/InvoiceHeader/InvoiceNumber/text()').getstringval(), 1) as "Número",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Invoices/Invoice[1]/InvoiceHeader/InvoiceSeriesCode/text()').getstringval(), 1) as "Serie",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Invoices/Invoice[1]/InvoiceIssueData/IssueDate/text()').getstringval(), 1) as "Fecha",
       dbms_xmlgen.convert(xmltype(data_clob).extract( '//Invoices/Invoice[1]/Items/InvoiceLine[1]/FileReference/text()').getstringval(), 1) as "Referencia"
from test_xml;
CIF Comprador   Nombre Comprador   Oficina Cont.   Órgano Ges.   Unidad Tr.   Número   Serie   Fecha        Referencia
0000000000B     Juana Mauriño      E00000012       E00000034     E00000033    18       -       2010-03-10   000298172

La función DBMS_XMLGEN.CONVERT permite codificar y decodificar las entidades XML (p. ej: la secuencia «& amp;» para el carácter &). Los valores que admite el parámetro flag son:

  -- DBMS_XMLGEN.CONVERT conversion types
  ENTITY_ENCODE CONSTANT conversionType := 0;
  ENTITY_DECODE CONSTANT conversionType := 1;

SELECT + XMLQUERY

Ejemplo de select directa sobre tabla con el XML en un campo Clob y la función XMLQuery:

select xmlcast(xmlquery( '//Parties/BuyerParty/TaxIdentification/TaxIdentificationNumber/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "CIF Comprador",
       xmlcast(xmlquery( '//Parties/BuyerParty/Individual/Name/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) ||' '||
       xmlcast(xmlquery( '//Parties/BuyerParty/Individual/FirstSurname/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Nombre Comprador",
       xmlcast(xmlquery( '//Parties/BuyerParty/AdministrativeCentres/AdministrativeCentre[RoleTypeCode = "01"]/CentreCode/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Oficina Cont.",
       xmlcast(xmlquery( '//Parties/BuyerParty/AdministrativeCentres/AdministrativeCentre[RoleTypeCode = "02"]/CentreCode/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Órgano Ges.",
       xmlcast(xmlquery( '//Parties/BuyerParty/AdministrativeCentres/AdministrativeCentre[RoleTypeCode = "03"]/CentreCode/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Unidad Tr.",
       xmlcast(xmlquery( '//Invoices/Invoice[1]/InvoiceHeader/InvoiceNumber/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Número",
       xmlcast(xmlquery( '//Invoices/Invoice[1]/InvoiceHeader/InvoiceSeriesCode/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Serie",
       xmlcast(xmlquery( '//Invoices/Invoice[1]/InvoiceIssueData/IssueDate/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Fecha",
       xmlcast(xmlquery( '//Invoices/Invoice[1]/Items/InvoiceLine[1]/FileReference/text()' PASSING xmltype(data_clob) RETURNING CONTENT) as VARCHAR2(4000)) as "Referencia"
from test_xml;
CIF Comprador   Nombre Comprador   Oficina Cont.   Órgano Ges.   Unidad Tr.   Número   Serie   Fecha        Referencia
0000000000B     Juana Mauriño      E00000012       E00000034     E00000033    18       -       2010-03-10   000298172

Si el XML se encuentra en una columna de tipo Blob, debemos indicar el ID del juego de caracteres al utilizar XMLType:

select dbms_xmlgen.convert(xmltype(data_blob, NLS_CHARSET_ID('AL32UTF8')).extract('//FileHeader/SchemaVersion/text()').getstringval(), 1) as "Versión"
from test_xml;

Recursos

Ejemplo de XML Facturae: http://descemp.pistigu.com/recursos/ejemplos/facturae-v3-2-ejemplo-dir3/

Anuncio publicitario

PL/SQL generate XML output

Para generar XML desde PL/SQL existen diversos métodos que van desde una select que utilice funciones XML_TYPE al empleo de los paquetes PL/SQL integrados en Oracle. En nuestro ejemplo vamos a utilizar DBMS_XMLDOM para montar un XML como este:

<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
  <node01 Attr01="Atribute 01" Attr02="Atribute 02">Node 01 text</node01>
  <node02>Node 02 text</node02>
</root>

Paquete de utilidades XML

El primer paso consiste en compilar el siguiente paquete en la base de datos. Esto nos va a permitir agilizar la creación de los nodos utilizando una serie de tablas para registrar cada nodo y el procedimiento de de escritura de estos nodos en el XML que estemos generando:

CREATE OR REPLACE PACKAGE create_xml IS
 
-- Declare XML types
   type ry_xmlattrib is record (
                                p_attrib_name     varchar2(512),
                                p_attrib_value    varchar2(32767)
                               );
   type ty_xmlattrib is table of ry_xmlattrib;
 
   type ry_xmlelement is record (
                                 p_parent         dbms_xmldom.DOMNode,
                                 p_element_name   varchar2(512),
                                 p_element        dbms_xmldom.DOMElement,
                                 p_attribs        ty_xmlattrib,
                                 p_node           dbms_xmldom.DOMNode,
                                 p_text_value     varchar2(32767),
                                 p_text           dbms_xmldom.DOMText,
                                 p_textnode       dbms_xmldom.DOMNode
                                );
   type ty_xmlelement is table of ry_xmlelement;

   type ty_varchar2 is table of varchar2(32767) index by varchar2(64);
 

-- Add node to ty_xmlelement
    procedure add_node(
                       p_tb_xmlelements  in out  ty_xmlelement,
                       p_tb_index_nodes  in out  ty_varchar2,
                       p_index_id        in      varchar2,
                       p_parent_index_id in      varchar2,
                       p_node_name       in      varchar2,
                       p_text_value      in      varchar2,
                       p_attribs         in      ty_xmlattrib
                      );
-- Write node to XML
   procedure write_node(
                        p_domdoc    in out   dbms_xmldom.DOMDocument,
                        p_reg_nodo  in out   ry_xmlelement
                       );
 
END create_xml;

CREATE OR REPLACE PACKAGE BODY create_xml AS

-- Add node to ty_xmlelement
procedure add_node(
                   p_tb_xmlelements  in out  ty_xmlelement,
                   p_tb_index_nodes  in out  ty_varchar2,
                   p_index_id        in      varchar2,
                   p_parent_index_id in      varchar2,
                   p_node_name       in      varchar2,
                   p_text_value      in      varchar2,
                   p_attribs         in      ty_xmlattrib
                  )
is
begin

   -- Add node
   p_tb_xmlelements.extend;
   p_tb_index_nodes( p_index_id ) := p_tb_xmlelements.last;
   p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_attribs := create_xml.ty_xmlattrib();
   p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_parent := p_tb_xmlelements( p_tb_index_nodes( p_parent_index_id ) ).p_node;
   p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_element_name := p_node_name;
   p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_text_value := p_text_value;
   
   -- Add attribs
   if p_attribs is not null then
      p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_attribs := create_xml.ty_xmlattrib();
      for i_att in 1..p_attribs.count() loop
          p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_attribs.extend();
          p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_attribs(i_att).p_attrib_name := p_attribs(i_att).p_attrib_name;
          p_tb_xmlelements( p_tb_index_nodes( p_index_id ) ).p_attribs(i_att).p_attrib_value := p_attribs(i_att).p_attrib_value;
      end loop;
   end if;
   
end add_node;

-- Write node to XML
procedure write_node(
                     p_domdoc    in out   dbms_xmldom.DOMDocument,
                     p_reg_nodo  in out   ry_xmlelement
                    )
is
begin
   
   -- Write node
   p_reg_nodo.p_element := dbms_xmldom.createElement(p_domdoc, p_reg_nodo.p_element_name );
     
   if p_reg_nodo.p_attribs is not null then
       if p_reg_nodo.p_attribs.count() > 0 then
          for i_att in 1..p_reg_nodo.p_attribs.count() loop
             dbms_xmldom.setAttribute(
                                      p_reg_nodo.p_element, 
                                      p_reg_nodo.p_attribs(i_att).p_attrib_name, 
                                      p_reg_nodo.p_attribs(i_att).p_attrib_value
                                     );
          end loop;
       end if;
   end if;
     
   p_reg_nodo.p_node := dbms_xmldom.appendChild(p_reg_nodo.p_parent,dbms_xmldom.makeNode(p_reg_nodo.p_element));
   p_reg_nodo.p_text := dbms_xmldom.createTextNode(p_domdoc, p_reg_nodo.p_text_value );
   p_reg_nodo.p_textnode := dbms_xmldom.appendChild(p_reg_nodo.p_node,dbms_xmldom.makeNode(p_reg_nodo.p_text));

end write_node;

END create_xml;

Ejemplo

El siguiente procedimiento utiliza el paquete create_xml para generar el XML de ejemplo con el que se ha iniciado esta entrada:

-- Generate XML (example)
CREATE OR REPLACE PROCEDURE xml_example
IS
 
  l_domdoc dbms_xmldom.DOMDocument;
  l_root_node dbms_xmldom.DOMNode;
  
  tb_xmlelements create_xml.ty_xmlelement := create_xml.ty_xmlelement();
  tb_index create_xml.ty_varchar2;
  tb_attribs create_xml.ty_xmlattrib := create_xml.ty_xmlattrib();
    
  v_buffer varchar2(32767);
  
BEGIN
  
   -- Create an empty XML document
   l_domdoc := dbms_xmldom.newDomDocument;
  
   -- Set version
   dbms_xmldom.setVersion(l_domdoc, '1.0');
   dbms_xmldom.setCharset(l_domdoc, 'ISO-8859-1');
  
   -- Create a root node
   l_root_node := dbms_xmldom.makeNode(l_domdoc);
   tb_xmlelements.extend;
   tb_index('root') := tb_xmlelements.last;

   tb_xmlelements(tb_index('root')).p_attribs      := create_xml.ty_xmlattrib();
   tb_xmlelements(tb_index('root')).p_parent       := l_root_node;
   tb_xmlelements(tb_index('root')).p_element_name := 'root';
   tb_xmlelements(tb_index('root')).p_text_value   := '';
       
   create_xml.write_node(l_domdoc, tb_xmlelements(tb_index('root')));
       
   -- Create a new node with text and attributes
   tb_attribs.extend();
   tb_attribs(1).p_attrib_name := 'Attr01';
   tb_attribs(1).p_attrib_value := 'Atribute 01';
  
   tb_attribs.extend();
   tb_attribs(2).p_attrib_name := 'Attr02';
   tb_attribs(2).p_attrib_value := 'Atribute 02';

   create_xml.add_node(tb_xmlelements, tb_index, 'node01', 'root', 'node01', 'Node 01 text', tb_attribs );
   create_xml.write_node(l_domdoc, tb_xmlelements(tb_index('node01')));
       
   -- Create a additional node with text
   tb_attribs.delete;
   create_xml.add_node(tb_xmlelements, tb_index, 'node02', 'root', 'node02', 'Node 02 text', tb_attribs );
   create_xml.write_node(l_domdoc, tb_xmlelements(tb_index('node02')));

   -- Result
   dbms_xmldom.writeToBuffer(l_domdoc, v_buffer);
   dbms_output.put_line(v_buffer);
   dbms_xmldom.freeDocument(l_domdoc);
 
END xml_example;

Encoding

Hay que tener especial cuidado con la declaración del encoding del XML. La forma de declararlo varía dependiendo del destino al que se vaya a enviar el XML: salida dbms_output, un XMLType o un Lob. En algunos conextos, en lugar de utilizar el procedimiento dbms_xmldom.setCharset(l_domdoc, ‘ISO-8859-1’) hay que manipular el procedimiento setVersion de la siguiente forma:

dbms_xmldom.setVersion(l_domdoc, '1.0" encoding="ISO-8859-1');