XML namespaces

http://www.w3schools.com/xml/xml_namespaces.asp

based on mailing list topics here and here

Dealing with xml namespaces can be a bit difficult in Bots. They can greatly complicate grammars and mappings. Often your EDI partner will want to send or receive XML with namespaces, but actually Bots does not need (or want) them as there are rarely any naming conflicts within a single XML file!

Where an XML file has only one namespace, usually the "default" namespace is used.
eg. xmlns="schemas-hiwg-org-au:EnvelopeV1.0

If the file has multiple namespaces, then namespace prefixes are used. These prefixes can be any value.
eg. xmlns:ENV="schemas-hiwg-org-au:EnvelopeV1.0"

There are several ways to deal with namespaces in Bots:

1. Ignore incoming XML namespace

For incoming XML files that are to be mapped into something else, you probably don't need the namespaces at all. In my opinion this is the "best" way. See Ignore incoming XML namespace for an example preprocessing script to achieve this.

2. Use incoming XML namespace

When a namespace must be used, it is needed in many places (structure, recorddefs, mapping) but you want to specify the namespace string, which can be quite long, only in one place (DRY principle). This has several benefits:

  • If it needs to change, this is easy to do in one place only
  • grammar and mapping will be smaller, and look neater

For example, the incoming XML file may look like ...

<?xml version="1.0"?>
<Orders xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns="http://www.company.com/EDIOrders"
     targetNamespace="http//www.company.com/EDIOrders">
      <Order>
        <OrderNumber>239062415</OrderNumber>
        <DateOrdered>2014-02-24</DateOrdered>
        <LineCount>10</LineCount>
etc...

If you created your grammar with xml2botsgrammar, it probably has the namespace repeated over and over in structure and recorddefs, like this...

structure=    [
{ID:'{http://www.company.com/EDIOrders}Orders',MIN:1,MAX:1,LEVEL:[
    {ID:'{http://www.company.com/EDIOrders}Order',MIN:1,MAX:99999,
        QUERIES:{
             'botskey':      {'BOTSID':'{http://www.company.com/EDIOrders}Order','{http://www.company.com/EDIOrders}OrderNumber':None},
            },
        LEVEL:[
        {ID:'{http://www.company.com/EDIOrders}OrderItems',MIN:0,MAX:1,LEVEL:[
            {ID:'{http://www.company.com/EDIOrders}OrderLine',MIN:1,MAX:99999},
            ]},
        ]},
    ]}
]

recorddefs = {
   '{http://www.company.com/EDIOrders}Orders':[
            ['BOTSID','M',256,'A'],
            ['{http://www.company.com/EDIOrders}Orders__targetNamespace','C',256,'AN'],
          ],
   '{http://www.company.com/EDIOrders}Order':[
            ['BOTSID','M',256,'A'],
            ['{http://www.company.com/EDIOrders}OrderNumber','C', 20,'AN'],
            ['{http://www.company.com/EDIOrders}DateOrdered','C', 10,'AN'],
            ['{http://www.company.com/EDIOrders}LineCount','C', 5,'R'],

# etc...

So, we want to improve this. In your grammar, first add the namespace as a string constant. This is the only place it should be specified, and everywhere else refers to it.

Note: If the XML has multiple namespaces, you can use the same technique and just add more constants (xmlns1, xmlns2, etc).
Also include it in the syntax dict. This allows us to reference it later in mappings.

xmlns='{http://www.company.com/EDIOrders}'

syntax = {
        'xmlns':xmlns,
}

In your grammar, replace all instances of the namespace string with the constant, so it looks like this...

structure=    [
{ID:xmlns+'Orders',MIN:1,MAX:1,LEVEL:[
    {ID:xmlns+'Order',MIN:1,MAX:99999,
        QUERIES:{
             'botskey':      {'BOTSID':xmlns+'Order',xmlns+'OrderNumber':None},
            },
        LEVEL:[
        {ID:xmlns+'OrderItems',MIN:0,MAX:1,LEVEL:[
            {ID:xmlns+'OrderLine',MIN:1,MAX:99999},
            ]},
        ]},
    ]}
]

recorddefs = {
   xmlns+'Orders':[
            ['BOTSID','M',256,'A'],
            [xmlns+'Orders__targetNamespace','C',256,'AN'],
          ],
   xmlns+'Order':[
            ['BOTSID','M',256,'A'],
            [xmlns+'OrderNumber','C', 20,'AN'],
            [xmlns+'DateOrdered','C', 10,'AN'],
            [xmlns+'LineCount','C', 5,'R'],

# etc...

Now in the mapping script, read the xmlns value from grammar.

import bots.grammar as grammar

    xmlns = grammar.grammarread('xml',inn.ta_info['messagetype']).syntax['xmlns']

Then use it wherever needed in the mapping, like this...

    # Get OrderNumber from XML with namespace
    OrderNumber = inn.get({'BOTSID':xmlns+'Order',xmlns+'OrderNumber':None})

3. Outgoing XML with only a default namespace

This can be done by defining xmlns as a tag, and setting it in mapping. It eliminates the need to use namespace prefix on every record and field.
example grammar

recorddefs = {
    'shipment':
        [
        ['BOTSID', 'M', 20, 'AN'],
        ['shipment__xmlns', 'C', 80, 'AN'],   # xmlns is added as a tag (note double underscore)
        ['ediCustomerNumber', 'M', 12, 'N'],
        ['ediParm1', 'M', 1, 'N'],
        ['ediParm2', 'M', 1, 'AN'],
        ['ediParm3', 'M', 1, 'AN'],
        ['ediReference', 'M', 35, 'AN'],
        ['ediFunction1', 'M', 3, 'AN'],
        ['ediCustomerSearchName', 'M', 20, 'AN'],
        ],
}

example mapping

    # xmlns tag for shipment
    out.put({'BOTSID':'shipment','shipment__xmlns':'http://www.company.com/logistics/shipment'})

example output

<?xml version="1.0" encoding="utf-8" ?>
<shipment xmlns="http://www.company.com/logistics/shipment">
    <ediCustomerNumber>191</ediCustomerNumber>
    <ediParm1>4</ediParm1>
    <ediParm2>s</ediParm2>
    <ediParm3>d</ediParm3>
    <ediReference>SCN1022164911</ediReference>
    <ediFunction1>9</ediFunction1>
    <ediCustomerSearchName>SCHA</ediCustomerSearchName>
</shipment>

4. Outgoing XML with default namespace prefixes (ns0, ns1, etc)

This is the default behaviour of the python elementtree module used in Bots. The actual prefix used is not important to XML meaning, so in theory your EDI partners should not care what prefixes you use.

example grammar

xmlns_env='{schemas-hiwg-org-au:EnvelopeV1.0}'
xmlns='{schemas-hiwg-org-au:InvoiceV3.0}'

syntax = {
        'xmlns_env':xmlns_env,
        'xmlns':xmlns,
        'merge':False,
        'indented':True,
}

nextmessage = ({'BOTSID':xmlns_env+'Envelope'},{'BOTSID':'Documents'},{'BOTSID':xmlns+'Invoice'})

structure = [
{ID:xmlns_env+'Envelope',MIN:0,MAX:99999,
    QUERIES:{
        'frompartner':{'BOTSID':xmlns_env+'Envelope',xmlns_env+'SenderID':None},
        'topartner':{'BOTSID':xmlns_env+'Envelope',xmlns_env+'RecipientID':None},
        },
    LEVEL:[
    {ID:'Documents',MIN:0,MAX:99999,LEVEL:[
        {ID:xmlns+'Invoice',MIN:0,MAX:99999,
            QUERIES:{
                'botskey':({'BOTSID':xmlns+'Invoice',xmlns+'DocumentNo':None}),
                },
            LEVEL:[
            {ID:xmlns+'Supplier',MIN:0,MAX:99999},
            {ID:xmlns+'Buyer',MIN:0,MAX:99999},
            {ID:xmlns+'Delivery',MIN:0,MAX:99999},
            {ID:xmlns+'Line',MIN:0,MAX:99999},
            {ID:xmlns+'Trailer',MIN:0,MAX:99999},
        ]},
    ]},
]},
]

example output

<?xml version="1.0" encoding="utf-8" ?>
<ns0:Envelope xmlns:ns0="schemas-hiwg-org-au:EnvelopeV1.0" xmlns:ns1="schemas-hiwg-org-au:InvoiceV3.0">
    <ns0:SenderID>sender</ns0:SenderID>
    <ns0:RecipientID>recipient</ns0:RecipientID>
    <ns0:DocumentCount>1</ns0:DocumentCount>
    <Documents>
        <ns1:Invoice>
            <ns1:TradingPartnerID>ID1</ns1:TradingPartnerID>
            <ns1:MessageType>INVOIC</ns1:MessageType>
            <ns1:VersionControlNo>3.0</ns1:VersionControlNo>
            <ns1:DocumentType>TAX INVOICE</ns1:DocumentType>

5. Outgoing XML with specific namespace prefixes

Your EDI partner may request a specific namespace prefix be used; This is technically un-necessary and bad design, but they may insist on it anyway!

example grammar

xmlns_env='{schemas-hiwg-org-au:EnvelopeV1.0}'
xmlns='{schemas-hiwg-org-au:InvoiceV3.0}'

syntax = {
        'xmlns_env':xmlns_env,
        'xmlns':xmlns,
        'namespace_prefixes':[('ENV',xmlns_env.strip('{}')),('INV',xmlns.strip('{}'))], # use ENV, INV instead of ns0, ns1
        'merge':False,
        'indented':True,
}

structure = [
{ID:xmlns_env+'Envelope',MIN:0,MAX:99999,
    QUERIES:{
        'frompartner':{'BOTSID':xmlns_env+'Envelope',xmlns_env+'SenderID':None},
        'topartner':{'BOTSID':xmlns_env+'Envelope',xmlns_env+'RecipientID':None},
        },
    LEVEL:[
    {ID:'Documents',MIN:0,MAX:99999,LEVEL:[
        {ID:xmlns+'Invoice',MIN:0,MAX:99999,
            QUERIES:{
                'botskey':({'BOTSID':xmlns+'Invoice',xmlns+'DocumentNo':None}),
                },
            LEVEL:[
            {ID:xmlns+'Supplier',MIN:0,MAX:99999},
            {ID:xmlns+'Buyer',MIN:0,MAX:99999},
            {ID:xmlns+'Delivery',MIN:0,MAX:99999},
            {ID:xmlns+'Line',MIN:0,MAX:99999},
            {ID:xmlns+'Trailer',MIN:0,MAX:99999},
        ]},
    ]},
]},
]

example output

<?xml version="1.0" encoding="utf-8" ?>
<ENV:Envelope xmlns:ENV="schemas-hiwg-org-au:EnvelopeV1.0" xmlns:INV="schemas-hiwg-org-au:InvoiceV3.0">
    <ENV:SenderID>sender</ENV:SenderID>
    <ENV:RecipientID>recipient</ENV:RecipientID>
    <ENV:DocumentCount>1</ENV:DocumentCount>
    <Documents>
        <INV:Invoice>
            <INV:TradingPartnerID>ID1</INV:TradingPartnerID>
            <INV:MessageType>INVOIC</INV:MessageType>
            <INV:VersionControlNo>3.0</INV:VersionControlNo>
            <INV:DocumentType>TAX INVOICE</INV:DocumentType>