:pencil: A fix.

:pencil: A fix.

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 entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace labels = "http://28.io/modules/xbrl/labels";
import module namespace config = "http://apps.28.io/config";

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

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

declare option rest:response "first-item";

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token          as string?  external;
declare  %rest:case-insensitive                 variable $profile-name   as string  external := $config:profile-name;
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 := "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 %rest:distinct  variable $disclosure     as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $section        as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $role           as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $eid            as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $label          as string*  external;
declare  %rest:case-insensitive                 variable $report         as string?  external;
declare  %rest:case-insensitive %rest:distinct  variable $name           as string*  external;
declare  %rest:case-insensitive                 variable $onlyNames      as boolean? external := false;
declare  %rest:case-insensitive                 variable $onlyTextBlocks as boolean? external := ();
declare  %rest:case-insensitive                 variable $abstract       as boolean? external := ();
declare  %rest:case-insensitive                 variable $kind           as string*  external := ();
declare  %rest:case-insensitive                 variable $count          as boolean external := false;
declare  %rest:case-insensitive                 variable $language           as string  external := "en-US";

(: 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 $kind as string* := api:preprocess-concept-kind($kind)
let $section := distinct-values(($section, $role))

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)

let $entities as object* :=
    ($entities[entities:eid($$) = $archives.Entity],
    let $not-found := $archives.Entity[not entities:eid($entities) = $$]
    where exists($not-found)
    return entities:entities($not-found))

let $report as object? := reports:reports($report)

let $concepts as object* :=
    multiplexer:concepts(
      $profile-name,
      $archives,
      $name,
      $disclosure,
      $section,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"],
      $report,
      $onlyNames)[
    (empty($onlyTextBlocks) or $$.IsTextBlock eq $onlyTextBlocks) and
    (empty($abstract) or $$.IsAbstract eq $abstract) and
    (empty($kind) or $$.Kind = $kind)
  ]

let $result :=
  let $all-aids as string* := distinct-values($concepts.Archive)
  let $roles as string* := distinct-values($concepts.Role)
  let $components := components:components-for-archives-and-roles($all-aids, $roles)
  return {
    ReportElements : [
      switch(true)
      case $count return ()
      case $onlyNames return distinct-values($concepts.Name)
      default return
        for $concept in $concepts
        group by $archive := $concept.Archive,  $role := $concept.Role
        let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
        return
              let $archive-object as object := $archives[$$._id eq $archive]
              let $entity as object := $entities[entities:eid($$) = $archive-object.Entity]
              let $metadata := {
                  Archive : $archive,
                  Section : $component.Label,
                  SectionURI : $component.Role
              }
              let $metadata-sec := {
                  CIK : entities:eid($entity),
                  EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                  FiscalYear : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                  FiscalPeriod : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
              }[$profile-name eq "sec"]
              for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    {
                      Label: values(labels:labels(
                        $concept.Name,
                        $labels:STANDARD_LABEL_ROLE,
                        $language,
                        $concept,
                        (),
                        ()
                      ))
                    },
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: backend:url("labels", {|
                        {
                          token: $token,
                          concept: $original-name,
                          aid: $archive,
                          section: $role,
                          format: $format,
                          profile-name: $profile-name
                        }
                      |}),
                      Facts:
                        if($concept.Kind eq "Concept")
                        then backend:url("facts", {|
                          {
                            token: $token,
                            "xbrl:Concept": $original-name,
                            aid: $archive,
                            format: $format,
                            profile-name: $profile-name
                          },
                          {
                            fiscalYear: "ALL",
                            fiscalPeriod: "ALL",
                            fiscalPeriodType: "ALL"
                          }[$profile-name = ("sec", "japan")]
                        |})
                        else "None"
                    },
                    trim($concept, ("Origin", "_id", "Archive", "Role", "Name",  "Label", "Labels", "IsAbstract", "SubstitutionGroup")),
                    $metadata,
                    $metadata-sec
                |}
        ]
    }
let $comment := {
    NumConcepts: count($result.ReportElements[]),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml($res.ReportElements[], "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          api:json-to-csv($res.ReportElements[])
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return api:check-and-return-results($token, $results, $format)

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 entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace labels = "http://28.io/modules/xbrl/labels";
import module namespace config = "http://apps.28.io/config";

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

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

declare option rest:response "first-item";

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token          as string?  external;
declare  %rest:case-insensitive                 variable $profile-name   as string  external := $config:profile-name;
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 := "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 %rest:distinct  variable $disclosure     as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $section        as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $role           as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $eid            as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $label          as string*  external;
declare  %rest:case-insensitive                 variable $report         as string?  external;
declare  %rest:case-insensitive %rest:distinct  variable $name           as string*  external;
declare  %rest:case-insensitive                 variable $onlyNames      as boolean? external := false;
declare  %rest:case-insensitive                 variable $onlyTextBlocks as boolean? external := ();
declare  %rest:case-insensitive                 variable $abstract       as boolean? external := ();
declare  %rest:case-insensitive                 variable $kind           as string*  external := ();
declare  %rest:case-insensitive                 variable $count          as boolean external := false;
declare  %rest:case-insensitive                 variable $language           as string  external := "en-US";

(: 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 $kind as string* := api:preprocess-concept-kind($kind)
let $section := distinct-values(($section, $role))

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)

let $entities as object* :=
    ($entities[entities:eid($$) = $archives.Entity],
    let $not-found := $archives.Entity[not entities:eid($entities) = $$]
    where exists($not-found)
    return entities:entities($not-found))

let $report as object? := reports:reports($report)

let $concepts as object* :=
    multiplexer:concepts(
      $profile-name,
      $archives,
      $name,
      $disclosure,
      $section,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"],
      $report,
      $onlyNames)[
    (empty($onlyTextBlocks) or $$.IsTextBlock eq $onlyTextBlocks) and
    (empty($abstract) or $$.IsAbstract eq $abstract) and
    (empty($kind) or $$.Kind = $kind)
  ]

let $result :=
  let $all-aids as string* := distinct-values($concepts.Archive)
  let $roles as string* := distinct-values($concepts.Role)
  let $components := components:components-for-archives-and-roles($all-aids, $roles)
  return {
    ReportElements : [
      switch(true)
      case $count return ()
      case $onlyNames return distinct-values($concepts.Name)
      default return
        for $concept in $concepts
        group by $archive := $concept.Archive,  $role := $concept.Role
        let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
        return
              let $archive-object as object := $archives[$$._id eq $archive]
              let $entity as object := $entities[entities:eid($$) = $archive-object.Entity]
              let $metadata := {
                  Archive : $archive,
                  Section : $component.Label,
                  SectionURI : $component.Role
              }
              let $metadata-sec := {
                  CIK : entities:eid($entity),
                  EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                  FiscalYear : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                  FiscalPeriod : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
              }[$profile-name eq "sec"]
              for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    {
                      Label: labels:labels(
                        $original-name,
                        $labels:STANDARD_LABEL_ROLE,
                        $language,
                        $component.Concepts[],
                        (),
                        ()
                      ).$original-name
                    },
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: backend:url("labels", {|
                        {
                          token: $token,
                          concept: $original-name,
                          aid: $archive,
                          section: $role,
                          format: $format,
                          profile-name: $profile-name
                        }
                      |}),
                      Facts:
                        if($concept.Kind eq "Concept")
                        then backend:url("facts", {|
                          {
                            token: $token,
                            "xbrl:Concept": $original-name,
                            aid: $archive,
                            format: $format,
                            profile-name: $profile-name
                          },
                          {
                            fiscalYear: "ALL",
                            fiscalPeriod: "ALL",
                            fiscalPeriodType: "ALL"
                          }[$profile-name = ("sec", "japan")]
                        |})
                        else "None"
                    },
                    trim($concept, ("Origin", "_id", "Archive", "Role", "Name",  "Label", "Labels", "IsAbstract", "SubstitutionGroup")),
                    $metadata,
                    $metadata-sec
                |}
        ]
    }
let $comment := {
    NumConcepts: count($result.ReportElements[]),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml($res.ReportElements[], "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          api:json-to-csv($res.ReportElements[])
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return api:check-and-return-results($token, $results, $format)


:pencil: Putting English first.

:pencil: Putting English first.

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 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)"
        }
      ]
    }
);

(:~
 :
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 := ($concepts, $labels:standard-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 ()
};

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 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)"
        }
      ]
    }
);

(:~
 :
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 := ($concepts, $labels:standard-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 $perfect-matches as object* :=
        $concept-labels[$$.Language = $normalized-languages]
      let $approximate-matches as object* :=
        $concept-labels[$$.Language = $approximate-languages]
      let $label as string? :=
        ($perfect-matches.Value,
         $approximate-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 ()
};