:lipstick: Fix warning.

:lipstick: Fix warning.

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>
};

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
  let $textBlock as boolean := boolean($concept.IsTextBlock)
  return
      <element xmlns="http://www.w3.org/2001/XMLSchema"
          id="{$id}"
          name="{$name}"
          nillable="{$nillable}"
          substitutionGroup="{$substitutionGroup}"
          abstract="{$abstract}"
          type="{$type}">{
              (
                  $periodType,
                  $balance
              )
      }</element>
};


:pencil: Add comments.

:pencil: Add comments.

jsoniq version "1.0";
(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for handling networks.

 :
 :
A network is a graph of concepts and resources, of which the edges
 : share the same semantics.

 :
 :
There are several kinds of networks.
 : A network can be a tree or a DAG of concepts. It can be a bipartite
 : graph mapping concepts to resources.

 :
 :
XBRL defines a number of standard networks: presentation, calculation,
 : definition (essence-alias, general-special, requires-element, similar-tuples, domain-member),
 : and label networks.

 :
 :
With this module, you can retrieve the networks contained in a component, and you
 : can retrieve a standard XBRL network with the provided short names.

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

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

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";

(:~
 : Short name of the presentation network.
 :)
declare variable $networks:PRESENTATION_NETWORK as xs:string := "Presentation";

(:~
 : Short name of the calculation network.
 :)
declare variable $networks:CALCULATION_NETWORK as xs:string := "Calculation";

(:~
 : Short name of the essence-alias definition network.
 :)
declare variable $networks:ESSENCE_ALIAS_NETWORK as xs:string := "EssenceAlias";

(:~
 : Short name of the general-special definition network.
 :)
declare variable $networks:GENERAL_SPECIAL_NETWORK as xs:string := "GeneralSpecial";

(:~
 : Short name of the requires-element definition network.
 :)
declare variable $networks:REQUIRES_ELEMENT_NETWORK as xs:string := "RequiresElement";

(:~
 : Short name of the similar-tuples definition network.
 :)
declare variable $networks:SIMILAR_TUPLES_NETWORK as xs:string := "SimilarTuples";

(:~
 : Short name of the domain-member definition network.
 :)
declare variable $networks:DOMAIN_MEMBER_NETWORK as xs:string := "DomainMember";

(:~
 :
Retrieves all networks in the supplied components.

 :
 : @param $components a sequence of components.
 :
 : @return all networks.
 :)
declare function networks:networks-for-components($components as item*) as object*
{
  $components.Networks[]
};

(:~
 :
Retrieves the standardized networks in the supplied components with the given short names.

 :
 : @param $components a sequence of components.
 : @param $short-name the short names of the networks.
 :
 : @return the standardized networks with these short names.
 :)
declare function networks:networks-for-components-and-short-names(
    $components as item*,
    $short-name as string*
) as object*
{
    $components.Networks[][$$.ShortName = $short-name]
};

(:~
:
Merges the supplied networks, grouping by arc/link name/role.

:
: @param $networks the input networks.
:
: @return the merged networks.
:)
declare function networks:merge($networks as object*) as object*
{
    for $networks in $networks
    group by
      $linkName := $networks.LinkName,
      $arcName := $networks.ArcName,
      $linkRole := $networks.LinkRole,
      $arcRole := $networks.ArcRole
    return {
        LinkName: $linkName,
        LinkRole: $linkRole,
        ArcName: $arcName,
        ArcRole: $arcRole,
        Kind: $networks[1].Kind,
        ShortName: $networks[1].ShortName,
        CyclesAllowed: $networks[1].CyclesAllowed,
        Trees: [
          for $concept in $networks.Trees[]
          group by $name := $concept.Name
          return {
            Name: $name,
            To: [ networks:merge-trees($concept.To[]) ]
          }
        ]
    }
};

declare %private function networks:merge-trees($trees as object*) as object*
{
    for $trees in $trees
    group by $name := $trees.Name
    (: We count the groups. :)
    count $count
    (: The groups with a total label are pushed to the back. :)
    let $count :=
        if($trees.PreferredLabelRole = "http://www.xbrl.org/2003/role/totalLabel")
        then 100000
        else $count
    (: We reorder, pushing totals to the back. :)
    order by $count
    (: And we count again so that the groups with the totals are numbered
       correctly. :)
    count $order
    return {|
        trim($trees[1], ("Label", "To", "Order")),
        {
            Order :$order
        },
        let $to := $trees.To[]
        where exists($to)
        return
        { To: [ networks:merge-trees($to) ] }
    |}
};

(:~
 :
Transforms a network into an XBRL extended link
 :    which can be added to a linkbase.

 :
 : @param $networks the networks to transform into one extended link.
 :
 : @return the extended link element
 :)
declare function networks:to-xbrl(
    $network as object,
    $component as object,
    $taxonomyName as string
) as element()*
{
  let $role as string := $component.Role
  let $networkTitle as string := ($network.Label, $component.Label || " " || $network.ShortName)[1]
  let $extendedLink as xs:QName :=
    switch(true)
    case ($network.Kind eq "Hypercube") return xs:QName("link:definitionLink")
    case ($network.Name eq "xbrl28:ImpliedTable") return xs:QName("link:definitionLink")
    case ($network.ShortName eq "Presentation") return xs:QName("link:presentationLink")
    case ($network.ShortName eq "DomainMember") return xs:QName("link:definitionLink")
    case ($network.ShortName eq "Calculation") return xs:QName("link:calculationLink")
    default return error(xs:QName("err:UnknownNetwork"), "Cannot determine the type of the Network " || $network.Name)

  let $locatorsAndArcs as element()* :=
    switch(true)
    case $network.ShortName eq "DomainMember" return ()
    case ($network.Kind eq "Hypercube" or $network.Name eq "xbrl28:ImpliedTable") return
    (
      if($network.Name eq "xbrl28:ImpliedTable")
      then ()
      else (
        (: network of primary items (domain-member) :)
        for $primaryItem at $pos in $network.Aspects."xbrl:Concept".Members[]
        where exists($primaryItem.Members[])
        return concepts:tree-to-xbrl($primaryItem,
                                    "http://xbrl.org/int/dim/arcrole/domain-member",
                                    $taxonomyName, 1, $pos),
        hypercubes:to-xbrl($network, $taxonomyName)
      )
    )

    case ($network.ShortName eq "Presentation") return
    (
      $network.Trees[] ! concepts:tree-to-xbrl($$,
                                              "http://www.xbrl.org/2003/arcrole/parent-child",
                                              $taxonomyName, 1, 1)
    )

    case ($network.ShortName eq "Calculation") return
    (
      $network.Trees[] ! concepts:tree-to-xbrl($$,
                                              "http://www.xbrl.org/2003/arcrole/summation-item",
                                              $taxonomyName, 1, 1)
    )

    default return error(xs:QName("err:UnknownNetwork"), "Cannot determine the type of the Network " || $network.Name)
  where exists($locatorsAndArcs)
  return
    element {
      $extendedLink
    }
    {
      attribute { "xlink:type" } { "extended" },
      attribute { "xlink:role" } { $role },
      attribute { "xlink:title" } { $networkTitle },
      $locatorsAndArcs
    }
};

jsoniq version "1.0";
(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for handling networks.

 :
 :
A network is a graph of concepts and resources, of which the edges
 : share the same semantics.

 :
 :
There are several kinds of networks.
 : A network can be a tree or a DAG of concepts. It can be a bipartite
 : graph mapping concepts to resources.

 :
 :
XBRL defines a number of standard networks: presentation, calculation,
 : definition (essence-alias, general-special, requires-element, similar-tuples, domain-member),
 : and label networks.

 :
 :
With this module, you can retrieve the networks contained in a component, and you
 : can retrieve a standard XBRL network with the provided short names.

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

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

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";

(:~
 : Short name of the presentation network.
 :)
declare variable $networks:PRESENTATION_NETWORK as xs:string := "Presentation";

(:~
 : Short name of the calculation network.
 :)
declare variable $networks:CALCULATION_NETWORK as xs:string := "Calculation";

(:~
 : Short name of the essence-alias definition network.
 :)
declare variable $networks:ESSENCE_ALIAS_NETWORK as xs:string := "EssenceAlias";

(:~
 : Short name of the general-special definition network.
 :)
declare variable $networks:GENERAL_SPECIAL_NETWORK as xs:string := "GeneralSpecial";

(:~
 : Short name of the requires-element definition network.
 :)
declare variable $networks:REQUIRES_ELEMENT_NETWORK as xs:string := "RequiresElement";

(:~
 : Short name of the similar-tuples definition network.
 :)
declare variable $networks:SIMILAR_TUPLES_NETWORK as xs:string := "SimilarTuples";

(:~
 : Short name of the domain-member definition network.
 :)
declare variable $networks:DOMAIN_MEMBER_NETWORK as xs:string := "DomainMember";

(:~
 :
Retrieves all networks in the supplied components.

 :
 : @param $components a sequence of components.
 :
 : @return all networks.
 :)
declare function networks:networks-for-components($components as item*) as object*
{
  $components.Networks[]
};

(:~
 :
Retrieves the standardized networks in the supplied components with the given short names.

 :
 : @param $components a sequence of components.
 : @param $short-name the short names of the networks.
 :
 : @return the standardized networks with these short names.
 :)
declare function networks:networks-for-components-and-short-names(
    $components as item*,
    $short-name as string*
) as object*
{
    $components.Networks[][$$.ShortName = $short-name]
};

(:~
:
Merges the supplied networks, grouping by arc/link name/role.

:
: @param $networks the input networks.
:
: @return the merged networks.
:)
declare function networks:merge($networks as object*) as object*
{
    for $networks in $networks
    group by
      $linkName := $networks.LinkName,
      $arcName := $networks.ArcName,
      $linkRole := $networks.LinkRole,
      $arcRole := $networks.ArcRole
    return {
        LinkName: $linkName,
        LinkRole: $linkRole,
        ArcName: $arcName,
        ArcRole: $arcRole,
        Kind: $networks[1].Kind,
        ShortName: $networks[1].ShortName,
        CyclesAllowed: $networks[1].CyclesAllowed,
        Trees: [
          for $concept in $networks.Trees[]
          group by $name := $concept.Name
          return {
            Name: $name,
            To: [ networks:merge-trees($concept.To[]) ]
          }
        ]
    }
};

declare %private function networks:merge-trees($trees as object*) as object*
{
    for $trees in $trees
    group by $name := $trees.Name
    count $order
    let $order :=
        if($trees.PreferredLabelRole = "http://www.xbrl.org/2003/role/totalLabel")
        then 1000000
        else $order
    order by $order
    count $count
    return {|
        trim($trees[1], ("Label", "To", "Order")),
        {
            Order :$count
        },
        let $to := $trees.To[]
        where exists($to)
        return
        { To: [ networks:merge-trees($to) ] }
    |}
};

(:~
 :
Transforms a network into an XBRL extended link
 :    which can be added to a linkbase.

 :
 : @param $networks the networks to transform into one extended link.
 :
 : @return the extended link element
 :)
declare function networks:to-xbrl(
    $network as object,
    $component as object,
    $taxonomyName as string
) as element()*
{
  let $role as string := $component.Role
  let $networkTitle as string := ($network.Label, $component.Label || " " || $network.ShortName)[1]
  let $extendedLink as xs:QName :=
    switch(true)
    case ($network.Kind eq "Hypercube") return xs:QName("link:definitionLink")
    case ($network.Name eq "xbrl28:ImpliedTable") return xs:QName("link:definitionLink")
    case ($network.ShortName eq "Presentation") return xs:QName("link:presentationLink")
    case ($network.ShortName eq "DomainMember") return xs:QName("link:definitionLink")
    case ($network.ShortName eq "Calculation") return xs:QName("link:calculationLink")
    default return error(xs:QName("err:UnknownNetwork"), "Cannot determine the type of the Network " || $network.Name)

  let $locatorsAndArcs as element()* :=
    switch(true)
    case $network.ShortName eq "DomainMember" return ()
    case ($network.Kind eq "Hypercube" or $network.Name eq "xbrl28:ImpliedTable") return
    (
      if($network.Name eq "xbrl28:ImpliedTable")
      then ()
      else (
        (: network of primary items (domain-member) :)
        for $primaryItem at $pos in $network.Aspects."xbrl:Concept".Members[]
        where exists($primaryItem.Members[])
        return concepts:tree-to-xbrl($primaryItem,
                                    "http://xbrl.org/int/dim/arcrole/domain-member",
                                    $taxonomyName, 1, $pos),
        hypercubes:to-xbrl($network, $taxonomyName)
      )
    )

    case ($network.ShortName eq "Presentation") return
    (
      $network.Trees[] ! concepts:tree-to-xbrl($$,
                                              "http://www.xbrl.org/2003/arcrole/parent-child",
                                              $taxonomyName, 1, 1)
    )

    case ($network.ShortName eq "Calculation") return
    (
      $network.Trees[] ! concepts:tree-to-xbrl($$,
                                              "http://www.xbrl.org/2003/arcrole/summation-item",
                                              $taxonomyName, 1, 1)
    )

    default return error(xs:QName("err:UnknownNetwork"), "Cannot determine the type of the Network " || $network.Name)
  where exists($locatorsAndArcs)
  return
    element {
      $extendedLink
    }
    {
      attribute { "xlink:type" } { "extended" },
      attribute { "xlink:role" } { $role },
      attribute { "xlink:title" } { $networkTitle },
      $locatorsAndArcs
    }
};


Update entities.jq

Update entities.jq

import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace response = "http://www.28msec.com/modules/http-response";

declare %an:nondeterministic function local:test-entities($expected as integer, $params as string) as atomic
{
    let $actual as integer := count(parse-json(http-client:get(
        "http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/entities.jq?_method=POST" || $params
    ).body.content).Entities[])
    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]"
};

declare %an:sequential function local:check($o as object) as object
{
    if (not(every $k in (keys($o) ! $o.$$) satisfies ($k instance of boolean and $k)))
    then {
            response:status-code(500);
            $o
    } else
            $o
};

let $db := request:param-values("db", "all-sec-filings")
return local:check({
    dow30: local:test-entities(30, "&tag=DOW30"),
    cik: local:test-entities(1, "&cik=4962"),
    ticker: local:test-entities(1, "&ticker=wmt"),
    ticker2: local:test-entities(2, "&ticker=wmt&ticker=ko"),
    sic: local:test-entities(switch($db) case "all-dow30" return 2 default return 74, "&sic=4813"),
    mixed: local:test-entities(switch($db) case "all-dow30" return 5 default return 77, "&cik=4962&sic=4813&ticker=wmt&ticker=ko")
})

import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace response = "http://www.28msec.com/modules/http-response";

declare %an:nondeterministic function local:test-entities($expected as integer*, $params as string) as atomic
{
    let $actual as integer := count(parse-json(http-client:get(
        "http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/entities.jq?_method=POST" || $params
    ).body.content).Entities[])
    return
        if ($actual = $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]"
};

declare %an:sequential function local:check($o as object) as object
{
    if (not(every $k in (keys($o) ! $o.$$) satisfies ($k instance of boolean and $k)))
    then {
            response:status-code(500);
            $o
    } else
            $o
};

let $db := request:param-values("db", "all-sec-filings")
return local:check({
    dow30: local:test-entities(30, "&tag=DOW30"),
    cik: local:test-entities(1, "&cik=4962"),
    ticker: local:test-entities(1, "&ticker=wmt"),
    ticker2: local:test-entities(2, "&ticker=wmt&ticker=ko"),
    sic: local:test-entities(switch($db) case "all-dow30" return 2 default return 74, "&sic=4813"),
    mixed: local:test-entities(switch($db) case "all-dow30" return 5 default return 77, "&cik=4962&sic=4813&ticker=wmt&ticker=ko")
})


:green_heart: Fix tests.

:green_heart: Fix tests.

import module namespace test = "http://apps.28.io/test";

let $is-japan-big := test:is-japan-big()
return
test:check-all-success({|
    {
        ambiguous-concepts-fix: test:invoke-and-assert-deep-equal(
          "facttable-for-component",
          {
            aid: "TD2014110700368",
            role: "http://disclosure.edinet-fsa.go.jp/role/jppfs/rol_QuarterlyConsolidatedStatementOfCashFlows-indirect"
          },
          function($res as item*) as item* { count($res.FactTable[]) },
          78
        )
    }[$is-japan-big],
  {
    rule-error: test:invoke-and-assert-deep-equal(
        "facttable-for-component",
        {
          aid: "S1002FC3",
          role: "http://disclosure.edinet-fsa.go.jp/role/jppfs/rol_BalanceSheet",
          validate: "true"
        },
        function($res as item*) as item* { count($res.FactTable[][$$.Aspects."xbrl:Concept" eq "xbrl28:Error"]) },
        0 (: The error disappeared after version got implemented ;-) We need to find another error... :)
      )
    }
|})

import module namespace test = "http://apps.28.io/test";

let $is-japan-big := test:is-japan-big()
return
test:check-all-success({|
    {
        ambiguous-concepts-fix: test:invoke-and-assert-deep-equal(
          "facttable-for-component",
          {
            aid: "TD2014110700368",
            role: "http://disclosure.edinet-fsa.go.jp/role/jppfs/rol_QuarterlyConsolidatedStatementOfCashFlows-indirect"
          },
          function($res as item*) as item* { count($res.FactTable[]) },
          78
        )
    }[$is-japan-big],
  {
    rule-error: test:invoke-and-assert-deep-equal(
        "facttable-for-component",
        {
          aid: "S1002FC3",
          role: "http://disclosure.edinet-fsa.go.jp/role/jppfs/rol_BalanceSheet",
          validate: "true"
        },
        function($res as item*) as item* { count($res.FactTable[][$$.Aspects."xbrl:Concept" eq "xbrl28:Error"]) },
        2
      )
    }
|})


:fire: Remove code.

:fire: Remove code.

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";

import module namespace sec-networks = "http://28.io/modules/xbrl/profiles/sec/networks";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";

import module namespace response = "http://www.28msec.com/modules/http-response";

import module namespace session = "http://apps.28.io/session";
import module namespace csv = "http://zorba.io/modules/json-csv";

declare function local:to-xml-rec($o as object*, $level as integer) as element()*
{
    for $o in $o
    return
        element { if ($o.Kind eq "Domain") then "Member" else $o.Kind } {
            attribute { "name" } { $o.Name },
            attribute { "label" } { $o.Label },
            if (exists($o.Balance)) then attribute { "balance" } { $o.Balance } else (),
            if (exists($o.DataType)) then attribute { "dataType" } { $o.DataType } else (),
            if (exists($o.BaseType)) then attribute { "baseDataType" } { $o.BaseType } else (),
            if (exists($o.IsAbstract)) then attribute { "abstract" } { $o.IsAbstract } else (),
            if (exists($o.PeriodType)) then attribute { "periodType" } { $o.PeriodType } else (),
            attribute { "level" } { $level },
            local:to-xml-rec($o.Children[], $level + 1)
        }
};

declare function local:to-xml($model as object) as node()*
{
    <Component>
        <Network entityRegistrantName="{$model.EntityRegistrantName}"
                 accessionNumber="{$model.AccessionNumber}"
                 tableName="{$model.TableName}"
                 cik="{$model.CIK}"
                 label="{$model.Label}"
                 networkIdentifier="{$model.NetworkIdentifier}"
                 formType="{$model.FormType}"
                 fiscalPeriod="{$model.FiscalPeriod}"
                 fiscalYear="{$model.FiscalYear}"
                 acceptanceDatetime="{$model.AcceptanceDatetime}"
                 disclosure="{$model.Disclosure}"
                 >{
            local:to-xml-rec($model.ModelStructure[], 0)
        }</Network>
    </Component>
};

declare function local:to-csv-rec($objects as object*, $level as integer) as object*
{
    for $o in $objects
    let $object := {
        "Label" : $o.Label,
        "Name" : $o.Name,
        "ObjectClass" : if ($o.Kind eq "Domain") then "Member" else $o.Kind,
        "DataType" : $o.DataType,
        "BaseDataType" : $o.BaseType,
        "Balance" : $o.Balance,
        "Abstract" : $o.IsAbstract,
        "PeriodType" : $o.PeriodType,
        "Level" : $level
    }
    return (
        $object,
        local:to-csv-rec($o.Children[], $level + 1)
    )
};

declare function local:to-csv($model as object) as string
{
    let $lines := local:to-csv-rec($model.ModelStructure, 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;

session:audit-call($token);

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $components  := sec-networks:components(
    $archives,
    $cid,
    $reportElement,
    $disclosure,
    $networkIdentifier,
    $label)
let $model-structures :=
    for $component in $components
    let $archive   := archives:archives($component.Archive)
    let $entity    := entities:entities($archive.Entity)
    return if($profile-name eq "sec")
    then {
        CIK : entities:eid($entity),
        EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
        ModelStructure : [ sec-networks:model-structures($component) ],
        TableName : components:hypercubes($component),
        Label : $component.Label,
        AccessionNumber : $component.Archive,
        FormType : $archive.Profiles.SEC.FormType,
        FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
        FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
        AcceptanceDatetime : filings:acceptance-dateTimes($archive),
        NetworkIdentifier: $component.Role,
        Disclosure : $component.Profiles.SEC.Disclosure
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        ModelStructure : [ components:model-structures($component) ]
    }
let $result := { ModelStructures: $model-structures }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return if (exists($components))
    then api:serialize($result, $comment, $serializers, $format, "components")
    else {
        response:status-code(404);
        response:content-type("application/json");
        session:error("component not found", "json")
    }

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";

import module namespace sec-networks = "http://28.io/modules/xbrl/profiles/sec/networks";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";

import module namespace response = "http://www.28msec.com/modules/http-response";

import module namespace session = "http://apps.28.io/session";
import module namespace csv = "http://zorba.io/modules/json-csv";

declare function local:to-xml-rec($o as object*, $level as integer) as element()*
{
    for $o in $o
    return
        element { if ($o.Kind eq "Domain") then "Member" else $o.Kind } {
            attribute { "name" } { $o.Name },
            attribute { "label" } { $o.Label },
            if (exists($o.Balance)) then attribute { "balance" } { $o.Balance } else (),
            if (exists($o.DataType)) then attribute { "dataType" } { $o.DataType } else (),
            if (exists($o.BaseType)) then attribute { "baseDataType" } { $o.BaseType } else (),
            if (exists($o.IsAbstract)) then attribute { "abstract" } { $o.IsAbstract } else (),
            if (exists($o.PeriodType)) then attribute { "periodType" } { $o.PeriodType } else (),
            attribute { "level" } { $level },
            local:to-xml-rec($o.Children[], $level + 1)
        }
};

declare function local:to-xml($model as object) as node()*
{
    <Component>
        <Network entityRegistrantName="{$model.EntityRegistrantName}"
                 accessionNumber="{$model.AccessionNumber}"
                 tableName="{$model.TableName}"
                 cik="{$model.CIK}"
                 label="{$model.Label}"
                 networkIdentifier="{$model.NetworkIdentifier}"
                 formType="{$model.FormType}"
                 fiscalPeriod="{$model.FiscalPeriod}"
                 fiscalYear="{$model.FiscalYear}"
                 acceptanceDatetime="{$model.AcceptanceDatetime}"
                 disclosure="{$model.Disclosure}"
                 >{
            local:to-xml-rec($model.ModelStructure[], 0)
        }</Network>
    </Component>
};

declare function local:to-csv-rec($objects as object*, $level as integer) as object*
{
    for $o in $objects
    let $object := {
        "Label" : $o.Label,
        "Name" : $o.Name,
        "ObjectClass" : if ($o.Kind eq "Domain") then "Member" else $o.Kind,
        "DataType" : $o.DataType,
        "BaseDataType" : $o.BaseType,
        "Balance" : $o.Balance,
        "Abstract" : $o.IsAbstract,
        "PeriodType" : $o.PeriodType,
        "Level" : $level
    }
    return (
        $object,
        local:to-csv-rec($o.Children[], $level + 1)
    )
};

declare function local:to-csv($model as object) as string
{
    let $lines := local:to-csv-rec($model.ModelStructure, 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

declare function local:enrich-json-rec($objects as object*, $level as integer) as object*
{
    for $object in $objects
    return
        copy $o := $object
        modify (
            if (exists($o.Children)) then delete json $o("Children") else (),
            insert json { Level : $level } into $o,
            if ($o.Kind eq "Domain") then replace value of json $o("Kind") with "Member" else ()
        )
        return {|
            $o,
            let $children := local:enrich-json-rec($object.Children[], $level + 1)
            return if (exists($children)) then { Children : [ $children ] } else ()
        |}
};

declare function local:enrich-json($component as object) as object
{
    {
        ModelStructure : [ local:enrich-json-rec($component.ModelStructure, 0) ] ,
        CIK : $component.CIK,
        EntityRegistrantName : $component.EntityRegistrantName,
        Label : $component.Label,
        AccessionNumber : $component.AccessionNumber,
        TableName : $component.TableName,
        FormType : $component.FormType,
        FiscalPeriod : $component.FiscalPeriod,
        FiscalYear : $component.FiscalYear,
        AcceptanceDatetime : $component.AcceptanceDatetime,
        NetworkIdentifier: $component.NetworkIdentifier,
        Disclosure : $component.Disclosure
    }
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;

session:audit-call($token);

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $components  := sec-networks:components(
    $archives,
    $cid,
    $reportElement,
    $disclosure,
    $networkIdentifier,
    $label)
let $model-structures :=
    for $component in $components
    let $archive   := archives:archives($component.Archive)
    let $entity    := entities:entities($archive.Entity)
    return if($profile-name eq "sec")
    then {
        CIK : entities:eid($entity),
        EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
        ModelStructure : [ sec-networks:model-structures($component) ],
        TableName : components:hypercubes($component),
        Label : $component.Label,
        AccessionNumber : $component.Archive,
        FormType : $archive.Profiles.SEC.FormType,
        FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
        FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
        AcceptanceDatetime : filings:acceptance-dateTimes($archive),
        NetworkIdentifier: $component.Role,
        Disclosure : $component.Profiles.SEC.Disclosure
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        ModelStructure : [ components:model-structures($component) ]
    }
let $result := { ModelStructures: $model-structures }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return if (exists($components))
    then api:serialize($result, $comment, $serializers, $format, "components")
    else {
        response:status-code(404);
        response:content-type("application/json");
        session:error("component not found", "json")
    }