Return to ODS MARKUP

Base SAS

An XBRL Tagset for XML

XBRL is an XML format that is used for financial data exchange. More information about XBRL can be found at the XBRL Web site.

In SAS 9.1 and later, you can create XBRL XML from SAS. It is not difficult, but you must understand how this new tagset can be adapted for your own needs. Be aware that the downloads xbrl.tpl and were created in 2004. Because the XBRL specification continues to evolve, you probably need to modify the tagset and XML map.

The Simplest Form

This tagset works with the Output Delivery System (ODS) and PROC PRINT, or with the LIBNAME engine. Using ODS is the simplest way. The prerequisite is that you have two data sets: one with the XML document's metadata, and one with the all of the items. See the PROC PRINT output of an example.

As long as your column names match the names shown in the PROC PRINT, then everything will work just fine. Your job would look something like this:

ods tagsets.xbrl file="print_Cash_Flow_xbrl.xml";

ods html file="print_xbrl.html";

proc print data=CashFlow.Header; run;
proc print data=CashFlow.Items; run;

ods _all_ close;

Changing the Column Names

Generally the names of the data columns is not a problem, because the generation of XBRL will be part of a bigger solution. The solution can easily create the data sets with the names this tagset uses. If the data sets are created from an XBRL file using the LIBNAME's XML map, then the names will match automatically. The XML map link may not display properly in your browser. Download it directly or with the XBRL examples (.zip).

The output could be horribly malformed if the column names of your data sets don't match the column names expected by the tagset. You can change the names in your data set, or you can change them in the tagset by creating a new one.

This tagset was written to make this easy. Below is a template for creating a new tagset with different column names. All that is necessary is that the name on the right-hand side matches the column name in your data set. It is also possible to change the name spaces that will be printed in the first group tag. If no changes are needed in an event, then that event definition can be deleted from your new tagset.

proc template;

define tagset tagsets.myxbrl;

    /*-- These first four events define                             --*/
    /*-- a dictionary that can be redefined if the data             --*/
    /*-- names don't match those that we are expecting.  Just       --*/
    /*-- change the value on the right to match your name.          --*/
    /*--------------------------------------------------------4Feb 04-*/
    define event report_columns;
        /*-- These are the columns for the outermost group tags.    --*/
        /*-- report_type is the type of report.  Most of these      --*/
        /*-- only occur once, in the first data set, as a definition--*/
        /*-- for the overall report. Group type is used in the      --*/
        /*-- outermost group tag, and report type is used as the    --*/
        /*-- type on the secondary group tag which identifies the   --*/
        /*-- report type - cashflow, conditions, etc.               --*/
        /*----------------------------------------------------3Feb 04-*/
        set $names['report_type']          'report_type';
        set $names['group_schemaLocation'] 'group_schemaLocation';
        set $names['group_entity']         'group_entity';
        set $names['group_units']          'group_units';
        set $names['group_scaleFactor']    'group_scaleFactor';
        set $names['group_precision']      'group_precision';
        set $names['group_decimalPattern'] 'group_decimalPattern';
        set $names['group_formatName']     'group_formatName';
        /* Change These as needed ----------^^^^^^^^^^^^^^^^^^^^^ */

    define event group_columns;
        /*-- group_type and group_id define each group.  The first  --*/
        /*-- data set has both a report_type and a group_type, which--*/
        /*-- creates two group tags.                                --*/
        /*----------------------------------------------------3Feb 04-*/
        set $names['group_type']  'group_type';
        set $names['group_id']    'group_id';
        /* Change These as needed -^^^^^^^^^^ */

    define event label_columns;
        /*-- Sub-groups generally only have a type, and this label information.--*/
        /*----------------------------------------------------3Feb 04-*/
        set $names['label']       'label';
        set $names['label_href']  'label_href';
        set $names['label_lang']  'label_lang';
        /* Change These as needed -^^^^^^^^^^^ */

    define event item_columns;
        /*-- Items have a period and a value...                     --*/
        /*----------------------------------------------------3Feb 04-*/
        set $names['item']        'item';
        set $names['item_period'] 'item_period';
        /* Change These as needed -^^^^^^^^^^^^ */

    /*-- Set your name spaces here.  put name space URLs in either  --*/
    /*-- the generic_name_spaces list or the prefixed_name_spaces   --*/
    /*-- dictionary.  The prefixed dictionary will produce an entry --*/
    /*-- based on the key and the value, where the key becomes a    --*/
    /*-- part of the attribute name like this: xlmns:key="value".   --*/
    /*-- Override this event as needed.                             --*/
    /*--------------------------------------------------------4Feb 04-*/
    define event define_name_spaces;
        /* xlmns="" */
        set $generic_name_spaces[] "";

        /* xlmns:ci="" */
        set $prefixed_name_spaces['ci'] "";

    /*-- This is the name of the data set which holds the items.    --*/
    /*--                                                            --*/
    /*-- This is only required if using the LIBNAME engine.         --*/
    /*-- PROC PRINT and ODS do not care about this.                 --*/
    /*--                                                            --*/
    /*-- The easiest way to change these is by creating a new       --*/
    /*-- tagset that overrides this event.                          --*/
    /*--------------------------------------------------------4Feb 04-*/
    define event items_dataset_name;
        set $Items_data           'Items';


A Round-Trip, Using XML Map and the XBRL Tagset

Right out of the box, you can read an XBRL file with the XML map and then write it straight back out using the XBRL tagset. Here's the complete job:

%inc "xbrl.tpl";

/* load the xbrl file into two data sets */

filename  CashFlow 'Cash_Flow_xbrl.xml';
filename  SXLEMAP  '';
libname   CashFlow xml xmlmap=SXLEMAP access=READONLY;

/* create another XML file from the data sets. */
ods tagsets.xbrl file="print_Cash_Flow_xbrl.xml";

/* create an HTML report too */
ods html file="print_xbrl.html";

proc print data=CashFlow.Header; run;
proc print data=CashFlow.Items; run;

ods _all_ close;


With the tools in this topic, you should be able to create XBRL XML output from SAS. This solution might not work perfectly for everyone. But with some tweaking it should come close. All of the files needed for these examples are available for download in a .zip file. You could also use the ideas from this tagset to create new tagsets to output DDI XML or XMLA. The possibilities are endless.

Your Turn

Send e-mail to with your comments.