:arrow_up: Migrate concepts module.

:arrow_up: Migrate concepts module.

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

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

 :
 : @author Federico Cavalieri
 :)
module namespace concepts = "http://28.io/modules/xbrl/concepts";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace mw = "http://28.io/modules/xbrl/data-source-wrapper";

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

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

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

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

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

(:~
 : Name of the field pointing to the name.
 :)
declare variable $concepts:NAME as xs:string := "Name";

(:~
 : Name of the field pointing to the labels.
 :)
declare variable $concepts:LABELS as xs:string := "Labels";

(:~
 : The default component link role.
 :)
declare variable $concepts:DEFAULT_COMPONENT_LINK_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/link";

(:~
 : Allow any component link role
 :)
declare variable $concepts:ANY_COMPONENT_LINK_ROLE as xs:string :=
    ":any component:";

(:~
 : A joker to ask for all concept names.
 :)
declare variable $concepts:ALL_CONCEPT_NAMES as xs:string := ":any concept:";

(:~
 : A joker to ask for all concept labels.
 :)
declare variable $concepts:ALL_CONCEPT_LABELS as xs:string := ":any concept label:";

(:~
 : The concept report element
 :)
declare variable $concepts:CONCEPT_REPORT_ELEMENT as xs:string := "xbrl:Concept";

(:~
 : The period report element
 :)
declare variable $concepts:PERIOD_REPORT_ELEMENT as xs:string := "xbrl:Period";

(:~
 : The entity report element
 :)
declare variable $concepts:ENTITY_REPORT_ELEMENT as xs:string := "xbrl:Entity";

(:~
 : The unit report element
 :)
declare variable $concepts:UNIT_REPORT_ELEMENT as xs:string := "xbrl:Unit";

(:~
 : The archive report element
 :)
declare variable $concepts:ARCHIVE_REPORT_ELEMENT as xs:string := "xbrl28:Archive";

(:~
 : The implied table report element
 :)
declare variable $concepts:IMPLIED_TABLE_REPORT_ELEMENT as xs:string := "xbrl28:ImpliedTable";

(:~
 : The non-numeric table report element
 :)
declare variable $concepts:NON_NUMERIC_REPORT_ELEMENT as xs:string := "xbrl:NonNumeric";

(:~
  The report elements that are built-in, that is, not in original filings.
:)
declare variable $concepts:BUILT_IN_REPORT_ELEMENTS as string* := (
    $concepts:CONCEPT_REPORT_ELEMENT,
    $concepts:ENTITY_REPORT_ELEMENT,
    $concepts:PERIOD_REPORT_ELEMENT,
    $concepts:UNIT_REPORT_ELEMENT,
    $concepts:ARCHIVE_REPORT_ELEMENT,
    $concepts:IMPLIED_TABLE_REPORT_ELEMENT,
    $concepts:NON_NUMERIC_REPORT_ELEMENT,
    "sec:Accepted",
    "sec:FiscalPeriod",
    "sec:FiscalPeriodType",
    "sec:FiscalYear",
    "sec:Accepted",
    "fsa:FiscalPeriod",
    "fsa:FiscalPeriodType",
    "fsa:FiscalYear",
    "fsa:Submitted"
);

(:~
 :
Retrieves all concepts.

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

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*
  ) as object*
{
  concepts:concepts($concept-names, $archives-or-ids, $component-roles, $labels, ())
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 : @param $options one option OnlyNames to only output concepts with the Name field.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*,
    $options as object?
  ) as object*
{
  let $projection as object := if($options.OnlyNames eq true) then
  {
    Name: 1,
    IsAbstract: 1,
    IsTextBlock: 1
  } else {}
  let $archives := archives:aid($archives-or-ids)
  return
  if (exists($archives))
  then mw:find($concepts:col,
    {|
      {
        $concepts:ARCHIVE : [ $archives ]
      },
      {
        $concepts:ROLE : [ $component-roles ]
      }[not $component-roles = $concepts:ANY_COMPONENT_LINK_ROLE],
      {
        $concepts:NAME : [ $concept-names ]
      }[not $concept-names = $concepts:ALL_CONCEPT_NAMES],
      {
        "Labels" : { "Value" : [ $labels ] }
      }[not $labels = $concepts:ALL_CONCEPT_LABELS]
    |},
    $projection
  )
  else ()
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*
  ) as object*
{
  concepts:concepts(
    $concept-names,
    $archives-or-ids,
    $component-roles,
    $concepts:ALL_CONCEPT_LABELS
  )
};

(:~
 :
Retrieves the concepts which:
 :  - concept name matches a given one;
 :  - archive number matches that of a given component;
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts-for-components(
    $concept-names as string*,
    $labels as string*,
    $component-or-ids as item*,
    $options as object?) as object*
{
  for $component in components:components($component-or-ids)
  return concepts:concepts($concept-names,
                           $component.Archive[not $$ instance of null],
                           $component.Role,
                           $labels,
                           $options)
};

(:~
 :
Transforms a concept tree into XBRL arcs and locators.
 :    Those can then be added to an extended link.

 :
 : @param $trees the trees to transform.
 :
 : @return the locator and arcs elements
 :)
declare function concepts:tree-to-xbrl(
    $tree as object,
    $arcRole as string,
    $taxonomyName as string,
    $level as integer,
    $pos as integer
) as element()*
{
  let $children as object* := ($tree.To[], $tree.Members[])
  let $fromID as string := replace($tree.Name, ":", "_")
  let $fromLabel as string := $fromID || "_" || $level || "_" || $pos
  let $fromLocator as element() := <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($tree.Name, ":")[1]}.xsd#{$fromID}" xlink:label="{$fromLabel}" xlink:title="{normalize-space($tree.Label)}" />

  return
  (
    $fromLocator,

    for $child at $pos in $children
    order by $child.Order ascending
    let $toID as string := replace($child.Name, ":", "_")
    let $toLabel as string := $toID || "_" || ($level + 1) || "_" || $pos
    let $order as decimal :=
      if(empty($child.Order) or $child.Order eq null)
      then 1
      else decimal($child.Order)
    let $arc as element() :=
      element {
        switch ($arcRole)
        case "http://www.xbrl.org/2003/arcrole/concept-label" return xs:QName("link:labelArc")
        case "http://www.xbrl.org/2003/arcrole/parent-child" return xs:QName("link:presentationArc")
        case "http://www.xbrl.org/2003/arcrole/summation-item" return xs:QName("link:calculationArc")
        case "http://xbrl.org/int/dim/arcrole/all"
        case "http://xbrl.org/int/dim/arcrole/hypercube-dimension"
        case "http://xbrl.org/int/dim/arcrole/dimension-domain"
        case "http://xbrl.org/int/dim/arcrole/dimension-default"
        case "http://xbrl.org/int/dim/arcrole/domain-member"
        return xs:QName("link:definitionArc")
        default return error(xs:QName("err:UnknownArcRole"), "arcrole " || $arcRole || " unknown or not supported.")
      }
      {
        attribute { "xlink:type" } { "arc" },
        attribute { "xlink:arcrole" } { $arcRole },
        attribute { "xlink:from" } { $fromLabel },
        attribute { "xlink:to" } { $toLabel },
        attribute { "use" } { "optional" },
        attribute { "order" } { $order },
        (attribute { "weight" } { $child.Weight } )[$arcRole eq "http://www.xbrl.org/2003/arcrole/summation-item"]
      }
    return
      (
        $arc,
        concepts:tree-to-xbrl($child, $arcRole, $taxonomyName, $level+1, $pos)
      )
  )
};

(:~
 :
Transforms concepts into XBRL definitions (XML Schema element definition).

 :
 : @param $concepts the concepts to transform.
 :
 : @return the XML Schema element definitions.
 :)
declare function concepts:to-xbrl(
    $concepts as object*) as element()*
{
  for $concept as object in $concepts
  where not $concept.Name = $concepts:BUILT_IN_REPORT_ELEMENTS
  let $name as string := substring-after($concept.Name, ":")
  let $id as string := replace($concept.Name, ":", "_")
  let $substitutionGroup as string := $concept.SubstitutionGroup
  let $nillable as boolean := (boolean($concept.IsNillable), false)[1]
  let $abstract as boolean := (boolean($concept.IsAbstract), false)[1]
  let $periodType as attribute()? := if(exists($concept.PeriodType)) then attribute { "xbrli:periodType" } {$concept.PeriodType } else ()
  let $balance as attribute()? :=
      (attribute {"xbrli:balance"} { $concept.Balance })[
          $concept.DataType eq "xbrli:monetaryItemType" and
          exists($concept.Balance)]
  let $type as string := $concept.DataType
  return
      <element xmlns="http://www.w3.org/2001/XMLSchema"
          id="{$id}"
          name="{$name}"
          nillable="{$nillable}"
          substitutionGroup="{$substitutionGroup}"
          abstract="{$abstract}"
          type="{$type}">{
              (
                  $periodType,
                  $balance
              )
      }</element>
};

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

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

 :
 : @author Federico Cavalieri
 :)
module namespace concepts = "http://28.io/modules/xbrl/concepts";

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

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

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

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

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

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

(:~
 : Name of the field pointing to the name.
 :)
declare variable $concepts:NAME as xs:string := "Name";

(:~
 : Name of the field pointing to the labels.
 :)
declare variable $concepts:LABELS as xs:string := "Labels";

(:~
 : The default component link role.
 :)
declare variable $concepts:DEFAULT_COMPONENT_LINK_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/link";

(:~
 : Allow any component link role
 :)
declare variable $concepts:ANY_COMPONENT_LINK_ROLE as xs:string :=
    ":any component:";

(:~
 : A joker to ask for all concept names.
 :)
declare variable $concepts:ALL_CONCEPT_NAMES as xs:string := ":any concept:";

(:~
 : A joker to ask for all concept labels.
 :)
declare variable $concepts:ALL_CONCEPT_LABELS as xs:string := ":any concept label:";

(:~
 : The concept report element
 :)
declare variable $concepts:CONCEPT_REPORT_ELEMENT as xs:string := "xbrl:Concept";

(:~
 : The period report element
 :)
declare variable $concepts:PERIOD_REPORT_ELEMENT as xs:string := "xbrl:Period";

(:~
 : The entity report element
 :)
declare variable $concepts:ENTITY_REPORT_ELEMENT as xs:string := "xbrl:Entity";

(:~
 : The unit report element
 :)
declare variable $concepts:UNIT_REPORT_ELEMENT as xs:string := "xbrl:Unit";

(:~
 : The archive report element
 :)
declare variable $concepts:ARCHIVE_REPORT_ELEMENT as xs:string := "xbrl28:Archive";

(:~
 : The implied table report element
 :)
declare variable $concepts:IMPLIED_TABLE_REPORT_ELEMENT as xs:string := "xbrl28:ImpliedTable";

(:~
 : The non-numeric table report element
 :)
declare variable $concepts:NON_NUMERIC_REPORT_ELEMENT as xs:string := "xbrl:NonNumeric";

(:~
  The report elements that are built-in, that is, not in original filings.
:)
declare variable $concepts:BUILT_IN_REPORT_ELEMENTS as string* := (
    $concepts:CONCEPT_REPORT_ELEMENT,
    $concepts:ENTITY_REPORT_ELEMENT,
    $concepts:PERIOD_REPORT_ELEMENT,
    $concepts:UNIT_REPORT_ELEMENT,
    $concepts:ARCHIVE_REPORT_ELEMENT,
    $concepts:IMPLIED_TABLE_REPORT_ELEMENT,
    $concepts:NON_NUMERIC_REPORT_ELEMENT,
    "sec:Accepted",
    "sec:FiscalPeriod",
    "sec:FiscalPeriodType",
    "sec:FiscalYear",
    "sec:Accepted",
    "fsa:FiscalPeriod",
    "fsa:FiscalPeriodType",
    "fsa:FiscalYear",
    "fsa:Submitted"
);

(:~
 :
Retrieves all concepts.

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

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*
  ) as object*
{
  concepts:concepts($concept-names, $archives-or-ids, $component-roles, $labels, ())
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 : @param $labels labels for exact matches.
 : @param $options one option OnlyNames to only output concepts with the Name field.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*,
    $labels as string*,
    $options as object?
  ) as object*
{
  let $projection as object := if($options.OnlyNames eq true) then
  {
    Name: 1,
    IsAbstract: 1,
    IsTextBlock: 1
  } else {}
  let $archives := archives:aid($archives-or-ids)
  return
  if (exists($archives))
  then mw:find($concepts:col,
    {|
      {
        $concepts:ARCHIVE : [ $archives ]
      },
      {
        $concepts:ROLE : [ $component-roles ]
      }[not $component-roles = $concepts:ANY_COMPONENT_LINK_ROLE],
      {
        $concepts:NAME : [ $concept-names ]
      }[not $concept-names = $concepts:ALL_CONCEPT_NAMES],
      {
        "Labels" : { "Value" : [ $labels ] }
      }[not $labels = $concepts:ALL_CONCEPT_LABELS]
    |},
    $projection
  )
  else ()
};

(:~
 :
Retrieves the concepts which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $archives-or-ids the archives or their IDs.
 : @param $component-roles the component roles.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives-or-ids as item*,
    $component-roles as string*
  ) as object*
{
  concepts:concepts(
    $concept-names,
    $archives-or-ids,
    $component-roles,
    $concepts:ALL_CONCEPT_LABELS
  )
};

(:~
 :
Retrieves the concepts which:
 :  - concept name matches a given one;
 :  - archive number matches that of a given component;
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $labels labels for exact matches.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts-for-components(
    $concept-names as string*,
    $labels as string*,
    $component-or-ids as item*,
    $options as object?) as object*
{
  for $component in components:components($component-or-ids)
  return concepts:concepts($concept-names,
                           $component.Archive[not $$ instance of null],
                           $component.Role,
                           $labels,
                           $options)
};

(:~
 :
Transforms a concept tree into XBRL arcs and locators.
 :    Those can then be added to an extended link.

 :
 : @param $trees the trees to transform.
 :
 : @return the locator and arcs elements
 :)
declare function concepts:tree-to-xbrl(
    $tree as object,
    $arcRole as string,
    $taxonomyName as string,
    $level as integer,
    $pos as integer
) as element()*
{
  let $children as object* := ($tree.To[], $tree.Members[])
  let $fromID as string := replace($tree.Name, ":", "_")
  let $fromLabel as string := $fromID || "_" || $level || "_" || $pos
  let $fromLocator as element() := <link:loc xlink:type="locator" xlink:href="{$taxonomyName||"-"||tokenize($tree.Name, ":")[1]}.xsd#{$fromID}" xlink:label="{$fromLabel}" xlink:title="{normalize-space($tree.Label)}" />

  return
  (
    $fromLocator,

    for $child at $pos in $children
    order by $child.Order ascending
    let $toID as string := replace($child.Name, ":", "_")
    let $toLabel as string := $toID || "_" || ($level + 1) || "_" || $pos
    let $order as decimal :=
      if(empty($child.Order) or $child.Order eq null)
      then 1
      else decimal($child.Order)
    let $arc as element() :=
      element {
        switch ($arcRole)
        case "http://www.xbrl.org/2003/arcrole/concept-label" return xs:QName("link:labelArc")
        case "http://www.xbrl.org/2003/arcrole/parent-child" return xs:QName("link:presentationArc")
        case "http://www.xbrl.org/2003/arcrole/summation-item" return xs:QName("link:calculationArc")
        case "http://xbrl.org/int/dim/arcrole/all"
        case "http://xbrl.org/int/dim/arcrole/hypercube-dimension"
        case "http://xbrl.org/int/dim/arcrole/dimension-domain"
        case "http://xbrl.org/int/dim/arcrole/dimension-default"
        case "http://xbrl.org/int/dim/arcrole/domain-member"
        return xs:QName("link:definitionArc")
        default return error(xs:QName("err:UnknownArcRole"), "arcrole " || $arcRole || " unknown or not supported.")
      }
      {
        attribute { "xlink:type" } { "arc" },
        attribute { "xlink:arcrole" } { $arcRole },
        attribute { "xlink:from" } { $fromLabel },
        attribute { "xlink:to" } { $toLabel },
        attribute { "use" } { "optional" },
        attribute { "order" } { $order },
        (attribute { "weight" } { $child.Weight } )[$arcRole eq "http://www.xbrl.org/2003/arcrole/summation-item"]
      }
    return
      (
        $arc,
        concepts:tree-to-xbrl($child, $arcRole, $taxonomyName, $level+1, $pos)
      )
  )
};

(:~
 :
Transforms concepts into XBRL definitions (XML Schema element definition).

 :
 : @param $concepts the concepts to transform.
 :
 : @return the XML Schema element definitions.
 :)
declare function concepts:to-xbrl(
    $concepts as object*) as element()*
{
  for $concept as object in $concepts
  where not $concept.Name = $concepts:BUILT_IN_REPORT_ELEMENTS
  let $name as string := substring-after($concept.Name, ":")
  let $id as string := replace($concept.Name, ":", "_")
  let $substitutionGroup as string := $concept.SubstitutionGroup
  let $nillable as boolean := (boolean($concept.IsNillable), false)[1]
  let $abstract as boolean := (boolean($concept.IsAbstract), false)[1]
  let $periodType as attribute()? := if(exists($concept.PeriodType)) then attribute { "xbrli:periodType" } {$concept.PeriodType } else ()
  let $balance as attribute()? :=
      (attribute {"xbrli:balance"} { $concept.Balance })[
          $concept.DataType eq "xbrli:monetaryItemType" and
          exists($concept.Balance)]
  let $type as string := $concept.DataType
  return
      <element xmlns="http://www.w3.org/2001/XMLSchema"
          id="{$id}"
          name="{$name}"
          nillable="{$nillable}"
          substitutionGroup="{$substitutionGroup}"
          abstract="{$abstract}"
          type="{$type}">{
              (
                  $periodType,
                  $balance
              )
      }</element>
};


Update sections.jq

Update sections.jq

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

(:~
 :
This module provides functionality for validating sections.

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

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

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

(:~
 :
Checks whether all report elements have metadata.

 :
 : @param $section a section.
 :
 : @return all report elements without metadata.
 :)
declare %private function sections:missing-metadata(
    $section as object
) as string*
{
  let $metadata-names := $section.Concepts[].Name
  for $name in distinct-values(descendant-objects((
      $section.Hypercubes,
      $section.Networks[])
  ).Name)
  where not $name = $metadata-names
  return $name
};

(:~
 :
Checks whether all report elements have metadata.

 :
 : @param $section a section.
 :
 : @return all report elements without metadata.
 :)
declare %private function sections:presentation-errors(
    $section as object
) as string*
{
  let $disallowed := {
    "Abstract" : [ "Dimension", "Member", "LineItems" ],
    "Hypercube" : [ "Hypercube", "Member", "Abstract", "Concept" ],
    "Dimension" : [ "Hypercube", "Dimension", "LineItems", "Abstract", "Concept" ],
    "Member" : [ "Hypercube", "Dimension", "LineItems", "Abstract", "Concept" ],
    "LineItems" : [ "Hypercube", "Dimension", "LineItems", "Member" ],
    "Concept" : [ "Abstract" ]
  }
  let $not-advised := {
    "Abstract" : [ ],
    "Hypercube" : [ ],
    "Dimension" : [ ],
    "Member" : [ ],
    "LineItems" : [ ],
    "Concept" : [ "Concept" ]
  }
  let $presentation-network as object? := networks:networks-for-components-and-short-names(
      $section,
      $networks:PRESENTATION_NETWORK)
  return (
    if(empty($presentation-network))
    then "[Error] Presentation network is missing."
    else (),
    for $node as object in $presentation-network.Trees[]
    let $kind as string := $node.Kind
    return (
      if($kind = ("Dimension", "Member", "LineItems"))
      then ("[Error] Disallowed edge in presentation network: Network -> " || $kind || " (" || $node.Name || ")")
      else (),
      if($kind = ("Concept"))
      then ("[Warning] Not-advised edge in presentation network: Network -> " || $kind || " (" || $node.Name || ")")
      else ()
    ),
    for $node as object in descendant-objects($presentation-network)[exists($$.Name)]
    let $kind as string := $node.Kind
    let $children-kinds := distinct-values($node.To[].Kind)
    where exists($children-kinds)
    let $disallowed as string* := $children-kinds[$$ = $disallowed.$kind[] ]
    let $not-advised as string* := $children-kinds[$$ = $not-advised.$kind[] ]
    return (
      $disallowed !
      ("[Error] Disallowed edge in presentation network: " || $kind || " (" || $node.Name || ") " || " -> " || $$),
      $not-advised !
      ("[Warning] Not-advised edge in presentation network: " || $kind || " (" || $node.Name || ") " || " -> " || $$)
    )
  )
};

(:~
 :
Checks components for semantic validation errors.

 :
 : @param $section a section.
 :
 : @return a sequence of error strings, empty if valid.
 :)
declare function sections:validation-errors(
    $section as object
) as string*
{
  let $missing := sections:missing-metadata($section)
  where exists($missing)
  return "[Error] Some metadata is missing (" || string-join($missing, ", ") || ")",
  sections:presentation-errors($section)
};

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

(:~
 :
This module provides functionality for validating sections.

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

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

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

(:~
 :
Checks whether all report elements have metadata.

 :
 : @param $section a section.
 :
 : @return all report elements without metadata.
 :)
declare %private function sections:missing-metadata(
    $section as object
) as string*
{
  let $metadata-names := $section.Concepts[].Name
  for $name in distinct-values(descendant-objects((
      $section.Hypercubes,
      $section.Networks[])
  ).Name)
  where not $name = $metadata-names
  return $name
};

(:~
 :
Checks whether all report elements have metadata.

 :
 : @param $section a section.
 :
 : @return all report elements without metadata.
 :)
declare %private function sections:presentation-errors(
    $section as object
) as string*
{
  let $disallowed := {
    "Abstract" : [ "Dimension", "Member", "LineItems" ],
    "Hypercube" : [ "Hypercube", "Member", "Abstract", "Concept" ],
    "Dimension" : [ "Hypercube", "Dimension", "LineItems", "Abstract", "Concept" ],
    "Member" : [ "Hypercube", "Dimension", "LineItems", "Abstract", "Concept" ],
    "LineItems" : [ "Hypercube", "Dimension", "LineItems", "Member" ],
    "Concept" : [ "Abstract" ]
  }
  let $not-advised := {
    "Abstract" : [ ],
    "Hypercube" : [ ],
    "Dimension" : [ ],
    "Member" : [ ],
    "LineItems" : [ ],
    "Concept" : [ "Concept" ]
  }
  let $presentation-network as object? := networks:networks-for-components-and-short-names(
      $section,
      $networks:PRESENTATION_NETWORK)
  return (
    if(empty($presentation-network))
    then "[Error] Presentation network is missing."
    else (),
    for $node as object in $presentation-network.Trees[]
    let $kind as string := $node.Kind
    return (
      if($kind = ("Dimension", "Member", "LineItems"))
      then ("[Error] Disallowed edge in presentation network: Network -> " || $kind)
      else (),
      if($kind = ("Concept"))
      then ("[Warning] Not-advised edge in presentation network: Network -> " || $kind)
      else ()
    ),
    for $node as object in descendant-objects($presentation-network)[exists($$.Name)]
    let $kind as string := $node.Kind
    let $children-kinds := distinct-values($node.To[].Kind)
    where exists($children-kinds)
    let $disallowed as string* := $children-kinds[$$ = $disallowed.$kind[] ]
    let $not-advised as string* := $children-kinds[$$ = $not-advised.$kind[] ]
    return (
      $disallowed !
      ("[Error] Disallowed edge in presentation network: " || $kind || " -> " || $$),
      $not-advised !
      ("[Warning] Not-advised edge in presentation network: " || $kind || " -> " || $$)
    )
  )
};

(:~
 :
Checks components for semantic validation errors.

 :
 : @param $section a section.
 :
 : @return a sequence of error strings, empty if valid.
 :)
declare function sections:validation-errors(
    $section as object
) as string*
{
  let $missing := sections:missing-metadata($section)
  where exists($missing)
  return "[Error] Some metadata is missing (" || string-join($missing, ", ") || ")",
  sections:presentation-errors($section)
};


:green-heart: Fix code.

:green-heart: Fix code.

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

(:~
 :
This module provides a driver to access
 : ElasticSearch.

 :
 : @author Dennis Knochenwefel
 :)
module namespace es = "http://28.io/modules/elasticsearch";

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

declare namespace ver = "http://zorba.io/options/versioning";

declare option ver:module-version "1.0";

declare %private variable $es:CREDENTIALS-CATEGORY as xs:string :=
    "ElasticSearch";
declare %private variable $es:RETRY-OPTIONS :=
  {
    retry :
    {
      "delay" : [1000, 2000, 4000],
      "on-connection-error" : true(),
      "on-statuses" : [ 500, 503 ]
    }
  };

declare function es:connection($credentials-name as xs:string?)
as object?
{
  let $credentials as object? := credentials:credentials(
      $es:CREDENTIALS-CATEGORY,
      $credentials-name)
  where exists($credentials)
  return
      let $urls as string+ :=
        for $url in $credentials.urls[]
        return $url || ("/"[not ends-with($url, "/")])
      let $user as string := $credentials.user
      let $password as string := $credentials.pass
      return
        {
          "urls": [
            for $url in $urls
            return
              "https://" || encode-for-uri($user) || ":" ||
                            encode-for-uri($password) ||"@" || $url
          ]
        }
};

declare %private function es:url(
  $connection as object,
  $paths as string*
) as string
{
  es:url($connection, $paths, ())
};

declare %private function es:url(
  $connection as object,
  $paths as string*,
  $parameters as string*
) as string
{
  let $server as string := $connection.urls[][2]
  let $parameters as string* :=
    for $parameter in $parameters
    return encode-for-uri($parameter)
  let $path as string := string-join($paths, "/")
  let $query as string := string-join($parameters, "&")
  return
    $server || string-join(($path, $query), "?")
};

declare %private function es:request(
  $method as string,
  $url as string,
  $body as item?
) as object
{
  {|
    {
      "method": $method,
      "href": $url,
      "options": $es:RETRY-OPTIONS
    },
    switch(true)

    case empty($body) return ()

    case $body instance of string
    return
      {
        "body":
        {
          "media-type": "text/plain",
          "content": $body
        }
      }

    case $body instance of object
    case $body instance of array
    return
      {
        "body":
        {
          "media-type": "application/json",
          "content": serialize($body)
        }
      }

    default return
      error(xs:QName("es:UNKNOWN_BODY_TYPE"),
            "cannot create request from body content",
            $body)
  |}
};

declare %an:nondeterministic function es:exists(
  $connection as object,
  $index as string
) as boolean
{
  es:exists($connection, $index, (), ())
};

declare %an:nondeterministic function es:exists(
  $connection as object,
  $index as string,
  $type as string?
) as boolean
{
  es:exists($connection, $index, $type, ())
};

declare %an:nondeterministic function es:exists(
  $connection as object,
  $index as string,
  $type as string?,
  $id as string?
) as boolean
{
  let $url as string := es:url($connection, ($index, $type, $id))
  let $request as object := es:request("HEAD", $url, ())
  let $response as object := http:send-nondeterministic-request($request)
  return
    switch ($response.status)
    case 200
    case 204 return true
    case 404 return false
    default return error(xs:QName("es:SERVER_ERROR"),
                         $response.body.content,
                         $response)
};

declare %an:sequential function es:create(
  $connection as object,
  $index as string
) as object
{
  es:create($connection, $index, ())
};

declare %an:sequential function es:create(
  $connection as object,
  $index as string,
  $mapping as object?
) as object
{
  let $method as string :=
    if(empty($mapping))
    then "POST"
    else "PUT"
  let $url as string := es:url($connection, ($index))
  let $request as object := es:request($method, $url, $mapping)
  let $response as object := http:send-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:SERVER_ERROR"), $response.body.content, $response)
};

declare %an:sequential function es:create(
  $connection as object,
  $index as string,
  $type as string,
  $mapping as object
) as object?
{
  let $url as string := es:url($connection, ($index, "_mapping", $type))
  let $request as object := es:request("PUT", $url, $mapping)
  let $response as object := http:send-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:SERVER_ERROR"), $response.body.content, $response)
};

declare %an:sequential function es:delete(
  $connection as object,
  $index as string
) as object?
{
  es:delete($connection, $index, (),())
};

declare %an:sequential function es:delete(
  $connection as object,
  $index as string,
  $type as string?
) as object?
{
  es:delete($connection, $index, $type, ())
};

declare %an:sequential function es:delete(
  $connection as object,
  $index as string,
  $type as string?,
  $id as string?
) as object?
{
  let $url as string := es:url($connection, ($index, $type, $id))
  let $request as object := es:request("DELETE", $url, ())
  let $response as object := http:send-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:SERVER_ERROR"), $response.body.content, $response)
};

declare %an:nondeterministic function es:get(
  $connection as object,
  $index as string,
  $type as string,
  $id as string
) as object?
{
  let $url as string := es:url($connection, ($index, $type, $id))
  let $response as object := http:get($url)
  return
    switch ($response.status)
    case 200 return parse-json($response.body.content)
    case 404 return ()
    default return error(xs:QName("es:CONNECTION_ERROR"),
                         $response.body.content,
                         $response)
};

declare %an:sequential function es:insert(
  $connection as object,
  $index as string,
  $type as string,
  $doc as object
) as object?
{
  let $id as string? := $doc._id
  return es:insert($connection, $index, $type, $id, $doc)
};

declare %an:sequential function es:insert(
  $connection as object,
  $index as string,
  $type as string,
  $id as string?,
  $doc as object
) as object?
{
  let $method as string := if(empty($id)) then "POST" else "PUT"
  let $url as string := es:url($connection, ($index, $type, $id))
  let $request as object := es:request($method, $url, $doc)
  let $response as object := http:send-request($request)
  return
    switch ($response.status)
    case 200
    case 201 return parse-json($response.body.content)
    default return error(xs:QName("es:CONNECTION_ERROR"),
                         $response.body.content,
                         $response)
};

declare %an:nondeterministic function es:list(
  $connection as object,
  $index as string,
  $type as string
) as object?
{
  let $url as string := es:url($connection, ($index, $type, "_search"))
  let $request as object := es:request("GET", $url, ())
  let $response as object := http:send-nondeterministic-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:CONNECTION_ERROR"),
                        $response.body.content,
                        $response)
};

declare %an:nondeterministic function es:search(
  $connection as object,
  $index as string,
  $type as string,
  $query as object
) as object?
{
  let $url as string := es:url($connection, ($index, $type, "_search"))
  let $request as object := es:request("GET", $url, $query)
  let $response as object := http:send-nondeterministic-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:CONNECTION_ERROR"),
                        $response.body.content,
                        $response)
};

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

(:~
 :
This module provides a driver to access
 : ElasticSearch.

 :
 : @author Dennis Knochenwefel
 :)
module namespace es = "http://28.io/modules/elasticsearch";

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

declare namespace ver = "http://zorba.io/options/versioning";

declare option ver:module-version "1.0";

declare %private variable $es:CREDENTIALS-CATEGORY as xs:string :=
    "ElasticSearch";
declare %private variable $es:RETRY-OPTIONS :=
  {
    retry :
    {
      "delay" : [1000, 2000, 4000],
      "on-connection-error" : true(),
      "on-statuses" : [ 500, 503 ]
    }
  };

declare function es:connection($credentials-name as xs:string?)
as object?
{
  let $credentials as object? := credentials:credentials(
      $es:CREDENTIALS-CATEGORY,
      $credentials-name)
  where exists($credentials)
  return
    then
      let $urls as string+ :=
        for $url in $credentials.urls[]
        return $url || ("/"[not ends-with($url, "/")])
      let $user as string := $credentials.user
      let $password as string := $credentials.pass
      return
        {
          "urls": [
            for $url in $urls
            return
              "https://" || encode-for-uri($user) || ":" ||
                            encode-for-uri($password) ||"@" || $url
          ]
        }
    else ()
};

declare %private function es:url(
  $connection as object,
  $paths as string*
) as string
{
  es:url($connection, $paths, ())
};

declare %private function es:url(
  $connection as object,
  $paths as string*,
  $parameters as string*
) as string
{
  let $server as string := $connection.urls[][2]
  let $parameters as string* :=
    for $parameter in $parameters
    return encode-for-uri($parameter)
  let $path as string := string-join($paths, "/")
  let $query as string := string-join($parameters, "&")
  return
    $server || string-join(($path, $query), "?")
};

declare %private function es:request(
  $method as string,
  $url as string,
  $body as item?
) as object
{
  {|
    {
      "method": $method,
      "href": $url,
      "options": $es:RETRY-OPTIONS
    },
    switch(true)

    case empty($body) return ()

    case $body instance of string
    return
      {
        "body":
        {
          "media-type": "text/plain",
          "content": $body
        }
      }

    case $body instance of object
    case $body instance of array
    return
      {
        "body":
        {
          "media-type": "application/json",
          "content": serialize($body)
        }
      }

    default return
      error(xs:QName("es:UNKNOWN_BODY_TYPE"),
            "cannot create request from body content",
            $body)
  |}
};

declare %an:nondeterministic function es:exists(
  $connection as object,
  $index as string
) as boolean
{
  es:exists($connection, $index, (), ())
};

declare %an:nondeterministic function es:exists(
  $connection as object,
  $index as string,
  $type as string?
) as boolean
{
  es:exists($connection, $index, $type, ())
};

declare %an:nondeterministic function es:exists(
  $connection as object,
  $index as string,
  $type as string?,
  $id as string?
) as boolean
{
  let $url as string := es:url($connection, ($index, $type, $id))
  let $request as object := es:request("HEAD", $url, ())
  let $response as object := http:send-nondeterministic-request($request)
  return
    switch ($response.status)
    case 200
    case 204 return true
    case 404 return false
    default return error(xs:QName("es:SERVER_ERROR"),
                         $response.body.content,
                         $response)
};

declare %an:sequential function es:create(
  $connection as object,
  $index as string
) as object
{
  es:create($connection, $index, ())
};

declare %an:sequential function es:create(
  $connection as object,
  $index as string,
  $mapping as object?
) as object
{
  let $method as string :=
    if(empty($mapping))
    then "POST"
    else "PUT"
  let $url as string := es:url($connection, ($index))
  let $request as object := es:request($method, $url, $mapping)
  let $response as object := http:send-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:SERVER_ERROR"), $response.body.content, $response)
};

declare %an:sequential function es:create(
  $connection as object,
  $index as string,
  $type as string,
  $mapping as object
) as object?
{
  let $url as string := es:url($connection, ($index, "_mapping", $type))
  let $request as object := es:request("PUT", $url, $mapping)
  let $response as object := http:send-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:SERVER_ERROR"), $response.body.content, $response)
};

declare %an:sequential function es:delete(
  $connection as object,
  $index as string
) as object?
{
  es:delete($connection, $index, (),())
};

declare %an:sequential function es:delete(
  $connection as object,
  $index as string,
  $type as string?
) as object?
{
  es:delete($connection, $index, $type, ())
};

declare %an:sequential function es:delete(
  $connection as object,
  $index as string,
  $type as string?,
  $id as string?
) as object?
{
  let $url as string := es:url($connection, ($index, $type, $id))
  let $request as object := es:request("DELETE", $url, ())
  let $response as object := http:send-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:SERVER_ERROR"), $response.body.content, $response)
};

declare %an:nondeterministic function es:get(
  $connection as object,
  $index as string,
  $type as string,
  $id as string
) as object?
{
  let $url as string := es:url($connection, ($index, $type, $id))
  let $response as object := http:get($url)
  return
    switch ($response.status)
    case 200 return parse-json($response.body.content)
    case 404 return ()
    default return error(xs:QName("es:CONNECTION_ERROR"),
                         $response.body.content,
                         $response)
};

declare %an:sequential function es:insert(
  $connection as object,
  $index as string,
  $type as string,
  $doc as object
) as object?
{
  let $id as string? := $doc._id
  return es:insert($connection, $index, $type, $id, $doc)
};

declare %an:sequential function es:insert(
  $connection as object,
  $index as string,
  $type as string,
  $id as string?,
  $doc as object
) as object?
{
  let $method as string := if(empty($id)) then "POST" else "PUT"
  let $url as string := es:url($connection, ($index, $type, $id))
  let $request as object := es:request($method, $url, $doc)
  let $response as object := http:send-request($request)
  return
    switch ($response.status)
    case 200
    case 201 return parse-json($response.body.content)
    default return error(xs:QName("es:CONNECTION_ERROR"),
                         $response.body.content,
                         $response)
};

declare %an:nondeterministic function es:list(
  $connection as object,
  $index as string,
  $type as string
) as object?
{
  let $url as string := es:url($connection, ($index, $type, "_search"))
  let $request as object := es:request("GET", $url, ())
  let $response as object := http:send-nondeterministic-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:CONNECTION_ERROR"),
                        $response.body.content,
                        $response)
};

declare %an:nondeterministic function es:search(
  $connection as object,
  $index as string,
  $type as string,
  $query as object
) as object?
{
  let $url as string := es:url($connection, ($index, $type, "_search"))
  let $request as object := es:request("GET", $url, $query)
  let $response as object := http:send-nondeterministic-request($request)
  return
    if($response.status eq 200)
    then parse-json($response.body.content)
    else error(xs:QName("es:CONNECTION_ERROR"),
                        $response.body.content,
                        $response)
};


:green_heart: Fix warning.

:green_heart: Fix warning.

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

(:~
 :
This module provides functions for storing, retrieving, and modifying
 : reports. Reports can be used in BizQL queries. They can be
 : instantiated generating a business report.

 :
 :
Reports are nothing else than a user-defined component. However,
 : one of their hypercubes (the default hypercube), as well as two of their
 : networks (presentation, concept-map) are special. A reports is identified
 : with an RID (Report schema ID).

 :
 :
With this module, you can retrieve facts belonging to a report.
 : The concept map network will be used
 : by default to map the report's concepts to reported concepts.

 :
 :
Reports are stored in the collection reports in
 : the project's MongoDB database.

 :
 :
A report needs to be a syntactically valid JSound document.

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

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

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

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

(:~
 :
Retrieves all reports.

 :
 : @return all reports.
 :)
declare function reports:reports() as object*
{
  collection($reports:col)
};

(:~
 :
Return the reports with the given RIDs.

 :
 : @param $reports-or-ids the report ids (RIDs) or the
 : reports themselves.
 :
 : @return the reports with the given RIDs or the
 :   empty sequence if no report was found or the input
 : is an empty sequence.
 :)
declare function reports:reports($reports-or-ids as item*) as object*
{
  let $ids as string* :=
    for $s in $reports-or-ids
    where $s instance of string
    return $s
  let $schemas as object* :=
    for $s in $reports-or-ids
    where $s instance of object
    return $s
  return
    (
      $schemas,
      if (exists($ids))
      then find($reports:col,
                { "_id" : { "$in" : [ distinct-values($ids) ! reports:rid($$) ] } } )
      else ()
    )
};

(:~
 :
Adds the given report to the database.

 :
 : @param $report the report schema to add.
 :
 : @return the empty sequence.
 :
 : @error reports:INVALID-REPORT if the given report object does not
 :   contain a name field
 : @error reports:EXISTS if a report with the given name already exists
 :)
declare %an:sequential function reports:add($report as object) as ()
{
  if (empty($report._id))
  then error(QName("reports:INVALID-REPORT"),
    "report does not contain mandatory _id field.");
  else if (exists(reports:reports($report._id)))
       then fn:error(QName("reports:EXISTS"),
                     $report._id || ": report already exists");
       else insert($reports:col, $report);
};

(:~
 :
Updates a report.

 :
 :
Replaces a report in the database with the given report.
 : The report to be replaced is identified by the value of the _id
 : field of the given schema (RID).

 :
 : @param $report the new report
 :
 : @return the empty sequence
 :
 : @error reports:DOES-NOT-EXIST if a report with the given name does not exist.
 :)
declare %an:sequential function reports:update($report as object)
as ()
{
  let $existing := reports:reports($report._id)
  return if (empty($existing))
         then error(QName("reports:DOES-NOT-EXIST"),
             $report._id || ": report does not exist");
         else edit($existing, $report);
};

(:~
 :
Deletes a report from the database.

 :
 : @param $report-or-id the report to delete or its RID.
 :
 : @return the empty sequence.
 :
 : @error report:DOES-NOT-EXIST if no report with the given RID exists.
 :)
declare %an:sequential function reports:delete($report-or-id as item)
as ()
{
  let $report := reports:reports($report-or-id)
  let $id := reports:rid($report-or-id)
  return if (empty($report))
  then error(QName("reports:DOES-NOT-EXIST"),
    $id || ": report does not exist");
  else delete($report);
};

(:~
 :
 :
Converts the input to a normalized report identifier (RID). The input
 : can be either a pure RID, or a report object which contains an RID.

 :
 : @param $report-or-id a report identifier (RID)) or a report object.
 :
 : @error reports:INVALID_PARAMETER if the RID or report is not valid
 :
 : @return the normalized RID.
 :)
declare function reports:rid($report-or-id as item) as atomic
{
  typeswitch ($report-or-id)
  case object return
    let $rid := $report-or-id._id
    return if(exists($rid))
           then $rid
           else error(QName("reports:INVALID_PARAMETER"),
                      "report does not contain mandatory _id field.")
  case $rid as atomic return $rid
  default return error(
      QName("reports:INVALID_PARAMETER"),
      serialize($report-or-id) || ": Invalid report or RID (must be an object or an atomic): "
  )
};

(:~
 : Returns the concept map network contained in the report, if any.
 :
 : @param $report-or-id a report identifier (RID)) or a report object.
 : @return the concept map or the empty sequence.
 :)
declare function reports:concept-map($report-or-id as item) as object?
{
    let $report as object? := reports:reports($report-or-id)
    return networks:networks-for-components-and-short-names(
        $report,
        "ConceptMap")
};

(:~
 : Returns the rules contained in the report, if any.
 :
 : @param $report-or-id a report identifier (RID)) or a report object.
 : @return the rule objects.
 :)
declare function reports:rules($report-or-id as item) as object*
{
    let $report as object? := reports:reports($report-or-id)
    return $report.Rules[]
};

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

(:~
 :
This module provides functions for storing, retrieving, and modifying
 : reports. Reports can be used in BizQL queries. They can be
 : instantiated generating a business report.

 :
 :
Reports are nothing else than a user-defined component. However,
 : one of their hypercubes (the default hypercube), as well as two of their
 : networks (presentation, concept-map) are special. A reports is identified
 : with an RID (Report schema ID).

 :
 :
With this module, you can retrieve facts belonging to a report.
 : The concept map network will be used
 : by default to map the report's concepts to reported concepts.

 :
 :
Reports are stored in the collection reports in
 : the project's MongoDB database.

 :
 :
A report needs to be a syntactically valid JSound document.

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

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

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

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

(:~
 :
Retrieves all reports.

 :
 : @return all reports.
 :)
declare function reports:reports() as object*
{
  collection($reports:col)
};

(:~
 :
Return the reports with the given RIDs.

 :
 : @param $reports-or-ids the report ids (RIDs) or the
 : reports themselves.
 :
 : @return the reports with the given RIDs or the
 :   empty sequence if no report was found or the input
 : is an empty sequence.
 :)
declare function reports:reports($reports-or-ids as item*) as object*
{
  let $ids as string* :=
    for $s in $reports-or-ids
    where $s instance of string
    return $s
  let $schemas as object* :=
    for $s in $reports-or-ids
    where $s instance of object
    return $s
  return
    (
      $schemas,
      if (exists($ids))
      then find($reports:col,
                { "_id" : { "$in" : [ distinct-values($ids) ! reports:rid($$) ] } } )
      else ()
    )
};

(:~
 :
Adds the given report to the database.

 :
 : @param $report the report schema to add.
 :
 : @return the empty sequence.
 :
 : @error reports:INVALID-REPORT if the given report object does not
 :   contain a name field
 : @error reports:EXISTS if a report with the given name already exists
 :)
declare %an:sequential function reports:add($report as object) as ()
{
  if (empty($report._id))
  then error(QName("reports:INVALID-REPORT"),
    "report does not contain mandatory _id field.");
  else if (exists(reports:reports($report._id)))
       then fn:error(QName("reports:EXISTS"),
                     $report._id || ": report already exists");
       else insert($reports:col, $report);
};

(:~
 :
Updates a report.

 :
 :
Replaces a report in the database with the given report.
 : The report to be replaced is identified by the value of the _id
 : field of the given schema (RID).

 :
 : @param $report the new report
 :
 : @return the empty sequence
 :
 : @error reports:DOES-NOT-EXIST if a report with the given name does not exist.
 :)
declare %an:sequential function reports:update($report as object)
as ()
{
  let $existing := reports:reports($report._id)
  return if (empty($existing))
         then error(QName("reports:DOES-NOT-EXIST"),
             $report._id || ": report does not exist");
         else edit($existing, $report);
};

(:~
 :
Deletes a report from the database.

 :
 : @param $report-or-id the report to delete or its RID.
 :
 : @return the empty sequence.
 :
 : @error report:DOES-NOT-EXIST if no report with the given RID exists.
 :)
declare %an:sequential function reports:delete($report-or-id as item)
as ()
{
  let $report := reports:reports($report-or-id)
  let $id := reports:rid($report-or-id)
  return if (empty($report))
  then error(QName("reports:DOES-NOT-EXIST"),
    $id || ": report does not exist");
  else delete($report);
};

(:~
 :
 :
Converts the input to a normalized report identifier (RID). The input
 : can be either a pure RID, or a report object which contains an RID.

 :
 : @param $report-or-id a report identifier (RID)) or a report object.
 :
 : @error reports:INVALID_PARAMETER if the RID or report is not valid
 :
 : @return the normalized RID.
 :)
declare function reports:rid($report-or-id as item) as atomic
{
  typeswitch ($report-or-id)
  case object return
    let $rid := $report-or-id._id
    return if(exists($rid))
           then $rid
           else error(QName("reports:INVALID_PARAMETER"),
                      "report does not contain mandatory _id field.")
  case $rid as atomic return $rid
  default return error(
      QName("reports:INVALID_PARAMETER"),
      serialize($report-or-id) || ": Invalid report or RID (must be an object or an atomic): "
  )
};

(:~
 : Returns the concept map network contained in the report, if any.
 :
 : @param $report-or-id a report identifier (RID)) or a report object.
 : @return the concept map or the empty sequence.
 :)
declare function reports:concept-map($report-or-id as item) as object?
{
    let $report as object? := reports:reports($report-or-id)
    return networks:networks-for-components-and-short-names(
        $report,
        "ConceptMap")
};

(:~
 : Returns the rules contained in the report, if any.
 :
 : @param $report-or-id a report identifier (RID)) or a report object.
 : @return the rule objects.
 :)
declare function reports:rules($report-or-id as item) as object*
{
    let $report as object? := reports:reports($report-or-id)
    return $report.Rules[]
};