jq: Introduce shorter issues filter alias

Better than find_issues since we use it a lot.

# You can use jq to analyse multiple huge log files at once:
#
#   `yarn -s jq -c 'issues|no_gui|xls|short' path/to/logs.txt*`
#
# The `yarn jq` script doesn't use `--slurp` so JSON lines are filtered one by
# one and memory usage is kept below 100MB.
#
# But we should be able to use it in another aggregation script since the lines
# amount should have been reduced by the initial fitering.
#
# TODO: Find a way to make aggregation work, e.g.:
#   `yarn jq 'find_issues|.msg' | yarn jq:frequencies`

# Errors
def error_level: 50;
def is_error: .level >= error_level;
def select_error: select(is_error);
def find_errors: select_error|{component,path,msg,time};

# Warnings
def warn_level: 40;
def is_warn: .level >= warn_level;
def is_warn_strict: .level == warn_level;
def select_warn: select(is_warn);
def select_warn_strict: select(is_warn_strict);
def find_warns: select_warn|{component,path,msg,time};
def find_warns_strict: select_warn_strict|{component,path,msg,time};

# Conflicts
def is_conflict: .msg == "resolveConflictAsync";
def select_conflict: select(is_conflict);
def find_conflicts: select_conflict|{path,time};

# Non-issues
def is_net_error: .msg | test("net::");
def is_maintenance_page: .msg | test("Maintenance en cours");
def is_seq_already_synced: .msg == "Seq was already synced!";
def is_pending_changes: .msg | test("Prepend [0-9]+ pending change");
def is_non_issue:
  (
    is_net_error or is_maintenance_page or is_seq_already_synced or
    is_pending_changes
  );

# Issues
def is_issue: (is_warn or is_conflict) and (is_non_issue | not);
def select_issue: select(is_issue);
def find_issues: select_issue|{component,path,msg,time,level};
def issues: select_issue;

# Path filtering
def filter_path(pattern): select((.path,.oldpath,"")|strings|test(pattern));

# Config info
def find_client_info:
  select(.appVersion)
    |del(.name)
    |del(.level)
    |del(.v)
    ;

# Utils
def frequencies:
  reduce .[] as $x ({};
    . as $counts | $counts + {($x): (1 + (($counts|getpath([$x])) // 0))}
  );

# You can use jq to analyse multiple huge log files at once:
#
#   `yarn -s jq -c 'issues|no_gui|xls|short' path/to/logs.txt*`
#
# The `yarn jq` script doesn't use `--slurp` so JSON lines are filtered one by
# one and memory usage is kept below 100MB.
#
# But we should be able to use it in another aggregation script since the lines
# amount should have been reduced by the initial fitering.
#
# TODO: Find a way to make aggregation work, e.g.:
#   `yarn jq 'find_issues|.msg' | yarn jq:frequencies`

# Errors
def error_level: 50;
def is_error: .level >= error_level;
def select_error: select(is_error);
def find_errors: select_error|{component,path,msg,time};

# Warnings
def warn_level: 40;
def is_warn: .level >= warn_level;
def is_warn_strict: .level == warn_level;
def select_warn: select(is_warn);
def select_warn_strict: select(is_warn_strict);
def find_warns: select_warn|{component,path,msg,time};
def find_warns_strict: select_warn_strict|{component,path,msg,time};

# Conflicts
def is_conflict: .msg == "resolveConflictAsync";
def select_conflict: select(is_conflict);
def find_conflicts: select_conflict|{path,time};

# Non-issues
def is_net_error: .msg | test("net::");
def is_maintenance_page: .msg | test("Maintenance en cours");
def is_seq_already_synced: .msg == "Seq was already synced!";
def is_pending_changes: .msg | test("Prepend [0-9]+ pending change");
def is_non_issue:
  (
    is_net_error or is_maintenance_page or is_seq_already_synced or
    is_pending_changes
  );

# Issues
def is_issue: (is_warn or is_conflict) and (is_non_issue | not);
def select_issue: select(is_issue);
def find_issues: select_issue|{component,path,msg,time,level};

# Path filtering
def filter_path(pattern): select((.path,.oldpath,"")|strings|test(pattern));

# Config info
def find_client_info:
  select(.appVersion)
    |del(.name)
    |del(.level)
    |del(.v)
    ;

# Utils
def frequencies:
  reduce .[] as $x ({};
    . as $counts | $counts + {($x): (1 + (($counts|getpath([$x])) // 0))}
  );


:green_heart: Add test.

:green_heart: Add test.

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

test:check-all-success({
    cocacola: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        fiscalYear: "2013",
        fiscalPeriod: "FY",
        merge: "false"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      1
    ),
    tickerrole: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      60
    ),
    tickerconcept: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        fiscalYear:"2013",
        concept:"us-gaap:Assets",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      76
    ),
    tickerfyfprole: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      70
    ),
    generic: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
        profile-name: "generic",
        aid: "0000021344-14-000008",
        role:"http://www.thecocacolacompany.com/role/ConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      76
    ),
    merge: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
            ticker: "ko",
            fiscalYear: [ "2012", "2013" ],
            fiscalPeriod: "FY",
            disclosure: "BalanceSheet"
          },
          function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
          153
        ),
    html: test:invoke-and-assert-status(
          "spreadsheet-for-component",
          {
            ticker:"ko",
            fiscalYear:"2012",
            fiscalPeriod:"Q1",
            networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets",
            format: "html"
          },
          302
    ),
    override: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
            ticker: "ko",
            fiscalYear: [ "2012", "2013" ],
            fiscalPeriod: "FY",
            disclosure: "BalanceSheet",
            override: "true"
          },
          function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
          72
        ),
        one-table: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
              "aid" : "0001214659-13-001731",
              "role" : "http://www.brin.com/role/SupplementalCashFlowInformationDetails",
              "hypercube" : "us-gaap:ScheduleOfShareBasedCompensationArrangementsByShareBasedPaymentAwardTable"
          },
          function($res as item*) as item* { count(flatten($res.TableSet[].TableCells.Facts).Value) },
          6
        ),
    qualcomm: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
          "cik" : "804328",
          "fiscalPeriod" : "FY",
          "fiscalYear" : "2013",
          "fiscalYear" : "2014",
          "fiscalPeriodType" : "instant",
          "fiscalPeriodType" : "YTD",
          "disclosure" : "IncomeStatement",
          "override" : "true"
      },
      function($res as item*) as item* { count(flatten($res.TableSet[].TableCells.Facts).Value) },
      6
    ),
})

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

test:check-all-success({
    cocacola: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        fiscalYear: "2013",
        fiscalPeriod: "FY",
        merge: "false"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      1
    ),
    tickerrole: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      60
    ),
    tickerconcept: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        fiscalYear:"2013",
        concept:"us-gaap:Assets",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      76
    ),
    tickerfyfprole: test:invoke-and-assert-deep-equal(
      "spreadsheet-for-component",
      {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      70
    ),
    generic: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
        profile-name: "generic",
        aid: "0000021344-14-000008",
        role:"http://www.thecocacolacompany.com/role/ConsolidatedBalanceSheets"
      },
      function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
      76
    ),
    merge: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
            ticker: "ko",
            fiscalYear: [ "2012", "2013" ],
            fiscalPeriod: "FY",
            disclosure: "BalanceSheet"
          },
          function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
          153
        ),
    html: test:invoke-and-assert-status(
          "spreadsheet-for-component",
          {
            ticker:"ko",
            fiscalYear:"2012",
            fiscalPeriod:"Q1",
            networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets",
            format: "html"
          },
          302
    ),
    override: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
            ticker: "ko",
            fiscalYear: [ "2012", "2013" ],
            fiscalPeriod: "FY",
            disclosure: "BalanceSheet",
            override: "true"
          },
          function($b as item*) as item* { count(flatten($b.TableSet[].TableCells.Facts).Value) },
          72
        ),
        one-table: test:invoke-and-assert-deep-equal(
          "spreadsheet-for-component",
          {
              "aid" : "0001214659-13-001731",
              "role" : "http://www.brin.com/role/SupplementalCashFlowInformationDetails",
              "hypercube" : "us-gaap:ScheduleOfShareBasedCompensationArrangementsByShareBasedPaymentAwardTable"
          },
          function($res as item*) as item* { count(flatten($res.TableSet[].TableCells.Facts).Value) },
          6
        )
})


:green_heart: Fix warning.

:green_heart: Fix warning.

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

(:~
 :
This module provides functionality for manipulating hypercubes.

 :
 :
A hypercube provides a multi-dimensional structure to organize facts.

 :
 :
The dimensions of a hypercube correspond to XBRL aspects (concepts, entities, periods,
 : units, further XBRL dimensions). An XBRL hypercube only is made of XBRL dimensions, however
 : including the other aspects as well is very useful in the context of NOLAP, so that 28.io
 : does so.

 :
 :
28.io introduces the notion of default hypercube. A default hypercube is implicitly added
 : to each component, and only contains the concept, entity, period and unit aspects.
 : In other words, the default hypercube
 : of a component contains all these facts that do not have any XBRL dimensions.

 :
 :
With this module, you can retrieve all hypercubes contained in a component. You can
 : retrieve all facts contained in a hypercube (default dimension values are processed automatically),
 : either in raw form, or organized as a (2D) fact table. You can also populate a network (for example,
 : a presentation network) with the facts contained in a hypercube.

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

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

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

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

(:~
 : Joker for all archives.
 :)
declare variable $hypercubes:ALL_ARCHIVES as boolean := true;

(:~
 : Joker for all archives.
 :)
declare variable $hypercubes:IMPLIED_TABLE as string := "xbrl28:ImpliedTable";

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

 :
 : @param $components a sequence of components.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercube-names-for-components($components as object*) as string*
{
  distinct-values(hypercubes:hypercubes-for-components($components).Name)
};

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

 :
 : @param $components a sequence of components.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components($components as object*) as object*
{
  for $hypercubes in $components.Hypercubes
  return typeswitch($hypercubes)
         case object return values($hypercubes)
         case array return $hypercubes[]
         default return ()
};

(:~
 :
Retrieves all hypercubes in the supplied components and
 : with the given names.

 :
 : @param $components a sequence of components.
 : @param $names a sequence of names.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components(
    $components as object*,
    $names as string*
) as object*
{
  hypercubes:hypercubes-for-components($components, $names, false)
};

(:~
 :
Retrieves all hypercubes in the supplied components and
 : with the given names.

 :
 : @param $components a sequence of components.
 : @param $names a sequence of names.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components(
    $components as object*,
    $names as string*,
    $merge as boolean
) as object*
{
  let $hypercubes := hypercubes:hypercubes-for-components($components)[$$.Name = $names]
  return if($merge and count($hypercubes) gt 1)
         then hypercubes:merge($hypercubes)
         else $hypercubes
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
 :
 : @param $hypercube a hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts(
    $hypercube as object
)
as object*
{
  facts:facts-for({Hypercube: $hypercube})
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
 :
 : @param $hypercube a hypercube.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts(
    $hypercube as object,
    $options as object?
)
as object*
{
  let $options as object := facts:merge-objects(
    { Hypercube: $hypercube },
    $options,
    true (: parameter has higher priority than option :)
  )
  return facts:facts-for($options)
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts-for-hypercube(
    $hypercube as object,
    $archives as item*
)
as object*
{
  hypercubes:facts-for-hypercube($hypercube, $archives, {})
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts-for-hypercube(
    $hypercube as object,
    $archives as item*,
    $options as object?
)
as item* (: object* :)
{
      let $options as object :=
        facts:merge-objects(
          {
            Hypercube: $hypercube,
            Filter: { $facts:ASPECTS : { $facts:ARCHIVE : [ archives:aid($archives) ] }}
          },
          $options,
          true (: parameter has higher priority than option :)
        )
      return facts:facts-for($options)
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*) as array
{
  hypercubes:fact-table-for-hypercube($hypercube, $archives, {})
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*,
  $options as object?) as array
{
  hypercubes:fact-table-for-hypercube-and-facts(
    $hypercube,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
Utility function to convert a sequence of facts to a fact table
 : given a hypercube. It is not recommended to use this function directly.
 : Use hypercubes:fact-table-for-hypercube instead.

 :
 :
 : @param $hypercube a hypercube.
 : @param $facts a sequence of facts that were retrieved with the hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube-and-facts(
  $hypercube as object,
  $facts as object*) as array
{
  let $dimensions := distinct-values((
      $facts:CONCEPT,
      $facts:ENTITY,
      $facts:PERIOD,
      $facts:UNIT,
      keys($hypercube.Aspects)))
  return [
    [ $dimensions, "Value" ],
    for $fact in $facts
    return [
      $dimensions ! (let $member := $fact.Aspects.($$)
                     return if(exists($member))
                            then $member
                            else null),
      $fact.Value
    ]
  ]
};

(:~
 :
 :
Populates a concept-tree network with all facts from the supplied archives,
 : that are relevant to the
 : supplied hypercube. Default dimension values are added to the facts
 : when missing.

 :
 : @param $networks networks.
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-facts(
  $networks as object*,
  $hypercube as object,
  $archives as item*) as object*
{
  hypercubes:populate-networks-with-facts($networks, $hypercube, $archives, {})
};

(:~
 :
 :
Populates a concept-tree network with all facts from the supplied archives,
 : that are relevant to the
 : supplied hypercube. Default dimension values are added to the facts
 : when missing.

 :
 : @param $networks networks.
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-facts(
  $networks as object*,
  $hypercube as object,
  $archives as item*,
  $options as object?) as object*
{
  hypercubes:populate-networks-with-retrieved-facts(
    $networks,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
A utility function that populates networks with facts against concepts that belong
 : to them. It is not recommended to use this function directly.
 : Use hypercubes:populate-networks-with-retrieved-facts.

 :
 : @param $networks networks.
 : @param $facts a sequence of facts.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-retrieved-facts(
  $networks as object*,
  $facts as object*) as object*
{
  let $fact-table-by-concepts := {|
    for $f in $facts
    group by $concept := $f.Aspects."xbrl:Concept"
    return { $concept : [ $f ] }
  |}
  return
    copy $result := $networks
    modify
      for $concept in descendant-objects($result.Trees)[exists($$.Name)]
      let $name := $concept.Name
      return insert json { "Facts" : $fact-table-by-concepts.$name } into $concept
    return $result
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing only the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited.

 :
 : @return dimensionless hypercube instantiation.
 :)
declare function hypercubes:dimensionless-hypercube() as object
{
  hypercubes:dimensionless-hypercube(())
};

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

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

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

 :
 : @return the hypercube instantiation.
 : @param $dimensions: an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have two (optional) fields: Default (a string) and Domain (an array of strings).
 :
 : @error hypercubes:invalid-type if a member of a typed dimension does not match the expected type
 : @error hypercubes:invalid-type if a member of an explicit dimension is not of type string
 :)
declare function hypercubes:user-defined-hypercube($dimensions as object?) as object
{
    hypercubes:user-defined-hypercube($dimensions, ())
};

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

 :
 : @return the hypercube instantiation.
 : @param $dimensions: an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have two (optional) fields: Default (a string) and Domain (an array of strings).
 : @param $options: an object with two options: Name (default: xbrl:UserDefinedHypercube) and Label
 : (default: User-defined Hypercube).
 :
 : @error hypercubes:invalid-type if a member of a typed dimension does not match the expected type
 : @error hypercubes:invalid-type if a member of an explicit dimension is not of type string
 :)
declare function hypercubes:user-defined-hypercube($dimensions as object?, $options as object?) as object
{
  {
    "Name" : ($options.Name, "xbrl:UserDefinedHypercube")[1],
    "Label" : ($options.Label, "User-defined Hypercube")[1],
    "IsOpen" : boolean($options.IsOpen),
    "Aspects" : {|
      for $dimension-name as string in distinct-values((
          "xbrl:Concept",
          "xbrl:Entity",
          "xbrl:Period",
          "xbrl:Unit",
          keys($dimensions)
      ))
      let $dimension-spec as object? := $dimensions.$dimension-name
      let $dimension-type as string? := $dimension-spec.Type
      let $dimension-domain as array? := $dimension-spec.Domain
      let $dimension-default as atomic? := $dimension-spec.Default
      let $slicer as boolean := boolean($dimension-spec.Slicer)
      let $column as integer? := $dimension-spec.Column
      let $domain-name := $dimension-name || "Domain"
      order by $column empty greatest
      return {
        $dimension-name : {|
          (: Name :)
          {
            "Name" : $dimension-name
          },

          (: Label :)
          switch($dimension-name)
          case "xbrl:Concept" return {
            "Label" : "XBRL Concept Dimension"
          }
          case "xbrl:Entity" return {
            "Label" : "XBRL Entity Dimension"
          }
          case "xbrl:Period" return {
            "Label" : "XBRL Period Dimension"
          }
          case "xbrl:Unit" return {
            "Label" : "XBRL Unit Dimension"
          }
          default return (),

          (: Default value :)
          switch(true)
          case exists($dimension-default) return {
            "Default" :
              hypercubes:check-type-and-return($dimension-default,
                                               ($dimension-type, "string")[1])
          }
          case $dimension-name eq "xbrl:Unit" return {
            "Default" : "xbrl:NonNumeric"
          }
          default return (),

          (: Explicit vs. Typed :)
          {
            "Kind" : if(exists($dimension-type))
                     then "TypedDimension"
                     else "ExplicitDimension"
          },

          (: Type :)
          { "Type": $dimension-type }[exists($dimension-type)],

          (: Slicer :)
          { "Slicer" : $slicer },

          (: Slicer :)
          { "Column" : $column }[exists($column)],

          (: Domain :)
          if (empty($dimension-type))
          then
          {
            "Members" : [
              for $dimension-member in distinct-values($dimension-domain[])
              return {
                  Name: hypercubes:check-type-and-return($dimension-member, "string")
              }
            ]
          }[exists($dimension-domain)]
          else {
            "DomainRestriction" : {
              "Name" : $domain-name,
              "Label" : $dimension-name || " Domain",
              "Enumeration" : [ hypercubes:check-type-and-return($dimension-domain[], $dimension-type) ]
            }
          }[exists($dimension-domain)]

        |}
      }
    |}
  }
};

declare %private function hypercubes:check-type-and-return(
    $dimension-members as atomic*,
    $dimension-type as string) as atomic*
{
  for $dimension-member as atomic in $dimension-members
  let $member-type as string := local-name-from-QName(schema:schema-type($dimension-member))
  let $expected-type as string := local-name-from-QName(xs:QName($dimension-type))
  return
  if (not $dimension-member instance of null
      and $member-type ne $dimension-type
      and ($member-type ne "int" or $expected-type ne "integer")
      )
  then error(
        xs:QName("hypercubes:invalid-type"),
        schema:schema-type($dimension-member) ||
          ": type doesn't match expected type (" || $dimension-type || ")")
  else $dimension-member
};

declare function hypercubes:modify-hypercube(
    $hypercube as object,
    $options as object?) as object
{
    let $options :=
    {|
        for $dimension in keys(($hypercube.Aspects, $options))
        let $hypercube-metadata := $hypercube.Aspects.$dimension
        let $new-metadata := $options.$dimension
        return {
            $dimension : if(exists($new-metadata))
            then
                $new-metadata
            else {|
                { Type : $hypercube.Aspects.$dimension.Type }[$hypercube-metadata.Kind eq "TypedDimension"],
                let $hypercube-domain :=
                  (
                     descendant-objects($hypercube-metadata.Members).Name,
                     jn:flatten($hypercube-metadata.DomainRestriction.Enumeration)
                  )
                where exists($hypercube-domain)
                return { Domain : [ distinct-values($hypercube-domain) ] },
                let $hypercube-default := $hypercube-metadata.Default
                where exists($hypercube-default)
                return { Default : $hypercube-default }
            |}
        }
    |}
    return hypercubes:user-defined-hypercube(
        $options,
        {
            Name: $hypercube.Name,
            Label: $hypercube.Label || " (with filter overrides)"
        }
    )
};

declare function hypercubes:merge($hypercubes as object*) as object
{
    hypercubes:user-defined-hypercube({|
        for $aspect in keys($hypercubes.Aspects)
        let $default := distinct-values($hypercubes.Aspects.$aspect.Default)
        let $domain := distinct-values((
            descendant-objects($hypercubes.Aspects.$aspect.Members).Name,
            $hypercubes.Aspects.$aspect.DomainRestriction.Enumeration[]
        ))
        let $kind as string? := distinct-values($hypercubes.Aspects.$aspect.Kind)
        let $type as string? := distinct-values($hypercubes.Aspects.$aspect.Type)
        return {
            $aspect : {|
                { Default: $default}[exists($default)],
                { Type : $type }[$kind eq "TypedDimension"],
                { Domain: [ distinct-values(($domain, $default)) ]}[exists($domain)]
            |}
        }
    |})
};

(:~
 :
Transforms a hypercube network - all, hypercube-dimension, dimension-default,
 :    dimension-domain, domain-member (except primary items).

 :
 : @param $hypercube the hypercube to transform.
 :
 : @return the XBRL locators and arcs to be put into an extended link.
 :)
declare function hypercubes:to-xbrl(
    $hypercube as object,
    $taxonomyName as string
) as element()*
{
  let $hypercubeID as string := replace($hypercube.Name, ":", "_")
  let $hypercubeLocator as element() := <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($hypercube.Name, ":")[1]}.xsd#{$hypercubeID}" xlink:label="{$hypercubeID}" xlink:title="{normalize-space($hypercube.Label)}" />
  return (
    (: primary items -> hypercube :)
    for $rootConcept at $pos in $hypercube.Aspects."xbrl:Concept".Members[]
    count $order
    let $fromID as string := replace($rootConcept.Name, ":", "_")
    let $fromLabel as string := $fromID || "_" || $pos
    return (
      <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($rootConcept.Name, ":")[1]}.xsd#{$fromID}" xlink:label="{$fromLabel}" xlink:title="{normalize-space($rootConcept.Label)}" />,
      <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/all" xlink:from="{$fromLabel}" xlink:to="{$hypercubeID}" use="optional" order="{$order}" xbrldt:contextElement="segment" />
    ),
    $hypercubeLocator,

    (: hypercube -> dimension :)
    for $dimension at $pos in values($hypercube.Aspects)[not $$.Name = $concepts:BUILT_IN_REPORT_ELEMENTS]
    let $dimensionID as string := replace($dimension.Name, ":", "_")
    let $dimensionLabel as string := $dimensionID || "_" || $pos
    let $default as string? := $dimension.Default
    return (
      <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/hypercube-dimension" xlink:from="{$hypercubeID}" xlink:to="{$dimensionLabel}" use="optional" order="{$pos}" />,
      <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($dimension.Name, ":")[1]}.xsd#{$dimensionID}" xlink:label="{$dimensionLabel}" xlink:title="{normalize-space($dimension.Label)}" />,

      (: dimension -> default :)
      if(exists($default))
      then
        let $defaultID as string := replace($default, ":", "_")
        let $defaultLabel as string := $defaultID || "_" || $pos
        return (
          <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/dimension-default" xlink:from="{$dimensionLabel}" xlink:to="{$defaultLabel}" use="optional" order="1" />,
          <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($default, ":")[1]}.xsd#{$defaultID}" xlink:label="{$defaultLabel}" xlink:title="{normalize-space(())}" />
        )
      else (),

      (: dimension -> domain :)
      for $domain at $pos in $dimension.Members[]
      count $order
      let $domainID as string := replace($domain.Name, ":", "_")
      let $domainLabel as string := $domainID || "_" || $pos
      return (
        <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/dimension-domain" xlink:from="{$dimensionLabel}" xlink:to="{$domainLabel}" use="optional" order="{$order}" />,
        <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($domain.Name, ":")[1]}.xsd#{$domainID}" xlink:label="{$domainLabel}" xlink:title="{normalize-space($domain.Label)}" />,

        (: domain -> member :)
        concepts:tree-to-xbrl($domain, "http://xbrl.org/int/dim/arcrole/domain-member", $taxonomyName, 1, $pos)
      )
    )
  )
};

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

(:~
 :
This module provides functionality for manipulating hypercubes.

 :
 :
A hypercube provides a multi-dimensional structure to organize facts.

 :
 :
The dimensions of a hypercube correspond to XBRL aspects (concepts, entities, periods,
 : units, further XBRL dimensions). An XBRL hypercube only is made of XBRL dimensions, however
 : including the other aspects as well is very useful in the context of NOLAP, so that 28.io
 : does so.

 :
 :
28.io introduces the notion of default hypercube. A default hypercube is implicitly added
 : to each component, and only contains the concept, entity, period and unit aspects.
 : In other words, the default hypercube
 : of a component contains all these facts that do not have any XBRL dimensions.

 :
 :
With this module, you can retrieve all hypercubes contained in a component. You can
 : retrieve all facts contained in a hypercube (default dimension values are processed automatically),
 : either in raw form, or organized as a (2D) fact table. You can also populate a network (for example,
 : a presentation network) with the facts contained in a hypercube.

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

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

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

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

(:~
 : Joker for all archives.
 :)
declare variable $hypercubes:ALL_ARCHIVES as boolean := true;

(:~
 : Joker for all archives.
 :)
declare variable $hypercubes:IMPLIED_TABLE as string := "xbrl28:ImpliedTable";

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

 :
 : @param $components a sequence of components.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercube-names-for-components($components as object*) as string*
{
  distinct-values(hypercubes:hypercubes-for-components($components).Name)
};

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

 :
 : @param $components a sequence of components.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components($components as object*) as object*
{
  for $hypercubes in $components.Hypercubes
  return typeswitch($hypercubes)
         case object return values($hypercubes)
         case array return $hypercubes[]
         default return ()
};

(:~
 :
Retrieves all hypercubes in the supplied components and
 : with the given names.

 :
 : @param $components a sequence of components.
 : @param $names a sequence of names.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components(
    $components as object*,
    $names as string*
) as object*
{
  hypercubes:hypercubes-for-components($components, $names, false)
};

(:~
 :
Retrieves all hypercubes in the supplied components and
 : with the given names.

 :
 : @param $components a sequence of components.
 : @param $names a sequence of names.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components(
    $components as object*,
    $names as string*,
    $merge as boolean
) as object*
{
  let $hypercubes := hypercubes:hypercubes-for-components($components)[$$.Name = $names]
  return if(count($hypercubes) gt 1)
         then hypercubes:merge($hypercubes)
         else $hypercubes
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
 :
 : @param $hypercube a hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts(
    $hypercube as object
)
as object*
{
  facts:facts-for({Hypercube: $hypercube})
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
 :
 : @param $hypercube a hypercube.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts(
    $hypercube as object,
    $options as object?
)
as object*
{
  let $options as object := facts:merge-objects(
    { Hypercube: $hypercube },
    $options,
    true (: parameter has higher priority than option :)
  )
  return facts:facts-for($options)
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts-for-hypercube(
    $hypercube as object,
    $archives as item*
)
as object*
{
  hypercubes:facts-for-hypercube($hypercube, $archives, {})
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts-for-hypercube(
    $hypercube as object,
    $archives as item*,
    $options as object?
)
as item* (: object* :)
{
      let $options as object :=
        facts:merge-objects(
          {
            Hypercube: $hypercube,
            Filter: { $facts:ASPECTS : { $facts:ARCHIVE : [ archives:aid($archives) ] }}
          },
          $options,
          true (: parameter has higher priority than option :)
        )
      return facts:facts-for($options)
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*) as array
{
  hypercubes:fact-table-for-hypercube($hypercube, $archives, {})
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied hypercube, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*,
  $options as object?) as array
{
  hypercubes:fact-table-for-hypercube-and-facts(
    $hypercube,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
Utility function to convert a sequence of facts to a fact table
 : given a hypercube. It is not recommended to use this function directly.
 : Use hypercubes:fact-table-for-hypercube instead.

 :
 :
 : @param $hypercube a hypercube.
 : @param $facts a sequence of facts that were retrieved with the hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube-and-facts(
  $hypercube as object,
  $facts as object*) as array
{
  let $dimensions := distinct-values((
      $facts:CONCEPT,
      $facts:ENTITY,
      $facts:PERIOD,
      $facts:UNIT,
      keys($hypercube.Aspects)))
  return [
    [ $dimensions, "Value" ],
    for $fact in $facts
    return [
      $dimensions ! (let $member := $fact.Aspects.($$)
                     return if(exists($member))
                            then $member
                            else null),
      $fact.Value
    ]
  ]
};

(:~
 :
 :
Populates a concept-tree network with all facts from the supplied archives,
 : that are relevant to the
 : supplied hypercube. Default dimension values are added to the facts
 : when missing.

 :
 : @param $networks networks.
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-facts(
  $networks as object*,
  $hypercube as object,
  $archives as item*) as object*
{
  hypercubes:populate-networks-with-facts($networks, $hypercube, $archives, {})
};

(:~
 :
 :
Populates a concept-tree network with all facts from the supplied archives,
 : that are relevant to the
 : supplied hypercube. Default dimension values are added to the facts
 : when missing.

 :
 : @param $networks networks.
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-facts(
  $networks as object*,
  $hypercube as object,
  $archives as item*,
  $options as object?) as object*
{
  hypercubes:populate-networks-with-retrieved-facts(
    $networks,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
A utility function that populates networks with facts against concepts that belong
 : to them. It is not recommended to use this function directly.
 : Use hypercubes:populate-networks-with-retrieved-facts.

 :
 : @param $networks networks.
 : @param $facts a sequence of facts.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-retrieved-facts(
  $networks as object*,
  $facts as object*) as object*
{
  let $fact-table-by-concepts := {|
    for $f in $facts
    group by $concept := $f.Aspects."xbrl:Concept"
    return { $concept : [ $f ] }
  |}
  return
    copy $result := $networks
    modify
      for $concept in descendant-objects($result.Trees)[exists($$.Name)]
      let $name := $concept.Name
      return insert json { "Facts" : $fact-table-by-concepts.$name } into $concept
    return $result
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing only the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited.

 :
 : @return dimensionless hypercube instantiation.
 :)
declare function hypercubes:dimensionless-hypercube() as object
{
  hypercubes:dimensionless-hypercube(())
};

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

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

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

 :
 : @return the hypercube instantiation.
 : @param $dimensions: an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have two (optional) fields: Default (a string) and Domain (an array of strings).
 :
 : @error hypercubes:invalid-type if a member of a typed dimension does not match the expected type
 : @error hypercubes:invalid-type if a member of an explicit dimension is not of type string
 :)
declare function hypercubes:user-defined-hypercube($dimensions as object?) as object
{
    hypercubes:user-defined-hypercube($dimensions, ())
};

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

 :
 : @return the hypercube instantiation.
 : @param $dimensions: an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have two (optional) fields: Default (a string) and Domain (an array of strings).
 : @param $options: an object with two options: Name (default: xbrl:UserDefinedHypercube) and Label
 : (default: User-defined Hypercube).
 :
 : @error hypercubes:invalid-type if a member of a typed dimension does not match the expected type
 : @error hypercubes:invalid-type if a member of an explicit dimension is not of type string
 :)
declare function hypercubes:user-defined-hypercube($dimensions as object?, $options as object?) as object
{
  {
    "Name" : ($options.Name, "xbrl:UserDefinedHypercube")[1],
    "Label" : ($options.Label, "User-defined Hypercube")[1],
    "IsOpen" : boolean($options.IsOpen),
    "Aspects" : {|
      for $dimension-name as string in distinct-values((
          "xbrl:Concept",
          "xbrl:Entity",
          "xbrl:Period",
          "xbrl:Unit",
          keys($dimensions)
      ))
      let $dimension-spec as object? := $dimensions.$dimension-name
      let $dimension-type as string? := $dimension-spec.Type
      let $dimension-domain as array? := $dimension-spec.Domain
      let $dimension-default as atomic? := $dimension-spec.Default
      let $slicer as boolean := boolean($dimension-spec.Slicer)
      let $column as integer? := $dimension-spec.Column
      let $domain-name := $dimension-name || "Domain"
      order by $column empty greatest
      return {
        $dimension-name : {|
          (: Name :)
          {
            "Name" : $dimension-name
          },

          (: Label :)
          switch($dimension-name)
          case "xbrl:Concept" return {
            "Label" : "XBRL Concept Dimension"
          }
          case "xbrl:Entity" return {
            "Label" : "XBRL Entity Dimension"
          }
          case "xbrl:Period" return {
            "Label" : "XBRL Period Dimension"
          }
          case "xbrl:Unit" return {
            "Label" : "XBRL Unit Dimension"
          }
          default return (),

          (: Default value :)
          switch(true)
          case exists($dimension-default) return {
            "Default" :
              hypercubes:check-type-and-return($dimension-default,
                                               ($dimension-type, "string")[1])
          }
          case $dimension-name eq "xbrl:Unit" return {
            "Default" : "xbrl:NonNumeric"
          }
          default return (),

          (: Explicit vs. Typed :)
          {
            "Kind" : if(exists($dimension-type))
                     then "TypedDimension"
                     else "ExplicitDimension"
          },

          (: Type :)
          { "Type": $dimension-type }[exists($dimension-type)],

          (: Slicer :)
          { "Slicer" : $slicer },

          (: Slicer :)
          { "Column" : $column }[exists($column)],

          (: Domain :)
          if (empty($dimension-type))
          then
          {
            "Members" : [
              for $dimension-member in distinct-values($dimension-domain[])
              return {
                  Name: hypercubes:check-type-and-return($dimension-member, "string")
              }
            ]
          }[exists($dimension-domain)]
          else {
            "DomainRestriction" : {
              "Name" : $domain-name,
              "Label" : $dimension-name || " Domain",
              "Enumeration" : [ hypercubes:check-type-and-return($dimension-domain[], $dimension-type) ]
            }
          }[exists($dimension-domain)]

        |}
      }
    |}
  }
};

declare %private function hypercubes:check-type-and-return(
    $dimension-members as atomic*,
    $dimension-type as string) as atomic*
{
  for $dimension-member as atomic in $dimension-members
  let $member-type as string := local-name-from-QName(schema:schema-type($dimension-member))
  let $expected-type as string := local-name-from-QName(xs:QName($dimension-type))
  return
  if (not $dimension-member instance of null
      and $member-type ne $dimension-type
      and ($member-type ne "int" or $expected-type ne "integer")
      )
  then error(
        xs:QName("hypercubes:invalid-type"),
        schema:schema-type($dimension-member) ||
          ": type doesn't match expected type (" || $dimension-type || ")")
  else $dimension-member
};

declare function hypercubes:modify-hypercube(
    $hypercube as object,
    $options as object?) as object
{
    let $options :=
    {|
        for $dimension in keys(($hypercube.Aspects, $options))
        let $hypercube-metadata := $hypercube.Aspects.$dimension
        let $new-metadata := $options.$dimension
        return {
            $dimension : if(exists($new-metadata))
            then
                $new-metadata
            else {|
                { Type : $hypercube.Aspects.$dimension.Type }[$hypercube-metadata.Kind eq "TypedDimension"],
                let $hypercube-domain :=
                  (
                     descendant-objects($hypercube-metadata.Members).Name,
                     jn:flatten($hypercube-metadata.DomainRestriction.Enumeration)
                  )
                where exists($hypercube-domain)
                return { Domain : [ distinct-values($hypercube-domain) ] },
                let $hypercube-default := $hypercube-metadata.Default
                where exists($hypercube-default)
                return { Default : $hypercube-default }
            |}
        }
    |}
    return hypercubes:user-defined-hypercube(
        $options,
        {
            Name: $hypercube.Name,
            Label: $hypercube.Label || " (with filter overrides)"
        }
    )
};

declare function hypercubes:merge($hypercubes as object*) as object
{
    hypercubes:user-defined-hypercube({|
        for $aspect in keys($hypercubes.Aspects)
        let $default := distinct-values($hypercubes.Aspects.$aspect.Default)
        let $domain := distinct-values((
            descendant-objects($hypercubes.Aspects.$aspect.Members).Name,
            $hypercubes.Aspects.$aspect.DomainRestriction.Enumeration[]
        ))
        let $kind as string? := distinct-values($hypercubes.Aspects.$aspect.Kind)
        let $type as string? := distinct-values($hypercubes.Aspects.$aspect.Type)
        return {
            $aspect : {|
                { Default: $default}[exists($default)],
                { Type : $type }[$kind eq "TypedDimension"],
                { Domain: [ distinct-values(($domain, $default)) ]}[exists($domain)]
            |}
        }
    |})
};

(:~
 :
Transforms a hypercube network - all, hypercube-dimension, dimension-default,
 :    dimension-domain, domain-member (except primary items).

 :
 : @param $hypercube the hypercube to transform.
 :
 : @return the XBRL locators and arcs to be put into an extended link.
 :)
declare function hypercubes:to-xbrl(
    $hypercube as object,
    $taxonomyName as string
) as element()*
{
  let $hypercubeID as string := replace($hypercube.Name, ":", "_")
  let $hypercubeLocator as element() := <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($hypercube.Name, ":")[1]}.xsd#{$hypercubeID}" xlink:label="{$hypercubeID}" xlink:title="{normalize-space($hypercube.Label)}" />
  return (
    (: primary items -> hypercube :)
    for $rootConcept at $pos in $hypercube.Aspects."xbrl:Concept".Members[]
    count $order
    let $fromID as string := replace($rootConcept.Name, ":", "_")
    let $fromLabel as string := $fromID || "_" || $pos
    return (
      <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($rootConcept.Name, ":")[1]}.xsd#{$fromID}" xlink:label="{$fromLabel}" xlink:title="{normalize-space($rootConcept.Label)}" />,
      <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/all" xlink:from="{$fromLabel}" xlink:to="{$hypercubeID}" use="optional" order="{$order}" xbrldt:contextElement="segment" />
    ),
    $hypercubeLocator,

    (: hypercube -> dimension :)
    for $dimension at $pos in values($hypercube.Aspects)[not $$.Name = $concepts:BUILT_IN_REPORT_ELEMENTS]
    let $dimensionID as string := replace($dimension.Name, ":", "_")
    let $dimensionLabel as string := $dimensionID || "_" || $pos
    let $default as string? := $dimension.Default
    return (
      <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/hypercube-dimension" xlink:from="{$hypercubeID}" xlink:to="{$dimensionLabel}" use="optional" order="{$pos}" />,
      <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($dimension.Name, ":")[1]}.xsd#{$dimensionID}" xlink:label="{$dimensionLabel}" xlink:title="{normalize-space($dimension.Label)}" />,

      (: dimension -> default :)
      if(exists($default))
      then
        let $defaultID as string := replace($default, ":", "_")
        let $defaultLabel as string := $defaultID || "_" || $pos
        return (
          <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/dimension-default" xlink:from="{$dimensionLabel}" xlink:to="{$defaultLabel}" use="optional" order="1" />,
          <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($default, ":")[1]}.xsd#{$defaultID}" xlink:label="{$defaultLabel}" xlink:title="{normalize-space(())}" />
        )
      else (),

      (: dimension -> domain :)
      for $domain at $pos in $dimension.Members[]
      count $order
      let $domainID as string := replace($domain.Name, ":", "_")
      let $domainLabel as string := $domainID || "_" || $pos
      return (
        <link:definitionArc xlink:type="arc" xlink:arcrole="http://xbrl.org/int/dim/arcrole/dimension-domain" xlink:from="{$dimensionLabel}" xlink:to="{$domainLabel}" use="optional" order="{$order}" />,
        <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($domain.Name, ":")[1]}.xsd#{$domainID}" xlink:label="{$domainLabel}" xlink:title="{normalize-space($domain.Label)}" />,

        (: domain -> member :)
        concepts:tree-to-xbrl($domain, "http://xbrl.org/int/dim/arcrole/domain-member", $taxonomyName, 1, $pos)
      )
    )
  )
};


:green_heart: Fix test.

:green_heart: Fix test.

機械装置及び運搬具（純額）

その他有価証券評価差額金

退職給付に係る調整累計額

その他の包括利益累計額合計

機械装置及び運搬具（純額）

その他有価証券評価差額金

退職給付に係る調整累計額

その他の包括利益累計額合計


:lipstick: Fixing spaces in core.jq (to reduce diff in other PR

jsoniq version "1.0";

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

(:~
 :
This module provides functionality for querying fiscal reports (10-K, 10-Q)
 : submitted to the SEC.

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

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

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

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

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

(:~
 : Joker for all fiscal periods.
 :)
declare variable $fiscal-core:ALL_FISCAL_PERIODS as string := "ALL";

(:~
 : Joker for all fiscal period types.
 :)
declare variable $fiscal-core:ALL_FISCAL_PERIOD_TYPES as string := "ALL";

(:~
 : Joker for all fiscal years.
 :)
declare variable $fiscal-core:ALL_FISCAL_YEARS as integer := 0;

(:~
 : Joker for the latest fiscal years.
 :)
declare variable $fiscal-core:LATEST_FISCAL_YEAR as integer := 1;

(:~
 :
Return a distinct set of companies identified by either
 :   CIKs, tags, tickers, or sics.

 :
 : @param $ciks a set of CIKs.
 : @param $tags a set of tags (ALL retrieves all companies).
 : @param $tickers a set of tickers.
 : @param $sics a set of SIC codes.
 :
 : @return the companies with the given identifiers, tags, tickers, or sic codes.
 :)
declare function fiscal-core:filings(
    $entities-or-eids as item*,
    $fiscal-periods as string*,
    $fiscal-years as integer*,
    $archives-or-aids as item*) as object*
{
    let $entities := entities:entities($entities-or-eids)
    return
        if(exists(index-of($fiscal-years, $fiscal-core:LATEST_FISCAL_YEAR)))
        then
            for $entity in $entities
            return
            fiscal-core:latest-filings($entity, $fiscal-periods)
        else
            let $fiscal-years as integer* :=
                $fiscal-years[$$ ne $fiscal-core:LATEST_FISCAL_YEAR]
            return
                fiscal-core:filings-for-entities-and-fiscal-periods-and-years($entities, $fiscal-periods, $fiscal-years),
    archives:archives($archives-or-aids)
};

declare function fiscal-core:latest-filings(
    $entities as object*,
    $fiscal-periods as string*) as object*
{
    for $entity in $entities
    for $fiscal-period in $fiscal-periods
    let $latest-fiscal-year :=
        fiscal-core:latest-reported-fiscal-period($entity, $fiscal-period).year
    return fiscal-core:filings-for-entities-and-fiscal-periods-and-years(
        $entity,
        $fiscal-period,
        $latest-fiscal-year cast as integer?
    )
};

declare function fiscal-core:filter-override(
    $entities-or-eids as item*,
    $fiscal-years as integer*,
    $fiscal-periods as string*,
    $fiscal-period-types as string*,
    $archives-or-aids as item*
) as object?
{
    let $override := exists(($entities-or-eids, $fiscal-years, $fiscal-periods, $fiscal-period-types, $archives-or-aids))
    where $override
    let $eids := entities:eid($entities-or-eids)
    let $entities := entities:entities($entities-or-eids)
    let $aids := archives:aid($archives-or-aids)
    let $latest-filings := fiscal-core:latest-filings($entities, $fiscal-periods)
    let $is-latest := exists(index-of($fiscal-years, $fiscal-core:LATEST_FISCAL_YEAR))
    let $fiscal-years := if(exists(index-of($fiscal-years, $fiscal-core:ALL_FISCAL_YEARS)))
                         then ()
                         else $fiscal-years[$$ ne  $fiscal-core:LATEST_FISCAL_YEAR]
    let $fiscal-periods := if(exists(index-of($fiscal-periods, $fiscal-core:ALL_FISCAL_PERIODS)))
                           then ()
                           else $fiscal-periods
    let $override-fiscal-period-types := exists($fiscal-period-types)
    let $fiscal-period-types := if(exists(index-of($fiscal-period-types, $fiscal-core:ALL_FISCAL_PERIOD_TYPES)))
                                then ()
                                else $fiscal-period-types
    return
     {
        "xbrl:Entity" : {|
          { Type: "string" },
          { Domain: [ $eids ] }[exists($eids)]
        |},

        "xbrl28:Archive" : {|
          { Type: "string" },
          { Domain : [ if($is-latest) then archives:aid($latest-filings) else $aids ] }
          [exists(if($is-latest) then archives:aid($latest-filings) else $aids)]
        |},

        "sec:FiscalYear" : {|
          { Type: "integer" },
          { Domain: [ $fiscal-years ] }[exists($fiscal-years)]
        |},

        "sec:FiscalPeriod" : {|
          { Type: "string" },
          { Domain: [ $fiscal-periods ] }[exists($fiscal-periods)]
        |},

        "sec:FiscalPeriodType" : {|
          { Type: "string" },
          if($override-fiscal-period-types)
          then { Domain: [ $fiscal-period-types ]}[exists($fiscal-period-types)]
          else { Domain: [ "instant", "YTD" ] }
        |}
    }
};

(:~
 :
Retrieves the fiscal period of a filing.

 :
 : @param $filing-or-id a filing or its id, or the empty sequence.
 :
 : @return the fiscal period (FY, YTD2, YTD3, Q1, Q2 or Q3) if any.
 :)
declare function fiscal-core:fiscal-period-for-filing(
    $filing-or-id as item?) as string?
{
  archives:archives($filing-or-id).Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
};

(:~
 :
Retrieves the fiscal period of a fact.

 :
 : @param $fact a fact object or the empty sequence.
 :
 : @return the fiscal period (FY, YTD2, YTD3, Q1, Q2 or Q3) if any.
 :)
declare function fiscal-core:fiscal-period-for-fact(
    $fact as object?) as string?
{
  $fact.Aspects."sec:FiscalPeriod"
};

(:~
 :
Retrieves the fiscal period of a filing or a fact.

 :
 : @deprecated use fiscal-period-for-fact#1 and fiscal-period-for-filing#1
 :
 : @param $filing-fact-or-id a filing, a fact, or its id.
 :
 : @return the fiscal period (FY, YTD2, YTD3, Q1, Q2 or Q3).
 :)
declare function fiscal-core:fiscal-period(
    $filing-fact-or-id as item?) as string?
{
  if(empty($filing-fact-or-id))
  then ()
  else
    typeswitch ($filing-fact-or-id)
    case object return
      switch (true)
      (: archive :)
      case exists($filing-fact-or-id.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus)
        return $filing-fact-or-id.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
      (: fact :)
      case exists($filing-fact-or-id.Aspects."sec:FiscalPeriod")
        return $filing-fact-or-id.Aspects."sec:FiscalPeriod"
      default return ()
    default return
      let $id as xs:string := string($filing-fact-or-id)
      return
        switch(true)
        (:
          "The first ten digits are the CIK of the entity that logged
           in to submit the filing. The next two digits define the
           current year. The final digits are assigned sequentially by
           CIK starting with one on the first business day of each year."

        http://www.sec.gov/info/edgar/edgarfm-vol2-v25.pdf :)
        case matches($id, "\\d{10}-\\d{2}-\\d{6}")
          return archives:archives($id).Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
        default return facts:facts($id).Aspects."sec:FiscalPeriod"
};

(:~
 :
Retrieves the fiscal year of a filing.

 :
 : @param $filing-or-id a filing or its id, or the empty sequence.
 :
 : @return the fiscal year (e.g., 2012) if any.
 :)
declare function fiscal-core:fiscal-year-for-filing(
    $filing-or-id as item?) as integer?
{
  archives:archives($filing-or-id).Profiles.SEC.Fiscal.DocumentFiscalYearFocus
};

(:~
 :
Retrieves the fiscal year of a fact.

 :
 : @param $fact a fact object or the empty sequence.
 :
 : @return the fiscal year (e.g., 2012) if any.
 :)
declare function fiscal-core:fiscal-year-for-fact(
    $fact as object?) as integer?
{
  $fact.Aspects."sec:FiscalYear"
};

(:~
 :
Retrieves the fiscal year of a filing or a fact.

 :
 : @deprecated use fiscal-core:fiscal-year-for-archive#1 or fiscal-core:fiscal-year-for-fact#1.
 :
 : @param $filing-fact-or-id a filing, a fact, or its id.
 :
 : @return the fiscal year (e.g., 2012).
 :)
declare function fiscal-core:fiscal-year(
    $filing-fact-or-id as item?) as integer?
{
  if(empty($filing-fact-or-id))
  then ()
  else
    typeswitch ($filing-fact-or-id)
    case object return
      switch (true)
      (: archive :)
      case exists($filing-fact-or-id.Profiles.SEC.Fiscal.DocumentFiscalYearFocus)
        return $filing-fact-or-id.Profiles.SEC.Fiscal.DocumentFiscalYearFocus
      (: fact :)
      case exists($filing-fact-or-id.Aspects."sec:FiscalYear")
        return $filing-fact-or-id.Aspects."sec:FiscalYear"
      default return ()
    default return
      let $id as xs:string := string($filing-fact-or-id)
      return
        switch(true)
        (:
          "The first ten digits are the CIK of the entity that logged
           in to submit the filing. The next two digits define the
           current year. The final digits are assigned sequentially by
           CIK starting with one on the first business day of each year."

          http://www.sec.gov/info/edgar/edgarfm-vol2-v25.pdf :)
        case matches($id, "\\d{10}-\\d{2}-\\d{6}")
          return archives:archives($id).Profiles.SEC.Fiscal.DocumentFiscalYearFocus
        default return facts:facts($id).Aspects."sec:FiscalYear"
};

(:~
 :
Retrieves all filings by entities and for given fiscal periods and years.

 :
 : @param $entities-or-ids entities or their ids.
 : @param $fiscal-period-focus fiscal periods (FY, YTD1, YTD2, YTD3, Q1, Q2, Q3, Q4) or $fiscal-core:ALL_FISCAL_PERIODS to accept all..
 : @param $fiscal-year-focus fiscal years or $fiscal-core:ALL_FISCAL_YEARS to accept all..
 :
 : @return all filings that match the criteria.
 :)
declare function
fiscal-core:filings-for-entities-and-fiscal-periods-and-years(
    $entities-or-ids as item*,
    $fiscal-period-focus as string*,
    $fiscal-year-focus as integer*) as object*
{
  let $fiscal-period-focus :=
    switch(true)
    case $fiscal-period-focus = ( "10-K", "FY") return "FY"
    case $fiscal-period-focus eq "10-Q" return ( "Q3", "Q2", "Q1" )
    case $fiscal-period-focus eq "YTD1" return "Q1"
    case $fiscal-period-focus eq "YTD2" return "Q2"
    case $fiscal-period-focus eq "YTD3" return "Q3"
    default return $fiscal-period-focus
  for $cik-or-entity in $entities-or-ids
  let $entity-id as xs:string := companies:eid($cik-or-entity)
  let $query := {|
      { "Entity" : $entity-id },
      switch (true)
      case deep-equal($fiscal-period-focus, $fiscal-core:ALL_FISCAL_PERIODS)
        return ()
      case count($fiscal-period-focus) eq 1
        return
        {
          "Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus": $fiscal-period-focus
        }
      default return
        {
          "Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus":
            { "$in" : [ $fiscal-period-focus ] }
        },
      switch (true)
      case deep-equal($fiscal-year-focus, $fiscal-core:ALL_FISCAL_YEARS)
        return ()
      case count($fiscal-year-focus) eq 1
        return
        {
          "Profiles.SEC.Fiscal.DocumentFiscalYearFocus": $fiscal-year-focus
        }
      default
        return
        {
          "Profiles.SEC.Fiscal.DocumentFiscalYearFocus":
            { "$in" : [ $fiscal-year-focus ] }
        }
    |}
  return mw:find($archives:col, $query)
};

(:~
 :
Retrieves the balance sheet for the given entities and fiscal periods and years.

 :
 : @param $entity-or-ids a list of entities or their EIDs.
 : @param $fiscal-period-focus the periods (YTD1, YTD2, YTD3, Q1, Q2, Q3 or FY) or $fiscal-core:ALL_FISCAL_PERIODS to accept all.
 : @param $fiscal-year-focus the years (2012) or $fiscal-core:ALL_FISCAL_YEARS to accept all.
 :
 : @error entities:INVALID_PARAMETER if the CIK or entity is not valid.
 :
 : @return the balance sheet at that date and for that formType.
 :)
declare function fiscal-core:balance-sheets-for-fiscal-periods-and-years(
    $entity-or-ids as item*,
    $fiscal-period-focus as string*,
    $fiscal-year-focus as integer*) as object*
{
  for $archive in
    fiscal-core:filings-for-entities-and-fiscal-periods-and-years($entity-or-ids,
                                                                  $fiscal-period-focus,
                                                                  $fiscal-year-focus)
  return sec-networks:networks-for-filings-and-disclosures(
      $archive,
      $sec-networks:BALANCE_SHEET)
};

(:~
 :
Retrieves facts for the given aspects, fiscal periods and years.

 :
 : @param $aspects an object containing aspects to filter, among which
 :                 xbrl:Concept, xbrl:Entity and xbrl:Period (all optional).
 : @param $fiscal-period-focus the periods (YTD1, YTD2, YTD3, Q1, Q2, Q3 or FY).
 : @param $fiscal-year-focus the years (2012)
 :
 : @return all facts matching the given aspects, fiscal period and year.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function
  fiscal-core:facts-for-aspects-and-fiscal-periods-and-years(
    $aspects as object?,
    $fiscal-period-focus as string*,
    $fiscal-year-focus as integer*) as object*
{
  fiscal-core:facts-for-aspects-and-fiscal-periods-and-years(
    $aspects,
    $fiscal-period-focus,
    $fiscal-year-focus,
    ())
};

(:~
 :
Retrieves facts for the given aspects, fiscal periods and years.

 :
 : @param $aspects an object containing aspects to filter, among which
 :                 xbrl:Concept, xbrl:Entity and xbrl:Period (all optional).
 : @param $fiscal-period-focus the periods (YTD1, YTD2, YTD3, Q1, Q2, Q3 or FY) or $fiscal-core:ALL_FISCAL_PERIODS to accept all.
 : @param $fiscal-year-focus the years (2012) or $fiscal-core:ALL_FISCAL_YEARS to accept all.
 : @param $options standard SEC BizQL options.
 :
 : @return all facts matching the given aspects, fiscal period and year.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function
  fiscal-core:facts-for-aspects-and-fiscal-periods-and-years(
    $aspects as object?,
    $fiscal-period-focus as string*,
    $fiscal-year-focus as integer*,
    $options as object?) as object*
{
  let $filter as object := {|
    {
      "Filter":
        {|
          if(exists($aspects))
          then { $facts:ASPECTS : $aspects }
          else (),
          if( deep-equal($fiscal-period-focus, $fiscal-core:ALL_FISCAL_PERIODS) and deep-equal($fiscal-year-focus, $fiscal-core:ALL_FISCAL_YEARS))
          then { "Profiles.SEC.Fiscal" : { "$exists" : true } }
          else { "Profiles": { "SEC": { "Fiscal":
                 {|
                   if(deep-equal($fiscal-period-focus, $fiscal-core:ALL_FISCAL_PERIODS) )
                   then ()
                   else { "Period" : [ $fiscal-period-focus ] },
                   if( deep-equal($fiscal-year-focus, $fiscal-core:ALL_FISCAL_YEARS) )
                   then ()
                   else { "Year": [ $fiscal-year-focus ] }
                 |}
               }}}
        |}
    },
    if (empty($options.Hypercube))
    then { Hypercube: sec:dimensionless-hypercube(()) }
    else ()
  |}
  let $facts :=
    facts:facts-for(
      facts:merge-objects($filter, $options, true (: giving parameters higher priority :))
    )
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
Retrieves facts for the given entities, concepts, fiscal periods and years.

 :
 : @param $entities-or-ids a list of entities or their EIDs.
 : @param $concepts a list of concept names (us-gaap:Assets)
 : @param $fiscal-period-focus the periods (YTD1, YTD2, YTD3, Q1, Q2, Q3 or FY) or $fiscal-core:ALL_FISCAL_PERIODS to accept all.
 : @param $fiscal-year-focus the years (2012) or $fiscal-core:ALL_FISCAL_YEARS to accept all.
 :
 : @error entities:INVALID_PARAMETER if the CIK or entity is not valid.
 : @return all facts matching entities, concepts, fiscal periods and years
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function
  fiscal-core:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
    $entities-or-ids as item*,
    $concepts as string*,
    $fiscal-period-focus as string*,
    $fiscal-year-focus as integer*) as object*
{
  fiscal-core:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
    $entities-or-ids,
    $concepts,
    $fiscal-period-focus,
    $fiscal-year-focus,
    ())
};

(:~
 :
Retrieves facts for the given entities, concepts, fiscal periods and years.

 :
 : @param $entities-or-ids a list of entities or their EIDs.
 : @param $concepts a list of concept names (us-gaap:Assets)
 : @param $fiscal-period-focus the periods (YTD1, YTD2, YTD3, Q1, Q2, Q3 or FY) or $fiscal-core:ALL_FISCAL_PERIODS to accept all.
 : @param $fiscal-year-focus the years (2012) or $fiscal-core:ALL_FISCAL_YEARS to accept all.
 : @param $options standard SEC BizQL options.
 :
 : @error entities:INVALID_PARAMETER if the CIK or entity is not valid.
 : @return all facts matching entities, concepts, fiscal periods and years
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function
  fiscal-core:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
    $entities-or-ids as item*,
    $concepts as string*,
    $fiscal-period-focus as string*,
    $fiscal-year-focus as integer*,
    $options as object?) as object*
{
  let $entity-ids as xs:string* := $entities-or-ids ! companies:eid($$)
  let $aspects := {|
    if(exists($entity-ids))
    then { "xbrl:Entity": [ $entity-ids ] }
    else (),
    if(exists($concepts))
    then { "xbrl:Concept": [ $concepts ] }
    else ()
  |}
  return
    fiscal-core:facts-for-aspects-and-fiscal-periods-and-years(
      $aspects,
      $fiscal-period-focus,
      $fiscal-year-focus,
      $options)
};

(:~
 :
Retrieves the latest reported fiscal period and year for the
 : supplied entity.

 :
 : @param $entity-or-id an entity or its EID.
 :
 : @return an object with two fields: period and year.
 :)
declare function fiscal-core:latest-reported-fiscal-period(
    $entity-or-id as item) as object?
{
  (
  let $entity-or-id := companies:eid($entity-or-id)
  for $archive in archives:archives-for-entities($entity-or-id)
  let $archive-fiscal-period := fiscal-core:fiscal-period-for-filing($archive)
  let $archive-fiscal-year := fiscal-core:fiscal-year-for-filing($archive)
  group by $archive-fiscal-year
  order by $archive-fiscal-year descending empty least
  return {
    year: $archive-fiscal-year,
    period: switch(true)
            case $archive-fiscal-period = "FY" return "FY"
            case $archive-fiscal-period = ("Q3", "YTD3") return "Q3"
            case $archive-fiscal-period = ("Q2", "YTD2") return "Q2"
            case $archive-fiscal-period = ("Q1", "YTD1") return "Q1"
            default return () }
  )[1]
};

(:~
 :
Retrieves the latest fiscal period and year for the
 : supplied entity.

 :
 : @param $entity-or-id an entity or its EID.
 : @param $type 10-K or 10-Q or FY, YTD1, YTD2, YTD3, Q1, Q2, or Q3, or $fiscal-core:ALL_FISCAL_PERIODS
 : to accept all.
 :
 : @return an object with two fields: period and year.
 :)
declare function fiscal-core:latest-reported-fiscal-period(
    $entity-or-id as item,
    $type as string) as object?
{
  if(deep-equal($type, $fiscal-core:ALL_FISCAL_PERIODS))
  then fiscal-core:latest-reported-fiscal-period($entity-or-id)
  else
  (
  let $type-filter :=
    switch(true)
    case $type = ( "10-K", "FY") return "FY"
    case $type eq "10-Q" return ( "Q3", "Q2", "Q1" )
    case $type eq "YTD1" return "Q1"
    case $type eq "YTD2" return "Q2"
    case $type eq "YTD3" return "Q3"
    default return $type
  let $entity-or-id := companies:eid($entity-or-id)
  for $archive in archives:archives-for-entities($entity-or-id)
  let $archive-fiscal-period := fiscal-core:fiscal-period-for-filing($archive)
  let $archive-fiscal-year := fiscal-core:fiscal-year-for-filing($archive)
  where $archive-fiscal-period = $type-filter
  group by $archive-fiscal-year
  order by $archive-fiscal-year descending empty least
  return {
    year: $archive-fiscal-year,
    period:
      switch(true)
      case $archive-fiscal-period = "FY" and ($type eq "10-K" or $type eq "FY") return "FY"
      case $archive-fiscal-period = "Q3" and ($type eq "10-Q" or $type eq "Q3") return "Q3"
      case $archive-fiscal-period = "Q2" and ($type eq "10-Q" or $type eq "Q2") return "Q2"
      case $archive-fiscal-period = "Q1" and ($type eq "10-Q" or $type eq "Q1") return "Q1"
      default return () }
  )[1]
};


:lipstick: Fix warnings by removing unused modules and variables.

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)

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

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

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

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

(: 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 $entities as object*  := entities:entities($archives.Entity)
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)