:fire: Fix warning.

:fire: Fix warning.

jsoniq version "1.0";

(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for querying XBRL Models of financial reports submitted to the SEC.
 :

 :
 :
Standard $options Parameter

 :
 :
Most functions in the BizQL package allow an additional $options
 :    parameter. The options parameter is a JSON object which is defined in the documentation
 :    of the facts module.

 :
 :
In addition to the option fields defined in the
 :    facts module the following
 :    fields can be used in the options of a function in the SEC profile:

 :
 :

 :
HideAmendedFacts: if set to true (default behavior) all facts that have been
 :     amended are not included in the result.

 :
IncludeImpliedTable: set to true in order to output the implied table if
 :     there is no Table (default is false).

 :

 :
 : @author Charles Hoffman
 : @author Matthias Brantner
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";

declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";

(:~
 : Scheme for the SEC entity identifiers (i.e. http://www.sec.gov/CIK)
 :)
declare variable $sec:CIK as xs:string := "http://www.sec.gov/CIK";

(:~
 : Joker value for all archives or all concepts.
 :)
declare variable $sec:ALL_OF_THEM as boolean := true;

(:~
 : All prefixes pre-declared in SEC's EDGAR.
 :)
declare variable $sec:EDGAR_PREFIXES :=
  ( "xbrli", "link", "xl", "xlink", "xbrldt", "xbrldi", "nonnum",
    "num", "ref", "us-gaap", "dei", "us-types", "invest", "country",
    "currency", "exch", "naics", "sic", "stpr", "rr", "rr-ent",
    "rr-cal", "rr-def", "rr-pre" );

(:~
 : All SEC characteristics that are always there with their defaults
 :)
declare variable $sec:DEFAULTS as object := {
    "dei:LegalEntityAxis" : {
      Default: "sec:DefaultLegalEntity",
      Domain: [ "sec:DefaultLegalEntity" ]
    },
    "sec:Accepted" : {
      Type: "xs:string"
    },
    "sec:FiscalYear" : {
      Type: "xs:integer"
    },
    "sec:FiscalPeriod" : {
      Type: "xs:string"
    }
  };

(:~
 :
Return all facts that match a given filter object optionally interpreted
 :    in the context of an optionally given hypercube.
 :    Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 : @param $options standard fact retrieving options.
 :
 : @error facts:INVALID-RULE-TYPE the type of a rule is not unknown/invalid
 : @error facts:RULE-EXECUTION-ERROR a rule raised an error whilst being executed
 : @error facts:FILTER-TOO-GENERIC The filter object must have at least one of the
 :        fields Archive, Aspects.xbrl:Concept, Aspects.xbrl:Period, or
 :        Aspects.xbrl:Entity.
 : @return all facts satisfying the filter and options.
 :)
declare function sec:facts-for(
  $options as object?) as object*
{
  let $facts := facts:facts-for($options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*) as object*
{
  sec:facts-for-archives-and-concepts($archive_or_ids, $concepts, ())
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 : @param $options standard SEC BizQL options.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*,
    $options as object?) as object*
{
  let $options := if(empty($options.Hypercube))
                  then facts:merge-objects(
                      {
                          "Hypercube" : sec:dimensionless-hypercube(())
                      },
                      $options,
                      true)
                  else $options
  let $facts :=
    facts:facts-for-archives-and-concepts(
      $archive_or_ids,
      $concepts,
      $options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Filters a list of facts and returns only the latest amending facts.
 :    Accordingly, all amended facts are filtered out.

 :
 : @param $facts a list of facts.
 : @param $options optional parameters:
 :

 :
HideAmendedFacts (boolean, true by default): activates or deactivates amendment hiding. If false,
 :     the function will just return the input facts.

 :

 :
 : @return a sequence of facts without amended facts.
 :)
declare function sec:hide-amended-facts(
    $facts as object*,
    $options as object?
)
as object*
{
  let $hide-amended-facts as boolean := ($options.HideAmendedFacts, true)[1]
  return
    if(not $hide-amended-facts)
    then $facts
    else
      for $grouped-facts in $facts
      group by
        $entity := $grouped-facts.Aspects."xbrl:Entity",
        $fiscal-period := $grouped-facts.Aspects."sec:FiscalPeriod",
        $fiscal-year := $grouped-facts.Aspects."sec:FiscalYear"
      return
        if (empty($fiscal-period) or empty($fiscal-year) or $fiscal-period instance of null or $fiscal-year instance of null)
        then
          (: $grouped-facts is already grouped by entity.
             Now, group by period as a fallback. :)
          for $regrouped-facts in $grouped-facts
          group by $period := $regrouped-facts.Aspects."xbrl:Period"
          return
            let $latest-accepted :=
              max(distinct-values($regrouped-facts.Aspects."sec:Accepted"))
            return
              if (empty($latest-accepted))
              then $regrouped-facts
              else
                (: hide amended facts :)
                $regrouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]

        else
          let $latest-accepted :=
            max(distinct-values($grouped-facts.Aspects."sec:Accepted"))
          return
            if (empty($latest-accepted))
            then $grouped-facts
            else
              (: hide amended facts :)
              $grouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]
};

(:~
 :
Return all facts for concepts that are defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having one of the following prefixes:

 :
 :

 :
xbrli

 :
link

 :
xl

 :
xlink

 :
xbrldt

 :
xbrldi

 :
nonnum

 :
num

 :
ref

 :
us-gaap

 :
dei

 :
us-types

 :
invest

 :
country

 :
currency

 :
exch

 :
naics

 :
sic

 :
stpr

 :
rr

 :
rr-ent

 :
rr-cal

 :
rr-def

 :
rr-pre

 :

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having one of the above prefixes.
 :)
declare function sec:edgar-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: false
          }
        }
      }
    })
};

(:~
 :
Return all facts for concepts that are NOT defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having none of the prefixes listed under function
 : sec:edgar-facts-for-archives.

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having none of the common edgar prefixes.
 :)
declare function sec:extension-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: true
          }
        }
      }
    })
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited, however
 :    you can restrict it using the options. If you need more
 :    XBRL dimensions, use the user-defined-hypercube function instead.

 :
In the SEC profile, there is an additional axis that is always here:
 : dei:LegalEntityAxis, with a default value sec:DefaultLegalEntity and a filtering
 : only accepting this default value.

 :
 : @return dimensionless hypercube instantiation.
 : @param $options: additional options among which:
 : - Concepts: an array of concept names to include in the hypercube.
 : - Periods: an array of periods to include in the hypercube.
 : - Entities: an array of EIDs to include in the hypercube.
 : - Units: an array of units to include in the hypercube.
 : - LegalEntities: an array of legal entities to include in the hypercube.
 :)
declare function sec:dimensionless-hypercube($options as object?) as object
{
  sec:user-defined-hypercube(
    {|
      if(exists($options.Concepts))
      then { "xbrl:Concept" : { Domain: $options.Concepts } }
      else (),
      if(exists($options.Entities))
      then { "xbrl:Entity" : { Domain: [ entities:eid($options.Entities[]) ] } }
      else (),
      if(exists($options.Periods))
      then { "xbrl:Period" : { Domain: $options.Periods } }
      else (),
      if(exists($options.Units))
      then { "xbrl:Unit" : { Domain: $options.Units } }
      else (),
      if(exists($options.LegalEntities))
      then { "dei:LegalEntityAxis" : { Domain: $options.LegalEntities } }
      else ()
    |}
  )
};

(:~
 :
Returns an instantiation of a Hypercube with no extra dimensions besides the legal entity axis.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The characteristic dei:LegalEntityAxis is also here. It has
 :    a default value of sec:DefaultLegalEntity and restricts the value
 :    space to this default value.

 :
 :
 : @return the hypercube instantiation.
 :)
declare function sec:user-defined-hypercube() as object
{
  sec:user-defined-hypercube(())
};

(:~
 :
Returns an instantiation of a Hypercube containing the specified dimensions.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The following dimensions are always present, but the values can be overriden:

 :
{
 :     "dei:LegalEntityAxis" : {
 :       Default: "sec:DefaultLegalEntity", -- overridable with HasDefault : false
 :       Domain: [ "sec:DefaultLegalEntity" ] -- overridable with Domain: null
 :     },
 :     "sec:Accepted" : {
 :       Type: "string"
 :     },
 :     "sec:FiscalYear" : {
 :       Type: "integer"
 :     },
 :     "sec:FiscalPeriod" : {
 :       Type: "string"
 :     }
 :    }

 :
 : @return the hypercube instantiation.
 : @param $dimensions
an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have four (optional) fields:

 :

 :
Type (string, absent by default): the name of a type (string, integer, ...). This creates a typed dimension (if absent, an explicit dimension).

 :
Default (a string, absent by default): must be either castable to Type, or null (explicit dimensions: must be a string with a member name, null not allowed).

 :
HasDefault (a boolean, true by default): if false, the dimension has no default even if Default is present (this includes the possibility to override predefined dimensions).

 :
Domain (null or an array of atomics, absent by default): restricts the domain of the dimension to the specified values, that must all be castable to Type or null (or all strings with member names for explicit dimensions). Don't forget to include the default value in there if any. If null is used instead of an array, this removes any predefined restrictions.
.
 :

 :)
declare function sec:user-defined-hypercube($dimensions as object?) as object
{
  let $dimensions := facts:merge-objects($dimensions, $sec:DEFAULTS, true)
  let $extended-dimensions := {|
    for $d in keys($dimensions)
    let $dimension-spec := $dimensions.$d

    let $dimension-default as atomic? :=
      if(not $dimension-spec.HasDefault eq false)
      then $dimension-spec.Default
      else ()

    let $dimension-domain as array? :=
        typeswitch($dimension-spec.Domain)
        case null return ()
        default return $dimension-spec.Domain

    return {
      $d : {|
        { Default : $dimension-default }[exists($dimension-default)],
        { Type : $dimension-spec.Type }[exists($dimension-spec.Type)],
        { Domain : $dimension-domain }[exists($dimension-domain)]
      |}
    }
  |}
  return hypercubes:user-defined-hypercube($extended-dimensions)
};

jsoniq version "1.0";

(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for querying XBRL Models of financial reports submitted to the SEC.
 :

 :
 :
Standard $options Parameter

 :
 :
Most functions in the BizQL package allow an additional $options
 :    parameter. The options parameter is a JSON object which is defined in the documentation
 :    of the facts module.

 :
 :
In addition to the option fields defined in the
 :    facts module the following
 :    fields can be used in the options of a function in the SEC profile:

 :
 :

 :
HideAmendedFacts: if set to true (default behavior) all facts that have been
 :     amended are not included in the result.

 :
IncludeImpliedTable: set to true in order to output the implied table if
 :     there is no Table (default is false).

 :

 :
 : @author Charles Hoffman
 : @author Matthias Brantner
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace report-schemas = "http://28.io/modules/xbrl/report-schemas";

declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";

(:~
 : Scheme for the SEC entity identifiers (i.e. http://www.sec.gov/CIK)
 :)
declare variable $sec:CIK as xs:string := "http://www.sec.gov/CIK";

(:~
 : Joker value for all archives or all concepts.
 :)
declare variable $sec:ALL_OF_THEM as boolean := true;

(:~
 : All prefixes pre-declared in SEC's EDGAR.
 :)
declare variable $sec:EDGAR_PREFIXES :=
  ( "xbrli", "link", "xl", "xlink", "xbrldt", "xbrldi", "nonnum",
    "num", "ref", "us-gaap", "dei", "us-types", "invest", "country",
    "currency", "exch", "naics", "sic", "stpr", "rr", "rr-ent",
    "rr-cal", "rr-def", "rr-pre" );

(:~
 : All SEC characteristics that are always there with their defaults
 :)
declare variable $sec:DEFAULTS as object := {
    "dei:LegalEntityAxis" : {
      Default: "sec:DefaultLegalEntity",
      Domain: [ "sec:DefaultLegalEntity" ]
    },
    "sec:Accepted" : {
      Type: "xs:string"
    },
    "sec:FiscalYear" : {
      Type: "xs:integer"
    },
    "sec:FiscalPeriod" : {
      Type: "xs:string"
    }
  };

(:~
 :
Return all facts that match a given filter object optionally interpreted
 :    in the context of an optionally given hypercube.
 :    Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 : @param $options standard fact retrieving options.
 :
 : @error facts:INVALID-RULE-TYPE the type of a rule is not unknown/invalid
 : @error facts:RULE-EXECUTION-ERROR a rule raised an error whilst being executed
 : @error facts:FILTER-TOO-GENERIC The filter object must have at least one of the
 :        fields Archive, Aspects.xbrl:Concept, Aspects.xbrl:Period, or
 :        Aspects.xbrl:Entity.
 : @return all facts satisfying the filter and options.
 :)
declare function sec:facts-for(
  $options as object?) as object*
{
  let $facts := facts:facts-for($options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*) as object*
{
  sec:facts-for-archives-and-concepts($archive_or_ids, $concepts, ())
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 : @param $options standard SEC BizQL options.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*,
    $options as object?) as object*
{
  let $options := if(empty($options.Hypercube))
                  then facts:merge-objects(
                      {
                          "Hypercube" : sec:dimensionless-hypercube(())
                      },
                      $options,
                      true)
                  else $options
  let $facts :=
    facts:facts-for-archives-and-concepts(
      $archive_or_ids,
      $concepts,
      $options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Filters a list of facts and returns only the latest amending facts.
 :    Accordingly, all amended facts are filtered out.

 :
 : @param $facts a list of facts.
 : @param $options optional parameters:
 :

 :
HideAmendedFacts (boolean, true by default): activates or deactivates amendment hiding. If false,
 :     the function will just return the input facts.

 :

 :
 : @return a sequence of facts without amended facts.
 :)
declare function sec:hide-amended-facts(
    $facts as object*,
    $options as object?
)
as object*
{
  let $hide-amended-facts as boolean := ($options.HideAmendedFacts, true)[1]
  return
    if(not $hide-amended-facts)
    then $facts
    else
      for $grouped-facts in $facts
      group by
        $entity := $grouped-facts.Aspects."xbrl:Entity",
        $fiscal-period := $grouped-facts.Aspects."sec:FiscalPeriod",
        $fiscal-year := $grouped-facts.Aspects."sec:FiscalYear"
      return
        if (empty($fiscal-period) or empty($fiscal-year) or $fiscal-period instance of null or $fiscal-year instance of null)
        then
          (: $grouped-facts is already grouped by entity.
             Now, group by period as a fallback. :)
          for $regrouped-facts in $grouped-facts
          group by $period := $regrouped-facts.Aspects."xbrl:Period"
          return
            let $latest-accepted :=
              max(distinct-values($regrouped-facts.Aspects."sec:Accepted"))
            return
              if (empty($latest-accepted))
              then $regrouped-facts
              else
                (: hide amended facts :)
                $regrouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]

        else
          let $latest-accepted :=
            max(distinct-values($grouped-facts.Aspects."sec:Accepted"))
          return
            if (empty($latest-accepted))
            then $grouped-facts
            else
              (: hide amended facts :)
              $grouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]
};

(:~
 :
Return all facts for concepts that are defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having one of the following prefixes:

 :
 :

 :
xbrli

 :
link

 :
xl

 :
xlink

 :
xbrldt

 :
xbrldi

 :
nonnum

 :
num

 :
ref

 :
us-gaap

 :
dei

 :
us-types

 :
invest

 :
country

 :
currency

 :
exch

 :
naics

 :
sic

 :
stpr

 :
rr

 :
rr-ent

 :
rr-cal

 :
rr-def

 :
rr-pre

 :

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having one of the above prefixes.
 :)
declare function sec:edgar-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: false
          }
        }
      }
    })
};

(:~
 :
Return all facts for concepts that are NOT defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having none of the prefixes listed under function
 : sec:edgar-facts-for-archives.

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having none of the common edgar prefixes.
 :)
declare function sec:extension-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: true
          }
        }
      }
    })
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited, however
 :    you can restrict it using the options. If you need more
 :    XBRL dimensions, use the user-defined-hypercube function instead.

 :
In the SEC profile, there is an additional axis that is always here:
 : dei:LegalEntityAxis, with a default value sec:DefaultLegalEntity and a filtering
 : only accepting this default value.

 :
 : @return dimensionless hypercube instantiation.
 : @param $options: additional options among which:
 : - Concepts: an array of concept names to include in the hypercube.
 : - Periods: an array of periods to include in the hypercube.
 : - Entities: an array of EIDs to include in the hypercube.
 : - Units: an array of units to include in the hypercube.
 : - LegalEntities: an array of legal entities to include in the hypercube.
 :)
declare function sec:dimensionless-hypercube($options as object?) as object
{
  sec:user-defined-hypercube(
    {|
      if(exists($options.Concepts))
      then { "xbrl:Concept" : { Domain: $options.Concepts } }
      else (),
      if(exists($options.Entities))
      then { "xbrl:Entity" : { Domain: [ entities:eid($options.Entities[]) ] } }
      else (),
      if(exists($options.Periods))
      then { "xbrl:Period" : { Domain: $options.Periods } }
      else (),
      if(exists($options.Units))
      then { "xbrl:Unit" : { Domain: $options.Units } }
      else (),
      if(exists($options.LegalEntities))
      then { "dei:LegalEntityAxis" : { Domain: $options.LegalEntities } }
      else ()
    |}
  )
};

(:~
 :
Returns an instantiation of a Hypercube with no extra dimensions besides the legal entity axis.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The characteristic dei:LegalEntityAxis is also here. It has
 :    a default value of sec:DefaultLegalEntity and restricts the value
 :    space to this default value.

 :
 :
 : @return the hypercube instantiation.
 :)
declare function sec:user-defined-hypercube() as object
{
  sec:user-defined-hypercube(())
};

(:~
 :
Returns an instantiation of a Hypercube containing the specified dimensions.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The following dimensions are always present, but the values can be overriden:

 :
{
 :     "dei:LegalEntityAxis" : {
 :       Default: "sec:DefaultLegalEntity", -- overridable with HasDefault : false
 :       Domain: [ "sec:DefaultLegalEntity" ] -- overridable with Domain: null
 :     },
 :     "sec:Accepted" : {
 :       Type: "string"
 :     },
 :     "sec:FiscalYear" : {
 :       Type: "integer"
 :     },
 :     "sec:FiscalPeriod" : {
 :       Type: "string"
 :     }
 :    }

 :
 : @return the hypercube instantiation.
 : @param $dimensions
an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have four (optional) fields:

 :

 :
Type (string, absent by default): the name of a type (string, integer, ...). This creates a typed dimension (if absent, an explicit dimension).

 :
Default (a string, absent by default): must be either castable to Type, or null (explicit dimensions: must be a string with a member name, null not allowed).

 :
HasDefault (a boolean, true by default): if false, the dimension has no default even if Default is present (this includes the possibility to override predefined dimensions).

 :
Domain (null or an array of atomics, absent by default): restricts the domain of the dimension to the specified values, that must all be castable to Type or null (or all strings with member names for explicit dimensions). Don't forget to include the default value in there if any. If null is used instead of an array, this removes any predefined restrictions.
.
 :

 :)
declare function sec:user-defined-hypercube($dimensions as object?) as object
{
  let $dimensions := facts:merge-objects($dimensions, $sec:DEFAULTS, true)
  let $extended-dimensions := {|
    for $d in keys($dimensions)
    let $dimension-spec := $dimensions.$d

    let $dimension-default as atomic? :=
      if(not $dimension-spec.HasDefault eq false)
      then $dimension-spec.Default
      else ()

    let $dimension-domain as array? :=
        typeswitch($dimension-spec.Domain)
        case null return ()
        default return $dimension-spec.Domain

    return {
      $d : {|
        { Default : $dimension-default }[exists($dimension-default)],
        { Type : $dimension-spec.Type }[exists($dimension-spec.Type)],
        { Domain : $dimension-domain }[exists($dimension-domain)]
      |}
    }
  |}
  return hypercubes:user-defined-hypercube($extended-dimensions)
};


:bug: Fix order field if double.

:bug: Fix order field if double.

jsoniq version "1.0";
(:
 : Copyright 2014 28msec Inc.
 :)

(:~
 :
This module provides functionality for retrieving concepts
 : and labels.

 :
 : @author Federico Cavalieri
 :)
module namespace concepts = "http://28.io/modules/xbrl/concepts";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace mw = "http://28.io/modules/xbrl/mongo-wrapper";

declare namespace xbrli = "http://www.xbrl.org/2003/instance";
declare namespace xlink = "http://www.w3.org/1999/xlink";
declare namespace link = "http://www.xbrl.org/2003/linkbase";

declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";

(:~
 : Name of the collection the concepts are stored in.
 :)
declare variable $concepts:col as xs:string := "concepts";

(:~
 : Name of the field pointing to the archive.
 :)
declare variable $concepts:ARCHIVE as xs:string := "Archive";

(:~
 : Name of the field pointing to the role.
 :)
declare variable $concepts:ROLE as xs:string := "Role";

(:~
 : Name of the field pointing to the name.
 :)
declare variable $concepts:NAME as xs:string := "Name";

(:~
 : Name of the field pointing to the labels.
 :)
declare variable $concepts:LABELS as xs:string := "Labels";

(:~
 : The default component link role.
 :)
declare variable $concepts:DEFAULT_COMPONENT_LINK_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/link";

(:~
 : Allow any component link role
 :)
declare variable $concepts:ANY_COMPONENT_LINK_ROLE as xs:string :=
    ":any component:";

(:~
 : A joker to ask for all concept names.
 :)
declare variable $concepts:ALL_CONCEPT_NAMES as xs:string := ":any concept:";

(:~
 : A joker to ask for all concept labels.
 :)
declare variable $concepts:ALL_CONCEPT_LABELS as xs:string := ":any concept label:";

(:~
 : The concept report element
 :)
declare variable $concepts:CONCEPT_REPORT_ELEMENT as xs:string := "xbrl:Concept";

(:~
 : The period report element
 :)
declare variable $concepts:PERIOD_REPORT_ELEMENT as xs:string := "xbrl:Period";

(:~
 : The entity report element
 :)
declare variable $concepts:ENTITY_REPORT_ELEMENT as xs:string := "xbrl:Entity";

(:~
 : The unit report element
 :)
declare variable $concepts:UNIT_REPORT_ELEMENT as xs:string := "xbrl:Unit";

(:~
 : The archive report element
 :)
declare variable $concepts:ARCHIVE_REPORT_ELEMENT as xs:string := "xbrl28:Archive";

(:~
 : The implied table report element
 :)
declare variable $concepts:IMPLIED_TABLE_REPORT_ELEMENT as xs:string := "xbrl28:ImpliedTable";

(:~
 : The non-numeric table report element
 :)
declare variable $concepts:NON_NUMERIC_REPORT_ELEMENT as xs:string := "xbrl:NonNumeric";

(:~
  The report elements that are built-in, that is, not in original filings.
:)
declare variable $concepts:BUILT_IN_REPORT_ELEMENTS as string* := (
    $concepts:CONCEPT_REPORT_ELEMENT,
    $concepts:ENTITY_REPORT_ELEMENT,
    $concepts:PERIOD_REPORT_ELEMENT,
    $concepts:UNIT_REPORT_ELEMENT,
    $concepts:ARCHIVE_REPORT_ELEMENT,
    $concepts:IMPLIED_TABLE_REPORT_ELEMENT,
    $concepts:NON_NUMERIC_REPORT_ELEMENT,
    "sec:Accepted",
    "sec:FiscalPeriod",
    "sec:FiscalPeriodType",
    "sec:FiscalYear",
    "sec:Accepted",
    "fsa:FiscalPeriod",
    "fsa:FiscalPeriodType",
    "fsa:FiscalYear",
    "fsa:Submitted"
);

(:~
 :
Retrieves all concepts.

 :
 : @return all concepts.
 :)
declare function concepts:concepts() as object*
{
  mw:find($concepts:col,{})
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*
  ) as object*
{
  concepts:concepts($concept-names, $archives-or-ids, $component-roles, $labels, ())
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 : @param $options one option OnlyNames to only output concepts with the Name field.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*,
    $options as object?
  ) as object*
{
  let $projection as object := if($options.OnlyNames eq true) then
  {
    Name: 1,
    IsAbstract: 1,
    IsTextBlock: 1
  } else {}
  let $archives := archives:aid($archives-or-ids)
  return
  if (exists($archives))
  then mw:find($concepts:col,
    {|
      {
        $concepts:ARCHIVE : { "$in" : [ $archives ] }
      },
      {
        $concepts:ROLE : { "$in" :
          [ $component-roles ]
        }
      }[not $component-roles = $concepts:ANY_COMPONENT_LINK_ROLE],
      {
        $concepts:NAME : { "$in" : [ $concept-names ] }
      }[not $concept-names = $concepts:ALL_CONCEPT_NAMES],
      {
        "Labels.Value" : { "$in" : [ $labels ] }
      }[not $labels = $concepts:ALL_CONCEPT_LABELS]
    |},
    $projection
  )
  else ()
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*
  ) as object*
{
  concepts:concepts(
    $concept-names,
    $archives-or-ids,
    $component-roles,
    $concepts:ALL_CONCEPT_LABELS
  )
};

(:~
 :
Retrieves the concepts which:
 :  - concept name matches a given one;
 :  - archive number matches that of a given component;
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts-for-components(
    $concept-names as string*,
    $labels as string*,
    $component-or-ids as item*,
    $options as object?) as object*
{
  for $component in components:components($component-or-ids)
  return concepts:concepts($concept-names,
                           $component.Archive[not $$ instance of null],
                           $component.Role,
                           $labels,
                           $options)
};

(:~
 :
Transforms a concept tree into XBRL arcs and locators.
 :    Those can then be added to an extended link.

 :
 : @param $trees the trees to transform.
 :
 : @return the locator and arcs elements
 :)
declare function concepts:tree-to-xbrl(
    $tree as object,
    $arcRole as string,
    $taxonomyName as string,
    $level as integer,
    $pos as integer
) as element()*
{
  let $children as object* := ($tree.To[], $tree.Members[])
  let $fromID as string := replace($tree.Name, ":", "_")
  let $fromLabel as string := $fromID || "_" || $level || "_" || $pos
  let $fromLocator as element() := <link:loc xlink:type="locator" xlink:href="{$taxonomyName}.xsd#{$fromID}" xlink:label="{$fromLabel}" xlink:title="{normalize-space($tree.Label)}" />

  return
  (
    $fromLocator,

    for $child at $pos in $children
    order by $child.Order ascending
    let $toID as string := replace($child.Name, ":", "_")
    let $toLabel as string := $toID || "_" || ($level + 1) || "_" || $pos
    let $order as decimal :=
      if(empty($child.Order) or $child.Order eq null)
      then 1
      else decimal($child.Order)
    let $arc as element() :=
      element {
        switch ($arcRole)
        case "http://www.xbrl.org/2003/arcrole/concept-label" return xs:QName("link:labelArc")
        case "http://www.xbrl.org/2003/arcrole/parent-child" return xs:QName("link:presentationArc")
        case "http://www.xbrl.org/2003/arcrole/summation-item" return xs:QName("link:calculationArc")
        case "http://xbrl.org/int/dim/arcrole/all"
        case "http://xbrl.org/int/dim/arcrole/hypercube-dimension"
        case "http://xbrl.org/int/dim/arcrole/dimension-domain"
        case "http://xbrl.org/int/dim/arcrole/dimension-default"
        case "http://xbrl.org/int/dim/arcrole/domain-member"
        return xs:QName("link:definitionArc")
        default return error(xs:QName("err:UnknownArcRole"), "arcrole " || $arcRole || " unknown or not supported.")
      }
      {
        attribute { "xlink:type" } { "arc" },
        attribute { "xlink:arcrole" } { $arcRole },
        attribute { "xlink:from" } { $fromLabel },
        attribute { "xlink:to" } { $toLabel },
        attribute { "use" } { "optional" },
        attribute { "order" } { $order },
        (attribute { "weight" } { $child.Weight } )[$arcRole eq "http://www.xbrl.org/2003/arcrole/summation-item"]
      }
    return
      (
        $arc,
        concepts:tree-to-xbrl($child, $arcRole, $taxonomyName, $level+1, $pos)
      )
  )
};

(:~
 :
Transforms concepts into XBRL definitions (XML Schema element definition).

 :
 : @param $concepts the concepts to transform.
 :
 : @return the XML Schema element definitions.
 :)
declare function concepts:to-xbrl(
    $concepts as object*) as element()*
{
  for $concept as object in $concepts
  where not $concept.Name = $concepts:BUILT_IN_REPORT_ELEMENTS
  let $name as string := substring-after($concept.Name, ":")
  let $id as string := replace($concept.Name, ":", "_")
  let $substitutionGroup as string := $concept.SubstitutionGroup
  let $nillable as boolean := (boolean($concept.IsNillable), false)[1]
  let $abstract as boolean := (boolean($concept.IsAbstract), false)[1]
  let $periodType as attribute()? := if(exists($concept.PeriodType)) then attribute { "xbrli:periodType" } {$concept.PeriodType } else ()
  let $balance as attribute()? :=
      (attribute {"xbrli:balance"} { $concept.Balance })[
          $concept.DataType eq "xbrli:monetaryItemType" and
          exists($concept.Balance)]
  let $type as string := $concept.DataType
  return
      <element xmlns="http://www.w3.org/2001/XMLSchema"
          id="{$id}"
          name="{$name}"
          nillable="{$nillable}"
          substitutionGroup="{$substitutionGroup}"
          abstract="{$abstract}"
          type="{$type}">{
              (
                  $periodType,
                  $balance
              )
      }</element>
};

jsoniq version "1.0";
(:
 : Copyright 2014 28msec Inc.
 :)

(:~
 :
This module provides functionality for retrieving concepts
 : and labels.

 :
 : @author Federico Cavalieri
 :)
module namespace concepts = "http://28.io/modules/xbrl/concepts";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace mw = "http://28.io/modules/xbrl/mongo-wrapper";

declare namespace xbrli = "http://www.xbrl.org/2003/instance";
declare namespace xlink = "http://www.w3.org/1999/xlink";
declare namespace link = "http://www.xbrl.org/2003/linkbase";

declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";

(:~
 : Name of the collection the concepts are stored in.
 :)
declare variable $concepts:col as xs:string := "concepts";

(:~
 : Name of the field pointing to the archive.
 :)
declare variable $concepts:ARCHIVE as xs:string := "Archive";

(:~
 : Name of the field pointing to the role.
 :)
declare variable $concepts:ROLE as xs:string := "Role";

(:~
 : Name of the field pointing to the name.
 :)
declare variable $concepts:NAME as xs:string := "Name";

(:~
 : Name of the field pointing to the labels.
 :)
declare variable $concepts:LABELS as xs:string := "Labels";

(:~
 : The default component link role.
 :)
declare variable $concepts:DEFAULT_COMPONENT_LINK_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/link";

(:~
 : Allow any component link role
 :)
declare variable $concepts:ANY_COMPONENT_LINK_ROLE as xs:string :=
    ":any component:";

(:~
 : A joker to ask for all concept names.
 :)
declare variable $concepts:ALL_CONCEPT_NAMES as xs:string := ":any concept:";

(:~
 : A joker to ask for all concept labels.
 :)
declare variable $concepts:ALL_CONCEPT_LABELS as xs:string := ":any concept label:";

(:~
 : The concept report element
 :)
declare variable $concepts:CONCEPT_REPORT_ELEMENT as xs:string := "xbrl:Concept";

(:~
 : The period report element
 :)
declare variable $concepts:PERIOD_REPORT_ELEMENT as xs:string := "xbrl:Period";

(:~
 : The entity report element
 :)
declare variable $concepts:ENTITY_REPORT_ELEMENT as xs:string := "xbrl:Entity";

(:~
 : The unit report element
 :)
declare variable $concepts:UNIT_REPORT_ELEMENT as xs:string := "xbrl:Unit";

(:~
 : The archive report element
 :)
declare variable $concepts:ARCHIVE_REPORT_ELEMENT as xs:string := "xbrl28:Archive";

(:~
 : The implied table report element
 :)
declare variable $concepts:IMPLIED_TABLE_REPORT_ELEMENT as xs:string := "xbrl28:ImpliedTable";

(:~
 : The non-numeric table report element
 :)
declare variable $concepts:NON_NUMERIC_REPORT_ELEMENT as xs:string := "xbrl:NonNumeric";

(:~
 :
Retrieves all concepts.

 :
 : @return all concepts.
 :)
declare function concepts:concepts() as object*
{
  mw:find($concepts:col,{})
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*
  ) as object*
{
  concepts:concepts($concept-names, $archives-or-ids, $component-roles, $labels, ())
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 : @param $options one option OnlyNames to only output concepts with the Name field.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*,
    $options as object?
  ) as object*
{
  let $projection as object := if($options.OnlyNames eq true) then
  {
    Name: 1,
    IsAbstract: 1,
    IsTextBlock: 1
  } else {}
  let $archives := archives:aid($archives-or-ids)
  return
  if (exists($archives))
  then mw:find($concepts:col,
    {|
      {
        $concepts:ARCHIVE : { "$in" : [ $archives ] }
      },
      {
        $concepts:ROLE : { "$in" :
          [ $component-roles ]
        }
      }[not $component-roles = $concepts:ANY_COMPONENT_LINK_ROLE],
      {
        $concepts:NAME : { "$in" : [ $concept-names ] }
      }[not $concept-names = $concepts:ALL_CONCEPT_NAMES],
      {
        "Labels.Value" : { "$in" : [ $labels ] }
      }[not $labels = $concepts:ALL_CONCEPT_LABELS]
    |},
    $projection
  )
  else ()
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*
  ) as object*
{
  concepts:concepts(
    $concept-names,
    $archives-or-ids,
    $component-roles,
    $concepts:ALL_CONCEPT_LABELS
  )
};

(:~
 :
Retrieves the concepts which:
 :  - concept name matches a given one;
 :  - archive number matches that of a given component;
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts-for-components(
    $concept-names as string*,
    $labels as string*,
    $component-or-ids as item*,
    $options as object?) as object*
{
  for $component in components:components($component-or-ids)
  return concepts:concepts($concept-names,
                           $component.Archive[not $$ instance of null],
                           $component.Role,
                           $labels,
                           $options)
};

(:~
 :
Transforms a concept tree into XBRL arcs and locators.
 :    Those can then be added to an extended link.

 :
 : @param $trees the trees to transform.
 :
 : @return the locator and arcs elements
 :)
declare function concepts:tree-to-xbrl(
    $tree as object,
    $arcRole as string,
    $taxonomyName as string,
    $level as integer,
    $pos as integer
) as element()*
{
  let $children as object* := ($tree.To[], $tree.Members[])
  let $fromID as string := replace($tree.Name, ":", "_")
  let $fromLabel as string := $fromID || "_" || $level || "_" || $pos
  let $fromLocator as element() := <link:loc xlink:type="locator" xlink:href="{$taxonomyName}.xsd#{$fromID}" xlink:label="{$fromLabel}" xlink:title="{normalize-space($tree.Label)}" />

  return
  (
    $fromLocator,

    for $child at $pos in $children
    order by $child.Order ascending
    let $toID as string := replace($child.Name, ":", "_")
    let $toLabel as string := $toID || "_" || ($level + 1) || "_" || $pos
    let $order as decimal :=
      if(empty($child.Order) or $child.Order eq null)
      then 1
      else $child.Order
    let $arc as element() :=
      element {
        switch ($arcRole)
        case "http://www.xbrl.org/2003/arcrole/concept-label" return xs:QName("link:labelArc")
        case "http://www.xbrl.org/2003/arcrole/parent-child" return xs:QName("link:presentationArc")
        case "http://www.xbrl.org/2003/arcrole/summation-item" return xs:QName("link:calculationArc")
        case "http://xbrl.org/int/dim/arcrole/all"
        case "http://xbrl.org/int/dim/arcrole/hypercube-dimension"
        case "http://xbrl.org/int/dim/arcrole/dimension-domain"
        case "http://xbrl.org/int/dim/arcrole/dimension-default"
        case "http://xbrl.org/int/dim/arcrole/domain-member"
        return xs:QName("link:definitionArc")
        default return error(xs:QName("err:UnknownArcRole"), "arcrole " || $arcRole || " unknown or not supported.")
      }
      {
        attribute { "xlink:type" } { "arc" },
        attribute { "xlink:arcrole" } { $arcRole },
        attribute { "xlink:from" } { $fromLabel },
        attribute { "xlink:to" } { $toLabel },
        attribute { "use" } { "optional" },
        attribute { "order" } { $order },
        (attribute { "weight" } { $child.Weight } )[$arcRole eq "http://www.xbrl.org/2003/arcrole/summation-item"]
      }
    return
      (
        $arc,
        concepts:tree-to-xbrl($child, $arcRole, $taxonomyName, $level+1, $pos)
      )
  )
};

(:~
 :
Transforms concepts into XBRL definitions (XML Schema element definition).

 :
 : @param $concepts the concepts to transform.
 :
 : @return the XML Schema element definitions.
 :)
declare function concepts:to-xbrl(
    $concepts as object*) as element()*
{
  for $concept as object in $concepts
  where not $concept.Name = ($concepts:CONCEPT_REPORT_ELEMENT,
                             $concepts:ENTITY_REPORT_ELEMENT,
                             $concepts:PERIOD_REPORT_ELEMENT,
                             $concepts:UNIT_REPORT_ELEMENT,
                             $concepts:ARCHIVE_REPORT_ELEMENT,
                             $concepts:IMPLIED_TABLE_REPORT_ELEMENT,
                             $concepts:NON_NUMERIC_REPORT_ELEMENT)
  let $name as string := substring-after($concept.Name, ":")
  let $id as string := replace($concept.Name, ":", "_")
  let $substitutionGroup as string := $concept.SubstitutionGroup
  let $nillable as boolean := (boolean($concept.IsNillable), false)[1]
  let $abstract as boolean := (boolean($concept.IsAbstract), false)[1]
  let $periodType as attribute()? := if(exists($concept.PeriodType)) then attribute { "xbrli:periodType" } {$concept.PeriodType } else ()
  let $balance as attribute()? :=
      (attribute {"xbrli:balance"} { $concept.Balance })[
          $concept.DataType eq "xbrli:monetaryItemType" and
          exists($concept.Balance)]
  let $type as string := $concept.DataType
  return
      <element xmlns="http://www.w3.org/2001/XMLSchema"
          id="{$id}"
          name="{$name}"
          nillable="{$nillable}"
          substitutionGroup="{$substitutionGroup}"
          abstract="{$abstract}"
          type="{$type}">{
              (
                  $periodType,
                  $balance
              )
      }</element>
};