How to use cl_xslt_processor to parse an XML document with namespaces?

Viewed 211

I have the following XML

<?xml version="1.0" encoding="UTF-8"?>
<!--Version #1.0-->
<NominaIndividual xmlns="dian:gov:co:facturaelectronica:NominaIndividual"
                  xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
                  xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
                  xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
                  xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  SchemaLocation=""
                  xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd">

    <Devengados>
        <Anticipos>
            <Anticipo>1.00</Anticipo>
            <Anticipo>2.00</Anticipo>
            <Anticipo>3.00</Anticipo>
        </Anticipos>
    </Devengados>

    <Deducciones>
        <Anticipos>
            <Anticipo>4.00</Anticipo>
            <Anticipo>5.00</Anticipo>
            <Anticipo>6.00</Anticipo>
        </Anticipos> 
    </Deducciones> 

</NominaIndividual>

This XML is condensed into an XSTRING. From this XSTRING I would like to retrieve all nodes I want, for example NominaIndividual/Devengados/Anticipos.

As of now, I can successfully do it with the code below, but ONLY if I completely remove all the namespaces from NominaIndividual:

    DATA lo_xslt  TYPE REF TO cl_xslt_processor.
    DATA lo_nodes TYPE REF TO if_ixml_node_collection.

     CREATE OBJECT lo_xslt.

    lo_xslt->set_source_xstring( iv_xml ).

    lo_xslt->set_expression( '//Devengados/Anticipos/Anticipo').  
    lo_xslt->run( '' ).

    lo_nodes    = lo_xslt->get_nodes( ).
    ro_iterator = lo_nodes->create_iterator( ).
    ro_node     = ro_iterator->get_next( ).

In this code, that last object ro_node contains all the information I need. But the thing is: this object is only created if I completely delete all the namespaces from NominaIndividual:

For example, if I modify the XML to this, it works:

<?xml version="1.0" encoding="UTF-8"?>
<!--Version #1.0-->
<NominaIndividual>
   ...
</NominaIndividual>

But this, that is the original XML, doesnt:

<?xml version="1.0" encoding="UTF-8"?>
<!--Version #1.0-->
<NominaIndividual xmlns="dian:gov:co:facturaelectronica:NominaIndividual"
                  xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
                  xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
                  xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
                  xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  SchemaLocation=""
                  xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd">
   ...
</NominaIndividual>

This said, how to I navigate through this XML tree via XPATH maintaining the namespaces? I know that the method cl_xslt_processor->set_expression has an argument about namespaces, but I don`t know how to use ir correctly.

2 Answers

You can use wildcard for parent elements like this:

DATA(l_xslt) = new cl_xslt_processor( ).

l_xslt->set_source_string(
` <?xml version="1.0" encoding="UTF-8"?> ` &&
` <NominaIndividual xmlns="dian:gov:co:facturaelectronica:NominaIndividual" ` &&
`                   xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" ` &&
`                   xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ` &&
...
`         </Anticipos>  ` &&
`    </Deducciones>  ` &&
` </NominaIndividual> ` ).

l_xslt->set_expression( expression = '//*[local-name()="Anticipo"]' ).
l_xslt->run( '' ).
DATA(nodes) = l_xslt->get_nodes( ).

DO nodes->get_length( ) TIMES.
  DATA(node) = nodes->get_item( sy-index - 1 ).
  WRITE node->get_value( ).
ENDDO.

The general rule when specifying namespaces in set_expression() method is via space:

l_xslt->set_expression(
  expression = 'sp1:a/sp2:foo'
  nsdeclarations = 'sp1 http://uri.com sp2 http://uri2.com sp3 http://uri3.com'
).

Finally, DO NOT use it. It's deprecated (p.24), use XSLT transformations instead

enter image description here

To work with namespaces, the method SET_EXPRESSION has the parameter nsdeclarations that you use to map the namespace prefixes used in the parameter expression to the actual namespace URIs. I extended the answer by adding an additional namespace demonstration:URI for Devengados to demonstrate how to indicate several namespaces. Note that, as per the XML standards, the namespace prefix can be anything, what is important is the namespace URI.

  DATA(iv_xml) =
  ` <?xml version="1.0" encoding="UTF-8"?> ` &&
  ` <NominaIndividual xmlns="dian:gov:co:facturaelectronica:NominaIndividual" ` &&
  `                   xmlns:demo="demonstration:URI" ` &&
  `                   xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" ` &&
  `                   xmlns:ds="http://www.w3.org/2000/09/xmldsig#">` &&
  `    <demo:Devengados>` &&
  `        <Anticipos>` &&
  `            <Anticipo>1.00</Anticipo>` &&
  `            <Anticipo>2.00</Anticipo>` &&
  `            <Anticipo>3.00</Anticipo>` &&
  `        </Anticipos>` &&
  `    </demo:Devengados>` &&
  ` </NominaIndividual> `.

  DATA(xml_utf8) = cl_abap_codepage=>convert_to( iv_xml ).

  DATA lo_xslt  TYPE REF TO cl_xslt_processor.
  DATA lo_nodes TYPE REF TO if_ixml_node_collection.

  CREATE OBJECT lo_xslt.

  lo_xslt->set_source_xstring( xml_utf8 ).

  lo_xslt->set_expression(
              expression     = `//pp:Devengados/dd:Anticipos/dd:Anticipo`
              nsdeclarations = `pp demonstration:URI `
                            && `dd dian:gov:co:facturaelectronica:NominaIndividual` ).
  lo_xslt->run( '' ).

  lo_nodes = lo_xslt->get_nodes( ).
  DO lo_nodes->get_length( ) TIMES.
    WRITE / lo_nodes->get_item( sy-index - 1 )->get_value( ).
  ENDDO.

will print:

1.00
2.00
3.00
Related