Search code examples
phparrayssimplexmlphp-7.3

Using SimpleXML to build XBRL, how to implement namespace


I am using SimpleXML to construct XML to be used as XBRL.

Question:

How can I implement namespace on child element the correct way, using SimpleXML as base?

Observations:

  • Missing namespace (thus there are no [xbrli:xbrl], [se-cd-base:CompanyName].

  • Missing encoding string.


My code:

<?php

$test_array = [
  'TheCompany' => 'CompanyName'
];

$xml = new SimpleXMLElement('<xbrli/>');
array_walk_recursive($test_array, array ($xml, 'addChild'));
print $xml->asXML();

Result

<?xml version="1.0"?>
<xbrli>
  <CompanyName>
    TheCompany
  </CompanyName>
</xbrli>

Wanted result (XBRL)

<?xml version="1.0" encoding="UTF-8"?>

  <xbrli:xbrl xmlns:link = "http://www.xbrl.org/2003/linkbase">

  <link:schemaRef
    xlink:type="simple"
    xlink:href="http://xbrl.taxonomier.se/se/fr/gaap/k2/risbs/2017-09-30/se-k2-risbs-2017-09-30.xsd"/
    >

  <se-cd-base:CompanyName
    contextRef="period0">
    TheCompany
  </se-cd-base:CompanyName>

</xbrli:xbrl>

Solution

  • When using namespaces in an XML document, you need to think about three things:

    • The namespace URI. This is the globally unique identifier that tools will recognise as being the same namespace (the URI doesn't have to point anywhere, it's just a way of organising who "owns" the identifier).
    • The local prefix. This is an arbitrary string which a particular document, or even part of a document, associates with a particular namespace URI, basically just to keep things more compact. This is the part before the : in a tag like <xbrli:xbrl>. There is also a default namespace for each part of a document, for elements with no prefix.
    • The element or attribute name within that namespace. This is the part after the : in a tag like <xbrli:xbrl>.

    I mention all this to understand why the sample XML you provide is invalid, because it looks like you want to use four namespaces:

    1. Namespace http://www.xbrl.org/2003/linkbase which you have given the local prefix link
    2. An unknown namespace which you have given the local prefix xbrli; I'll call this one http://example.org/xbrli
    3. An unknown namespace which you have given the local prefix se-cd-base; I'll call this one http://example.org/se-cd-base
    4. An unknown namespace which you have given the local prefix xlink; I'll call this one http://example.org/xlink (unless this was a typo and should have been another reference to http://www.xbrl.org/2003/linkbase?)

    Now let's try to construct a valid version of your XML using SimpleXML...

    First, we need to create the root element, which is in the http://example.org/xbrli namespace; SimpleXML doesn't have a way to create a document without any nodes, so we have to write the first node by hand and parse it:

    // Using xbrli as prefix for http://example.org/xbrli
    $xml = new SimpleXMLElement('<xbrli xmlns="http://example.org/xbrli"/>');
    // Or using http://example.org/xbrli as the default namespace for the document
    $xml = new SimpleXMLElement('<xbrli xmlns="http://example.org/xbrli"/>');
    

    Next, we want a child element schemaRef in the http://www.xbrl.org/2003/linkbase namespace. We do this by passing the namespace as the third parameter to addChild, and including the prefix in the element name if we want one:

    // Using link as the prefix for http://www.xbrl.org/2003/linkbase
    $schemaRef = $xml->addChild('link:schemaRef', null, 'http://www.xbrl.org/2003/linkbase');
    // Or making http://www.xbrl.org/2003/linkbase the default namespace for this section
    $schemaRef = $xml->addChild('schemaRef', null, 'http://www.xbrl.org/2003/linkbase');
    

    Next, we want to add attributes in the http://example.org/xlink namespace. The arguments to addAttribute are similar to the above, but the prefix is mandatory:

    $schemaRef->addAttribute('xlink:type', 'simple', 'http://example.org/xlink');
    $schemaRef->addAttribute('xlink:href', 'http://xbrl.taxonomier.se/se/fr/gaap/k2/risbs/2017-09-30/se-k2-risbs-2017-09-30.xsd', 'http://example.org/xlink');
    

    Now repeat for the CompanyName element; note that unprefixed attributes have a rather odd definition in the namespaces spec but we'll keep it as per your example:

    $CompanyName = $xml->addChild('se-cd-base:CompanyName', 'The Company', 'http://example.org/se-cd-base');
    // Again, we can declare a default namespace rather than a prefix:
    $CompanyName = $xml->addChild('CompanyName', 'The Company', 'http://example.org/se-cd-base');
    // Attribute with no namespace
    $CompanyName->addAttribute('contextRef', 'period0');
    

    Now put it all together, and check with echo $xml->asXML(); and we get something like this (whitespace added manually):

    <?xml version="1.0"?>
    <xbrli xmlns="http://example.org/xbrli">
        <link:schemaRef
            xmlns:link="http://www.xbrl.org/2003/linkbase" 
            xmlns:xlink="http://example.org/xlink"
            xlink:type="simple"
            xlink:href="http://xbrl.taxonomier.se/se/fr/gaap/k2/risbs/2017-09-30/se-k2-risbs-2017-09-30.xsd"
        />
        <se-cd-base:CompanyName 
            xmlns:se-cd-base="http://example.org/se-cd-base" 
            contextRef="period0"
        >The Company
        </se-cd-base:CompanyName>
    </xbrli>
    

    Or an equivalent document using default namespaces rather than prefixes:

    <?xml version="1.0"?>
    <xbrli xmlns="http://example.org/xbrli">
        <schemaRef 
            xmlns="http://www.xbrl.org/2003/linkbase"
            xmlns:xlink="http://example.org/xlink"
            xlink:type="simple"
            xlink:href="http://xbrl.taxonomier.se/se/fr/gaap/k2/risbs/2017-09-30/se-k2-risbs-2017-09-30.xsd"
        />
        <CompanyName
            xmlns="http://example.org/se-cd-base"
            contextRef="period0"
        >
        The Company
        </CompanyName>
    </xbrli>