:pencil: Output one schema for each prefix.

jsoniq version "1.0";

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

(:~
 :
This module provides functionality for exporting archives to the XBRL format.

 :
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace xbrl-export = "http://28.io/modules/xbrl/export";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace labels = "http://28.io/modules/xbrl/labels";

import module namespace archive = "http://zorba.io/modules/archive";
import module namespace credentials = "http://www.28msec.com/modules/credentials";
import schema namespace mongos = "http://www.28msec.com/modules/mongodb/types";

declare variable $xbrl-export:serialization-parameters :=
    <serialization-parameters xmlns="http://www.w3.org/2010/xslt-xquery-serialization">
      <method value="xml"/>
      <indent value="yes"/>
      <encoding value="UTF-8"/>
    </serialization-parameters>;

declare %private function xbrl-export:comment($options as object?) as comment()
{
  comment { "
    Generated: " || current-dateTime() || " (" || credentials:credentials("MongoDB", "xbrl").db || ")
    "||($options.Comment, "")[1]||"
    Generated by 28msec (CellStore/28.io)
  " }
};

declare function xbrl-export:schema(
    $archive as object,
    $options as object?)
as object
{
  {|
    let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
    let $components as object* := components:components-for-archives($archive)
    let $concept-names as string* := distinct-values($components.Concepts[].Name[not $$ = $concepts:BUILT_IN_REPORT_ELEMENTS])
    let $prefixes as string* :=  distinct-values($concept-names ! (tokenize($$, ":")[1]))[not $$ = ("sec", "xbrl", "xbrl28", "fsa")]
    for $prefix as string in $prefixes
    let $namespace as string := ($archive.Namespaces.$prefix, "http://28.io/unknown" || $prefix)[1]
    return { $prefix: [
      xbrl-export:comment($options),
      <schema targetNamespace="{$namespace}"
          elementFormDefault="qualified"
          xmlns="http://www.w3.org/2001/XMLSchema"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          xmlns:link="http://www.xbrl.org/2003/linkbase"
          xmlns:xbrli="http://www.xbrl.org/2003/instance"
          xmlns:xbrldt="http://xbrl.org/2005/xbrldt"
          xmlns:nonnum="http://www.xbrl.org/dtr/type/non-numeric">
      {
          for $ns in keys($archive.Namespaces)
          return namespace { $ns } { $archive.Namespaces.$ns },
          for $p as string in $prefixes
          let $n as string := ($archive.Namespaces.$p, "http://28.io/unknown" || $p)[1]
          return <import namespace="{$n}" schemaLocation="{$taxonomyName}-{$p}.xsd"/>,
          <import namespace="http://www.xbrl.org/2003/instance" schemaLocation="http://www.xbrl.org/2003/xbrl-instance-2003-12-31.xsd"/>,
          <import namespace="http://xbrl.org/2005/xbrldt" schemaLocation="http://www.xbrl.org/2005/xbrldt-2005.xsd" />,
          <import namespace="http://www.xbrl.org/dtr/type/non-numeric" schemaLocation="http://www.xbrl.org/dtr/type/nonNumeric-2009-12-16.xsd" />,
          components:to-xbrl-annotation($components, $taxonomyName),
          for $concept as element() in concepts:to-xbrl($components.Concepts[])
          group by $name := $concept/@name
          return $concept[1]
      }
      </schema>
    ] }
  |}
};

declare function xbrl-export:labels(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      labels:to-xbrl($components.Concepts[], $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:presentation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $presentationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:PRESENTATION_NETWORK)
      where exists($presentationNetwork) and $component.Role ne null
      return networks:to-xbrl($presentationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:calculation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $calculationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
      where exists($calculationNetwork) and $component.Role ne null
      return networks:to-xbrl($calculationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:definition(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
      {
        for $ns in keys($archive.Namespaces)
        return namespace { $ns } { $archive.Namespaces.$ns },
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/all" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#all"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-domain" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-domain"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/domain-member" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#domain-member"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/hypercube-dimension" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#hypercube-dimension"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/notAll" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#notAll"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-default" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-default"/>,
        components:to-xbrl-roleRefs($components, $taxonomyName),
        for $component in $components
        for $hypercube in values($component.Hypercubes)
        where $component.Role ne null
        return networks:to-xbrl($hypercube, $component, $taxonomyName)
      }
    </linkbase>
  )
};

declare function xbrl-export:archive(
    $archive as item
) as item()*
{
  xbrl-export:archive($archive, {})
};

declare function xbrl-export:archive(
    $archive as item,
    $options as object?)
as item()*
{
  let $archive as object := archives:archives($archive)
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $schemas as object := {|
    let $schemas-by-prefix := xbrl-export:schema($archive, $options)
    for $prefix in keys($schemas-by-prefix)
    let $nodes as node()* := $schemas-by-prefix.$prefix[]
    return { $prefix: serialize($nodes, $xbrl-export:serialization-parameters) }
  |}
  let $labels as string := serialize(xbrl-export:labels($archive, $options), $xbrl-export:serialization-parameters)
  let $presentation as string := serialize(xbrl-export:presentation($archive, $options), $xbrl-export:serialization-parameters)
  let $calculation as string := serialize(xbrl-export:calculation($archive, $options), $xbrl-export:serialization-parameters)
  let $definition as string := serialize(xbrl-export:definition($archive, $options), $xbrl-export:serialization-parameters)
  let $archive as xs:base64Binary := archive:create(
    (
      { "type" : "directory", "name" : $taxonomyName },
      for $prefix as string in keys($schemas)
      return { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "-" || $prefix || ".xsd" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_lab.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_pre.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_cal.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_def.xml" }
    ),
    (
      values($schemas),
      $labels,
      $presentation,
      $calculation,
      $definition
    )
  )
  return
    (
      {
        "content-type": "application/zip",
        "serialization":
        {
          "method": "binary"
        },
        "headers": {
          "Pragma": "public",
          "Expires": "0",
          "Cache-Control": "must-revalidate, post-check=0, pre-check=0",
          "Content-Disposition": "attachment; filename='" || $taxonomyName || ".xbrlx';",
          "Content-Transfer-Encoding": "binary"
        }
      },
      $archive
    )
};

jsoniq version "1.0";

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

(:~
 :
This module provides functionality for exporting archives to the XBRL format.

 :
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace xbrl-export = "http://28.io/modules/xbrl/export";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace labels = "http://28.io/modules/xbrl/labels";

import module namespace archive = "http://zorba.io/modules/archive";
import module namespace credentials = "http://www.28msec.com/modules/credentials";
import schema namespace mongos = "http://www.28msec.com/modules/mongodb/types";

declare variable $xbrl-export:serialization-parameters :=
    <serialization-parameters xmlns="http://www.w3.org/2010/xslt-xquery-serialization">
      <method value="xml"/>
      <indent value="yes"/>
      <encoding value="UTF-8"/>
    </serialization-parameters>;

declare %private function xbrl-export:comment($options as object?) as comment()
{
  comment { "
    Generated: " || current-dateTime() || " (" || credentials:credentials("MongoDB", "xbrl").db || ")
    "||($options.Comment, "")[1]||"
    Generated by 28msec (CellStore/28.io)
  " }
};

declare function xbrl-export:schema(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <schema targetNamespace="http://www.xbrl-ch.ch/ch/fr/co/gp/2015-02-26"
        elementFormDefault="qualified"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:link="http://www.xbrl.org/2003/linkbase"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xmlns:xbrldt="http://xbrl.org/2005/xbrldt"
        xmlns:nonnum="http://www.xbrl.org/dtr/type/non-numeric">
    {
        for $ns in keys($archive.Namespaces)
        return namespace { $ns } { $archive.Namespaces.$ns },
        <import namespace="http://www.xbrl.org/2003/instance" schemaLocation="http://www.xbrl.org/2003/xbrl-instance-2003-12-31.xsd"/>,
        <import namespace="http://xbrl.org/2005/xbrldt" schemaLocation="http://www.xbrl.org/2005/xbrldt-2005.xsd" />,
        <import namespace="http://www.xbrl.org/dtr/type/non-numeric" schemaLocation="http://www.xbrl.org/dtr/type/nonNumeric-2009-12-16.xsd" />,
        components:to-xbrl-annotation($components, $taxonomyName),
        for $concept as element() in concepts:to-xbrl($components.Concepts[])
        group by $name := $concept/@name
        return $concept[1]
    }
    </schema>
  )
};

declare function xbrl-export:labels(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      labels:to-xbrl($components.Concepts[], $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:presentation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $presentationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:PRESENTATION_NETWORK)
      where exists($presentationNetwork) and $component.Role ne null
      return networks:to-xbrl($presentationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:calculation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $calculationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
      where exists($calculationNetwork) and $component.Role ne null
      return networks:to-xbrl($calculationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:definition(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
      {
        for $ns in keys($archive.Namespaces)
        return namespace { $ns } { $archive.Namespaces.$ns },
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/all" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#all"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-domain" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-domain"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/domain-member" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#domain-member"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/hypercube-dimension" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#hypercube-dimension"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/notAll" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#notAll"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-default" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-default"/>,
        components:to-xbrl-roleRefs($components, $taxonomyName),
        for $component in $components
        for $hypercube in values($component.Hypercubes)
        where $component.Role ne null
        return networks:to-xbrl($hypercube, $component, $taxonomyName)
      }
    </linkbase>
  )
};

declare function xbrl-export:archive(
    $archive as item
) as item()*
{
  xbrl-export:archive($archive, {})
};

declare function xbrl-export:archive(
    $archive as item,
    $options as object?)
as item()*
{
  let $archive as object := archives:archives($archive)
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $schema as string := serialize(xbrl-export:schema($archive, $options), $xbrl-export:serialization-parameters)
  let $labels as string := serialize(xbrl-export:labels($archive, $options), $xbrl-export:serialization-parameters)
  let $presentation as string := serialize(xbrl-export:presentation($archive, $options), $xbrl-export:serialization-parameters)
  let $calculation as string := serialize(xbrl-export:calculation($archive, $options), $xbrl-export:serialization-parameters)
  let $definition as string := serialize(xbrl-export:definition($archive, $options), $xbrl-export:serialization-parameters)
  let $archive as xs:base64Binary := archive:create(
    (
      { "type" : "directory", "name" : $taxonomyName },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || ".xsd" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_lab.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_pre.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_cal.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_def.xml" }
    ),
    (
      $schema,
      $labels,
      $presentation,
      $calculation,
      $definition
    )
  )
  return
    (
      {
        "content-type": "application/zip",
        "serialization":
        {
          "method": "binary"
        },
        "headers": {
          "Pragma": "public",
          "Expires": "0",
          "Cache-Control": "must-revalidate, post-check=0, pre-check=0",
          "Content-Disposition": "attachment; filename='" || $taxonomyName || ".xbrlx';",
          "Content-Transfer-Encoding": "binary"
        }
      },
      $archive
    )
};


Add position of claims (per property) and aliases (per language)

Keeping the position allows to select the first one listed, which
is typically the preferred and most common value.

Related issue: #5

# Parse JSON data returned by Special:Entity API of Wikidata
# and convert it to CSV format

# headers
[
  "Entity",
  "Group",
  "Value Name",
  "Value Type",
  "Value Position",
  "Value",
  "Rank",
  "Start Time",
  "End Time"
]
,
(
  # records
  .entities[]
  | [
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      ""
    ] as $empty_record
  | .id as $entity
  | (
      .aliases[]
      | to_entries
      | .[]
      | (.key + 1) as $position
      | .value
      | [
          $entity,
          "alias",
          "language",
          .language,
          $position,
          .value,
          "",
          "",
          ""
        ]
    ),
    $empty_record,
    (
      .descriptions[]
      | [
          $entity,
          "description",
          "language",
          .language,
          "",
          .value,
          "",
          "",
          ""
        ]
    ),
    $empty_record,
    (
      .labels[]
      | [
          $entity,
          "label",
          "language",
          .language,
          "",
          .value,
          "",
          "",
          ""
        ]
    ),
    $empty_record,
    (
      .claims[]
      | to_entries
      | .[]
      | (.key + 1) as $position
      | .value
      | .rank as $rank
      | .qualifiers.P580[0].datavalue.value.time as $start_time
      | .qualifiers.P582[0].datavalue.value.time as $end_time
      | .mainsnak
      | .property as $value_name
      | if .snaktype == "novalue"
        then
          empty
        elif .datatype == "globe-coordinate"
        then
          .datavalue.value
          | { latitude, longitude, altitude }
          | to_entries
          | .[]
          | .key as $value_type
          | .value
          | values
          | [
              $entity,
              "claim",
              $value_name,
              $value_type,
              $position,
              .,
              $rank,
              $start_time,
              $end_time
            ]
        else
          .datatype as $value_type
          | (
              .datavalue.value
              | (
                     scalars
                  // ."numeric-id"
                  // .amount
                  // .text
                  // .time
                )
            )
          | [
              $entity,
              "claim",
              $value_name,
              $value_type,
              $position,
              .,
              $rank,
              $start_time,
              $end_time
            ]
      end
    ),
    $empty_record,
    (
      .sitelinks[]
      | [
          $entity,
          "sitelink",
          "site",
          .site,
          "",
          .title,
          "",
          "",
          ""
        ]
    )
)
# Note: @csv requires --raw-output parameter on jq command line
| @csv

# Parse JSON data returned by Special:Entity API of Wikidata
# and convert it to CSV format

# headers
[
  "Entity",
  "Group",
  "Value Name",
  "Value Type",
  "Value",
  "Rank",
  "Start Time",
  "End Time"
]
,
(
  # records
  .entities
  | .[]
  | [
      "",
      "",
      "",
      "",
      "",
      "",
      "",
      ""
    ] as $empty_record
  | .id as $entity
  | (
      .aliases
      | .[]
      | .[]
      | [
          $entity,
          "alias",
          "language",
          .language,
          .value,
          "",
          "",
          ""
        ]
    ),
    $empty_record,
    (
      .descriptions
      | .[]
      | [
          $entity,
          "description",
          "language",
          .language,
          .value,
          "",
          "",
          ""
        ]
    ),
    $empty_record,
    (
      .labels
      | .[]
      | [
          $entity,
          "label",
          "language",
          .language,
          .value,
          "",
          "",
          ""
        ]
    ),
    $empty_record,
    (
      .claims
      | .[]
      | .[]
      | .rank as $rank
      | .qualifiers.P580[0].datavalue.value.time as $start_time
      | .qualifiers.P582[0].datavalue.value.time as $end_time
      | .mainsnak
      | .property as $value_name
      | if .snaktype == "novalue"
        then
          empty
        elif .datatype == "globe-coordinate"
        then
          .datavalue.value
          | { latitude, longitude, altitude }
          | to_entries
          | .[]
          | .key as $value_type
          | .value
          | values
          | [
              $entity,
              "claim",
              $value_name,
              $value_type,
              .,
              $rank,
              $start_time,
              $end_time
            ]
        else
          .datatype as $value_type
          | (
              .datavalue.value
              | (
                     scalars
                  // ."numeric-id"
                  // .amount
                  // .text
                  // .time
                )
            )
          | [
              $entity,
              "claim",
              $value_name,
              $value_type,
              .,
              $rank,
              $start_time,
              $end_time
            ]
      end
    ),
    $empty_record,
    (
      .sitelinks
      | .[]
      | [
          $entity,
          "sitelink",
          "site",
          .site,
          .title,
          "",
          "",
          ""
        ]
    )
)
# Note: @csv requires --raw-output parameter on jq command line
| @csv


Update facts.jq

Update facts.jq

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

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

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

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

declare function local:contains-aspect($name as string) as boolean
{
  exists(request:param-names()[starts-with($$, $name)])
};

declare function local:cast-sequence($values as atomic*, $type as string) as atomic*
{
  for $value in $values
  return
      switch ($type)
      case "integer" return $value cast as integer
      case "string" return $value cast as string
      default return error(xs:QName("local:unsupported-type"), $type || ": unsupported type")
};

declare function local:hypercube(
    $entities as object*,
    $fiscalPeriods as string*,
    $fiscalYears as integer*,
    $aids as string*) as object
{
    let $shortcut-hypercube-spec :=
    {|
        let $concepts := request:param-values("concept")
        return { "xbrl:Concept" : { Domain : [ $concepts ] } }[exists($concepts)],
        fiscal-core:filter-override(
            $entities,
            $fiscalYears,
            $fiscalPeriods,
            $aids)
    |}
    let $main-hypercube-spec :=
    {|
        {
            "dei:LegalEntityAxis" : {
                "Domain" : [ "sec:DefaultLegalEntity" ],
                "Default" : "sec:DefaultLegalEntity"
            }
        }[not local:contains-aspect("dei:LegalEntityAxis")],

        for $parameter in (
            request:param-names(),
            keys($shortcut-hypercube-spec),
            "sec:Accepted",
            "sec:FiscalPeriod",
            "sec:FiscalYear")
        where contains($parameter, ":")
        group by $dimension-name :=
            switch(true)
            case ends-with(lower-case($parameter), "::default")
                return substring-before($parameter, "::default")
            case ends-with(lower-case($parameter), ":default")
                return substring-before($parameter, ":default")
            case ends-with(lower-case($parameter), "::type")
                return substring-before($parameter, "::type")
            default
                return $parameter
        let $all as boolean :=
            (request:param-values($dimension-name) ! upper-case($$)) = "ALL"
        let $user-defined-type as string? :=
            (request:param-values($dimension-name || "::type"), request:param-values($dimension-name || ":type"))[1]
        let $type as string? :=
            switch(true)
            case exists($user-defined-type)
                return $user-defined-type
            case exists($shortcut-hypercube-spec.$dimension-name.Type)
                return $shortcut-hypercube-spec.$dimension-name.Type
            default
                return ()
        let $values := (request:param-values($dimension-name), $shortcut-hypercube-spec.$dimension-name.Domain[])
        let $typed-values := if (exists($type)) then local:cast-sequence($values, $type) else $values
        let $has-default := ($parameter = $dimension-name || "::default") or ($parameter = $dimension-name || ":default")
        let $default-value := (request:param-values($dimension-name || "::default"), request:param-values($dimension-name || ":default"))[1]
        let $typed-default-value := if (exists($type)) then local:cast-sequence($default-value, $type) else $default-value
        return
        {
            $dimension-name : {|
                { "Type" : $type }[exists($type)],
                { "Domain" : [ $typed-values ] }[(exists($typed-values) and not($all))],
                { "Default" : $typed-default-value }[$has-default]
            |}
        }
    |}
    return hypercubes:user-defined-hypercube($main-hypercube-spec)
};

(: 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 $aid               as string* external;
declare  %rest:case-insensitive                 variable $map               as string? external;
declare  %rest:case-insensitive                 variable $rule              as string? external;
declare  %rest:case-insensitive                 variable $report            as string? external;
declare  %rest:case-insensitive                 variable $validate          as boolean external := false;
declare  %rest:case-insensitive                 variable $additional-rules  as string? external;

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)

(: Object resolution :)
let $entities as object* :=
    companies:companies(
        $cik,
        $tag,
        $ticker,
        $sic)
let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rule as item* :=
    (
        if(exists($report))
        then reports:rules($report)
        else rules:rules($rule),
        if(exists($additional-rules))
        then rules:rules($additional-rules)
        else ()
    )

let $hypercube := local:hypercube($entities, $fiscalPeriod, $fiscalYear, $aid)

let $facts :=
  sec:facts-for(
    {|
      {
        Hypercube : $hypercube,
        Validate: $validate
      },
      { "ConceptMaps" : $map }[exists($map)],
      { "Rules" : [ $rule ] }[exists($rule)]
    |}
  )

let $facts := api:normalize-facts($facts)

let $result := {
    NetworkIdentifier : "http://bizql.io/facts",
    TableName : "xbrl:Facts",
    FactTable : [ $facts ]
}
let $comment :=
{
    NumFacts: count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}
let $serializers := {
    to-xml : function($res as object) as node()* {
           <FactTable NetworkIdentifier="http://bizql.io/facts"
            TableName="xbrl:Facts">{
                conversion:facts-to-xml($res.FactTable[], { Caller: "Report"})
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        (conversion:facts-to-csv($res.FactTable[], { Caller: "Report" }), "")[1]
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "facts")
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 conversion = "http://28.io/modules/xbrl/conversion";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace rules = "http://28.io/modules/xbrl/rules";

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

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

declare function local:contains-aspect($name as string) as boolean
{
  exists(request:param-names()[starts-with($$, $name)])
};

declare function local:cast-sequence($values as atomic*, $type as string) as atomic*
{
  for $value in $values
  return
      switch ($type)
      case "integer" return $value cast as integer
      case "string" return $value cast as string
      default return error(xs:QName("local:unsupported-type"), $type || ": unsupported type")
};

declare function local:hypercube(
    $entities as object*,
    $fiscalPeriods as string*,
    $fiscalYears as integer*,
    $aids as string*) as object
{
    let $shortcut-hypercube-spec :=
    {|
        let $concepts := request:param-values("concept")
        return { "xbrl:Concept" : { Domain : [ $concepts ] } }[exists($concepts)],
        fiscal-core:filter-override(
            $entities,
            $fiscalYears,
            $fiscalPeriods,
            $aids)
    |}
    let $main-hypercube-spec :=
    {|
        {
            "dei:LegalEntityAxis" : {
                "Domain" : [ "sec:DefaultLegalEntity" ],
                "Default" : "sec:DefaultLegalEntity"
            }
        }[not local:contains-aspect("dei:LegalEntityAxis")],

        for $parameter in (
            request:param-names(),
            keys($shortcut-hypercube-spec),
            "sec:Accepted",
            "sec:FiscalPeriod",
            "sec:FiscalYear")
        where contains($parameter, ":")
        group by $dimension-name :=
            switch(true)
            case ends-with(lower-case($parameter), "::default")
                return substring-before($parameter, "::default")
            case ends-with(lower-case($parameter), ":default")
                return substring-before($parameter, ":default")
            case ends-with(lower-case($parameter), "::type")
                return substring-before($parameter, "::type")
            default
                return $parameter
        let $all as boolean :=
            (request:param-values($dimension-name) ! upper-case($$)) = "ALL"
        let $user-defined-type as string? :=
            (request:param-values($dimension-name || "::type"), request:param-values($dimension-name || ":type"))[1]
        let $type as string? :=
            switch(true)
            case exists($user-defined-type)
                return $user-defined-type
            case exists($shortcut-hypercube-spec.$dimension-name.Type)
                return $shortcut-hypercube-spec.$dimension-name.Type
            default
                return ()
        let $values := (request:param-values($dimension-name), $shortcut-hypercube-spec.$dimension-name.Domain[])
        let $typed-values := if (exists($type)) then local:cast-sequence($values, $type) else $values
        let $has-default := ($parameter = $dimension-name || "::default") or ($parameter = $dimension-name || ":default")
        let $default-value := (request:param-values($dimension-name || "::default"), request:param-values($dimension-name || ":default"))[1]
        let $typed-default-value := if (exists($type)) then local:cast-sequence($default-value, $type) else $default-value
        return
        {
            $dimension-name : {|
                { "Type" : $type }[exists($type)],
                { "Domain" : [ $typed-values ] }[(exists($typed-values) and not($all))],
                { "Default" : $typed-default-value }[$has-default]
            |}
        }
    |}
    return hypercubes:user-defined-hypercube($main-hypercube-spec)
};

(: 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 $aid               as string* external;
declare  %rest:case-insensitive                 variable $map               as string? external;
declare  %rest:case-insensitive                 variable $rule              as string? external;
declare  %rest:case-insensitive                 variable $report            as string? external;
declare  %rest:case-insensitive                 variable $validate          as boolean external := false;
declare  %rest:case-insensitive                 variable $additional-rules  as string? external;

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)

(: Object resolution :)
let $entities as object* :=
    companies:companies(
        $cik,
        $tag,
        $ticker,
        $sic)
let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rule as item* :=
    (
        if(exists($report))
        then reports:rules($report)
        else rules:rules($rule),
        if(exists($additional-rules))
        then rules:rules($additional-rules)
        else ()
    )

let $hypercube := local:hypercube($entities, $fiscalPeriod, $fiscalYear, $aid)

let $facts :=
  sec:facts-for(
    {|
      {
        Hypercube : $hypercube,
        Validate: $validate
      },
      { "ConceptMaps" : $map }[exists($map)],
      { "Rules" : [ $rule ] }[exists($rule)]
    |}
  )

let $facts := api:normalize-facts($facts)

let $result := {
    NetworkIdentifier : "http://bizql.io/facts",
    TableName : "xbrl:Facts",
    FactTable : [ $facts ]
}
let $comment :=
{
    NumFacts: count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}
let $serializers := {
    to-xml : function($res as object) as node()* {
           <FactTable NetworkIdentifier="http://bizql.io/facts"
            TableName="xbrl:Facts">{
                conversion:facts-to-xml($res.FactTable[], { Caller: "Report"})
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        (conversion:facts-to-csv($res.FactTable[], { Caller: "Report" }), "")[1]
    }
}

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


Retrigering tests.

Retrigering tests.

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

(:~
 :
This module provides functions for storing, retrieving, and modifying
 : rules. Rules can be used in BizQL queries to:

 :
 :

 :
Impute Facts that were not reported within an instance, yet can be
 :       derived from reported facts.

 :
Compute arbitrary new Facts (Ratios, Calculated Facts, Facts from different
 :       Datasources/Instances, etc.).

 :
Run validation and/or verification rules

 :

 :
 :
A Rule is an object containing a BizQL fomula to execute if a condition is met.
 : For example if a user queries for a certain concept and a rule exists for this
 : concept then the rule is applied to get the fact.

 :
 :
With this module, you can retrieve the rules associated with each report
 : schema. You can also query for facts by making implicitly use of the mapping.

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

import module namespace report-schemas = "http://28.io/modules/xbrl/report-schemas";
import module namespace r = "http://zorba.io/modules/random";

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

(:~
 :
Retrieves all rules from all report schemas.

 :
 : @return all rules.
 :)
declare function rules:rules() as object*
{
  report-schemas:report-schemas().Rules[]
};

(:~
 :
Return the rules from the given report schemas.

 :
 : @param $rid the schemas containing the rules, or their RIDs.
 :
 : @return the rules from the report schemas.
 :)
declare function rules:rules($report-schemas-or-ids as item*)
as object*
{
  report-schemas:report-schemas($report-schemas-or-ids).Rules[]
};

(:~
 :
Serializes a fact to a simple string format that can be used in
 :    audit trail messages to trail the value of the fact.

 :
 :
The returned string follows the pattern: fact-concept-name[fact-value]

 :
 : @param $fact a single fact to serialize to a simple informative string
 :
 : @return the string with key information about the fact.
 :)
declare function rules:fact-trail(
  $fact as object?)
as string
{
  let $name as string :=
    if(exists($fact))
    then
      if(exists($fact.Aspects."xbrl:Concept"))
      then $fact.Aspects."xbrl:Concept"
      else serialize($fact.Aspects)
    else "empty-sequence"
  return rules:fact-trail($fact, $name)
};

(:~
 :
Serializes a fact to a simple string format that can be used in
 :    audit trail messages to trail the value of the fact.

 :
 :
The returned string follows the pattern: fact-concept-name[fact-value]

 :
 : @param $fact an empty-sequence or a single fact to serialize to a
 :              simple informative string
 : @param $name an alternative name to use if the $fact is an empty-sequence
 :              otherwise the name will be taken from the fact
 :
 : @return the string with key information about the fact.
 :)
declare function rules:fact-trail(
  $fact as object?,
  $name as string)
as string
{
  let $name as string :=
    if(exists($fact.Aspects."xbrl:Concept"))
    then $fact.Aspects."xbrl:Concept"
    else $name
  let $fact-type as string :=
    if(exists($fact.Type))
    then $fact.Type
    else "NumericValue"
  let $unit as atomic? := $fact.Aspects."xbrl:Unit"
  let $formated-value as string :=
    switch (true)
    case ($fact-type ne "NumericValue")
    return string($fact.Value)
    case $unit ne null and starts-with($unit, "iso4217:")
    return
      format-number(rules:decimal-value($fact),"###,###.##")
      || " " || substring-after($unit, "iso4217:")
    case exists($unit)
    return string(rules:decimal-value($fact)) || " " || $unit
    default
    return string(rules:decimal-value($fact))
  return
    $name || "[" || $formated-value || "]"
};

(:~
 :
Helper function to create a new fact within a rule using a template
 :    fact.

 :
 : @param $template-fact a fact object that will be used as a template for the
 :        newly created fact
 : @param $concept-name-or-aspects either a name of the concept for the newly
 :        created fact or a complete aspects object to be used in the new fact.
 : @param $value a value for the newly created fact
 : @param $rule the rule in which this fact has been created
 : @param $audit-trail-message a verbose string message explaining how and why
 :        this fact has been created
 : @param $source-facts sequence of facts that have been used to compute the new
 :        fact (this will only be added to the AuditTrails if the audit-trail
 :        option is set to "debug")
 : @param $options standard fact retrieving options.
 :
 : @return the newly created fact
 :)
declare %an:nondeterministic function rules:create-computed-fact(
  $template-fact as object,
  $concept-name-or-aspects as item,
  $value as item,
  $rule as object,
  $audit-trail-messages as string*,
  $source-facts as object*,
  $options as object?)
as object
{
  rules:create-computed-fact(
    $template-fact,
    $concept-name-or-aspects,
    $value,
    $rule,
    $audit-trail-messages,
    $source-facts,
    $options,
    (),
    ())
};

(:~
 :
Helper function to create a new fact within a rule using a template
 :    fact.

 :
 : @param $template-fact a fact object that will be used as a template for the
 :        newly created fact
 : @param $concept-name-or-aspects either a name of the concept for the newly
 :        created fact or a complete aspects object to be used in the new fact.
 : @param $value a value for the newly created fact
 : @param $rule the rule in which this fact has been created
 : @param $audit-trail-message a verbose string message explaining how and why
 :        this fact has been created
 : @param $source-facts sequence of facts that have been used to compute the new
 :        fact (this will only be added to the AuditTrails if the audit-trail
 :        option is set to "debug")
 : @param $options standard fact retrieving options.
 : @param $validated-facts in case of a validation rule: the sequence of
 :        validated facts
 : @param $validation-results the corresponding result (true|false) for each
 :        check (if there are 3 messages then there should be 3 results in
 :        the same order or none).
 :
 : @return the newly created fact
 :)
declare %an:nondeterministic function rules:create-computed-fact(
  $template-fact as object,
  $concept-name-or-aspects as item,
  $value as item,
  $rule as object,
  $audit-trail-messages as string*,
  $source-facts as object*,
  $options as object?,
  $validated-facts as object*,
  $validation-results as boolean*
) as object
{
  let $audit-trail-option as string :=
      lower-case((
          $options.audit-trail,
          $options.AuditTrail,
          "production"
      )[1])
  let $computed-concept :=
    if($concept-name-or-aspects instance of string)
    then $concept-name-or-aspects
    else $concept-name-or-aspects."xbrl:Concept"
  let $validated-concepts := jn:flatten($rule.ValidatedConcepts)
  let $audit-trails := (
    for $message at $pos in $audit-trail-messages
    let $passed := $validation-results[$pos]
    return {
      Id: $rule.Id,
      Type: $rule.Type,
      Label: $rule.Label,
      Message: $message,
      Data: {|
        { OutputConcept: $computed-concept },
        if($audit-trail-option eq "debug")
        then { SourceFacts: [ $source-facts ] }
        else (),
        if($rule.Type eq "xbrl28:validation"
           and exists($validated-concepts))
        then
          {
            ValidatedConcepts : [$validated-concepts],
            ValidatedFacts: [ $validated-facts ! $$._id ],
            ValidationPassed: $passed
          }
        else ()
      |}
    }
  )
  return
    rules:create-computed-fact(
      $template-fact,
      $concept-name-or-aspects,
      $value,
      $rule,
      $audit-trails,
      $options)
};

(:~
 :
Helper function to create a new fact within a rule using a template
 :    fact.

 :
 : @param $template-fact a fact object that will be used as a template for the
 :        newly created fact
 : @param $concept-name-or-aspects either a name of the concept for the newly
 :        created fact or a complete aspects object to be used in the new fact.
 : @param $value a value for the newly created fact
 : @param $rule the rule in which this fact has been created
 : @param $audit-trails a sequence of audit trail objects
 : @param $options standard fact retrieving options.
 :
 : @return the newly created fact
 :)
declare %an:nondeterministic function rules:create-computed-fact(
  $template-fact as object,
  $concept-name-or-aspects as item,
  $value as item,
  $rule as object,
  $audit-trails as object*,
  $options as object?
) as object
{
  copy $newfact := ($template-fact, $options)[1] (: just avoid warning :)
  modify (
    (: generate id :)
    let $id := r:uuid()
    return
      if (exists($newfact."_id"))
      then replace value of json $newfact."_id" with $id
      else insert json { "_id" : $id } into $newfact,

    (: set aspects or concept name :)
    switch (true)
    case ($concept-name-or-aspects instance of object)
    return replace value of json $newfact.Aspects
             with $concept-name-or-aspects
    case ($concept-name-or-aspects instance of string)
    return replace value of json $newfact.Aspects."xbrl:Concept"
             with $concept-name-or-aspects
    default
    return error(xs:QName("rules:INVALID-CONCEPT-NAME-OR-ASPECTS"),
                 "'" || $rule.Label || "': Invalid concept name or aspects "
                 || serialize($concept-name-or-aspects)),

    (: set Value :)
    replace value of json $newfact.Value with $value,

    (: set Type :)
    if ($rule.Type eq "xbrl28:validation")
    then
      if(exists($newfact.Type))
      then replace value of json $newfact.Type with "BooleanValue"
      else insert json { Type : "BooleanValue" } into $newfact
    else (),

    (: set xbrl28:Type :)
    let $xbrl28-Type :=
      switch (true)
      case ($rule.Type eq "xbrl28:validation")
      return "xbrl28:validation"
      case ($rule.Type eq "xbrl28:formula")
      return "xbrl28:formula"
      default return ()
    return
      if (exists($xbrl28-Type))
      then
        if(exists($newfact."xbrl28:Type"))
        then replace value of json $newfact."xbrl28:Type" with $xbrl28-Type
        else insert json { "xbrl28:Type" : $xbrl28-Type } into $newfact
      else (),

    (: set audit trail :)
    if(exists($newfact.AuditTrails))
    then replace value of json $newfact.AuditTrails with [ $audit-trails ]
    else insert json { AuditTrails: [ $audit-trails ] } into $newfact
  )
  return $newfact
};

(:~
 :
Returns the value of a given fact in case that it
 :    is castable to decimal. If no fact is given or a fact value
 :    is not castable to decimal 0 is returned instead.

 :
 :
This function is a helper to be used for arithmetic operations
 :    on facts within rules. As arithmetic operations can not be applied
 :    to sequences of values this function does only accept
 :    single facts.

 :
 : @param $fact an empty-sequence or a fact from which to
 :              get the decimal value. If more than one fact is passed
 :              as a parameter an error will be raised.
 :
 : @error err:XPTY0004 if more than one fact is passed as a parameter
 :
 : @return the decimal value of the fact or 0.
 :)
declare function rules:decimal-value($fact as object*)
as decimal
{
  switch (true)

  case (empty($fact))
  return decimal(0)

  case (count($fact) gt 1)
  return
    error(xs:QName("err:XPTY0004"),
          "sequence of more than one item can not be passed as parameter "
          || "to function rules:decimal-value(). The passed fact concepts"
          || " are: " || string-join($fact.Aspects."xbrl:Concept", ", ")
          || ". Fact details: " || serialize([$fact]) || ".")

  default
  return
   (
     let $value := $fact.Value
     return
       if($value castable as decimal)
       then decimal($value)
       else (),
     decimal(0)
   )[1]
};


Update spreadsheet-for-report.jq

Update spreadsheet-for-report.jq

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

declare %an:nondeterministic function local:test-spreadsheet($expected as integer, $params as object) as item
{
    let $endpoint := "spreadsheet-for-report"
    let $request := test:invoke($endpoint, $params)
    let $actual as integer := count($request[2].TableSet[].TableCells.Facts[][].Value)
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-report-does-not-exist($params as object) as item
{
    let $endpoint := "spreadsheet-for-report"
    let $request := test:invoke($endpoint, $params)
    let $status as integer := $request[1]
    return if ($status eq 404) then true else { url: test:url($endpoint, $params), unexpectedResponse: $request[2] }
};

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

local:check({
    cocacola: local:test-spreadsheet(96, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2013",
        fiscalPeriod:["Q1","YTD1"]}),
    tickerrole: local:test-spreadsheet(96, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:["Q1","YTD1"]}),
    tickerconcept: local:test-spreadsheet(508, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"ALL"}),
    tickerfyfprole: local:test-spreadsheet(193, {
        report:"FundamentalAccountingConcepts",
        ticker:["ko","wmt"],
        fiscalYear:"2013",
        fiscalPeriod:["FY", "YTD4", "QTD4"],
        eliminate:"true"}),
    reportDoesntExist: local:test-report-does-not-exist({
        report:"report-not-found",
        ticker:"MSFT",
        fiscalYear:"ALL",
        fiscalPeriod:["FY", "YTD4","QTD4"]})
})

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

declare %an:nondeterministic function local:test-spreadsheet($expected as integer, $params as object) as item
{
    let $endpoint := "spreadsheet-for-report"
    let $request := test:invoke($endpoint, $params)
    let $actual as integer := count($request[2].TableSet[].TableCells.Facts[][].Value)
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-report-does-not-exist($params as object) as item
{
    let $endpoint := "spreadsheet-for-report"
    let $request := test:invoke($endpoint, $params)
    let $status as integer := $request[1]
    return if ($status eq 404) then true else { url: test:url($endpoint, $params), unexpectedResponse: $request[2] }
};

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

local:check({
    cocacola: local:test-spreadsheet(96, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2013",
        fiscalPeriod:["Q1","YTD1"]}),
    tickerrole: local:test-spreadsheet(96, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:["Q1","YTD1"]}),
    tickerconcept: local:test-spreadsheet(410, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"ALL"}),
    tickerfyfprole: local:test-spreadsheet(193, {
        report:"FundamentalAccountingConcepts",
        ticker:["ko","wmt"],
        fiscalYear:"2013",
        fiscalPeriod:"FY",
        eliminate:"true"}),
    reportDoesntExist: local:test-report-does-not-exist({
        report:"report-not-found",
        ticker:"MSFT",
        fiscalYear:"ALL",
        fiscalPeriod:["FY", "YTD4","QTD4"]})
})


:pencil: Add validation errors for components.

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

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

 :
 :
Components help organizing the facts contained in archives
 : in smaller parts that "make sense" together. A component is identified
 : with a CID (component ID).

 :
 :
A component is made of networks (see the networks module) and hypercubes
 : (see the hypercubes module).

 :
 :
With this module, you can retrieve all components, all components belonging
 : to one or several archives. You can retrieve the CID of a component or retrieve
 : the components associated with a couple of CIDs.

 :
 :)
module namespace components = "http://28.io/modules/xbrl/components";

import module namespace functx = "http://www.functx.com";

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace resolution = "http://28.io/modules/xbrl/resolution";
import module namespace layout = "http://28.io/modules/xbrl/layout";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";

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

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

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

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

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

(:~
 : Name of the field pointing to the default language.
 :)
declare variable $components:DEFAULT-LANGUAGE as xs:string := "Language";

(:~
 : Placeholder for all archive IDs..
 :)
declare variable $components:ALL-ARCHIVES as xs:string := ":ALL:";

(:~
 : Placeholder for all component roles..
 :)
declare variable $components:ALL-ROLES as xs:string := ":ALL:";

(:~
 : Placeholder for all conceptss..
 :)
declare variable $components:ALL-CONCEPTS as xs:string := ":ALL:";

(:~
 : Placeholder for all labels..
 :)
declare variable $components:ALL-LABELS as xs:string := ":ALL:";

(:~
 :
Retrieves all components.

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

(:~
 :
Retrieves the components with the given CIDs.

 :
 : @param $component-or-ids the CIDs or the components themselves.
 :
 : @return the components whose _id field matches one of these CIDs.
 :)
declare function components:components($component-or-ids as item*) as object*
{
  let $ids as string* :=
    for $s in $component-or-ids
    where $s instance of string
    return $s
  let $components as object* :=
    for $s in $component-or-ids
    where $s instance of object
    return $s
  return
    (
      $components,
      if (exists($ids))
      then mw:find($components:col,{ "_id" : { "$in" : [ $ids ! components:cid($$) ] } })
      else ()
    )
};

(:~
 :
Retrieves the components with the given archives, roles, concepts, exact labels.

 :
 : @param $archives-or-ids a sequence of archives or AIDs.
 : @param $roles a sequence of component roles.
 : @param $concepts a sequence of concept names.
 : @param $exact-labels a sequence of exact labels to match.
 :
 : @error components:TOO-MANY-COMPONENTS if all parameters are set to ALL-*.
 :
 : @return the components whose _id field matches one of these CIDs.
 :)
declare function components:components-for(
  $archives-or-ids as item*,
  $roles as string*,
  $concepts as string*,
  $exact-labels as string*,
  $options as object*
) as object*
{
  let $aids as string* := if(functx:is-value-in-sequence($components:ALL-ARCHIVES, $archives-or-ids[$$ instance of atomic]))
               then $components:ALL-ARCHIVES
               else archives:aid($archives-or-ids)
  let $query := {|
    { "Archive" : { "$in" : [ $aids ] } }[not $aids = $components:ALL-ARCHIVES],
    { "Role" : { "$in" : [ $roles ] } }[not $roles = $components:ALL-ROLES],
    { "Concepts.Name" : { "$in" : [ $concepts ] } }[not $concepts = $components:ALL-CONCEPTS],
    { "Concepts.Labels.Value" : { "$in" : [ $exact-labels ] } }[not $exact-labels = $components:ALL-LABELS]
  |}
  return
    switch(true)
    case $roles = $components:ALL-ROLES and
         $aids = $components:ALL-ARCHIVES and
         $concepts = $components:ALL-CONCEPTS and
         $exact-labels = $components:ALL-LABELS
      return error(
        QName("components:TOO-MANY-COMPONENTS"),
        "Too many components to be returned because no filtering is done."
      )
    case $options.LabelsOnly
      return mw:find($components:col, $query, {
         "Archive" : 1,
         "Role" : 1,
         "Concepts.Labels" : 1,
         "Concepts.Name" : 1 }
      )
    default return mw:find($components:col, $query)
};

(:~
 :
Retrieves all components that belong to the supplied archives.

 :
 : @param $archive-or-id an archive or its AID.
 :
 : @return all components in the archive with this AID.
 :)
declare function components:components-for-archives($archive-or-ids as item*) as object*
{
  for $archive-or-id in $archive-or-ids
  return mw:find($components:col,{ $components:ARCHIVE: archives:aid($archive-or-id) })
};

(:~
 : Retrieves all components for the supplied filings and roles.
 :
 : @param $archives-or-ids a sequence of archives or AIDs.
 : @param $roles a sequence of role URIs.
 :
 : @return the components.
 :)
declare function components:components-for-archives-and-roles(
    $archives-or-ids as item*,
    $roles as string*) as object*
{
    let $aids := archives:aid($archives-or-ids)
    return
        mw:find($components:col,
        {
            $components:ARCHIVE: { "$in" : [ $aids ] },
            "Role": { "$in" : [ $roles ] }
        })
};

(:~
 : Retrieves all components for the supplied filings and that use the supplied concepts.
 :
 : @param $archives-or-ids a sequence of archives or AIDs.
 : @param $concepts a sequence of concept names.
 :
 : @return the components.
 :)
declare function components:components-for-archives-and-concepts(
    $archives-or-ids as item*,
    $concepts as string*) as object*
{
    let $aids as string* := archives:aid($archives-or-ids)
    let $concepts := mw:find($concepts:col,
        {|
            (
                { "Name" : { "$in" : [ $concepts ] } },
                { "Archive" : { "$in" : [ $aids ] } }
            )
        |})
    let $roles := $concepts.Role
    for $component in components:components-for-archives-and-roles($aids, $roles)
    where (some $concept in $concepts satisfies $concept.Archive eq $component.Archive and $concept.Role eq $component.Role)
    return $component
};

(:~
 :
Retrieves all definition models in a component.

 :
 : @param $component a component object.
 :
 : @return all definition models
 :)
declare function components:definition-models-for-components(
    $components as object*) as object*
{
    $components.DefinitionModels[]
};

(:~
 :
Retrieves definition models in a component
 : that have the supplied names.

 :
 : @param $component a component object.
 : @param $names a sequence of names.
 :
 : @return the definition models.
 :)
declare function components:definition-models-for-components(
    $components as object*,
    $names as string*) as object
{
    $components.DefinitionModels[][$$.Name = $names]
};

(:~
 :
Selects a table from the component.

 :
 : @param $component a component object.
 : @param $options
some optional parameters, including:

 :

 :
HypercubeName: a string specifying which hypercube to use. By default, one of the non-default hypercubes will be arbitrarily chosen. If none
 :  is available, the default hypercube will be picked.

 :

 :
 : @error components:HYPERCUBE-DOES-NOT-EXIST if the specified hypercube is not found.
 : @return a definition model
 :)
declare function components:select-table(
    $component as object,
    $options as object?) as object
{
    let $user-chosen-table as object? := hypercubes:hypercubes-for-components($component, $options.HypercubeName)
    let $non-implicit-table as object? := hypercubes:hypercubes-for-components($component)[1]
    return
        switch(true)
        case exists($options.HypercubeName) and empty($user-chosen-table)
            return error(QName("components:HYPERCUBE-DOES-NOT-EXIST"), $options.HypercubeName || " : Hypercube does not exist.")
        case exists($user-chosen-table)
            return $user-chosen-table
        case exists($non-implicit-table)
            return $non-implicit-table
        default return error(QName("components:HYPERCUBE-DOES-NOT-EXIST"), "Components does not contain any hypercube.")
};

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

 :
The default hypercube will be taken, and the concept maps and rules will
 : be used if any.

 :
 : @param $component a component object.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function components:facts(
    $component as object
)
as object*
{
  components:facts($component, {})
};

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

 :
 :
 : @param $report-or-id a report or its RID,
 : @param $options standard fact retrieving options as well as
 :

 :
FilterOverride: hypercube dimension specs (as defined as hypercubes:user-defined-hypercube)
 :   to override filters in the report's hypercube.

 :
HypercubeName: picks a different hypercube than the default one.

 :

 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function components:facts(
    $component as object,
    $options as object?
)
as object*
{
  let $concept-map := networks:networks-for-components-and-short-names(
      $component,
      "ConceptMap")
  let $rules := (flatten($component.Rules), flatten($options.Rules))
  let $overriden-hypercube as object? :=
        if(exists($options.Hypercube))
        then $options.Hypercube
        else components:select-table($component, $options)
    let $hypercube :=
    if(exists($options.FilterOverride))
    then hypercubes:modify-hypercube(
      $overriden-hypercube,
      $options.FilterOverride
    )
    else $overriden-hypercube
  let $concepts as object* := $component.Concepts[]
  return hypercubes:facts(
          $hypercube,
          {|
            trim($options, "Rules"),
            { "ConceptMaps": $concept-map }[exists($concept-map)],
            { "Rules": $rules }[exists($rules)],
            { "Concepts": [ $concepts ] }[exists($concepts)]
          |}
         )
};

(:~
 :
 :
Retrieves the spreadsheet-like rendering for the component.

 :
The default definition model will be taken.

 :
The default hypercube will be taken, and the concept maps and rules will
 : be used if any.

 :
 : @param $component a component object.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function components:spreadsheet(
    $component as object
)
as object*
{
  components:spreadsheet($component, {})
};

(:~
 :
 :
Retrieves the spreadsheet-like rendering for the component.

 :
 : @param $report-or-id a report or its RID,
 : @param $options standard fact retrieving options as well as
 :

 :
FilterOverride: hypercube dimension specs (as defined as hypercubes:user-defined-hypercube)
 :   to override filters in the report's hypercube.

 :
HypercubeName: picks a different hypercube than the default one.

 :

 :
 :
 : @error err:XPTY0004 if zero, or more than one definition models are found.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function components:spreadsheet(
    $component as object,
    $options as object?
)
as object*
{
    let $definition-model as object :=
        switch(true)
        case exists($options.DefinitionModel)
            return $options.DefinitionModel
        case (exists($options.DefinitionModelName))
            return components:definition-models-for-components($component, $options.DefinitionModelName)
        default
            return components:definition-models-for-components($component)
    let $overriden-hypercube as object? :=
        if(exists($options.Hypercube))
        then $options.Hypercube
        else components:select-table($component, $options)
    let $concept-map as object?:= networks:networks-for-components-and-short-names(
      $component,
      "ConceptMap")
    let $rules as array? := $component.Rules
    let $concepts as object* := $component.Concepts[]
    let $new-options as object :=
        {|
            trim($options, "Rules"),
            {"ConceptMaps": $concept-map }[exists($concept-map)],
            { "Rules": [ ( flatten($rules[]), flatten($options.Rules) ) ] }[exists(($rules, $options.Rules))],
            {
                Hypercube:
                    if(exists($options.FilterOverride))
                    then hypercubes:modify-hypercube(
                        $overriden-hypercube,
                        $options.FilterOverride
                    )
                    else $overriden-hypercube
            }[exists($overriden-hypercube)],
            { "Concepts": [ $concepts ] }[exists($concepts)]
        |}
    let $structural-model as object := resolution:resolve(
        $definition-model,
        $component,
        $new-options)
    let $layout-model as object := layout:layout($structural-model, $new-options)
    let $accountant-model as object := accountant:flatten-row-headers($layout-model)
    let $result := if($options.FlattenRows) then $accountant-model else $layout-model
    return
        if($options.Debug)
        then copy $r := $result
             modify (
                 insert json { Definition: $definition-model } into $r,
                 insert json { Structural: $structural-model } into $r
             )
             return $r
        else $result
};

(:~
 : Return the number of hypercubes in each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of hypercubes
 :)
declare function components:num-hypercubes($components as object*) as integer*
{
  $components ! $$.Statistics.NumHypercubes
};

(:~
 : Return the number of networks in each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of networks
 :)
declare function components:num-networks($components as object*) as integer*
{
  $components ! $$.Statistics.NumNetworks
};

(:~
 : Return the number of (distinct) explicit dimensions in each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of dimensions
 :)
declare function components:num-explicit-dimensions($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctExplicitDimensions
};

(:~
 : Return the number of (distinct) domains in each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of domains
 :)
declare function components:num-domains($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctDomains
};

(:~
 : Return the number of (distinct) members in each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of members
 :)
declare function components:num-members($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctMembers
};

(:~
 : Return the number of (distinct) concrete primary items being
 : in a hypercube for each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of primary items
 :)
declare function components:num-concrete-primary-items-in-hypercubes($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctConcretePrimaryItemsInHypercubes
};

(:~
 : Return the number of (distinct) abstract primary items being
 : in a hypercube for each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of primary items
 :)
declare function components:num-abstract-primary-items-in-hypercubes($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctAbstractPrimaryItemsInHypercubes
};

(:~
 : Return the number of (distinct) concrete primary items not being
 : in a hypercube for each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of primary items
 :)
declare function components:num-concrete-primary-items-not-in-hypercubes($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctConcretePrimaryItemsNotInHypercubes
};

(:~
 : Return the number of (distinct) abstract primary items not being
 : in a hypercube for each of the given components.
 :
 : @param $components sequence of components.
 :
 : @return the said number of primary items
 :)
declare function components:num-distinct-abstract-primary-items-not-in-hypercubes($components as object*) as integer*
{
  $components ! $$.Statistics.NumDistinctAbstractPrimaryItemsNotInHypercubes
};

(:~
 :
 :
Converts the input to a normalized component id (CID). The input
 : can be either an CID, or an component object which contains an _id.

 :
 : @param $component-or-id an component object or an CID.
 :
 : @error components:INVALID_PARAMETER if the CID or component is not valid.
 :
 : @return the normalized CID.
 :)
declare function components:cid($component-or-id as item) as atomic
{
  typeswitch ($component-or-id)
  case object return
    let $id := $component-or-id._id
    return if(exists($id))
           then $id
           else error(QName("components:INVALID_PARAMETER"),
                      "Invalid component provided (no _id field)")
  case $id as atomic return $id
  default return error(
      QName("components:INVALID_PARAMETER"),
      "Invalid component or id (must be an object or an atomic): "
      || serialize($component-or-id))
};

declare function components:standard-typed-dimension-breakdown(
    $components as object*,
    $dimension-name as string,
    $options as object?) as object
{
    let $label as string := (
      concepts:labels(
        $dimension-name,
        $concepts:VERBOSE_LABEL_ROLE,
        ($options.Language, $components.$components:DEFAULT-LANGUAGE)[1],
        $components.Concepts[],
        $options
      ),
      concepts:labels(
        $dimension-name,
        $concepts:STANDARD_LABEL_ROLE,
        ($options.Language, $components.$components:DEFAULT-LANGUAGE)[1],
        $components.Concepts[],
        $options
      ),
      $dimension-name)[1]
    return
    {
      BreakdownLabels: [ $label || " breakdown" ],
      BreakdownTrees: [
        {
            Kind: "Rule",
            Abstract: true,
            Labels: [ $label ],
            Children: [
              {
                Kind: "Aspect",
                Aspect: $dimension-name
              }
            ]
        }
      ]
    }
};

declare function components:standard-explicit-dimension-breakdown(
    $components as object*,
    $dimension-name as string,
    $domain-names as string*,
    $role as string,
    $options as object?) as object
{
    let $label as string := (
      concepts:labels(
        $dimension-name,
        $concepts:VERBOSE_LABEL_ROLE,
        ($options.Language, $components.$components:DEFAULT-LANGUAGE)[1],
        $components.Concepts[],
        $options
      ),
      concepts:labels(
        $dimension-name,
        $concepts:STANDARD_LABEL_ROLE,
        ($options.Language, $components.$components:DEFAULT-LANGUAGE)[1],
        $components.Concepts[],
        $options
      ),
      $dimension-name)[1]
    return
    {
        BreakdownLabels: [ $label || " breakdown" ],
        BreakdownTrees: [
            {
                Kind: "Rule",
                Abstract: true,
                Labels: [ $label ],
                Children: [
                    for $domain as string in $domain-names
                    return {
                        Kind: "DimensionRelationship",
                        LinkRole: $role,
                        Dimension: $dimension-name,
                        RelationshipSource: $domain,
                        FormulaAxis: "descendant",
                        Generations: 0
                    }
                ]
            }
        ]
    }
};

declare function components:standard-concept-breakdown(
    $line-items-elements as string*,
    $role as string) as object
{
    {
        BreakdownLabels: [ "Breakdown on concepts" ],
        BreakdownTrees: [
            for $lineitems as string in $line-items-elements
            return {
                Kind: "ConceptRelationship",
                LinkName: "link:presentationLink",
                LinkRole: $role,
                ArcName: "link:presentationArc",
                ArcRole: "http://www.xbrl.org/2003/arcrole/parent-child",
                RelationshipSource: $lineitems,
                FormulaAxis: "descendant",
                Generations: 0,
                RollUpAgainstCalculationNetwork: false
            }
        ]
    }
};

(:~
 :
Builds a standard definition model out of the specified component.

 :
The concepts will be put on the y axis according to the presentation network.

 :
The other dimensions are put on the x axis, with one breakdown for each.

 :
Explicit dimensions are organized according to the dimension hierarchy from the domain-member network.

 :
Typed dimensions are organized according to the actual values.

 :
 :
One of the non-default hypercubes will be arbitrarily chosen. If none is available, the default hypercube will be picked.

 :
Auto slicing will be performed against the fact table
 :
 : @param $component a component object.
 :
 : @return a definition model
 :)
declare function components:standard-definition-models-for-components($components as object*) as object
{
    components:standard-definition-models-for-components($components, ())
};

(:~
 :
Builds a standard definition model out of the specified component.

 :
The concepts will be put on the y axis according to the presentation network.

 :
The other dimensions are put on the x axis, with one breakdown for each.

 :
Explicit dimensions are organized according to the dimension hierarchy from the domain-member network.

 :
Typed dimensions are organized according to the actual values.

 :
 : @param $component a component object.
 : @param $options
some optional parameters, including:

 :

 :
HypercubeName: a string specifying which hypercube to use. By default, one of the non-default hypercubes will be arbitrarily chosen. If none
 :  is available, the default hypercube will be picked.

 :
AutoSlice: a boolean specifying whether or not slicing should be done automatically, looking at the fact table. Deactivating auto slicing will
 :  lead to better performance, but a more verbose table. If AutoSlice is active, dimensions with only one value in the fact pool will become
 : global filters rather than breakdowns on the x axis.

 :
Slicers: an object with forced slicers.

 :

 :
 : @error components:HYPERCUBE-DOES-NOT-EXIST if the specified hypercube is not found.
 : @return a definition model
 :)
declare function components:standard-definition-models-for-components($components as object*, $options as object?) as object
{
    for $component in $components
    let $table as object := components:select-table($component, $options)

    let $auto-slice as boolean := empty($options.AutoSlice) or $options.AutoSlice
    let $facts as object*:=
        if($auto-slice)
        then hypercubes:facts($table)
        else ()
    let $dimensions as string*:= keys($table.Aspects)
    let $values-by-dimension as object := {|
        for $d in $dimensions
        return { $d : [ distinct-values($facts.Aspects.$d) ] }
    |}
    let $auto-slice-dimensions as string* :=
        keys($values-by-dimension)[size($values-by-dimension.$$) eq 1 and not ($$ = ("xbrl:Period", "sec:FiscalYear",  "sec:FiscalPeriod") ) ]
    let $user-slice-dimensions as string* :=
        keys($options.Slicers)

    let $column-dimensions as string* := keys($values-by-dimension)[not $$ =
        ("xbrl:Concept",
        "xbrl:Period",
        "xbrl:Unit",
        "xbrl:Entity",
        "xbrl28:Archive",
        $options.HideDimensions[],
        $auto-slice-dimensions,
        $user-slice-dimensions)]

    let $x-breakdowns as object* := (
        components:standard-typed-dimension-breakdown($components, "xbrl:Period", $options)
          [not (($auto-slice-dimensions, $user-slice-dimensions) = "xbrl:Period")],
        for $d as string in $column-dimensions
        let $dimension-object as object := $table.Aspects.$d
        let $is-typed as boolean := boolean($dimension-object.Kind eq "TypedDimension")
        return
            if($is-typed)
            then
              components:standard-typed-dimension-breakdown(
                $components,
                $d,
                $options)
            else
              components:standard-explicit-dimension-breakdown(
                $components,
                $d,
                $dimension-object.Members[].Name,
                $component.Role,
                $options),
        components:standard-typed-dimension-breakdown($components, "xbrl:Entity", $options)
          [not (($auto-slice-dimensions, $user-slice-dimensions) = "xbrl:Entity")]
    )

    let $lineitems as string* := components:line-items($component)
    let $presentation-network as object? := networks:networks-for-components-and-short-names($component, "Presentation")
    let $roots as string* := $presentation-network.Trees[].Name
    let $lineitems as string* := if(exists($lineitems)) then $lineitems else $roots
    let $y-breakdowns as object :=
      if(exists($presentation-network))
        then components:standard-concept-breakdown($lineitems, $component.Role)
        else components:standard-explicit-dimension-breakdown(
            $components,
            "xbrl:Concept",
            $table.Aspects."xbrl:Concept".Members[].Name,
            $component.Role,
            $options)

    return {
        ModelKind: "DefinitionModel",
        Labels: [$component.Label],
        Parameters: {},
        Breakdowns: {
            "x" : [
                $x-breakdowns
            ],
            "y": [
                $y-breakdowns
            ]
        },
        TableFilters: {|
            for $d as string in distinct-values(($auto-slice-dimensions, $user-slice-dimensions))
            return if($d = $user-slice-dimensions)
                   then { $d : $options.Slicers.$d }
                   else { $d : $values-by-dimension.$d[] },
            if (not $auto-slice)
            then { "xbrl28:Archive" : $component.Archive }
            else ()
        |}
    }
};

declare function components:filter-override(
    $entities-or-eids as item*,
    $archives-or-aids as item*
) as object?
{
    let $override := exists(($entities-or-eids, $archives-or-aids))
    where $override
    let $eids := entities:eid($entities-or-eids)
    let $aids := archives:aid($archives-or-aids)
    return
     {
        "xbrl:Entity" : {|
          { Type: "string" },
          { Domain: [ $eids ] }[exists($eids)]
        |},

        "xbrl28:Archive" : {|
          { Type: "string" },
          { Domain : [ $aids ] }[exists($aids)]
        |}
    }
};

(:~
:
Merges the supplied components, grouping networks by arc/link name/role
: and merging all hypercubes into a single one.

:
: @param $components the input components.
:
: @return the merged component.
:)
declare function components:merge($components as object*) as object
{
    {
        Archive: null,
        Role: $components.Role[1],
        Label: $components.Label[1],
        Networks: [
            networks:merge($components.Networks[])
        ],
        Hypercubes:
            let $merged := hypercubes:merge(values($components.Hypercubes))
            return { $merged.Name: $merged },
        Concepts: [
            for $concept in $components.Concepts[]
            group by $name := $concept.Name
            return $concept[1]
        ]
    }
};

(:~
 :
Returns the line items, that is the top-level abstracts or concepts in the
 : presentation network.

 :
 : @param $components the input components.
 :
 : @return the line items report elements.
 :)
declare function components:line-items($components as object*) as string*
{
    let $presentation-networks as object* := networks:networks-for-components-and-short-names($components, "Presentation")
    return components:line-items-recursive($components, $presentation-networks.Trees[])
};

declare %private function components:line-items-recursive($components as object*, $networks as object*) as string*
{
  let $concepts as object* := $components.Concepts[]
  for $network in $networks
  let $concept-name := $network.Name
  let $substitution-group as string := $concepts[$$.Name eq $concept-name].SubstitutionGroup[1]
  let $has-table-child as boolean := $concepts[$$.Name = $network.To[].Name].SubstitutionGroup = "xbrldt:hypercubeItem"
  return switch(true)
          case $has-table-child return components:line-items-recursive($components, $network.To[])
          case $substitution-group eq "xbrldt:dimensionItem" return ()
          case $substitution-group eq "xbrldt:hypercubeItem" return ()
          default return $concept-name
};

(:~
 :
Checks whether the component has a presentation network.

 :
 : @param $component a component.
 :
 : @return whether it has a presentation network.
 :)
declare %private function components:has-presentation-network(
    $component as object
) as boolean
{
  exists(networks:networks-for-components-and-short-names($component, $networks:PRESENTATION_NETWORK))
};

(:~
 :
Checks whether the component has a non-implied table.

 :
 : @param $component a component.
 :
 : @return whether it has a non-implied table.
 :)
declare %private function components:has-table(
    $component as object
) as boolean
{
  exists(hypercubes:hypercubes-for-components($component)[not $$.Name eq "xbrl28:ImpliedTable"])
};

(:~
 :
Checks components for semantic validation errors.

 :
 : @param $components a sequence of components.
 :
 : @return a sequence of error strings, empty if valid.
 :)
declare function components:validation-errors(
    $components as object*
) as string*
{
  for $component in $components
  return (
    "Presentation network is missing"[not components:has-presentation-network($component)],
    "Hypercube is missing (implied table)"[not components:has-table($component)]
  )
};