Fix xqlint warnings

Fix xqlint warnings

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

if (is-available-collection($user:collection))
then ();
else create($user:collection);

if (is-available-collection($user:pictures))
then ();
else create($user:pictures);

if (is-available-collection($session:tokens))
then ();
else create($session:tokens);

if (is-available-collection($user:roles))
then truncate($user:roles);
else create($user:roles);

if (is-available-collection($user:rights))
then truncate($user:rights);
else create($user:rights);

if (is-available-collection("reportcache"))
then truncate("reportcache");
else create("reportcache");

if (is-available-collection("reportschemas"))
then ();
else create("reportschemas");

if (is-available-collection("reports"))
then ();
else create("reports");

if (is-available-collection("audit"))
then ();
else create("audit");

flush();

(: RIGHTS :)
user:new-right("rights", "Rights", (), 1);
user:new-right("settings", "Settings", "rights", 1);
user:new-right("users", "Users", "settings", 1);
user:new-right("roles", "Roles", "settings", 2);
user:new-right("reports", "Reports", "settings", 3);

(: RIGHTS: users :)
user:new-right("users_new", "Create New User", "users", 3);
user:new-right("users_list", "List All Users", "users", 2);
user:new-right("users_get", "View User", "users", 1);
user:new-right("users_get_self", "Get Profile", "users_get", 1);
user:new-right("users_edit", "Edit User", "users", 4);
user:new-right("users_edit_self", "Update Profile", "users_get_self", 1);
user:new-right("users_suspend", "Suspend User", "users_edit", 3);
user:new-right("users_activate", "Reactivate User", "users_edit", 2);
user:new-right("users_reset_password", "Reset Anybodys Password", "users", 4);
user:new-right("users_reset_password_self", "Reset Own Password", "users_get_self", 1);
user:new-right("users_upload_picture", "Upload User Picture", "users_edit", 4);
user:new-right("users_upload_picture_self", "Upload Profile Picture", "users_edit_self", 1);
user:new-right("users_remove_picture", "Remove User Picture", "users_edit", 4);
user:new-right("users_remove_picture_self", "Remove Profile Picture", "users_edit_self", 2);

(: RIGHTS: roles :)
user:new-right("users_assign_role","Assign Role to User","roles", 4);
user:new-right("users_close_assignment","Close Role Assignment of User","roles", 5);
user:new-right("roles_list","List All Roles and Rights","roles", 1);
user:new-right("roles_new","Create New Role","roles", 2);
user:new-right("roles_change_permissions","Change Permissions for Roles","roles", 3);

(: RIGHTS: reports :)
user:new-right("reports_get", "View Report", "reports", 1);
user:new-right("reports_validate", "Validate a Report", "reports", 2);
user:new-right("reports_edit", "Edit Report", "reports", 3);
user:new-right("reports_create", "Create a new Report", "reports_edit", 1);
user:new-right("reports_remove", "Remove Report", "reports_edit", 2);

(: RIGHTS: Data :)
user:new-right("data", "Data Access", (), 1);
user:new-right("data_sec", "SEC data access", "data", 1);
user:new-right("data_sec_dow30", "SEC data access to DOW30", "data_sec", 2);

(: ROLES :)
user:new-role("admin", "Administrator");
user:new-role("anybody", "All Users");
user:new-role("reports-admin", "Users Allowed to edit reports");
user:new-role("sec_pro", "Paid users (SEC Pro)");
user:new-role("sec_enterprise", "Paid users (SEC Enterprise)");

flush();

(: assign ROLES -> RIGHTS :)
user:allow("admin", "rights");
user:allow("admin", "data");
user:allow("anybody", "users_get_self");
user:allow("anybody", "users_reset_password_self");
user:allow("anybody", "users_upload_picture_self");
user:allow("anybody", "users_edit_self");
user:allow("anybody", "users_remove_picture_self");
user:allow("reports-admin", "reports");
user:allow("sec_pro", "data_sec");
user:allow("sec_enterprise", "data_sec");
user:allow("anybody", "data_sec_dow30");

(: Pro user :)
variable $user := user:get-by-email("support@28.io");
if(empty($user))
then {
    $user := user:new("support@28.io", "System", "Administrator", "foobar", {| |});
    user:assign-role($user, "sec_pro", (), (), $user);
  }
else ();

(: Admin user :)
variable $admin := user:get-by-email("admin@28.io");
if(empty($admin))
then {
    $admin := user:new("admin@28.io", "System", "Administrator", "12345", {| |});
    user:assign-role($admin, "admin", (), (), $admin);
  }
else ();

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

if (is-available-collection($user:collection))
then ();
else create($user:collection);

if (is-available-collection($user:pictures))
then ();
else create($user:pictures);

if (is-available-collection($session:tokens))
then ();
else create($session:tokens);

if (is-available-collection($user:roles))
then truncate($user:roles);
else create($user:roles);

if (is-available-collection($user:rights))
then truncate($user:rights);
else create($user:rights);

if (is-available-collection("reportcache"))
then truncate("reportcache");
else create("reportcache");

if (is-available-collection("reportschemas"))
then ();
else create("reportschemas");

if (is-available-collection("reports"))
then ();
else create("reports");

if (is-available-collection("audit"))
then ();
else create("audit");

flush();

(: RIGHTS :)
user:new-right("rights", "Rights", (), 1);
user:new-right("settings", "Settings", "rights", 1);
user:new-right("users", "Users", "settings", 1);
user:new-right("roles", "Roles", "settings", 2);
user:new-right("reports", "Reports", "settings", 3);

(: RIGHTS: users :)
user:new-right("users_new", "Create New User", "users", 3);
user:new-right("users_list", "List All Users", "users", 2);
user:new-right("users_get", "View User", "users", 1);
user:new-right("users_get_self", "Get Profile", "users_get", 1);
user:new-right("users_edit", "Edit User", "users", 4);
user:new-right("users_edit_self", "Update Profile", "users_get_self", 1);
user:new-right("users_suspend", "Suspend User", "users_edit", 3);
user:new-right("users_activate", "Reactivate User", "users_edit", 2);
user:new-right("users_reset_password", "Reset Anybodys Password", "users", 4);
user:new-right("users_reset_password_self", "Reset Own Password", "users_get_self", 1);
user:new-right("users_upload_picture", "Upload User Picture", "users_edit", 4);
user:new-right("users_upload_picture_self", "Upload Profile Picture", "users_edit_self", 1);
user:new-right("users_remove_picture", "Remove User Picture", "users_edit", 4);
user:new-right("users_remove_picture_self", "Remove Profile Picture", "users_edit_self", 2);

(: RIGHTS: roles :)
user:new-right("users_assign_role","Assign Role to User","roles", 4);
user:new-right("users_close_assignment","Close Role Assignment of User","roles", 5);
user:new-right("roles_list","List All Roles and Rights","roles", 1);
user:new-right("roles_new","Create New Role","roles", 2);
user:new-right("roles_change_permissions","Change Permissions for Roles","roles", 3);

(: RIGHTS: reports :)
user:new-right("reports_get", "View Report", "reports", 1);
user:new-right("reports_validate", "Validate a Report", "reports", 2);
user:new-right("reports_edit", "Edit Report", "reports", 3);
user:new-right("reports_create", "Create a new Report", "reports_edit", 1);
user:new-right("reports_remove", "Remove Report", "reports_edit", 2);

(: RIGHTS: Data :)
user:new-right("data", "Data Access", (), 1);
user:new-right("data_sec", "SEC data access", "data", 1);
user:new-right("data_sec_dow30", "SEC data access to DOW30", "data_sec", 2);

(: ROLES :)
user:new-role("admin", "Administrator");
user:new-role("anybody", "All Users");
user:new-role("reports-admin", "Users Allowed to edit reports");
user:new-role("sec_pro", "Paid users (SEC Pro)");
user:new-role("sec_enterprise", "Paid users (SEC Enterprise)");

flush();

(: assign ROLES -> RIGHTS :)
user:allow("admin", "rights");
user:allow("admin", "data");
user:allow("anybody", "users_get_self");
user:allow("anybody", "users_reset_password_self");
user:allow("anybody", "users_upload_picture_self");
user:allow("anybody", "users_edit_self");
user:allow("anybody", "users_remove_picture_self");
user:allow("reports-admin", "reports");
user:allow("sec_pro", "data_sec");
user:allow("sec_enterprise", "data_sec");
user:allow("anybody", "data_sec_dow30");

(: Pro user :)
variable $user := user:get-by-email("support@28.io");
if(empty($user))
then {
    $user := user:new("support@28.io", "System", "Administrator", "foobar", {| |});
    user:assign-role($admin, "sec_pro", (), (), $user);
  }
else ();

(: Admin user :)
variable $admin := user:get-by-email("admin@28.io");
if(empty($admin))
then {
    $admin := user:new("admin@28.io", "System", "Administrator", "12345", {| |});
    user:assign-role($admin, "admin", (), (), $admin);
  }
else ();


:lipstick: Fix typo.

:lipstick: Fix typo.

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

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

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

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

declare option rest:response "first-item";

declare function local:to-csv($res as object*) as string*
{
    csv:serialize(
        for $a in $res
        for $c in $a.Components[]
        return {
            AcessionNumber : $a.AccessionNumber,
            NetworkIdentifier : $c.NetworkIdentifier,
            FactTable: $c.FactTable,
            SpreadSheet: $c.SpreadSheet,
            EntityRegistrantName : $a.EntityRegistrantName,
            CIK : $a.CIK,
            FiscalYear : $a.FiscalYear,
            FiscalPeriod : $a.FiscalPeriod,
            AcceptanceDateTime : $a.AcceptanceDatetime,
            FormType : $a.FormType,
            NetworkLabel : $c.NetworkLabel,
            Category : $c.Category,
            SubCategory : $c.SubCategory,
            Table : flatten($c.Table),
            Disclosure : $c.Disclosure,
            ReportElements : $c.ReportElements,
            Tables : $c.Tables,
            Axis : $c.Axis,
            Members : $c.Members,
            LineItems : $c.LineItems,
            Concepts : $c.Concepts,
            Abstracts : $c.Abstracts
        },
    { serialize-null-as : "" })
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode   as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)
let $networkIdentifier := distinct-values(($networkIdentifier, $role))

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

let $entities as object*  := entities:entities($archives.Entity)
let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"]
    )

let $res as object* :=
    switch($profile-name)
    case "sec" return
        for $r in $components
        let $disclosure := sec-networks:disclosures($r)
        where $disclosure ne "DefaultComponent"
        order by $r.Label
        group by $archive := $r.Archive
        let $archive := $archives[archives:aid($$) eq $archive]
        let $e := $entities[entities:eid($$) eq $archive.Entity]
        return
            {
               AccessionNumber : archives:aid($archive),
               EntityRegistrantName : $e.Profiles.SEC.CompanyName,
               CIK : entities:eid($e),
               FiscalYear :$archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
               FiscalPeriod :$archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
               AcceptanceDatetime : sec-filings:acceptance-dateTimes($archive),
               FormType : $archive.Profiles.SEC.FormType,
               Components : [
                    for $component in sec-networks:summaries($r)
                    return copy $c := $component
                    modify insert json {
                        FactTable: backend:url("facttable-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            }),
                        SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                            backend:url("spreadsheet-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            })),
                        ReportElements: backend:url("report-elements", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                            }),
                        ValidationErrors: [ components:validation-errors($r) ]
                    } into $c
                    return $c
               ]
           }
    default return
        for $r in $components
        return {|
          {
            Archive: $r.Archive,
            Label: $r.Label,
            Role: $r.Role,
            FactTable: backend:url("facttable-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                            }),
            SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                        backend:url("spreadsheet-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name,
                            eliminate: "true"
                        })),
            ReportElements: backend:url("report-elements", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                        }),
            NumRules: size($r.Rules),
            NumNetworks: components:num-networks($r),
            NumReportElements: components:num-report-elements($r),
            NumHypercubes: components:num-hypercubes($r),
            NumDimensions: components:num-dimensions($r),
            NumMembers: components:num-members($r),
            NumLineItems: components:num-line-items($r),
            NumAbstracts: components:num-abstracts($r),
            NumConcepts: components:num-concepts($r),
            Hypercubes: [ keys($r.Hypercubes) ],
            ValidationErrors: [ components:validation-errors($r) ]
          },
          { Disclosure: $r.Profiles.FSA.Disclosure}[$profile-name eq "japan"]
        |}
let $result := switch($profile-name)
               case "sec"
               return { Archives: [ $res ] }
               default return { Components : [ $res ] }
let $comment :=
 {
    NumComponents : count($components),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : switch($profile-name)
        case "sec"
        return function($res as object) as node() {
        <Archives>{
                  for $r in flatten($res.Archives)
                  return
                      <Archive id="{$r.AccessionNumber}">
                         <EntityRegistrantName>{$r.EntityRegistrantName}</EntityRegistrantName>
                         <CIK>{$r.CIK}</CIK>
                         <FiscalYear>{$r.FiscalYear}</FiscalYear>
                         <FiscalPeriod>{$r.FiscalPeriod}</FiscalPeriod>
                         <AcceptanceDatetime>{$r.AcceptanceDatetime}</AcceptanceDatetime>
                         <FormType>{$r.FormType}</FormType>
                         <Components>{
                             sec-networks:summaries-to-xml(flatten($r.Components))
                         }</Components>
                     </Archive>
             }</Archives>
        }
        default return function($res as object) as node() {
        <Components>{
          api:json-to-xml($res.Components[], "Component")
        }</Components>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec" return string-join(local:to-csv($res.Archives[]), "")
        default return api:json-to-csv($res.Components[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "components")
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return api:check-and-return-results($token, $results, $format)

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

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

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

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

declare option rest:response "first-item";

declare function local:to-csv($res as object*) as string*
{
    csv:serialize(
        for $a in $res
        for $c in $a.Components[]
        return {
            AcessionNumber : $a.AccessionNumber,
            NetworkIdentifier : $c.NetworkIdentifier,
            FactTable: $c.FactTable,
            SpreadSheet: $c.SpreadSheet,
            EntityRegistrantName : $a.EntityRegistrantName,
            CIK : $a.CIK,
            FiscalYear : $a.FiscalYear,
            FiscalPeriod : $a.FiscalPeriod,
            AcceptanceDateTime : $a.AcceptanceDatetime,
            FormType : $a.FormType,
            NetworkLabel : $c.NetworkLabel,
            Category : $c.Category,
            SubCategory : $c.SubCategory,
            Table : flatten($c.Table),
            Disclosure : $c.Disclosure,
            ReportElements : $c.ReportElements,
            Tables : $c.Tables,
            Axis : $c.Axis,
            Members : $c.Members,
            LineItems : $c.LineItems,
            Concepts : $c.Concepts,
            Abstracts : $c.Abstracts
        },
    { serialize-null-as : "" })
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode   as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)
let $networkIdentifier := distinct-values(($networkIdentifier, $role))

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

let $entities as object*  := entities:entities($archives.Entity)
let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"]
    )

let $res as object* :=
    switch($profile-name)
    case "sec" return
        for $r in $components
        let $disclosure := sec-networks:disclosures($r)
        where $disclosure ne "DefaultComponent"
        order by $r.Label
        group by $archive := $r.Archive
        let $archive := $archives[archives:aid($$) eq $archive]
        let $e := $entities[entities:eid($$) eq $archive.Entity]
        return
            {
               AccessionNumber : archives:aid($archive),
               EntityRegistrantName : $e.Profiles.SEC.CompanyName,
               CIK : entities:eid($e),
               FiscalYear :$archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
               FiscalPeriod :$archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
               AcceptanceDatetime : sec-filings:acceptance-dateTimes($archive),
               FormType : $archive.Profiles.SEC.FormType,
               Components : [
                    for $component in sec-networks:summaries($r)
                    return copy $c := $component
                    modify insert json {
                        FactTable: backend:url("facttable-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            }),
                        SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                            backend:url("spreadsheet-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            }),
                        ReportElements: backend:url("report-elements", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                            }),
                        ValidationErrors: [ components:validation-errors($r) ]
                    } into $c
                    return $c
               ]
           }
    default return
        for $r in $components
        return {|
          {
            Archive: $r.Archive,
            Label: $r.Label,
            Role: $r.Role,
            FactTable: backend:url("facttable-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                            }),
            SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                        backend:url("spreadsheet-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name,
                            eliminate: "true"
                        })),
            ReportElements: backend:url("report-elements", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                        }),
            NumRules: size($r.Rules),
            NumNetworks: components:num-networks($r),
            NumReportElements: components:num-report-elements($r),
            NumHypercubes: components:num-hypercubes($r),
            NumDimensions: components:num-dimensions($r),
            NumMembers: components:num-members($r),
            NumLineItems: components:num-line-items($r),
            NumAbstracts: components:num-abstracts($r),
            NumConcepts: components:num-concepts($r),
            Hypercubes: [ keys($r.Hypercubes) ],
            ValidationErrors: [ components:validation-errors($r) ]
          },
          { Disclosure: $r.Profiles.FSA.Disclosure}[$profile-name eq "japan"]
        |}
let $result := switch($profile-name)
               case "sec"
               return { Archives: [ $res ] }
               default return { Components : [ $res ] }
let $comment :=
 {
    NumComponents : count($components),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : switch($profile-name)
        case "sec"
        return function($res as object) as node() {
        <Archives>{
                  for $r in flatten($res.Archives)
                  return
                      <Archive id="{$r.AccessionNumber}">
                         <EntityRegistrantName>{$r.EntityRegistrantName}</EntityRegistrantName>
                         <CIK>{$r.CIK}</CIK>
                         <FiscalYear>{$r.FiscalYear}</FiscalYear>
                         <FiscalPeriod>{$r.FiscalPeriod}</FiscalPeriod>
                         <AcceptanceDatetime>{$r.AcceptanceDatetime}</AcceptanceDatetime>
                         <FormType>{$r.FormType}</FormType>
                         <Components>{
                             sec-networks:summaries-to-xml(flatten($r.Components))
                         }</Components>
                     </Archive>
             }</Archives>
        }
        default return function($res as object) as node() {
        <Components>{
          api:json-to-xml($res.Components[], "Component")
        }</Components>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec" return string-join(local:to-csv($res.Archives[]), "")
        default return api:json-to-csv($res.Components[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "components")
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return api:check-and-return-results($token, $results, $format)


Improve readability.

Improve readability.

#!/usr/bin/env jq
# This is a collection of filters for `jq`, intended to manipulate JSON objects
# into JSON representing a website index, for further processing in a template.

# Convert "truthy" value to actual boolean.
def bool:
    (. == {} or . == [] or . == false or . == null or . == 0) | not
;

# Group an array of objects into an object of arrays, such that the key-value
# pairs of the new object are an accumulation of those of the old object. For
# example, `[{"a":1, "b":2}, {"a":3}]` turns into `{"a":[1, 3], "b":[3]}`. This
# can be used to partition an array.
# Try: `[1,2,3] | map({(.%2|tostring):.}) | group`
def group:
    reduce ([.[] | to_entries[]] | group_by(.key))[] as $group
    ( {}
    ; .[ $group[0].key ] |= (. // []) + [ $group[].value ]
    )
;

# Wrap an object in other objects so that the original object exists at a
# particular "path". Like `{} | setpath(["a","b"], …)`, but instead of making
# objects like `{"a":{"b":…}}`, this makes objects like `{"name":"a",
# "contents":[{"name":"b",…}]}`.
def tree($path):
    def tree_aux($future):
        { "name": $future[0] } +
        if ($future | length) > 1 then
            { "contents": [ tree_aux($future[1:]) ] }
        else
            .
        end
    ;
    tree_aux($path)
;

# Merge two values. If the values are both objects, the values at equal indices
# will be recursively merged; if the values are both arrays, the arrays are
# concatenated; if the values are some other type, we only check if the values
# do not conflict.
def merge(other):
    if (. | type) == "object" and (other | type) == "object" then
        (. | to_entries) + (other | to_entries)
        | group_by(.key)
        | map({ "key": (.[0].key), "value" : (reduce .[1:][].value as $x (.[0].value; merge($x)))})
        | from_entries
    elif (. | type) == "array" and (other | type) == "array" then
        . + other
    elif . == other then
        .
    else
        error("trying to merge incompatible objects " + (.|type) + " and " + (other|type))
    end
;

# Merge an array of content by grouping it by name and then merging all the
# pages with the same name. This is necessary because simply merging content
# will only concatenate pages, even if they are the same page.
def merge_content:
    reduce .[] as $x ({}; merge($x)) |
    if has("contents") then
        .contents |= ( group_by(.name) | map(merge_content) )
    else
        .
    end
;

# Add paths to each object, that is, the names of the ancestors.
def add_paths:
    def add_path_aux($history):
        ($history + [.name]) as $present |
        .path = ($present | join("/")) |
        (.contents[]? |= add_path_aux($present))
    ;
    add_path_aux([])
;

# Any page gets a link to its HTML.
def add_links:
    if (has("path") and (.path | endswith(".md"))) then
        .link = (.path | rtrimstr(".md") | . + ".html")
    else
        .contents[]? |= add_links
    end
;

# Front matter is the page to be associated with the enclosing directory rather
# than itself.
def add_frontmatter:
    if has("contents") then
        ( .contents | map({(.name=="index.md" or .meta.frontmatter|tostring):.}) | group) as {$true, $false}
        | .frontmatter = $true[0]
        | .contents = ($false + $true[1:])
        | (.contents[] |= add_frontmatter)
    else
        .
    end
;

# If marked as such, drafts can be excluded from being uploaded and included in
# the table of contents. To not count as a draft, a document should either be
# marked as `publish`, or be *not* marked as `draft` and have children that
# *are* marked as `publish`.
def add_drafts:
    def is_publication:
        ( .publish or .meta.publish | bool)
        or
        ( (.meta.draft | bool | not) and ((.contents // []) | any(is_publication)) )
    ;
    if has("contents") then
        ( .contents | map({(is_publication|tostring):.}) | group) as {$true, $false}
        | .contents = ($true // [])
        | .drafts = ($false // [])
        | (.contents[] |= add_drafts)
    else
        .
    end
;

# A resource is anything that is neither a directory nor a page with metadata
# nor a forced link.
def add_resources:
    if has("contents") then
        ( .contents | map({(has("contents") or has("meta") or has("publish")|tostring):.}) | group) as {$true, $false}
        | .contents = ($true // [])
        | .resources = ($false // [])
        | (.contents[]? |= add_resources)
    else
        .
    end;

# Combines the given stream of JSON objects by merging them, and performs the
# given operations to turn it into a proper index.
def index:
    merge_content
    | add_paths
    | add_links
    | add_drafts
    | add_frontmatter
    | add_resources
;

#!/usr/bin/env jq
# This is a collection of filters for `jq`, intended to manipulate JSON objects
# into JSON representing a website index, for further processing in a template.

# Convert "truthy" value to actual boolean.
def bool:
    (. == {} or . == [] or . == false or . == null or . == 0) | not
;

# Group an array of objects into an object of arrays, such that the key-value
# pairs of the new object are an accumulation of those of the old object. For
# example, `[{"a":1, "b":2}, {"a":3}]` turns into `{"a":[1, 3], "b":[3]}`. This
# can be used to partition an array.
# Try: `[1,2,3] | map({(.%2|tostring):.}) | group`
def group:
    reduce ([.[] | to_entries[]] | group_by(.key))[] as $group
    ( {}
    ; .[ $group[0].key ] |= (. // []) + [ $group[].value ]
    )
;

# Wrap an object in other objects so that the original object exists at a
# particular "path". Like `{} | setpath(["a","b"], …)`, but instead of making
# objects like `{"a":{"b":…}}`, this makes objects like `{"name":"a",
# "contents":[{"name":"b",…}]}`.
def tree($path):
    def tree_aux($future):
        { "name": $future[0] } +
        if ($future | length) > 1 then
            { "contents": [ tree_aux($future[1:]) ] }
        else
            .
        end
    ;
    tree_aux($path)
;

# Add paths to each object, that is, the names of the ancestors.
def add_paths:
    def add_path_aux($history):
        ($history + [.name]) as $present |
        .path = ($present | join("/")) |
        (.contents[]? |= add_path_aux($present))
    ;
    add_path_aux([])
;

# Merge two values. If the values are both objects, the values at equal indices
# will be recursively merged; if the values are both arrays, the arrays are
# concatenated; if the values are some other type, we only check if the values
# do not conflict.
def merge(other):
    if (. | type) == "object" and (other | type) == "object" then
        (. | to_entries) + (other | to_entries)
        | group_by(.key)
        | map({ "key": (.[0].key), "value" : (reduce .[1:][].value as $x (.[0].value; merge($x)))})
        | from_entries
    elif (. | type) == "array" and (other | type) == "array" then
        . + other
    elif . == other then
        .
    else
        error("trying to merge incompatible objects " + (.|type) + " and " + (other|type))
    end
;

# Determine whether a page should be uploaded and included in the table of
# contents: it should either be marked as `publish`, or be *not* marked as
# `draft` and have children that *are* marked as `publish`.
def is_publication:
    ( .publish or .meta.publish | bool)
    or
    ( (.meta.draft | bool | not) and ((.contents // []) | any(is_publication)) )
;

# Merge an array of content by grouping it by name and then merging all the
# pages with the same name. This is necessary because simply merging content
# will only concatenate pages, even if they are the same page.
def merge_content:
    reduce .[] as $x ({}; merge($x)) |
    if has("contents") then
        .contents |= ( group_by(.name) | map(merge_content) )
    else
        .
    end
;

# Any page gets a link to its HTML.
def add_links:
    if (has("path") and (.path | endswith(".md"))) then
        .link = (.path | rtrimstr(".md") | . + ".html")
    else
        .contents[]? |= add_links
    end
;

# Front matter is the page to be associated with the enclosing directory rather
# than itself.
def add_frontmatter:
    if has("contents") then
        ( .contents | map({(.name=="index.md" or .meta.frontmatter|tostring):.}) | group) as {$true, $false}
        | .frontmatter = $true[0]
        | .contents = ($false + $true[1:])
        | (.contents[] |= add_frontmatter)
    else
        .
    end
;

# If marked as such, drafts can be excluded from being uploaded and included
# in the table of contents.
def add_drafts:
    if has("contents") then
        ( .contents | map({(is_publication|tostring):.}) | group) as {$true, $false}
        | .contents = ($true // [])
        | .drafts = ($false // [])
        | (.contents[] |= add_drafts)
    else
        .
    end
;

# A resource is anything that is neither a directory nor a page with metadata
# nor a forced link.
def add_resources:
    if has("contents") then
        ( .contents | map({(has("contents") or has("meta") or has("publish")|tostring):.}) | group) as {$true, $false}
        | .contents = ($true // [])
        | .resources = ($false // [])
        | (.contents[]? |= add_resources)
    else
        .
    end;

# Combines the given stream of JSON objects by merging them, and performs the
# given operations to turn it into a proper index.
def index:
    merge_content
    | add_paths
    | add_links
    | add_drafts
    | add_frontmatter
    | add_resources
;


:lipstick: Improve formatting.

:lipstick: Improve formatting.

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 html =
    "http://www.zorba-xquery.com/modules/converters/html";
import module namespace csv = "http://zorba.io/modules/json-csv";
import module namespace request = "http://www.28msec.com/modules/http-request";

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 :=
      let $newline-tokens :=
        for tumbling window $token as string* in tokenize($csv, "\"")
        start when true
        end $u at $j next $v when ($j mod 2 eq 1 and $u ne "")
                             or ($j mod 2 eq 0 and $v ne "")
        count $k
        return if($k mod 2 eq 1)
               then $token ! replace($$, "\n", "______28newline______")
               else "\"" || string-join($token, "\"") || "\""
      return tokenize(string-join($newline-tokens), "______28newline______")
    let $header := $rows[1]
    let $body := subsequence($rows, 2)
    let $display-cell := function($row as string?, $cell-tag-name as string) {
      for $cell in
        let $comma-tokens :=
          for tumbling window $token as string* in tokenize($row, "\"")
          start when true
          end $u at $j next $v when ($j mod 2 eq 1 and $u ne "")
                               or ($j mod 2 eq 0 and $v ne "")
          count $k
          return if($k mod 2 eq 1)
                 then $token ! replace($$, ",", "______28comma______")
                 else string-join($token, "\"")
        return tokenize(string-join($comma-tokens), "______28comma______")
      return switch(true)
             case contains($cell, "http://") and
                  (contains($cell, request:server-name()) or
                   contains($cell, "rendering.secxbrl.info"))
             return element { $cell-tag-name } { Link }
             case starts-with(replace($cell, "(\r|\n)", ""), "<")
             return element { $cell-tag-name } {html:parse($cell)/*}
             default return 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")
};


make article accumulation work with older jq

# list of known targets
def targets: ["html", "latex", "pdf", "stats"];
# extensions of the result files for each target
def target_extensions: {
    "html": [".html"],
    "stats": [".stats.json"],
    "latex": [".tex"]
};

# list of targets which do not have book dependencies
def no_dep_targets: ["pdf"];

def make_substitutions: {
    " ": "_",
    ":": "@COLON@",
    "(": "@LBR@",
    ")": "@RBR@",
    "/": "@SLASH@",
    "'": "@SQUOTE@",
    "\"": "@DQUOTE@",
    "*": "@STAR@",
    "=": "@EQ@",
    "$": "@DOLLAR@",
    "#": "@SHARP@",
    "%": "@PERC@"
};

# escape / unescape file paths for make
def escape_make:
    reduce (make_substitutions | keys | .[]) as $item (.; . = (. | gsub("[" + $item + "]";make_substitutions[$item])));
def unescape_make:
    reduce (make_substitutions | to_entries | .[]) as $item (.; . = (. | gsub($item.value;$item.key)));

# remove excluded chapters for $subtarget and empty parts from the sitemap.
def exclude_chapters:
    del(.parts[] | .chapters[] | select([.markers.exclude.subtargets[] | .name==$subtarget and .parameters==[]] | any == true)) |
    del(.parts[] | select(.chapters == []));

# generate a makefile specifying the dependencies for a book
# from the sutarget map
def generate_book_deps:
    # check if the given target is known
    if (targets | index($target) == null) then
        error("unknown target: " + $target)
    else . end |
    # output empty dependencies for no-dependency targets
    if (no_dep_targets | index($target) != null) then empty else . end |
    # produce target-independent dependencies
    (.parts[] | .chapters[] | $prefix + (.path | escape_make) + "/" + .revision) |
    $book_dep_target + ": " + . + ".section-dep",
    $book_dep_target + ": " + . + ".media-dep",
    $book_anchors_target + ": " + . + ".anchors",
    "include " + . + ".section-dep",
    "-include " + . + ".media-dep",
    $book_dep_target + ": " + . + (target_extensions[$target][] | . );

# extract article markers from the sitemap for an article,
# substituting the subtarget name by "current".
def article_markers:
    .parts[] | .chapters[] | select(.path==($article | unescape_make)) | .markers
    | del(.exclude.subtargets[] | select(.name != $subtarget))
    | del(.include.subtargets[] | select(.name != $subtarget))
	| (.exclude.subtargets[] | .name) |= "current"
	| (.include.subtargets[] | .name) |= "current";

# replace "latest" in chapter revision with the current revision,
# revisions are given by $revisions
def fill_sitemap_revisions:
    (.parts[] | .chapters[] | select(.revision=="latest"))
        |= (.revision=$revisions.articles[(.path | gsub(" ";"_"))]);

# extract some stats from the linter output of an article.
def lint_stats:
    reduce .[] as $item ({"kind": {}, "severity": {}, "lints_total": 0};
        (.kind[$item.kind] += 1)
        | (.severity[$item.severity] += 1)
        | (.lints_total += 1)
    );

def walk(f):
    . as $in
    | if type == "object" then
        reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
    elif type == "array" then map( walk(f) ) | f
    else f
    end;

def addmerge(o):
    reduce (o | paths(type != "object" and type != "string")) as $i (.;setpath($i; getpath($i) + (o | getpath($i))));

# list of known targets
def targets: ["html", "latex", "pdf", "stats"];
# extensions of the result files for each target
def target_extensions: {
    "html": [".html"],
    "stats": [".stats.json"],
    "latex": [".tex"]
};

# list of targets which do not have book dependencies
def no_dep_targets: ["pdf"];

def make_substitutions: {
    " ": "_",
    ":": "@COLON@",
    "(": "@LBR@",
    ")": "@RBR@",
    "/": "@SLASH@",
    "'": "@SQUOTE@",
    "\"": "@DQUOTE@",
    "*": "@STAR@",
    "=": "@EQ@",
    "$": "@DOLLAR@",
    "#": "@SHARP@",
    "%": "@PERC@"
};

# escape / unescape file paths for make
def escape_make:
    reduce (make_substitutions | keys | .[]) as $item (.; . = (. | gsub("[" + $item + "]";make_substitutions[$item])));
def unescape_make:
    reduce (make_substitutions | to_entries | .[]) as $item (.; . = (. | gsub($item.value;$item.key)));

# remove excluded chapters for $subtarget and empty parts from the sitemap.
def exclude_chapters:
    del(.parts[] | .chapters[] | select([.markers.exclude.subtargets[] | .name==$subtarget and .parameters==[]] | any == true)) |
    del(.parts[] | select(.chapters == []));

# generate a makefile specifying the dependencies for a book
# from the sutarget map
def generate_book_deps:
    # check if the given target is known
    if (targets | index($target) == null) then
        error("unknown target: " + $target)
    else . end |
    # output empty dependencies for no-dependency targets
    if (no_dep_targets | index($target) != null) then empty else . end |
    # produce target-independent dependencies
    (.parts[] | .chapters[] | $prefix + (.path | escape_make) + "/" + .revision) |
    $book_dep_target + ": " + . + ".section-dep",
    $book_dep_target + ": " + . + ".media-dep",
    $book_anchors_target + ": " + . + ".anchors",
    "include " + . + ".section-dep",
    "-include " + . + ".media-dep",
    $book_dep_target + ": " + . + (target_extensions[$target][] | . );

# extract article markers from the sitemap for an article,
# substituting the subtarget name by "current".
def article_markers:
    .parts[] | .chapters[] | select(.path==($article | unescape_make)) | .markers
    | del(.exclude.subtargets[] | select(.name != $subtarget))
    | del(.include.subtargets[] | select(.name != $subtarget))
	| (.exclude.subtargets[] | .name) |= "current"
	| (.include.subtargets[] | .name) |= "current";

# replace "latest" in chapter revision with the current revision,
# revisions are given by $revisions
def fill_sitemap_revisions:
    (.parts[] | .chapters[] | select(.revision=="latest"))
        |= (.revision=$revisions.articles[(.path | gsub(" ";"_"))]);

# extract some stats from the linter output of an article.
def lint_stats:
    reduce .[] as $item ({"kind": {}, "severity": {}, "lints_total": 0};
        (.kind[$item.kind] += 1)
        | (.severity[$item.severity] += 1)
        | (.lints_total += 1)
    );

def walk(f):
    . as $in
    | if type == "object" then
        reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
    elif type == "array" then map( walk(f) ) | f
    else f
    end;

def addmerge(o):
    reduce (o | paths(type != "object" and type != "string")) as $path (.;
        getpath($path) += (o | getpath($path)));