Fixing labels and ordering of totals.

Fixing labels and ordering of totals.

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

(:~
 :
This module provides functionality for handling networks.

 :
 :
A network is a graph of concepts and resources, of which the edges
 : share the same semantics.

 :
 :
There are several kinds of networks.
 : A network can be a tree or a DAG of concepts. It can be a bipartite
 : graph mapping concepts to resources.

 :
 :
XBRL defines a number of standard networks: presentation, calculation,
 : definition (essence-alias, general-special, requires-element, similar-tuples, domain-member),
 : and label networks.

 :
 :
With this module, you can retrieve the networks contained in a component, and you
 : can retrieve a standard XBRL network with the provided short names.

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

(:~
 : Short name of the presentation network.
 :)
declare variable $networks:PRESENTATION_NETWORK as xs:string := "Presentation";

(:~
 : Short name of the calculation network.
 :)
declare variable $networks:CALCULATION_NETWORK as xs:string := "Calculation";

(:~
 : Short name of the essence-alias definition network.
 :)
declare variable $networks:ESSENCE_ALIAS_NETWORK as xs:string := "EssenceAlias";

(:~
 : Short name of the general-special definition network.
 :)
declare variable $networks:GENERAL_SPECIAL_NETWORK as xs:string := "GeneralSpecial";

(:~
 : Short name of the requires-element definition network.
 :)
declare variable $networks:REQUIRES_ELEMENT_NETWORK as xs:string := "RequiresElement";

(:~
 : Short name of the similar-tuples definition network.
 :)
declare variable $networks:SIMILAR_TUPLES_NETWORK as xs:string := "SimilarTuples";

(:~
 : Short name of the domain-member definition network.
 :)
declare variable $networks:DOMAIN_MEMBER_NETWORK as xs:string := "DomainMember";

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

 :
 : @param $components a sequence of components.
 :
 : @return all networks.
 :)
declare function networks:networks-for-components($components as item*) as object*
{
  $components.Networks[]
};

(:~
 :
Retrieves the standardized networks in the supplied components with the given short names.

 :
 : @param $components a sequence of components.
 : @param $short-name the short names of the networks.
 :
 : @return the standardized networks with these short names.
 :)
declare function networks:networks-for-components-and-short-names(
    $components as item*,
    $short-name as string*
) as object*
{
    $components.Networks[][$$.ShortName = $short-name]
};

(:~
:
Merges the supplied networks, grouping by arc/link name/role.

:
: @param $networks the input networks.
:
: @return the merged networks.
:)
declare function networks:merge($networks as object*) as object*
{
    for $networks in $networks
    group by
      $linkName := $networks.LinkName,
      $arcName := $networks.ArcName,
      $linkRole := $networks.LinkRole,
      $arcRole := $networks.ArcRole
    return {
        LinkName: $linkName,
        LinkRole: $linkRole,
        ArcName: $arcName,
        ArcRole: $arcRole,
        Kind: $networks[1].Kind,
        ShortName: $networks[1].ShortName,
        CyclesAllowed: $networks[1].CyclesAllowed,
        Trees: {|
          for $root in keys($networks.Trees)
          return {
              $root : {
                Name: $networks.Trees.$root[1].Name,
                Label: $networks.Trees.$root[1].Label,
                To: [ networks:merge-trees($networks.Trees.$root.To[]) ]
              }
          }
        |}
    }
};

declare %private function networks:merge-trees($trees as object*) as object*
{
    for $trees in $trees
    group by $name := $trees.Name
    let $order :=
        if($trees.PreferredLabelRole = "http://www.xbrl.org/2003/role/totalLabel")
        then 1000000
        else min($trees.Order)
    order by $order
    count $count
    return {|
        trim($trees[1], ("To", "Order")),
        {
            Order :$count
        },
        let $to := $trees.To[]
        where exists($to)
        return
        { To: [ networks:merge-trees($to) ] }
    |}
};

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

(:~
 :
This module provides functionality for handling networks.

 :
 :
A network is a graph of concepts and resources, of which the edges
 : share the same semantics.

 :
 :
There are several kinds of networks.
 : A network can be a tree or a DAG of concepts. It can be a bipartite
 : graph mapping concepts to resources.

 :
 :
XBRL defines a number of standard networks: presentation, calculation,
 : definition (essence-alias, general-special, requires-element, similar-tuples, domain-member),
 : and label networks.

 :
 :
With this module, you can retrieve the networks contained in a component, and you
 : can retrieve a standard XBRL network with the provided short names.

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

(:~
 : Short name of the presentation network.
 :)
declare variable $networks:PRESENTATION_NETWORK as xs:string := "Presentation";

(:~
 : Short name of the calculation network.
 :)
declare variable $networks:CALCULATION_NETWORK as xs:string := "Calculation";

(:~
 : Short name of the essence-alias definition network.
 :)
declare variable $networks:ESSENCE_ALIAS_NETWORK as xs:string := "EssenceAlias";

(:~
 : Short name of the general-special definition network.
 :)
declare variable $networks:GENERAL_SPECIAL_NETWORK as xs:string := "GeneralSpecial";

(:~
 : Short name of the requires-element definition network.
 :)
declare variable $networks:REQUIRES_ELEMENT_NETWORK as xs:string := "RequiresElement";

(:~
 : Short name of the similar-tuples definition network.
 :)
declare variable $networks:SIMILAR_TUPLES_NETWORK as xs:string := "SimilarTuples";

(:~
 : Short name of the domain-member definition network.
 :)
declare variable $networks:DOMAIN_MEMBER_NETWORK as xs:string := "DomainMember";

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

 :
 : @param $components a sequence of components.
 :
 : @return all networks.
 :)
declare function networks:networks-for-components($components as item*) as object*
{
  $components.Networks[]
};

(:~
 :
Retrieves the standardized networks in the supplied components with the given short names.

 :
 : @param $components a sequence of components.
 : @param $short-name the short names of the networks.
 :
 : @return the standardized networks with these short names.
 :)
declare function networks:networks-for-components-and-short-names(
    $components as item*,
    $short-name as string*
) as object*
{
    $components.Networks[][$$.ShortName = $short-name]
};

(:~
:
Merges the supplied networks, grouping by arc/link name/role.

:
: @param $networks the input networks.
:
: @return the merged networks.
:)
declare function networks:merge($networks as object*) as object*
{
    for $networks in $networks
    group by
      $linkName := $networks.LinkName,
      $arcName := $networks.ArcName,
      $linkRole := $networks.LinkRole,
      $arcRole := $networks.ArcRole
    return {
        LinkName: $linkName,
        LinkRole: $linkRole,
        ArcName: $arcName,
        ArcRole: $arcRole,
        Kind: $networks[1].Kind,
        ShortName: $networks[1].ShortName,
        CyclesAllowed: $networks[1].CyclesAllowed,
        Trees: {|
          for $root in keys($networks.Trees)
          return {
              $root : {
                Name: $networks.Trees.$root[1].Name,
                Label: $networks.Trees.$root[1].Label,
                To: [ networks:merge-trees($networks.Trees.$root.To[]) ]
              }
          }
        |}
    }
};

declare %private function networks:merge-trees($trees as object*) as object*
{
    for $trees in $trees
    group by $name := $trees.Name
    let $order := min($trees.Order)
    order by $order
    count $count
    return {|
        {
            Name: $name,
            Label: $trees[1].Label,
            Use: $trees[1].Use,
            Priority: $trees[1].Priority,
            Order :$count
        },
        let $to := $trees.To[]
        where exists($to)
        return
        { To: [ networks:merge-trees($to) ] }
    |}
};


:bug: A fix.

:bug: A fix.

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

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((
    hypercubes:hypercube-names-for-components($section),
    descendant-objects($section.Networks[]).Name
  ))
  where not $name = $metadata-names
  return $name
};

(:~
 :
Checks for duplicate metadata.

 :
 : @param $section a section.
 :
 : @return all report elements with duplicate metadata.
 :)
declare %private function sections:duplicate-metadata(
    $section as object
) as string*
{
  for $report-element in $section.Concepts[]
  group by $name := $report-element.Name
  where count($report-element) gt 1
  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" :  [ "Hypercube", "Dimension", "LineItems", "Member" ]
  }
  let $not-advised := {
    "Abstract" : [ ],
    "Hypercube" : [ ],
    "Dimension" : [ ],
    "Member" : [ ],
    "LineItems" : [ ],
    "Concept" : [ "Concept", "Abstract" ]
  }
  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 as string* := sections:missing-metadata($section)
  where exists($missing)
  return "[Error] Some metadata is missing (" || string-join($missing, ", ") || ")",
  let $duplicate as string* := sections:duplicate-metadata($section)
  where exists($duplicate)
  return "[Error] Duplicate metadata for report elements (" || string-join($duplicate, ", ") || ")",
  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";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";

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(
    hypercubes:hypercube-names-for-components($section),
    descendant-objects($section.Networks[]).Name
  )
  where not $name = $metadata-names
  return $name
};

(:~
 :
Checks for duplicate metadata.

 :
 : @param $section a section.
 :
 : @return all report elements with duplicate metadata.
 :)
declare %private function sections:duplicate-metadata(
    $section as object
) as string*
{
  for $report-element in $section.Concepts[]
  group by $name := $report-element.Name
  where count($report-element) gt 1
  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" :  [ "Hypercube", "Dimension", "LineItems", "Member" ]
  }
  let $not-advised := {
    "Abstract" : [ ],
    "Hypercube" : [ ],
    "Dimension" : [ ],
    "Member" : [ ],
    "LineItems" : [ ],
    "Concept" : [ "Concept", "Abstract" ]
  }
  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 as string* := sections:missing-metadata($section)
  where exists($missing)
  return "[Error] Some metadata is missing (" || string-join($missing, ", ") || ")",
  let $duplicate as string* := sections:duplicate-metadata($section)
  where exists($duplicate)
  return "[Error] Duplicate metadata for report elements (" || string-join($duplicate, ", ") || ")",
  sections:presentation-errors($section)
};


:lipstick: Improve csv/html display of nested arrays.

jsoniq version "1.0";

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

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

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

declare function api:json-to-csv($objects as object*) as string
{
  let $flattened as object* := api:flatten-json-object($objects, ())
  return string-join(
    csv:serialize($flattened, { field-names: [ keys($flattened) ], serialize-null-as : "" }),
    "")
  };

declare function api:json-to-xml($objects as object*, $root-name as string) as element()*
{
  for $flattened-object as object in api:flatten-json-object($objects, { KeepArray: true })
  return element { $root-name } {
    for $key in keys($flattened-object)
    let $value := $flattened-object.$key
    return typeswitch($value)
           case array return
             let $key-with-s := if(substring($key, string-length($key), 1) eq "s")
                                then $key
                                else $key || "s"
             let $key-no-s := if(substring($key, string-length($key), 1) eq "s")
                                then substring($key, 1, string-length($key) - 1)
                                else $key
             return element {$key-with-s} { for $v in $value[] return element { $key-no-s } { $v } }
           default return element { $key } { $value }
  }
};

declare %private function api:flatten-json-object(
  $items as item*,
  $options as object?) as item*
{
  for $item in $items
  return typeswitch($item)
         case atomic return $item
         case array return let $atomic-items := flatten($item)[$$ instance of atomic]
                           return if ($options.KeepArray)
                                 then [ $atomic-items ]
                                  else string-join($atomic-items, ",\r")
         case object return {|
             for $key in keys($item)
             return typeswitch($item.$key)
                    case object return api:flatten-json-object($item.$key, $options)
                    default return { $key: api:flatten-json-object($item.$key, $options) }
         |}
         default return ()
};

declare function api:validate-regexp($name as string, $value as string?, $regexp as string)
as ()
{
  if (exists($value))
  then
    if (matches($value, "^" || $regexp || "$"))
    then ();
    else fn:error(xs:QName("api:bad-parameter"), "Provided parameter " || $name || " with value " || $value || " does not match reg. expression " || $regexp || ".");
  else ();
};

declare function api:validate-enum($name as string, $value as string?, $enum as string*)
as ()
{
  if (exists($value))
  then
    if ($value = $enum)
    then ();
    else fn:error(xs:QName("api:bad-parameter"), "Provided parameter " || $name || " with value " || $value || " is not one of these: " || string-join($enum,", ") || ".");
  else ();
};

declare %private function api:sgpl($count as integer, $singular as string, $plural as string)
as string
{
    if ($count eq 1)
    then $count || " " || $singular
    else $count || " " || $plural
};

declare function api:passed($timestamp as xs:dateTime?)
as string
{
    if (empty($timestamp) or $timestamp eq null)
    then "Never"
    else
        let $now := current-dateTime()
        let $difference := $now - $timestamp
        let $years := years-from-duration($difference)
        let $month := months-from-duration($difference)
        let $days := days-from-duration($difference)
        let $hours := hours-from-duration($difference)
        let $minutes := minutes-from-duration($difference)
        return
            if ($years gt 0) then api:sgpl($years,"year ago","years ago")
            else if ($month gt 0) then api:sgpl($month,"month ago","month ago")
            else if ($days gt 0) then api:sgpl($days, "day ago", "days ago")
            else if ($hours gt 0) then api:sgpl($hours, "hour ago", "hours ago")
            else if ($minutes gt 0) then api:sgpl($minutes, "minute ago", "minutes ago")
            else if ($difference div xs:dayTimeDuration("PT1S") lt 0) then "future"
            else "Just Now"
};

declare function api:success() as object
{
  { "success" : true }
};

declare function api:success($data as object()) as object
{
  {|
     {"success" : true },
     $data
  |}
};

declare function api:check-and-return-results(
    $token as string?,
    $results as item*,
    $format as string?
) as item*
{
    switch(session:has-access($token, "data_sec"))
    case $session:ACCESS-ALLOWED return
        $results
    case $session:ACCESS-DENIED return {
          { status: 403 },
          session:error("access denied", $format)
       }
    case $session:ACCESS-AUTH-REQUIRED return {
          { status: 401 },
          session:error("authentication required or session expired", $format)
       }
    default return error()
};

declare function api:normalize-facts(
    $facts as object*) as object*
{
    for $fact in $facts
    return {|
        {
            "Aspects" : {|
                trim($fact.Aspects, ("xbrl:Unit")),
                { "sec:Archive" : $fact.Aspects."xbrl28:Archive" }[exists($fact.Aspects."xbrl28:Archive")]
            |}
        },
        trim($fact, ("Aspects", "_id")),
        { Unit: $fact.Aspects."xbrl:Unit" }[exists($fact.Aspects."xbrl:Unit") and not exists($fact.Unit)]
    |}
};

declare function api:serialize(
    $result as json-item,
    $comment as object,
    $serializers as object,
    $format as string?,
    $file-name as string) as item*
{
    switch ($format)
    case "xml" return {
        {
            serialization: { method: "xml", "omit-xml-declaration" : false, indent : true }
        },
        session:comment("xml", $comment),
        $serializers.to-xml($result)
    }
    case "text" case "csv" return {
        {
            "content-type": "text/csv",
            headers: { "Content-Disposition": "attachment; filename=" || $file-name || ".csv" }
        },
        $serializers.to-csv($result)
    }
    case "html" return {
        let $csv as string := $serializers.to-csv($result)
        return
        {
            { "content-type": "text/html" },
            api:csv-to-html($csv)
        }
    }
    case "excel" return {
        {
            "content-type": "application/vnd.ms-excel",
            headers: { "Content-Disposition": "attachment; filename=" || $file-name || ".csv" }
        },
        $serializers.to-csv($result)
    }
    default return {
        { serialization: { method: "json", indent : true } },
        {|
            session:comment("json", $comment),
            $result
        |}
    }
};

declare function api:csv-to-html(
    $csv as string) as item*
{
    let $csv as string* := $csv
    let $rows := tokenize($csv, "\n")
    let $header := $rows[1]
    let $body := subsequence($rows, 2)
    let $display-cell := function($row as string?, $cell-tag-name as string) {
      for tumbling window $cells as string* in tokenize($row, ",")
      start $start at $i when true
      only end $end at $j when not contains(replace($start, "\"\"", ""), "\"") or ($j gt $i and contains(replace($end, "\"\"", ""), "\""))
      let $cell := replace(string-join($cells, ","), "\"", "")
      return if(contains($cell, "http://") and (contains($cell, "28.io") or contains($cell, "rendering.secxbrl.info")))
      then element { $cell-tag-name } { Link }
      else element { $cell-tag-name } {tokenize($cell, "\r") ! ($$,
)}
    }
    return

        <title>Cell Store REST API</title>

{$display-cell($header, "th")}

          {
            for $row as string in $body
            return
{$display-cell($row, "td")}

          }

};

declare function api:preprocess-fiscal-years($fiscal-years as string*) as integer*
{
  distinct-values(
    for $fy in $fiscal-years ! upper-case($$)
    return switch($fy)
           case "LATEST" return $sec-fiscal:LATEST_FISCAL_YEAR
           case "ALL" return $sec-fiscal:ALL_FISCAL_YEARS
           default return if($fy castable as integer) then integer($fy) else ()
  )
};

declare function api:preprocess-fiscal-periods($fiscal-periods as string*) as string*
{
  distinct-values(
    for $fp in $fiscal-periods ! upper-case($$)
    return switch($fp)
           case "Q1"
           case "Q2"
           case "Q3"
           case "FY"
             return $fp
           case "ALL"
             return $sec-fiscal:ALL_FISCAL_PERIODS
           default
             return error(xs:QName("local:INVALID-PERIOD"),
               $fp || ": fiscalPeriod values must be one or more of Q1, Q2, Q3, FY, ALL")
  )
};

declare function api:preprocess-fiscal-period-types($fiscal-period-types as string*) as string*
{
  distinct-values(
    for $fpt in $fiscal-period-types
    return switch($fpt)
           case "instant"
           case "QTD"
           case "YTD"
             return $fpt
           case "ALL"
             return $sec-fiscal:ALL_FISCAL_PERIOD_TYPES
           default
             return error(xs:QName("local:INVALID-PERIOD-TYPE"),
               $fpt || ": fiscalPeriodType values must be one or more of instant, YTD, QTD")
  )
};

declare function api:preprocess-format($format as string?, $request-uri as string) as string?
{
  let $request-path := tokenize($request-uri, "\\?")[1]
  return lower-case(($format, substring-after($request-path, ".jq."))[1])
};

declare function api:preprocess-tags($tags as string*) as string*
{
  distinct-values($tags ! upper-case($$))
};

declare function api:preprocess-concept-kind($kind as string*) as string*
{
  distinct-values(
    for $kind in $kind
    return switch(lower-case($kind))
           case "hypercube" return "Hypercube"
           case "dimension" return "Dimension"
           case "member" return "Member"
           case "lineitems" return "LineItems"
           case "abstract" return "Abstract"
           case "concept" return "Concept"
           default return ()
  )
};

declare function api:preprocess-boolean($name as string, $value as string)
as boolean
{
  if ($value eq "")
  then true
  else
  {
      api:validate-enum($name, lower-case($value), ("true", "false"));
      boolean($value)
  }
};

declare function api:not-found($name as string) as item*
{
  { status: 404, content-type: "application/json" },
  session:error("No such " || $name || " could be found.", "json")
};

jsoniq version "1.0";

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

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

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

declare function api:json-to-csv($objects as object*) as string
{
  let $flattened as object* := api:flatten-json-object($objects, ())
  return string-join(
    csv:serialize($flattened, { field-names: [ keys($flattened) ], serialize-null-as : "" }),
    "")
  };

declare function api:json-to-xml($objects as object*, $root-name as string) as element()*
{
  for $flattened-object as object in api:flatten-json-object($objects, { KeepArray: true })
  return element { $root-name } {
    for $key in keys($flattened-object)
    let $value := $flattened-object.$key
    return typeswitch($value)
           case array return
             let $key-with-s := if(substring($key, string-length($key), 1) eq "s")
                                then $key
                                else $key || "s"
             let $key-no-s := if(substring($key, string-length($key), 1) eq "s")
                                then substring($key, 1, string-length($key) - 1)
                                else $key
             return element {$key-with-s} { for $v in $value[] return element { $key-no-s } { $v } }
           default return element { $key } { $value }
  }
};

declare %private function api:flatten-json-object(
  $items as item*,
  $options as object?) as item*
{
  for $item in $items
  return typeswitch($item)
         case atomic return $item
         case array return let $atomic-items := flatten($item)[$$ instance of atomic]
                           return if ($options.KeepArray)
                                 then [ $atomic-items ]
                                  else string-join($atomic-items, ", ")
         case object return {|
             for $key in keys($item)
             return typeswitch($item.$key)
                    case object return api:flatten-json-object($item.$key, $options)
                    default return { $key: api:flatten-json-object($item.$key, $options) }
         |}
         default return ()
};

declare function api:validate-regexp($name as string, $value as string?, $regexp as string)
as ()
{
  if (exists($value))
  then
    if (matches($value, "^" || $regexp || "$"))
    then ();
    else fn:error(xs:QName("api:bad-parameter"), "Provided parameter " || $name || " with value " || $value || " does not match reg. expression " || $regexp || ".");
  else ();
};

declare function api:validate-enum($name as string, $value as string?, $enum as string*)
as ()
{
  if (exists($value))
  then
    if ($value = $enum)
    then ();
    else fn:error(xs:QName("api:bad-parameter"), "Provided parameter " || $name || " with value " || $value || " is not one of these: " || string-join($enum,", ") || ".");
  else ();
};

declare %private function api:sgpl($count as integer, $singular as string, $plural as string)
as string
{
    if ($count eq 1)
    then $count || " " || $singular
    else $count || " " || $plural
};

declare function api:passed($timestamp as xs:dateTime?)
as string
{
    if (empty($timestamp) or $timestamp eq null)
    then "Never"
    else
        let $now := current-dateTime()
        let $difference := $now - $timestamp
        let $years := years-from-duration($difference)
        let $month := months-from-duration($difference)
        let $days := days-from-duration($difference)
        let $hours := hours-from-duration($difference)
        let $minutes := minutes-from-duration($difference)
        return
            if ($years gt 0) then api:sgpl($years,"year ago","years ago")
            else if ($month gt 0) then api:sgpl($month,"month ago","month ago")
            else if ($days gt 0) then api:sgpl($days, "day ago", "days ago")
            else if ($hours gt 0) then api:sgpl($hours, "hour ago", "hours ago")
            else if ($minutes gt 0) then api:sgpl($minutes, "minute ago", "minutes ago")
            else if ($difference div xs:dayTimeDuration("PT1S") lt 0) then "future"
            else "Just Now"
};

declare function api:success() as object
{
  { "success" : true }
};

declare function api:success($data as object()) as object
{
  {|
     {"success" : true },
     $data
  |}
};

declare function api:check-and-return-results(
    $token as string?,
    $results as item*,
    $format as string?
) as item*
{
    switch(session:has-access($token, "data_sec"))
    case $session:ACCESS-ALLOWED return
        $results
    case $session:ACCESS-DENIED return {
          { status: 403 },
          session:error("access denied", $format)
       }
    case $session:ACCESS-AUTH-REQUIRED return {
          { status: 401 },
          session:error("authentication required or session expired", $format)
       }
    default return error()
};

declare function api:normalize-facts(
    $facts as object*) as object*
{
    for $fact in $facts
    return {|
        {
            "Aspects" : {|
                trim($fact.Aspects, ("xbrl:Unit")),
                { "sec:Archive" : $fact.Aspects."xbrl28:Archive" }[exists($fact.Aspects."xbrl28:Archive")]
            |}
        },
        trim($fact, ("Aspects", "_id")),
        { Unit: $fact.Aspects."xbrl:Unit" }[exists($fact.Aspects."xbrl:Unit") and not exists($fact.Unit)]
    |}
};

declare function api:serialize(
    $result as json-item,
    $comment as object,
    $serializers as object,
    $format as string?,
    $file-name as string) as item*
{
    switch ($format)
    case "xml" return {
        {
            serialization: { method: "xml", "omit-xml-declaration" : false, indent : true }
        },
        session:comment("xml", $comment),
        $serializers.to-xml($result)
    }
    case "text" case "csv" return {
        {
            "content-type": "text/csv",
            headers: { "Content-Disposition": "attachment; filename=" || $file-name || ".csv" }
        },
        $serializers.to-csv($result)
    }
    case "html" return {
        let $csv as string := $serializers.to-csv($result)
        return
        {
            { "content-type": "text/html" },
            api:csv-to-html($csv)
        }
    }
    case "excel" return {
        {
            "content-type": "application/vnd.ms-excel",
            headers: { "Content-Disposition": "attachment; filename=" || $file-name || ".csv" }
        },
        $serializers.to-csv($result)
    }
    default return {
        { serialization: { method: "json", indent : true } },
        {|
            session:comment("json", $comment),
            $result
        |}
    }
};

declare function api:csv-to-html(
    $csv as string) as item*
{
    let $csv as string* := $csv
    let $rows := tokenize($csv, "\n")
    let $header := $rows[1]
    let $body := subsequence($rows, 2)
    let $display-cell := function($row as string?, $cell-tag-name as string) {
      for tumbling window $cells as string* in tokenize($row, ",")
      start $start at $i when true
      only end $end at $j when not contains(replace($start, "\"\"", ""), "\"") or ($j gt $i and contains(replace($end, "\"\"", ""), "\""))
      let $cell := replace(string-join($cells, ","), "\"", "")
      return if(contains($cell, "http://") and (contains($cell, "28.io") or contains($cell, "rendering.secxbrl.info")))
      then element { $cell-tag-name } { Link }
      else element { $cell-tag-name } {$cell}
    }
    return

        <title>Cell Store REST API</title>

{$display-cell($header, "th")}

          {
            for $row as string in $body
            return
{$display-cell($row, "td")}

          }

};

declare function api:preprocess-fiscal-years($fiscal-years as string*) as integer*
{
  distinct-values(
    for $fy in $fiscal-years ! upper-case($$)
    return switch($fy)
           case "LATEST" return $sec-fiscal:LATEST_FISCAL_YEAR
           case "ALL" return $sec-fiscal:ALL_FISCAL_YEARS
           default return if($fy castable as integer) then integer($fy) else ()
  )
};

declare function api:preprocess-fiscal-periods($fiscal-periods as string*) as string*
{
  distinct-values(
    for $fp in $fiscal-periods ! upper-case($$)
    return switch($fp)
           case "Q1"
           case "Q2"
           case "Q3"
           case "FY"
             return $fp
           case "ALL"
             return $sec-fiscal:ALL_FISCAL_PERIODS
           default
             return error(xs:QName("local:INVALID-PERIOD"),
               $fp || ": fiscalPeriod values must be one or more of Q1, Q2, Q3, FY, ALL")
  )
};

declare function api:preprocess-fiscal-period-types($fiscal-period-types as string*) as string*
{
  distinct-values(
    for $fpt in $fiscal-period-types
    return switch($fpt)
           case "instant"
           case "QTD"
           case "YTD"
             return $fpt
           case "ALL"
             return $sec-fiscal:ALL_FISCAL_PERIOD_TYPES
           default
             return error(xs:QName("local:INVALID-PERIOD-TYPE"),
               $fpt || ": fiscalPeriodType values must be one or more of instant, YTD, QTD")
  )
};

declare function api:preprocess-format($format as string?, $request-uri as string) as string?
{
  let $request-path := tokenize($request-uri, "\\?")[1]
  return lower-case(($format, substring-after($request-path, ".jq."))[1])
};

declare function api:preprocess-tags($tags as string*) as string*
{
  distinct-values($tags ! upper-case($$))
};

declare function api:preprocess-concept-kind($kind as string*) as string*
{
  distinct-values(
    for $kind in $kind
    return switch(lower-case($kind))
           case "hypercube" return "Hypercube"
           case "dimension" return "Dimension"
           case "member" return "Member"
           case "lineitems" return "LineItems"
           case "abstract" return "Abstract"
           case "concept" return "Concept"
           default return ()
  )
};

declare function api:preprocess-boolean($name as string, $value as string)
as boolean
{
  if ($value eq "")
  then true
  else
  {
      api:validate-enum($name, lower-case($value), ("true", "false"));
      boolean($value)
  }
};

declare function api:not-found($name as string) as item*
{
  { status: 404, content-type: "application/json" },
  session:error("No such " || $name || " could be found.", "json")
};


:bug: Fix multiple prefixes.

:bug: Fix multiple prefixes.

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
          where $p ne $prefix
          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 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
    )
};