:green_heart: Fix warning.

:green_heart: Fix warning.

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

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

declare option rest:response "first-item";

(: 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 $eid           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 := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind    as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $aid           as string* external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;

(: 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 $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 $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

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

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return
         if(api:is-delete())
         then switch(session:has-access($token, "write_remove"))
              case $session:ACCESS-ALLOWED return
                    if(count($archives) gt 1)
                    then {
                        { status: 400 },
                        session:error("Cannot delete more than one archive at a time (safety mechanism)", $format)
                    }
                    else {
                        archives:remove($archives._id, true);
                        { status: 204 }
                    }
              case $session:ACCESS-DENIED return {
                    { status: 403 },
                    session:error("access denied", $format)
                 }
              case $session:ACCESS-AUTH-REQUIRED return {
                    { status: 401 },
                    session:error("authentication required or session expired", $format)
                 }
              default return error()
         else {
               { status: 405 },
               session:error("Method not allowed", $format)
            }

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

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 multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";
import module namespace xbrl-export = "http://28.io/modules/xbrl/export";

declare option rest:response "first-item";

(: 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 $eid           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 := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind    as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $aid           as string* external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $count         as boolean external := false;
declare  %rest:case-insensitive                 variable $language      as string  external := "en-US";
declare  %rest:case-insensitive                 variable $top           as nonNegativeInteger? external := ();
declare  %rest:case-insensitive                 variable $skip          as nonNegativeInteger? external := ();

(: 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 $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 $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

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

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return
         if(api:is-delete())
         then switch(session:has-access($token, "write_remove"))
              case $session:ACCESS-ALLOWED return
                    if(count($archives) gt 1)
                    then {
                        { status: 400 },
                        session:error("Cannot delete more than one archive at a time (safety mechanism)", $format)
                    }
                    else {
                        archives:remove($archives._id, true);
                        { status: 204 }
                    }
              case $session:ACCESS-DENIED return {
                    { status: 403 },
                    session:error("access denied", $format)
                 }
              case $session:ACCESS-AUTH-REQUIRED return {
                    { status: 401 },
                    session:error("authentication required or session expired", $format)
                 }
              default return error()
         else {
               { status: 405 },
               session:error("Method not allowed", $format)
            }


:lipstick: Fix warning.

:lipstick: Fix warning.

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

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

 :
 : @author Federico Cavalieri, Ghislain Fourny
 :)
module namespace labels = "http://28.io/modules/xbrl/labels";

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

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

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

(:~
 : A helper variable holding the default language ("en-US")
 :)
declare variable $labels:AMERICAN_ENGLISH as xs:string := "en-US";

(:~
 : The standard label role.
 :)
declare variable $labels:STANDARD_LABEL_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/label";

(:~
 : The verbose label role.
 :)
declare variable $labels:VERBOSE_LABEL_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/verboseLabel";

declare variable $labels:standard-concepts as object* :=
(
    {
      Name: "xbrl:Concept",
      Label: "Concept [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Concept [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Konzept [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Concept [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "項目(Concept)"
        }
      ]
    },
    {
      Name: "xbrl:Period",
      Label: "Period [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Période [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Period [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Periode [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "期間(Period)"
        }
      ]
    },
    {
      Name: "xbrl:Entity",
      Label: "Entity [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Entität [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Entité [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Reporting Entity [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "企業名(Entity)"
        }
      ]
    },
    {
      Name: "xbrl28:Archive",
      Label: "Archiv [Achse]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Archive [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Archiv [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Archive [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "ファイル(Archive)"
        }
      ]
    },
    {
      Name: "fsa:FiscalYear",
      Label: "Fiscal Year [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Année fiscale [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Steuerjahr [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "会計年度(Fiscal Year)"
        }
      ]
    },
    {
      Name: "fsa:FiscalPeriod",
      Label: "Fiscal Period [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "会計期間(Fiscal Period)"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Fiscal Period [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Période fiscal [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Steuerperiode [Achse]"
        }
      ]
    },
    {
      Name: "fsa:FiscalPeriodType",
      Label: "Fiscal Period Type [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "会計期間タイプ(Fiscal Period Type)"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Fiscal Period Type [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Type de période fiscale [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Steuerperiodeart [Achse]"
        }
      ]
    }
);

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts, one label for each concept.

 :
 :
Matching concepts are those which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $label-roles the label role.
 : @param $languages the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $entities the entities in which entity labels will be searched.
 : @param $options Optional parameters to control language matching.
 :
 : @return the matching labels (one associated with each input concept, as
 : an object).
 :)
declare function labels:labels(
    $concept-names as string*,
    $label-roles as string*,
    $languages as string*,
    $concepts as object*,
    $entities as object*,
    $options as object?
  ) as object
{
  let $concepts := ($labels:standard-concepts, $concepts)
  return {|
    let $normalized-languages as string* := $languages ! labels:normalize-language($$)
    return (
      for $concept as object in $concepts
      where ($concepts:ALL_CONCEPT_NAMES, $concept.$concepts:NAME) = $concept-names
      group by $concept.$concepts:NAME
      let $concept-labels as object* :=
        $concept.$concepts:LABELS[][$$.Role = $label-roles]
      let $available-languages as string* :=
        distinct-values($concept-labels.Language)
      let $approximate-languages as string* := labels:approximate-languages(
        $available-languages,
        $normalized-languages,
        $options
      )
      let $english-languages as string* := labels:approximate-languages(
        $available-languages,
        "en",
        $options
      )
      let $perfect-matches as object* :=
        $concept-labels[$$.Language = $normalized-languages]
      let $approximate-matches as object* :=
        $concept-labels[$$.Language = $approximate-languages]
      let $english-matches as object* :=
        $concept-labels[$$.Language = $english-languages]
      let $label as string? :=
        ($perfect-matches.Value,
         $approximate-matches.Value,
         $english-matches.Value,
         $concept-labels.Value,
         $concept.Label)[not $$ instance of null][1]
      where exists($label)
      return {
        $concept.Name : $label
      },

      for $concept in $concept-names
      where not (($concepts:ALL_CONCEPT_NAMES, $concepts.$concepts:NAME) = $concept)
      let $label as string* :=
          switch(true)
            case starts-with($concept, "iso4217:")
            return substring($concept, 9)
            case $concept castable as date
            return format-date(date($concept), "[MNn] [D], [Y]", "en", (), ())
            case (every $date in tokenize($concept, "/") satisfies $date castable as date)
            return "From " || string-join(
                     for $date in tokenize($concept, "/")
                     return format-date(date($date), "[MNn] [D], [Y]", "en", (), ()),
                   " to ")
            case contains($concept, " ")
            return
              switch(true)
              case $concept = entities:eid($entities) and $normalized-languages = ("ja")
              return ($entities[entities:eid($$) = $concept].Profiles.SEC.EntityRegistrantName,
                       $entities[entities:eid($$) = $concept].Profiles.FSA.SubmitterName)[1]
              case $concept = entities:eid($entities)
              return ($entities[entities:eid($$) = $concept].Profiles.SEC.CompanyName,
                       $entities[entities:eid($$) = $concept].Profiles.FSA.SubmitterNameAlphabetic)[1]
              default return ()
           default return ()
      where exists($label)
      return {
        $concept : $label
      }
    )
  |}
};

(:~
 :
Retrieves all the labels with the given label role and language for
 : all concepts used in the fact and matching a concept in the list of
 : concepts. Concepts used in a fact include not only those from the
 : 'xbrl:Concept' aspect, but also Members of any custom axis.

 :
 :
Matching concepts are those 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.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $facts a sequence of facts.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched (in the version-7 format).
 : @param $options optional parameters to control language matching.
 :
 : @return an object with matching concepts as keys and labels as values.
 :)
declare function labels:labels-for-facts(
    $facts as object*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $entities as object*,
    $options as object?
  ) as object?
{
    for $facts in $facts
    group by $archive := $facts."Aspects"."xbrl28:Archive"
    let $concept-names as string* :=
        distinct-values((
          values($facts.Aspects),
          keys($facts.Aspects)
    )[$$ instance of string])
    return
        {|
            labels:labels(
                    $concept-names,
                    $label-role,
                    $language,
                    $concepts,
                    $entities,
                    $options)
        |}
};

(:~
 :
Normalizes the language code. This normalized language code can then
 : be used to find the right labels in the concepts collection.

 :
 : @param $language the language identifier.
 :
 : @return the normalized language.
 :)
declare function labels:normalize-language($language as string) as string
{
  replace(lower-case($language), "_", "-")
};

declare %private function labels:approximate-languages(
    $all-languages as string*,
    $normalized-language as string,
    $options as object?
  ) as string*
{
    if(not $options.MatchDown cast as boolean? eq false)
    then $all-languages[starts-with($$, $normalized-language)]
    else (),
    if(not $options.MatchUp cast as boolean? eq false)
    then $all-languages[starts-with($normalized-language, $$)]
    else (),
    if ((not $options.MatchAnyVariant cast as boolean? eq false) and contains($normalized-language, "-"))
    then
      $all-languages[starts-with($$, substring-before($normalized-language, "-") || "-")]
    else ()
};

(:~
 :
Transforms concept labels into XBRL labelLink.

 :
 : @param $concepts the concepts to transform.
 :
 : @return the labelLink element to be put into a linkbase.
 :)
declare function labels:to-xbrl(
    $concepts as object*,
    $taxonomyName as string) as element()
{
  <link:labelLink xmlns:link="http://www.xbrl.org/2003/linkbase" xlink:type="extended" xlink:role="http://www.xbrl.org/2003/role/link">
  {
    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 $local-name as string := substring-after($concept.Name, ":")
    group by $concept.Name
    let $id as string := replace($local-name, ":", "_")
    return
      (
        <link:loc xlink:type="locator" xlink:href="{$taxonomyName}.xsd#{$id}" xlink:label="{$id}" />,
        for $label at $pos in $concept.Labels[]
        let $role as string := ($label.Role, "http://www.xbrl.org/2003/role/label")[1]
        let $lang as string := ($label.Language, "de")[1]
        group by $role, $lang
        let $value as string? := ($label.Value)[$$ instance of string][1]
        let $labelID as string := $id || "_label" || $pos[1]
        return (
            <link:label xlink:role="{$role}"
                   xlink:label="{$labelID}"
                   id="{$labelID}"
                   xml:lang="{$lang}"
                   xlink:type="resource">{$value}</link:label>,
            <link:labelArc xlink:type="arc"
                      xlink:arcrole="http://www.xbrl.org/2003/arcrole/concept-label"
                      xlink:from="{$id}"
                      xlink:to="{$labelID}" />
        )
      )
  }</link:labelLink>
};

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

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

 :
 : @author Federico Cavalieri, Ghislain Fourny
 :)
module namespace labels = "http://28.io/modules/xbrl/labels";

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

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

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

(:~
 : A helper variable holding the default language ("en-US")
 :)
declare variable $labels:AMERICAN_ENGLISH as xs:string := "en-US";

(:~
 : The standard label role.
 :)
declare variable $labels:STANDARD_LABEL_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/label";

(:~
 : The verbose label role.
 :)
declare variable $labels:VERBOSE_LABEL_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/verboseLabel";

declare variable $labels:standard-concepts as object* :=
(
    {
      Name: "xbrl:Concept",
      Label: "Concept [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Concept [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Konzept [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Concept [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "項目(Concept)"
        }
      ]
    },
    {
      Name: "xbrl:Period",
      Label: "Period [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Période [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Period [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Periode [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "期間(Period)"
        }
      ]
    },
    {
      Name: "xbrl:Entity",
      Label: "Entity [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Entität [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Entité [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Reporting Entity [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "企業名(Entity)"
        }
      ]
    },
    {
      Name: "xbrl28:Archive",
      Label: "Archiv [Achse]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Archive [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Archiv [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Archive [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "ファイル(Archive)"
        }
      ]
    },
    {
      Name: "fsa:FiscalYear",
      Label: "Fiscal Year [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Année fiscale [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Steuerjahr [Achse]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "会計年度(Fiscal Year)"
        }
      ]
    },
    {
      Name: "fsa:FiscalPeriod",
      Label: "Fiscal Period [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "会計期間(Fiscal Period)"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Fiscal Period [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Période fiscal [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Steuerperiode [Achse]"
        }
      ]
    },
    {
      Name: "fsa:FiscalPeriodType",
      Label: "Fiscal Period Type [Axis]",
      Kind: "Dimension",
      IsNillable: true,
      IsAbstract: true,
      PeriodType: "duration",
      SubstitutionGroup: "xbrldt:dimensionItem",
      DataType: "nonnum:domainItemType",
      BaseType: "string",
      ClosestSchemaBuiltinType: "xs:string",
      IsTextBlock: false,
      Labels: [
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "ja",
          Value: "会計期間タイプ(Fiscal Period Type)"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "en-us",
          Value: "Fiscal Period Type [Axis]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "fr",
          Value: "Type de période fiscale [Axe]"
        },
        {
          Role: "http://www.xbrl.org/2003/role/label",
          Language: "de",
          Value: "Steuerperiodeart [Achse]"
        }
      ]
    }
);

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts, one label for each concept.

 :
 :
Matching concepts are those which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $label-roles the label role.
 : @param $languages the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $entities the entities in which entity labels will be searched.
 : @param $options Optional parameters to control language matching.
 :
 : @return the matching labels (one associated with each input concept, as
 : an object).
 :)
declare function labels:labels(
    $concept-names as string*,
    $label-roles as string*,
    $languages as string*,
    $concepts as object*,
    $entities as object*,
    $options as object?
  ) as object
{
  let $concepts := ($labels:standard-concepts, $concepts)
  return {|
    let $normalized-languages as string* := $languages ! labels:normalize-language($$)
    return (
      for $concept as object in $concepts
      where ($concepts:ALL_CONCEPT_NAMES, $concept.$concepts:NAME) = $concept-names
      group by $concept.$concepts:NAME
      let $concept-labels as object* :=
        $concept.$concepts:LABELS[][$$.Role = $label-roles]
      let $available-languages as string* :=
        distinct-values($concept-labels.Language)
      let $approximate-languages as string* := labels:approximate-languages(
        $available-languages,
        $normalized-languages,
        $options
      )
      let $english-languages as string* := labels:approximate-languages(
        $available-languages,
        "en",
        $options
      )
      let $perfect-matches as object* :=
        $concept-labels[$$.Language = $normalized-languages]
      let $approximate-matches as object* :=
        $concept-labels[$$.Language = $approximate-languages]
      let $english-matches as object* :=
        $concept-labels[$$.Language = $english-languages]
      let $label as string? :=
        ($perfect-matches.Value,
         $approximate-matches.Value,
         $english-matches.Value,
         $concept-labels.Value,
         $concept.Label)[not $$ instance of null][1]
      where exists($label)
      return {
        $concept.Name : $label
      },

      for $concept in $concept-names
      where not (($concepts:ALL_CONCEPT_NAMES, $concepts.$concepts:NAME) = $concept)
      let $label as string* :=
          switch(true)
            case starts-with($concept, "iso4217:")
            return substring($concept, 9)
            case $concept castable as date
            return format-date(date($concept), "[MNn] [D], [Y]", "en", (), ())
            case (every $date in tokenize($concept, "/") satisfies $date castable as date)
            return "From " || string-join(
                     for $date in tokenize($concept, "/")
                     return format-date(date($date), "[MNn] [D], [Y]", "en", (), ()),
                   " to ")
            case contains($concept, " ")
            return
              switch(true)
              case $concept = entities:eid($entities) and $normalized-languages = ("ja")
              return ($entities[entities:eid($$) = $concept].Profiles.SEC.EntityRegistrantName,
                       $entities[entities:eid($$) = $concept].Profiles.FSA.SubmitterName)[1]
              case $concept = entities:eid($entities)
              return ($entities[entities:eid($$) = $concept].Profiles.SEC.CompanyName,
                       $entities[entities:eid($$) = $concept].Profiles.FSA.SubmitterNameAlphabetic)[1]
              default return ()
           default return ()
      where exists($label)
      return {
        $concept : $label
      }
    )
  |}
};

(:~
 :
Retrieves all the labels with the given label role and language for
 : all concepts used in the fact and matching a concept in the list of
 : concepts. Concepts used in a fact include not only those from the
 : 'xbrl:Concept' aspect, but also Members of any custom axis.

 :
 :
Matching concepts are those 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.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $facts a sequence of facts.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched (in the version-7 format).
 : @param $options optional parameters to control language matching.
 :
 : @return an object with matching concepts as keys and labels as values.
 :)
declare function labels:labels-for-facts(
    $facts as object*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $entities as object*,
    $options as object?
  ) as object?
{
    for $facts in $facts
    group by $archive := $facts."Aspects"."xbrl28:Archive"
    let $concept-names as string* :=
        distinct-values((
          values($facts.Aspects),
          keys($facts.Aspects)
    )[$$ instance of string])
    return
        {|
            labels:labels(
                    $concept-names,
                    $label-role,
                    $language,
                    $concepts,
                    $entities,
                    $options)
        |}
};

(:~
 :
Normalizes the language code. This normalized language code can then
 : be used to find the right labels in the concepts collection.

 :
 : @param $language the language identifier.
 :
 : @return the normalized language.
 :)
declare function labels:normalize-language($language as string) as string
{
  replace(lower-case($language), "_", "-")
};

declare %private function labels:approximate-languages(
    $all-languages as string*,
    $normalized-language as string,
    $options as object?
  ) as string*
{
    if(not $options.MatchDown cast as boolean? eq false)
    then $all-languages[starts-with($$, $normalized-language)]
    else (),
    if(not $options.MatchUp cast as boolean? eq false)
    then $all-languages[starts-with($normalized-language, $$)]
    else (),
    if ((not $options.MatchAnyVariant cast as boolean? eq false) and contains($normalized-language, "-"))
    then
      $all-languages[starts-with($$, substring-before($normalized-language, "-") || "-")]
    else ()
};

(:~
 :
Transforms concept labels into XBRL labelLink.

 :
 : @param $concepts the concepts to transform.
 :
 : @return the labelLink element to be put into a linkbase.
 :)
declare function labels:to-xbrl(
    $concepts as object*,
    $taxonomyName as string) as element()
{
  <link:labelLink xmlns:link="http://www.xbrl.org/2003/linkbase" xlink:type="extended" xlink:role="http://www.xbrl.org/2003/role/link">
  {
    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 $local-name as string := substring-after($concept.Name, ":")
    group by $concept.Name
    let $id as string := replace($local-namename, ":", "_")
    return
      (
        <link:loc xlink:type="locator" xlink:href="{$taxonomyName}.xsd#{$id}" xlink:label="{$id}" />,
        for $label at $pos in $concept.Labels[]
        let $role as string := ($label.Role, "http://www.xbrl.org/2003/role/label")[1]
        let $lang as string := ($label.Language, "de")[1]
        group by $role, $lang
        let $value as string? := ($label.Value)[$$ instance of string][1]
        let $labelID as string := $id || "_label" || $pos[1]
        return (
            <link:label xlink:role="{$role}"
                   xlink:label="{$labelID}"
                   id="{$labelID}"
                   xml:lang="{$lang}"
                   xlink:type="resource">{$value}</link:label>,
            <link:labelArc xlink:type="arc"
                      xlink:arcrole="http://www.xbrl.org/2003/arcrole/concept-label"
                      xlink:from="{$id}"
                      xlink:to="{$labelID}" />
        )
      )
  }</link:labelLink>
};


Rebuild next and prev pages upon changing a page.

In commit 2692b98bece152799fd7c92268a4d69b63b28ba5, I complained that
when a page gets added or changed, its next and previous pages are also
affected since they contain links to that page. This has been taken into
account: the source of those pages is a dependency of the current page.

Removing a page will probably still bring difficulties, but that is
harder to fix --- in that case, you'd just have to `touch` the next and
previous pages to signify that they should be rebuilt.

I also forgot to sort before adding the navigation links, which has been
rectified now.

#!/usr/bin/env jq
# This is a collection of filters for `jq`, intended to manipulate JSON objects
# into JSON representing a website index, for further processing in a template.

# Convert "truthy" value to actual boolean.
def bool:
    [. == (null,false,0,{},[])] | any | not
;

# Is a given array at least length $n?
def at_least($n):
    (. | length) >= $n
;

# Generate this object and all its descendants.
def entries:
    ., recurse(.contents?[]?)
;

# Wrap an object in other objects so that the original object exists at a
# particular "path". Like `{} | setpath(["a","b"], …)`, but instead of making
# objects like `{"a":{"b":…}}`, this makes objects like `{"name":"a",
# "contents":[{"name":"b",…}]}`. We use `name` and `contents` to be compatible
# with the output of the `tree` program on the shell.
def tree($path):
    if $path | at_least(2)
    then { "contents": [ tree($path[1:]) ] }
    else .
    end | .name = $path[0]
;

# Merge an array of page objects into a single page object.
def merge:
    map(to_entries) | add | group_by(.key) | map(
        { "key": (.[0].key)
        , "value": (
            if .[0].key == "contents"
            then [ .[].value ] | add | group_by(.name) | map(merge)
            elif [ .[].value == .[0].value ] | all
            then .[0].value
            else error("can't merge incompatible values for key '\(.[0].key)'")
            end
        ) }
    ) | from_entries
;

# Add paths to each object, that is, the names of the ancestors.
def directory:
    def f($d): .dir=($d|join("/")) | .name as $n | .contents[]?|=f($d+[$n]);
    f([])
;

# Every leaf node gets a basename, eg name without extension.
def basename:
    (entries | select(has("contents") | not)) |= (
        .basename = (.name | sub(".([A-z]*)$";""))
    )
;

# Any page gets its target formats.
def formats($all_formats):
    entries |= (
        .formats = [
            (.make // empty) |
            if . == "null" then empty else . end | # fix for wrong YAML parse
            if (. | type) == "array" then .[] else . end |
            tostring | ascii_downcase |
            if [. == $all_formats[]] | any then .
            elif . == "all" then $all_formats[]
            else error("unrecognized format: \(.)")
            end
        ]
    )
;

# Any page is linked to its first target format.
def link:
    (entries | select(.formats | at_least(1))) |= (
        .link = "\(.dir)/\(.basename).\(.formats[0])"
    )
;

# Front matter is the page to be associated with the enclosing directory rather
# than itself.
def frontmatter:
    (entries | select(has("contents"))) |= (
        (.frontmatter = [.contents[] | select(.frontmatter | bool)][0]) |
        (.contents = .contents - [.frontmatter])
    )
;

# Any page that has no link should be excluded from the index. It would be
# nicer if we could remove in one swoop with `entries | ... |= empty`. It only
# works sometimes, presumably since we'd change the objects we'd iterate over.
def clean:
    if has("contents") then .contents |= map(clean)
    elif has("link")   then .
    else empty end
;

# Add links to the next and previous page for every linked page.
def navigation:
    def f($paths; $i; $j; $key):
        setpath($paths[$i]+[$key];
            getpath($paths[$j]) | {link,title,"source":"\(.dir)/\(.name)"}
        )
    ;
    [ path(entries | select(.formats | at_least(1))) ] as $paths
    | ($paths | length) as $n
    | reduce range(0; $n-1) as $i (.; f($paths; $i; $i+1; "next"))
    | reduce range(1; $n)   as $i (.; f($paths; $i; $i-1; "prev"))
;

# Sort content first according to sort order in metadata.
def sorting:
    (entries | select(has("contents")) | .contents) |= (
        sort_by(.[.sortby // ""] // .sort // .title // .name)
    )
;

# Add a note that tells us whether this has only children who have no more
# subchildren.
def annotate_leaves:
    (entries | select(has("contents"))) |= (
        .only_leaves = (.contents | all(has("contents") | not))
    )
;

# Combines the given stream of JSON objects by merging them, and performs the
# given operations to turn it into a proper index.
def index($all_formats):
    merge
    | directory
    | basename
    | formats($all_formats)
    | link
    | clean
    | frontmatter
    | sorting
    | navigation
    | annotate_leaves
;

# Get target files and their dependencies as an enumeration of {"target":...,
# "dependencies":...} objects.
def targets($dest; $default_style):
    entries
    | ., (.frontmatter // empty)
    | .formats[] as $format
    | "\($dest)/\(.dir)/\(.basename).\($format)" as $doc
    | ["\($dest)/\(.dir)/\(.resources?[])"] as $external
    | ["\($dest)/\(.style // $default_style).css"] as $css
    | (if $format == "html" then ($css + $external) else [] end) as $linked
    | (if $format == "pdf"  then ($css + $external) else [] end) as $embedded
    | {"target": $format, "dependencies": ([$doc] + $linked) }
    , {"target": $doc, "dependencies": ($embedded + [ .next.source//empty, .prev.source//empty ]) }
    , {"target": $doc, "dependencies": ["NEXT_LINK := \( .next.link // empty)"]}
    , {"target": $doc, "dependencies": ["PREV_LINK := \( .prev.link // empty)"]}
    , {"target": $doc, "dependencies": ["NEXT_TITLE := \( .next.title // empty)"]}
    , {"target": $doc, "dependencies": ["PREV_TITLE := \( .prev.title // empty)"]}
;

#!/usr/bin/env jq
# This is a collection of filters for `jq`, intended to manipulate JSON objects
# into JSON representing a website index, for further processing in a template.

# Convert "truthy" value to actual boolean.
def bool:
    [. == (null,false,0,{},[])] | any | not
;

# Is a given array at least length $n?
def at_least($n):
    (. | length) >= $n
;

# Generate this object and all its descendants.
def entries:
    ., recurse(.contents?[]?)
;

# Wrap an object in other objects so that the original object exists at a
# particular "path". Like `{} | setpath(["a","b"], …)`, but instead of making
# objects like `{"a":{"b":…}}`, this makes objects like `{"name":"a",
# "contents":[{"name":"b",…}]}`. We use `name` and `contents` to be compatible
# with the output of the `tree` program on the shell.
def tree($path):
    if $path | at_least(2)
    then { "contents": [ tree($path[1:]) ] }
    else .
    end | .name = $path[0]
;

# Merge an array of page objects into a single page object.
def merge:
    map(to_entries) | add | group_by(.key) | map(
        { "key": (.[0].key)
        , "value": (
            if .[0].key == "contents"
            then [ .[].value ] | add | group_by(.name) | map(merge)
            elif [ .[].value == .[0].value ] | all
            then .[0].value
            else error("can't merge incompatible values for key '\(.[0].key)'")
            end
        ) }
    ) | from_entries
;

# Add paths to each object, that is, the names of the ancestors.
def directory:
    def f($d): .dir=($d|join("/")) | .name as $n | .contents[]?|=f($d+[$n]);
    f([])
;

# Every leaf node gets a basename, eg name without extension.
def basename:
    (entries | select(has("contents") | not)) |= (
        .basename = (.name | sub(".([A-z]*)$";""))
    )
;

# Any page gets its target formats.
def formats($all_formats):
    entries |= (
        .formats = [
            (.make // empty) |
            if . == "null" then empty else . end | # fix for wrong YAML parse
            if (. | type) == "array" then .[] else . end |
            tostring | ascii_downcase |
            if [. == $all_formats[]] | any then .
            elif . == "all" then $all_formats[]
            else error("unrecognized format: \(.)")
            end
        ]
    )
;

# Any page is linked to its first target format.
def link:
    (entries | select(.formats | at_least(1))) |= (
        .link = "\(.dir)/\(.basename).\(.formats[0])"
    )
;

# Front matter is the page to be associated with the enclosing directory rather
# than itself.
def frontmatter:
    (entries | select(has("contents"))) |= (
        (.frontmatter = [.contents[] | select(.frontmatter | bool)][0]) |
        (.contents = .contents - [.frontmatter])
    )
;

# Any page that has no link should be excluded from the index. It would be
# nicer if we could remove in one swoop with `entries | ... |= empty`. It only
# works sometimes, presumably since we'd change the objects we'd iterate over.
def clean:
    if has("contents") then .contents |= map(clean)
    elif has("link")   then .
    else empty end
;

# Add links to the next and previous page for every linked page.
def navigation:
    def f($paths; $i; $j; $key):
        setpath($paths[$i]+[$key];
            getpath($paths[$j]) | {link,title}
        )
    ;
    [ path(entries | select(.formats | at_least(1))) ] as $paths
    | ($paths | length) as $n
    | reduce range(0; $n-1) as $i (.; f($paths; $i; $i+1; "next"))
    | reduce range(1; $n)   as $i (.; f($paths; $i; $i-1; "prev"))
;

# Sort content first according to sort order in metadata.
def sorting:
    (entries | select(has("contents")) | .contents) |= (
        sort_by(.[.sortby // ""] // .sort // .title // .name)
    )
;

# Add a note that tells us whether this has only children who have no more
# subchildren.
def annotate_leaves:
    (entries | select(has("contents"))) |= (
        .only_leaves = (.contents | all(has("contents") | not))
    )
;

# Combines the given stream of JSON objects by merging them, and performs the
# given operations to turn it into a proper index.
def index($all_formats):
    merge
    | directory
    | basename
    | formats($all_formats)
    | link
    | clean
    | navigation
    | frontmatter
    | sorting
    | annotate_leaves
;

# Get target files and their dependencies as an enumeration of {"target":...,
# "dependencies":...} objects.
def targets($dest; $default_style):
    entries
    | ., (.frontmatter // empty)
    | .formats[] as $format
    | "\($dest)/\(.dir)/\(.basename).\($format)" as $doc
    | ["\($dest)/\(.dir)/\(.resources?[])"] as $external
    | ["\($dest)/\(.style // $default_style).css"] as $css
    | (if $format == "html" then ($css + $external) else [] end) as $linked
    | (if $format == "pdf"  then ($css + $external) else [] end) as $embedded
    | {"target": $format, "dependencies": ([$doc] + $linked) }
    , {"target": $doc, "dependencies": ($embedded) }
    , {"target": $doc, "dependencies": ["NEXT_LINK := \( .next.link // empty)"]}
    , {"target": $doc, "dependencies": ["PREV_LINK := \( .prev.link // empty)"]}
    , {"target": $doc, "dependencies": ["NEXT_TITLE := \( .next.title // empty)"]}
    , {"target": $doc, "dependencies": ["PREV_TITLE := \( .prev.title // empty)"]}
;


:lipstick: Fix warning by removing unused module.

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

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

(: 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 $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 $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 %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  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 %rest:distinct  variable $language           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $labelRole          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 $networkIdentifier := distinct-values(($networkIdentifier, $role))

(: Object resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic)
let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $aid)

let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label,
      { LabelsOnly: true, ExactLabelMatches: true})

let $res as object* :=
  for $component in $components
  for $concept in $component.Concepts[]
  where empty($reportElement) or $concept.Name = $reportElement
  for $found-label in $concept.Labels[]
  where (empty($label) or $found-label.Value = $label) and
        (empty($language) or $found-label.Language = $language) and
        (empty($labelRole) or $found-label.Role = $labelRole)
  return {|
    { Archive: $component.Archive },
    { ComponentRole: $component.Role },
    { Concept: $concept.Name },
    $found-label
  |}

let $result := { Labels: [ $res ] }
let $comment :=
 {
    NumLabels : count($res),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <Labels>{
          api:json-to-xml($res.Labels[], "Label")
        }</Labels>
    },
    to-csv : function($res as object) as string {
        api:json-to-csv($res.Labels[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "labels")
return api:check-and-return-results($token, $results, $format)

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

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

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

(: 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 $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 $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 %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  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 %rest:distinct  variable $language           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $labelRole          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 $networkIdentifier := distinct-values(($networkIdentifier, $role))

(: Object resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic)
let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $aid)

let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label,
      { LabelsOnly: true, ExactLabelMatches: true})

let $res as object* :=
  for $component in $components
  for $concept in $component.Concepts[]
  where empty($reportElement) or $concept.Name = $reportElement
  for $found-label in $concept.Labels[]
  where (empty($label) or $found-label.Value = $label) and
        (empty($language) or $found-label.Language = $language) and
        (empty($labelRole) or $found-label.Role = $labelRole)
  return {|
    { Archive: $component.Archive },
    { ComponentRole: $component.Role },
    { Concept: $concept.Name },
    $found-label
  |}

let $result := { Labels: [ $res ] }
let $comment :=
 {
    NumLabels : count($res),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <Labels>{
          api:json-to-xml($res.Labels[], "Label")
        }</Labels>
    },
    to-csv : function($res as object) as string {
        api:json-to-csv($res.Labels[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "labels")
return api:check-and-return-results($token, $results, $format)


:pencil: Add test.

:pencil: Add test.

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

test:check-all-success({
    cocacola: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      1
    ),
    tickerrole: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        fiscalYear:"2013",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      60
    ),
    tickerconcept: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        concept:"us-gaap:Assets",
        fiscalYear:"2013",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      76
    ),
    tickerfyfprole: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      70
    ),
    generic: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        profile-name: "generic",
        aid: "0000021344-14-000008",
        role: "http://www.thecocacolacompany.com/role/ConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      76
    ),
    merge: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
          ticker:"ko",
          fiscalYear: ["2012", "2013" ],
          fiscalPeriod: "FY",
          disclosure:"BalanceSheet",
          merge: "true"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      153
    ),
    merge-periods: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker: "ko",
        networkIdentifier: "http://www.thecocacolacompany.com/role/CondensedConsolidatedStatementsOfIncome",
        fiscalYear: 2014,
        fiscalPeriod: [ "Q1", "Q2", "Q3"],
        labels: "true"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      84
    ),
    filter: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
          ticker:"ko",
          fiscalYear: ["2012", "2013" ],
          fiscalPeriod: "FY",
          disclosure:"BalanceSheet",
          merge: "true",
          override: "true"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      72
    ),
    rules: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
          ticker:"ko",
          fiscalYear: ["2013", "2014" ],
          fiscalPeriod: "FY",
          disclosure:"BalanceSheet",
          merge: "true",
          override: "true",
          validate: "true"
      },
      function($b as item*) as item* { count($b.FactTable[].Valid[$$]) },
      13
    )
})

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

test:check-all-success({
    cocacola: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      1
    ),
    tickerrole: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        fiscalYear:"2013",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      60
    ),
    tickerconcept: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        concept:"us-gaap:Assets",
        fiscalYear:"2013",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      76
    ),
    tickerfyfprole: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      70
    ),
    generic: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        profile-name: "generic",
        aid: "0000021344-14-000008",
        role: "http://www.thecocacolacompany.com/role/ConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      76
    ),
    merge: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
          ticker:"ko",
          fiscalYear: ["2012", "2013" ],
          fiscalPeriod: "FY",
          disclosure:"BalanceSheet",
          merge: "true"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      153
    ),
    merge-periods: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
        ticker: "ko",
        networkIdentifier: "http://www.thecocacolacompany.com/role/CondensedConsolidatedStatementsOfIncome",
        fiscalYear: 2014,
        fiscalPeriod: [ "Q1", "Q2", "Q3"],
        labels: "true"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      84
    ),
    filter: test:invoke-and-assert-deep-equal(
      "facttable-for-component",
      {
          ticker:"ko",
          fiscalYear: ["2012", "2013" ],
          fiscalPeriod: "FY",
          disclosure:"BalanceSheet",
          merge: "true",
          override: "true"
      },
      function($b as item*) as item* { count($b.FactTable[]) },
      72
    )
})