:bug: fix deep equal array comparison

:bug: fix deep equal array comparison

jsoniq version "1.0";
module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace config = "http://apps.28.io/config";
import module namespace credentials = "http://www.28msec.com/modules/credentials";
import module namespace functx = "http://www.functx.com";
import module namespace backend = "http://apps.28.io/backend";

declare function test:is-dow30() as boolean{
  contains(credentials:credentials("MongoDB", "xbrl").db, "dow30")
};

declare function test:is-japan-big() as boolean{
  starts-with(credentials:credentials("MongoDB", "xbrl").db, "japanxbrl-big")
};

declare function test:url($endpoint as string, $parameters as object) as string
{
    test:url($endpoint, $parameters, false)
};

declare function test:url($endpoint as string, $parameters as object, $includeToken as boolean) as string
{
  backend:url(
    $endpoint,
    {|
      { token: if($includeToken) then $config:test-token else "{{token}}" },
      $parameters
    |}
  )
};

declare %an:nondeterministic function test:invoke($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-xml($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-xml($response.body.content))
};

declare %an:sequential function test:invoke-body($endpoint as string, $parameters as object, $body as string) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:post($url, $body, "application/x-www-form-urlencoded")
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-raw($endpoint as string, $parameters as object) as object
{
  let $url as string:= test:url($endpoint, $parameters, true)
  return http-client:get($url)
};

declare %an:nondeterministic function test:invoke-public($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:get-expected-result(
  $expected-file as string
) as item*
{
  parse-json(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:get-expected-result-xml(
  $expected-file as string
) as item*
{
  parse-xml(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*
) as item
{
  test:invoke-and-assert-deep-equal($endpoint, $parameters, $transform, $expected, {})
};

declare %an:nondeterministic function test:invoke-and-assert-status(
  $endpoint as string,
  $parameters as object,
  $expected-status as integer
) as item
{
  let $actual-status := test:invoke($endpoint, $parameters)[1]
  return if($actual-status eq $expected-status)
         then true
         else {
           url: test:url($endpoint, $parameters),
           expected: $expected-status,
           actual: $actual-status
         }
};

(:
  Note on NoArrayOrder option: it allows for false positives in case arrays have duplicate elements. The number
  of occurrences of duplicate elements is not checked.
:)
declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*,
  $options as object?
) as item
{
  let $request := switch($options.Format)
                  case "xml" return test:invoke-xml($endpoint, $parameters)
                  default return test:invoke($endpoint, $parameters)
  let $status as integer := $request[1]
  let $actual as item* := $transform($request[2])
  let $expected as item* := if($options.TrimIdField) then test:trim-ids($expected) else $expected
  let $actual as item* := if($options.TrimIdField) then test:trim-ids($actual) else $actual
  return if($options.NoArrayOrder)
          then test:assert-deep-equal-no-array-order($expected, $actual, $status, test:url($endpoint, $parameters))
          else test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $parameters))
};

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

declare %private function test:trim-ids($input as item*) as item*
{
  for $i in $input
  return typeswitch($i)
          case object return {|
                                 for $key in keys($i)
                                 where $key ne "_id"
                                 return {$key: test:trim-ids($i.$key) }
                               |}
          case array return [ test:trim-ids($i[]) ]
          default return $i
};

(:    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]":)

declare function test:assert-eq(
    $expected as atomic,
    $actual as atomic?,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case try {$expected eq $actual} catch * { false } return true
    default return
    {
        "url": $url,
        "expected": $expected,
        "actual": $actual
    }
};

declare function test:assert-deep-equal(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case functx:sequence-deep-equal($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:assert-deep-equal-no-array-order(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case test:deep-equal-no-array-order($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:deep-equal-no-array-order(
    $expected as item*,
    $actual as item*) as boolean
{
    if(count($expected) ne count($actual))
    then false
    else
      every $i in (1 to count($expected))
      satisfies
        let $expected := $expected[$i]
        let $actual := $actual[$i]
        return
        typeswitch(($expected, $actual))
        case object* return
          switch(true)
          case not (every $key in keys($expected) satisfies keys($actual) = $key)
            return false
          case not (every $key in keys($actual) satisfies keys($expected) = $key)
            return false
          default return
            let $recursive-calls :=
                for $key in keys($expected)
                return test:deep-equal-no-array-order($expected.$key, $actual.$key)
            return if (every $item in $recursive-calls satisfies $item)
                    then true
                    else false
        case array* return
          switch(true)
          case not size($expected) eq size($actual)
            return false
          case not (every $expected-item in $expected[] satisfies (
            some $actual-item in $actual[] satisfies test:deep-equal-no-array-order($expected-item, $actual-item)
          ))
            return false
          case not (every $actual-item in $actual[] satisfies (
            some $expected-item in $expected[] satisfies test:deep-equal-no-array-order($expected-item, $actual-item)
          ))
            return false
          default return true
        default return deep-equal($expected, $actual)
};

declare function test:assert-eq-array(
    $expected as array,
    $actual as array,
    $status as integer,
    $url as string) as item
{
    let $diff := try {
            {
                actual: [
                    for $a in flatten($actual)
                    where not($a = flatten($expected))
                    return $a ],
                actualDuplicates: [
                    for $a in flatten($actual)
                    where $a = flatten($expected) and count(flatten($actual)[$$ eq $a]) ne count(flatten($expected)[$$ eq $a])
                    return $a ],
                expected: [
                    for $e in flatten($expected)
                    where not($e = flatten($actual))
                    return $e ]
            }
        } catch * { "error: " || $err:description }

    return
        switch(true)
        case $status ne 200 return { "url": $url, status: $status }
        case empty(($diff.actual[],$diff.actualDuplicates[],$diff.expected[])) return true
        default return
        {
            "url": $url,
            "diff": $diff,
            "expected": $expected,
            "actual": $actual
        }
};

jsoniq version "1.0";
module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace config = "http://apps.28.io/config";
import module namespace credentials = "http://www.28msec.com/modules/credentials";
import module namespace functx = "http://www.functx.com";
import module namespace backend = "http://apps.28.io/backend";

declare function test:is-dow30() as boolean{
  contains(credentials:credentials("MongoDB", "xbrl").db, "dow30")
};

declare function test:is-japan-big() as boolean{
  starts-with(credentials:credentials("MongoDB", "xbrl").db, "japanxbrl-big")
};

declare function test:url($endpoint as string, $parameters as object) as string
{
    test:url($endpoint, $parameters, false)
};

declare function test:url($endpoint as string, $parameters as object, $includeToken as boolean) as string
{
  backend:url(
    $endpoint,
    {|
      { token: if($includeToken) then $config:test-token else "{{token}}" },
      $parameters
    |}
  )
};

declare %an:nondeterministic function test:invoke($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-xml($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-xml($response.body.content))
};

declare %an:sequential function test:invoke-body($endpoint as string, $parameters as object, $body as string) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:post($url, $body, "application/x-www-form-urlencoded")
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-raw($endpoint as string, $parameters as object) as object
{
  let $url as string:= test:url($endpoint, $parameters, true)
  return http-client:get($url)
};

declare %an:nondeterministic function test:invoke-public($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:get-expected-result(
  $expected-file as string
) as item*
{
  parse-json(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:get-expected-result-xml(
  $expected-file as string
) as item*
{
  parse-xml(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*
) as item
{
  test:invoke-and-assert-deep-equal($endpoint, $parameters, $transform, $expected, {})
};

declare %an:nondeterministic function test:invoke-and-assert-status(
  $endpoint as string,
  $parameters as object,
  $expected-status as integer
) as item
{
  let $actual-status := test:invoke($endpoint, $parameters)[1]
  return if($actual-status eq $expected-status)
         then true
         else {
           url: test:url($endpoint, $parameters),
           expected: $expected-status,
           actual: $actual-status
         }
};

(:
  Note on NoArrayOrder option: it allows for false positives in case arrays have duplicate elements. The number
  of occurrences of duplicate elements is not checked.
:)
declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*,
  $options as object?
) as item
{
  let $request := switch($options.Format)
                  case "xml" return test:invoke-xml($endpoint, $parameters)
                  default return test:invoke($endpoint, $parameters)
  let $status as integer := $request[1]
  let $actual as item* := $transform($request[2])
  let $expected as item* := if($options.TrimIdField) then test:trim-ids($expected) else $expected
  let $actual as item* := if($options.TrimIdField) then test:trim-ids($actual) else $actual
  return if($options.NoArrayOrder)
          then test:assert-deep-equal-no-array-order($expected, $actual, $status, test:url($endpoint, $parameters))
          else test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $parameters))
};

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

declare %private function test:trim-ids($input as item*) as item*
{
  for $i in $input
  return typeswitch($i)
          case object return {|
                                 for $key in keys($i)
                                 where $key ne "_id"
                                 return {$key: test:trim-ids($i.$key) }
                               |}
          case array return [ test:trim-ids($i[]) ]
          default return $i
};

(:    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]":)

declare function test:assert-eq(
    $expected as atomic,
    $actual as atomic?,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case try {$expected eq $actual} catch * { false } return true
    default return
    {
        "url": $url,
        "expected": $expected,
        "actual": $actual
    }
};

declare function test:assert-deep-equal(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case functx:sequence-deep-equal($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:assert-deep-equal-no-array-order(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case test:deep-equal-no-array-order($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:deep-equal-no-array-order(
    $expected as item*,
    $actual as item*) as boolean
{
    if(count($expected) ne count($actual))
    then false
    else
      every $i in count($expected)
      satisfies
        let $expected := $expected[$i]
        let $actual := $actual[$i]
        return
        typeswitch(($expected, $actual))
        case object* return
          switch(true)
          case not (every $key in keys($expected) satisfies keys($actual) = $key)
            return false
          case not (every $key in keys($actual) satisfies keys($expected) = $key)
            return false
          default return
            let $recursive-calls :=
                for $key in keys($expected)
                return test:deep-equal-no-array-order($expected.$key, $actual.$key)
            return if (every $item in $recursive-calls satisfies $item)
                    then true
                    else false
        case array* return
          switch(true)
          case not size($expected) eq size($actual)
            return false
          case not (every $expected-item in $expected[] satisfies (
            some $actual-item in $actual[] satisfies test:deep-equal-no-array-order($expected-item, $actual-item)
          ))
            return false
          case not (every $actual-item in $actual[] satisfies (
            some $expected-item in $expected[] satisfies test:deep-equal-no-array-order($expected-item, $actual-item)
          ))
            return false
          default return true
        default return deep-equal($expected, $actual)
};

declare function test:assert-eq-array(
    $expected as array,
    $actual as array,
    $status as integer,
    $url as string) as item
{
    let $diff := try {
            {
                actual: [
                    for $a in flatten($actual)
                    where not($a = flatten($expected))
                    return $a ],
                actualDuplicates: [
                    for $a in flatten($actual)
                    where $a = flatten($expected) and count(flatten($actual)[$$ eq $a]) ne count(flatten($expected)[$$ eq $a])
                    return $a ],
                expected: [
                    for $e in flatten($expected)
                    where not($e = flatten($actual))
                    return $e ]
            }
        } catch * { "error: " || $err:description }

    return
        switch(true)
        case $status ne 200 return { "url": $url, status: $status }
        case empty(($diff.actual[],$diff.actualDuplicates[],$diff.expected[])) return true
        default return
        {
            "url": $url,
            "diff": $diff,
            "expected": $expected,
            "actual": $actual
        }
};


Separate drafts from actual contents.

Separate drafts from actual contents.

This uncovered an obvious issue with the partitioning method: if
everything in the array is in the same group, then the "final" partition
is in fact the "first" partition. Should be fixed later.

#!/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.

# Select the descendant described by the array of names in `$path`.
def descend($path):
    if ($path | length) > 0
    then
        .contents[]? | select(.name == $path[0]) | descend($path[1:])
    else
        .
    end
;

# Insert a child element at a particular path.
# Like `setpath/2`, but instead of making objects like `{"a":{"b":{…}}}`, this
# makes objects like `{"contents":[{"name":"a", "contents":[{"name":"b",…}]}]}`
# I don't really like this function, because it's so verbose. Making a path
# expression for updating with the |= operator (see `descend/1`) doesn't seem
# to work when it is recursive?
def insert($path; $child):
    if ($path | length) > 0
    then
        if ([.contents[]? | select(.name == $path[0])] | length) > 0
        then
            (.contents[] | select(.name == $path[0])) |= (. | insert($path[1:]; $child))
        else
            (.contents = [.contents[]?] + [{"name":$path[0]} | insert($path[1:];$child)])
        end
    else
        . * $child
    end
;

# Merge together `filetree.json` and `*.meta.json` files, with behaviour
# depending on the filename of the object. Use with the `--null-input` switch.
def process_files:
    reduce inputs as $input
        (   {}
        ;   ($input | input_filename) as $f |
            if ($f | endswith(".meta.json")) then
                insert
                ( $f | ltrimstr($prefix) | rtrimstr(".meta.json") | split("/")
                ; {"meta": $input} )
            else
                reduce $input[] as $entry
                ( .
                ;   insert
                    ( $entry.path | split("/")
                    ; $entry )
                )
            end
        )
;

# Adds links to each page object. The link should be the same as the path,
# except in the case of directories, which may either link to its index.md, to
# a page with meta.frontmatter==true, or have no link at all. If there is only
# one child to a page, perhaps it should collapse onto its parent.
# Also not great.
def add_links:
    if (.path and (.path | endswith(".md")))
    then
        .link = (.path | rtrimstr(".md") | . + ".html")
    else
        ([ .contents[]? | add_links ] | group_by(.name=="index.md")) as $partition
        |   ($partition[0] // []) as $contentfiles
        |   ($partition[1] // []) as $indexfiles
        |   if ($indexfiles + $contentfiles | length) == 0 then
                .
            elif ($indexfiles | length) == 0 then
                .contents = $contentfiles
            else
                $indexfiles[0] as $index
                | .contents = $contentfiles + $indexfiles[1:]
                | .link = $index.link
            end
    end
;

# TODO: ISSUE if the partition is only true, then [0] will have true, not false

# A draft should not show up in the table of contents.
def take_drafts:
    if .contents then
        (   [ .contents[]? | take_drafts ] | group_by(.meta.draft or false)) as $partition
        |   .contents = ($partition[0] // [])
        |   .drafts = ($partition[1] // [])
    else
        .
    end
;

# Resources are images and other things that should not be part of the table of
# contents - anything that is neither a directory nor has metadata.
def take_resources:
    "wip"
;

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

#!/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.

# Select the descendant described by the array of names in `$path`.
def descend($path):
    if ($path | length) > 0
    then
        .contents[]? | select(.name == $path[0]) | descend($path[1:])
    else
        .
    end
;

# Insert a child element at a particular path.
# Like `setpath/2`, but instead of making objects like `{"a":{"b":{…}}}`, this
# makes objects like `{"contents":[{"name":"a", "contents":[{"name":"b",…}]}]}`
# I don't really like this function, because it's so verbose. Making a path
# expression for updating with the |= operator (see `descend/1`) doesn't seem
# to work when it is recursive?
def insert($path; $child):
    if ($path | length) > 0
    then
        if ([.contents[]? | select(.name == $path[0])] | length) > 0
        then
            (.contents[] | select(.name == $path[0])) |= (. | insert($path[1:]; $child))
        else
            (.contents = [.contents[]?] + [{"name":$path[0]} | insert($path[1:];$child)])
        end
    else
        . * $child
    end
;

# Merge together `filetree.json` and `*.meta.json` files, with behaviour
# depending on the filename of the object. Use with the `--null-input` switch.
def process_files:
    reduce inputs as $input
        (   {}
        ;   ($input | input_filename) as $f |
            if ($f | endswith(".meta.json")) then
                insert
                ( $f | ltrimstr($prefix) | rtrimstr(".meta.json") | split("/")
                ; {"meta": $input} )
            else
                reduce $input[] as $entry
                ( .
                ;   insert
                    ( $entry.path | split("/")
                    ; $entry )
                )
            end
        )
;

# Adds links to each page object. The link should be the same as the path,
# except in the case of directories, which may either link to its index.md, to
# a page with meta.frontmatter==true, or have no link at all. If there is only
# one child to a page, perhaps it should collapse onto its parent.
# Also not great.
def add_links:
    if (.path and (.path | endswith(".md")))
    then
        .link = (.path | rtrimstr(".md") | . + ".html")
    else
        ([ .contents[]? | add_links ] | group_by(.name=="index.md")) as $partition
        |   ($partition[0] // []) as $contentfiles
        |   ($partition[1] // []) as $indexfiles
        |   if ($indexfiles + $contentfiles | length) == 0 then
                .
            elif ($indexfiles | length) == 0 then
                .contents = $contentfiles
            else
                $indexfiles[0] as $index
                | .contents = $contentfiles + $indexfiles[1:]
                | .link = $index.link
            end
    end
;

# A draft should not show up in the table of contents.
def take_drafts:
    "wip"
;

# Resources are images and other things that should not be part of the table of
# contents - anything that is neither a directory nor has metadata.
def take_resources:
    "wip"
;

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


Remove descend/1.

Remove descend/1.

#!/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 the values of properties of an array of objects. For example, [{"a":1,
# "b":2}, {"a":3}] turns into {"a":[1, 3], "b":[3]}. This can be used to
# partition an array; try, for example:
# [1,2,3,4] | map({(if .%2==0 then "even" else "odd" end):.}) | group
def group:
    reduce ([.[] | to_entries[]] | group_by(.key))[] as $group
    ( {}
    ; .[ $group[0].key ] |= (. // []) + [ $group[].value ]
    )
;

# Insert a child element at a particular path.
# Like `setpath/2`, but instead of making objects like `{"a":{"b":{…}}}`, this
# makes objects like `{"contents":[{"name":"a", "contents":[{"name":"b",…}]}]}`
def insert($path; $child):
    if ($path | length) > 0 then
        if ([.contents[]? | select(.name == $path[0])] | length) > 0 then
            (.contents[] | select(.name == $path[0])) |= (. | insert($path[1:]; $child))
        else
            (.contents |= (. // []) + [ {} | .name = $path[0] | insert($path[1:]; $child)])
        end
    else
        . * $child
    end
;

# Merge together `filetree.json` and `*.meta.json` files, with behaviour
# depending on the filename of the object. Use with the `--null-input` switch.
def process_files:
    reduce inputs as $input
        (   {}
        ;   ($input | input_filename) as $f |
            if ($f | endswith(".meta.json")) then
                insert
                ( $f | ltrimstr($prefix) | rtrimstr(".meta.json") | split("/")
                ; {"meta": $input} )
            else
                reduce $input[] as $entry
                ( .
                ;   insert
                    ( $entry.path | split("/")
                    ; $entry )
                )
            end
        )
;

def classify_frontmatter:
    [ .[] | { ( if .name == "index.md" or .meta.frontmatter then "frontmatter" else "contents" end ) : . }]
    | group
    | .contents |= (. // [])
    | .frontmatter |= (. // [])
;

def classify_draft:
    [.[] | { (if .meta.draft then "drafts" else "contents" end ) : . }]
    | group
    | .contents |= (. // [])
    | .drafts |= (. // [])
;

def classify_resource:
    [ .[] | { ( if (.meta | bool) or (.contents | bool) then "contents" else "resources" end ) : . }]
    | group
    | .contents |= (. // [])
    | .resources |= (. // [])
;

def add_links:
    if (.path and (.path | endswith(".md")))
    then
        .link = (.path | rtrimstr(".md") | . + ".html")
    else
        .contents[]? |= add_links
    end
;

def add_frontmatter:
    if .contents | bool then
        . + (.contents | classify_frontmatter | (.contents[]? |= add_frontmatter) )
    else
        .
    end
;

def add_drafts:
    if .contents | bool then
        . + (.contents | classify_draft | (.contents[]? |= add_drafts))
    else
        .
    end
;

# Resources are images and other things that should not be part of the table of
# contents - anything that is neither a directory nor has metadata.
def add_resources:
    if .contents | bool then
        . + (.contents | classify_resource | (.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:
    process_files
    | add_links
    | add_frontmatter
    | add_drafts
    | 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 the values of properties of an array of objects. For example, [{"a":1,
# "b":2}, {"a":3}] turns into {"a":[1, 3], "b":[3]}. This can be used to
# partition an array; try, for example:
# [1,2,3,4] | map({(if .%2==0 then "even" else "odd" end):.}) | group
def group:
    reduce ([.[] | to_entries[]] | group_by(.key))[] as $group
    ( {}
    ; .[ $group[0].key ] |= (. // []) + [ $group[].value ]
    )
;

# Select the descendant described by the array of names in `$path`.
def descend($path):
    if ($path | length) > 0
    then
        .contents[]? | select(.name == $path[0]) | descend($path[1:])
    else
        .
    end
;

# Insert a child element at a particular path.
# Like `setpath/2`, but instead of making objects like `{"a":{"b":{…}}}`, this
# makes objects like `{"contents":[{"name":"a", "contents":[{"name":"b",…}]}]}`
# I don't really like this function, because it's so verbose. Making a path
# expression for updating with the |= operator (see `descend/1`) doesn't seem
# to work when it is recursive?
def insert($path; $child):
    if ($path | length) > 0
    then
        if ([.contents[]? | select(.name == $path[0])] | length) > 0
        then
            (.contents[] | select(.name == $path[0])) |= (. | insert($path[1:]; $child))
        else
            (.contents = [.contents[]?] + [{"name":$path[0]} | insert($path[1:];$child)])
        end
    else
        . * $child
    end
;

# Merge together `filetree.json` and `*.meta.json` files, with behaviour
# depending on the filename of the object. Use with the `--null-input` switch.
def process_files:
    reduce inputs as $input
        (   {}
        ;   ($input | input_filename) as $f |
            if ($f | endswith(".meta.json")) then
                insert
                ( $f | ltrimstr($prefix) | rtrimstr(".meta.json") | split("/")
                ; {"meta": $input} )
            else
                reduce $input[] as $entry
                ( .
                ;   insert
                    ( $entry.path | split("/")
                    ; $entry )
                )
            end
        )
;

def classify_frontmatter:
    [ .[] | { ( if .name == "index.md" or .meta.frontmatter then "frontmatter" else "contents" end ) : . }]
    | group
    | .contents |= (. // [])
    | .frontmatter |= (. // [])
;

def classify_draft:
    [.[] | { (if .meta.draft then "drafts" else "contents" end ) : . }]
    | group
    | .contents |= (. // [])
    | .drafts |= (. // [])
;

def classify_resource:
    [ .[] | { ( if (.meta | bool) or (.contents | bool) then "contents" else "resources" end ) : . }]
    | group
    | .contents |= (. // [])
    | .resources |= (. // [])
;

def add_links:
    if (.path and (.path | endswith(".md")))
    then
        .link = (.path | rtrimstr(".md") | . + ".html")
    else
        .contents[]? |= add_links
    end
;

def add_frontmatter:
    if .contents | bool then
        . + (.contents | classify_frontmatter | (.contents[]? |= add_frontmatter) )
    else
        .
    end
;

def add_drafts:
    if .contents | bool then
        . + (.contents | classify_draft | (.contents[]? |= add_drafts))
    else
        .
    end
;

# Resources are images and other things that should not be part of the table of
# contents - anything that is neither a directory nor has metadata.
def add_resources:
    if .contents | bool then
        . + (.contents | classify_resource | (.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:
    process_files
    | add_links
    | add_frontmatter
    | add_drafts
    | add_resources
;


Sort pages in table of contents.

Sort pages in table of contents.

#!/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
;

# Sort content first according to sort order in metadata.
def sort_content:
    if has("contents") then
        .contents |= (sort_by(.meta.sort // .meta.title // .name) | map(sort_content))
    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
    | sort_content
;

#!/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
;


:green_heart: Fix warning.

:green_heart: Fix warning.

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

declare variable $local:expected as object :=
    {
      "byconcept" : [ "1001000 - Statement - Consolidated Statement of Income", "1005000 - Statement - Consolidated Statement of Equity", "2404402 - Disclosure - Summarized Financial Data - Chevron U.S.A. Inc. (Details)", "2405402 - Disclosure - Summarized Financial Data - Chevron Transport Corporation (Details)", "2410402 - Disclosure - Operating Segments and Geographic Data (Details)", "2427402 - Disclosure - Earnings Per Share (Details)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF INCOME", "1005000 - Statement - CONSOLIDATED STATEMENTS OF SHAREOWNERS' EQUITY", "1002000 - Statement - CONSOLIDATED STATEMENTS OF OPERATIONS", "1004000 - Statement - CONSOLIDATED STATEMENTS OF CHANGES IN EQUITY", "2436402 - Disclosure - Basic and Diluted Earnings Per Share (Detail)", "1001000 - Statement - Consolidated Income Statements", "1001000 - Statement - Consolidated Statements Of Income (Audited)", "1002000 - Statement - Consolidated Statements Of Comprehensive Income (Audited)", "2415402 - Disclosure - Quarterly Financial Data (Unaudited) (Quarterly Financial Data) (Details)", "000100 - Statement - Consolidated Statement Of Income", "041101 - Disclosure - Earnings Per Share (Details)", "041702 - Disclosure - Disclosures About Segments And Related Information (Schedule Of Segments And Related Information) (Details)", "1003 - Statement - Consolidated Statements of Income", "1008 - Statement - Consolidated Statements of Changes in Equity", "1118 - Disclosure - Schedule of Quarterly Financial Information (Detail)", "1001000 - Statement - Consolidated Statements of Income", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Cash Flows", "1005000 - Statement - Consolidated Statements of Stockholders' Equity", "1003 - Statement - Income Statements", "1004 - Statement - Comprehensive Income Statements", "1008 - Statement - Cash Flows Statements", "1009 - Statement - Stockholders' Equity Statements", "1055 - Disclosure - Basic and Diluted Earnings Per Share (Detail)", "1121 - Disclosure - Quarterly Information (Unaudited) (Detail)", "1002000 - Statement - Consolidated Statements of Income", "1003000 - Statement - Consolidated Statements of Comprehensive Income", "1005000 - Statement - Consolidated Statements of Changes in Stockholders' Equity", "1006000 - Statement - Consolidated Statements of Cash Flows", "2429403 - Disclosure - Earnings Per Share (Details)", "2438402 - Disclosure - International Operations (Details)", "2440402 - Disclosure - Parent Company - Statements of Income (Details)", "2440404 - Disclosure - Parent Company - Statements of Cash Flows (Details2)", "1001000 - Statement - Consolidated Statements of Income", "1002000 - Statement - Consolidated Statements Of Earnings", "1003000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Stockholders' Equity", "1005000 - Statement - Consolidated Statements Of Cash Flows", "2415402 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Operations", "1004000 - Statement - Consolidated Statement of Cash Flows", "2404402 - Disclosure - Earnings Per Share (Details)", "2410403 - Disclosure - Equity (Changes in Noncontrolling Interests) (Details)", "2420404 - Disclosure - Segment Financial Data (Selected Quarterly Financial Data) (Details)", "2421402 - Disclosure - Selected Quarterly Financial Data - Unaudited (Details)", "1002000 - Statement - Consolidated Statements of Earnings", "1004000 - Statement - Consolidated Statements of Equity", "2419402 - Disclosure - Selected Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Income", "1002000 - Statement - Consolidated Statement of Comprehensive Income", "1004000 - Statement - Consolidated Statement of Equity", "2420402 - Disclosure - Earnings Per Share - Calculations of Earnings Per Share (Detail)", "1001000 - Statement - Consolidated Statements Of Operations", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements Of Cash Flows", "2401402 - Disclosure - Summary Of Business Segment Data (Details)", "2405403 - Disclosure - Earnings Per Share (Schedule Of Weighted-Average Number Of Shares Outstanding Used To Compute Earnings Per Share) (Details)", "2424403 - Disclosure - Quarterly Financial Data (Schedule Of Quarterly Financial Data) (Details)", "1003000 - Statement - Consolidated Statements of Operations", "1003000 - Statement - Consolidated Statements of Operations", "1005000 - Statement - Consolidated Statements of Changes in Shareholders' Equity", "2429403 - Disclosure - Quarterly Financial Data Quarterly Financial Data (Unaudited) (Details)", "2430405 - Disclosure - Schedule I Condensed Income Statement (Details)", "000100 - Statement - Consolidated Statements Of Income", "000400 - Statement - Consolidated Statements Of Changes In Stockholders' Equity", "041801 - Disclosure - Quarterly Financial Information (Unaudited) (Quarterly Financial Results) (Details)", "0110 - Statement - Consolidated Statements of Earnings", "0120 - Statement - Consolidated Statements of Comprehensive Income", "0140 - Statement - Consolidated Statements of Changes in Shareholders' Equity", "0150 - Statement - Consolidated Statements of Cash Flows", "0625 - Disclosure - Business Segments (Details)", "0630 - Disclosure - Parent Company (Details)", "06302 - Disclosure - Parent Company (Details 2)", "100020 - Statement - Statement of Earnings", "100030 - Statement - Statement of Comprehensive Income", "100040 - Statement - Statement of Changes in Shareowners' Equity", "100060 - Statement - Statement of Cash Flows", "401500 - Disclosure - Shareowners' Equity (Details)", "402800 - Disclosure - Quarterly Information (unaudited) (Details)", "1001000 - Statement - Consolidated Statements Of Income", "1001000 - Statement - Consolidated Statements Of Income", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Cash Flows", "1005000 - Statement - Consolidated Statements of Shareholders' Equity", "1003000 - Statement - Consolidated Statements Of Operations", "1004000 - Statement - Consolidated Statements of Comprehensive Income", "1005000 - Statement - Consolidated Statements Of Cash Flows", "1006000 - Statement - Consolidated Statements Of Equity", "2420402 - Disclosure - Net Income per Share (Calculation Of Basic And Diluted Net Income Per Share) (Detail)", "010020 - Statement - Consolidated Statements of Income", "010035 - Statement - Consolidated Statements of Comprehensive Income", "010060 - Statement - Consolidated Statements of Cash Flows", "010080 - Statement - Consolidated Statements of Shareholders' Equity", "042600 - Disclosure - Parent Company (Details)", "042620 - Disclosure - Parent Company (Details 2)", "042700 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Income", "1002000 - Statement - Consolidated Statement of Comprehensive Income", "1004000 - Statement - Consolidated Statement of Cash Flows", "1005000 - Statement - Consolidated Statement of Shareholders Equity", "2414402 - Disclosure - Quarterly Results (Detail)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF INCOME", "2475402 - Disclosure - Quarterly Financial Summary (Detail)", "000011 - Statement - Consolidated Statement of Income", "004010 - Disclosure - Significant Accounting Policies (Details)", "004170 - Disclosure - Quarterly Data (Unaudited) (Details)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF EARNINGS", "2418402 - Disclosure - EARNINGS PER SHARE (DETAILS)", "2447402 - Disclosure - QUARTERLY RESULTS (UNAUDITED) (DETAILS)" ]
    };

declare %an:nondeterministic function local:test-acceptance-date($expected as string, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as string? := $request[2].Archives[].AcceptanceDatetime
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status, test:url($endpoint, $params))
};

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

declare %an:sequential function local:test-components-result($expected as array, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Archives[].Components[].NetworkLabel ]
    let $status as integer := $request[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

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

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

local:check({
    cocacola: local:test-components(94, {
        ticker:"ko",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
    }),
    cocacolaincome: local:test-components(1, {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
    }),
    byconcept: local:test-components-result($local:expected.byconcept, {
        tag:"DOW30",
        concept:"us-gaap:NetIncomeLoss",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
    }),
    byfyfp: local:test-components(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    byfyfplowercase: local:test-components(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"q2",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    acceptance: local:test-acceptance-date("2014-07-30T15:44:58Z", {
        aid:"0000021344-14-000029"
    }),
    generic: local:test-generic-components(1, {
      profile-name: "generic",
      aid: "0000021344-14-000008",
      role: "http://www.thecocacolacompany.com/role/DocumentAndEntityInformationDocument"
    }),
    generic-aid: local:test-generic-components(95, {
      profile-name: "generic",
      aid: "0000021344-14-000008"
    }),
    example1: test:invoke-and-assert-deep-equal(
      "components",
      {aid:"0000021344-13-000039"},
      function($b as item*) as item* { [ trim($b.Archives[].Components[], ("FactTable", "SpreadSheet", "ReportElements", "ModelStructure", "ValidationErrors")) ] },
      test:get-expected-result("secxbrl/components-expected1.jq"),
      { NoArrayOrder: true }
    ),
    example2: test:invoke-and-assert-deep-equal(
      "components",
      {
        ticker:"ko",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* {
        [ trim(
          $b.Archives[].Components[],
          ("FactTable", "SpreadSheet", "ReportElements", "ModelStructure", "ValidationErrors")
        ) ]
      },
      test:get-expected-result("secxbrl/components-expected2.jq"),
      { NoArrayOrder: true }
    ),
    url: test:invoke-and-assert-deep-equal(
      "components",
      {aid:"0000021344-13-000039"},
      function($b as item*) as item* {
        project(
          $b.Archives[].Components[]
            [$$.NetworkIdentifier = "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation"],
          ("FactTable", "SpreadSheet", "ReportElements", "ModelStructure")
        )
      },
      {
        "FactTable" : backend:url("facttable-for-component", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec"
            }),
        "SpreadSheet" : backend:url("spreadsheet-for-component", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec",
            eliminate: "true"
            }),
        "ReportElements" :backend:url("report-elements", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec"
            }),
        "ModelStructure" : backend:url("modelstructure-for-component", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "json",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec"
            })
      },
      { NoArrayOrder: true }
    )
})

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

declare variable $local:expected as object :=
    {
      "byconcept" : [ "1001000 - Statement - Consolidated Statement of Income", "1005000 - Statement - Consolidated Statement of Equity", "2404402 - Disclosure - Summarized Financial Data - Chevron U.S.A. Inc. (Details)", "2405402 - Disclosure - Summarized Financial Data - Chevron Transport Corporation (Details)", "2410402 - Disclosure - Operating Segments and Geographic Data (Details)", "2427402 - Disclosure - Earnings Per Share (Details)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF INCOME", "1005000 - Statement - CONSOLIDATED STATEMENTS OF SHAREOWNERS' EQUITY", "1002000 - Statement - CONSOLIDATED STATEMENTS OF OPERATIONS", "1004000 - Statement - CONSOLIDATED STATEMENTS OF CHANGES IN EQUITY", "2436402 - Disclosure - Basic and Diluted Earnings Per Share (Detail)", "1001000 - Statement - Consolidated Income Statements", "1001000 - Statement - Consolidated Statements Of Income (Audited)", "1002000 - Statement - Consolidated Statements Of Comprehensive Income (Audited)", "2415402 - Disclosure - Quarterly Financial Data (Unaudited) (Quarterly Financial Data) (Details)", "000100 - Statement - Consolidated Statement Of Income", "041101 - Disclosure - Earnings Per Share (Details)", "041702 - Disclosure - Disclosures About Segments And Related Information (Schedule Of Segments And Related Information) (Details)", "1003 - Statement - Consolidated Statements of Income", "1008 - Statement - Consolidated Statements of Changes in Equity", "1118 - Disclosure - Schedule of Quarterly Financial Information (Detail)", "1001000 - Statement - Consolidated Statements of Income", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Cash Flows", "1005000 - Statement - Consolidated Statements of Stockholders' Equity", "1003 - Statement - Income Statements", "1004 - Statement - Comprehensive Income Statements", "1008 - Statement - Cash Flows Statements", "1009 - Statement - Stockholders' Equity Statements", "1055 - Disclosure - Basic and Diluted Earnings Per Share (Detail)", "1121 - Disclosure - Quarterly Information (Unaudited) (Detail)", "1002000 - Statement - Consolidated Statements of Income", "1003000 - Statement - Consolidated Statements of Comprehensive Income", "1005000 - Statement - Consolidated Statements of Changes in Stockholders' Equity", "1006000 - Statement - Consolidated Statements of Cash Flows", "2429403 - Disclosure - Earnings Per Share (Details)", "2438402 - Disclosure - International Operations (Details)", "2440402 - Disclosure - Parent Company - Statements of Income (Details)", "2440404 - Disclosure - Parent Company - Statements of Cash Flows (Details2)", "1001000 - Statement - Consolidated Statements of Income", "1002000 - Statement - Consolidated Statements Of Earnings", "1003000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Stockholders' Equity", "1005000 - Statement - Consolidated Statements Of Cash Flows", "2415402 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Operations", "1004000 - Statement - Consolidated Statement of Cash Flows", "2404402 - Disclosure - Earnings Per Share (Details)", "2410403 - Disclosure - Equity (Changes in Noncontrolling Interests) (Details)", "2420404 - Disclosure - Segment Financial Data (Selected Quarterly Financial Data) (Details)", "2421402 - Disclosure - Selected Quarterly Financial Data - Unaudited (Details)", "1002000 - Statement - Consolidated Statements of Earnings", "1004000 - Statement - Consolidated Statements of Equity", "2419402 - Disclosure - Selected Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Income", "1002000 - Statement - Consolidated Statement of Comprehensive Income", "1004000 - Statement - Consolidated Statement of Equity", "2420402 - Disclosure - Earnings Per Share - Calculations of Earnings Per Share (Detail)", "1001000 - Statement - Consolidated Statements Of Operations", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements Of Cash Flows", "2401402 - Disclosure - Summary Of Business Segment Data (Details)", "2405403 - Disclosure - Earnings Per Share (Schedule Of Weighted-Average Number Of Shares Outstanding Used To Compute Earnings Per Share) (Details)", "2424403 - Disclosure - Quarterly Financial Data (Schedule Of Quarterly Financial Data) (Details)", "1003000 - Statement - Consolidated Statements of Operations", "1003000 - Statement - Consolidated Statements of Operations", "1005000 - Statement - Consolidated Statements of Changes in Shareholders' Equity", "2429403 - Disclosure - Quarterly Financial Data Quarterly Financial Data (Unaudited) (Details)", "2430405 - Disclosure - Schedule I Condensed Income Statement (Details)", "000100 - Statement - Consolidated Statements Of Income", "000400 - Statement - Consolidated Statements Of Changes In Stockholders' Equity", "041801 - Disclosure - Quarterly Financial Information (Unaudited) (Quarterly Financial Results) (Details)", "0110 - Statement - Consolidated Statements of Earnings", "0120 - Statement - Consolidated Statements of Comprehensive Income", "0140 - Statement - Consolidated Statements of Changes in Shareholders' Equity", "0150 - Statement - Consolidated Statements of Cash Flows", "0625 - Disclosure - Business Segments (Details)", "0630 - Disclosure - Parent Company (Details)", "06302 - Disclosure - Parent Company (Details 2)", "100020 - Statement - Statement of Earnings", "100030 - Statement - Statement of Comprehensive Income", "100040 - Statement - Statement of Changes in Shareowners' Equity", "100060 - Statement - Statement of Cash Flows", "401500 - Disclosure - Shareowners' Equity (Details)", "402800 - Disclosure - Quarterly Information (unaudited) (Details)", "1001000 - Statement - Consolidated Statements Of Income", "1001000 - Statement - Consolidated Statements Of Income", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Cash Flows", "1005000 - Statement - Consolidated Statements of Shareholders' Equity", "1003000 - Statement - Consolidated Statements Of Operations", "1004000 - Statement - Consolidated Statements of Comprehensive Income", "1005000 - Statement - Consolidated Statements Of Cash Flows", "1006000 - Statement - Consolidated Statements Of Equity", "2420402 - Disclosure - Net Income per Share (Calculation Of Basic And Diluted Net Income Per Share) (Detail)", "010020 - Statement - Consolidated Statements of Income", "010035 - Statement - Consolidated Statements of Comprehensive Income", "010060 - Statement - Consolidated Statements of Cash Flows", "010080 - Statement - Consolidated Statements of Shareholders' Equity", "042600 - Disclosure - Parent Company (Details)", "042620 - Disclosure - Parent Company (Details 2)", "042700 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Income", "1002000 - Statement - Consolidated Statement of Comprehensive Income", "1004000 - Statement - Consolidated Statement of Cash Flows", "1005000 - Statement - Consolidated Statement of Shareholders Equity", "2414402 - Disclosure - Quarterly Results (Detail)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF INCOME", "2475402 - Disclosure - Quarterly Financial Summary (Detail)", "000011 - Statement - Consolidated Statement of Income", "004010 - Disclosure - Significant Accounting Policies (Details)", "004170 - Disclosure - Quarterly Data (Unaudited) (Details)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF EARNINGS", "2418402 - Disclosure - EARNINGS PER SHARE (DETAILS)", "2447402 - Disclosure - QUARTERLY RESULTS (UNAUDITED) (DETAILS)" ]
    };

declare %an:nondeterministic function local:test-acceptance-date($expected as string, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as string? := $request[2].Archives[].AcceptanceDatetime
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status, test:url($endpoint, $params))
};

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

declare %an:sequential function local:test-components-result($expected as array, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Archives[].Components[].NetworkLabel ]
    let $status as integer := $request[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

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

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

local:check({
    cocacola: local:test-components(94, {
        ticker:"ko",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
    }),
    cocacolaincome: local:test-components(1, {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
    }),
    byconcept: local:test-components-result($local:expected.byconcept, {
        tag:"DOW30",
        concept:"us-gaap:NetIncomeLoss",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
    }),
    byfyfp: local:test-components(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    byfyfplowercase: local:test-components(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"q2",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    acceptance: local:test-acceptance-date("2014-07-30T15:44:58Z", {
        aid:"0000021344-14-000029"
    }),
    generic: local:test-generic-components(1, {
      profile-name: "generic",
      aid: "0000021344-14-000008",
      role: "http://www.thecocacolacompany.com/role/DocumentAndEntityInformationDocument"
    }),
    generic-aid: local:test-generic-components(95, {
      profile-name: "generic",
      aid: "0000021344-14-000008"
    }),
    example1: test:invoke-and-assert-deep-equal(
      "components",
      {aid:"0000021344-13-000039"},
      function($b as item*) as item* { [ trim($b.Archives[].Components[], ("FactTable", "SpreadSheet", "ReportElements", "ModelStructure", "ValidationErrors")) ] },
      test:get-expected-result("secxbrl/components-expected1.jq"),
      { NoArrayOrder: true }
    ),
    example2: test:invoke-and-assert-deep-equal(
      "components",
      {
        ticker:"ko",
        fiscalYear: "2013",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* {
        [ trim(
          $b.Archives[].Components[],
          ("FactTable", "SpreadSheet", "ReportElements", "ModelStructure", "ValidationErrors")
        ) ]
      },
      test:get-expected-result("secxbrl/components-expected2.jq"),
      { NoArrayOrder: true }
    ),
    url: test:invoke-and-assert-deep-equal(
      "components",
      {aid:"0000021344-13-000039"},
      function($b as item*) as item* {
        project(
          $b.Archives[].Components[]
            [$$.NetworkIdentifier = "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation"],
          ("FactTable", "SpreadSheet", "ReportElements", "ModelStructure")
        )
      },
      {
        "FactTable" : backend:url("facttable-for-component", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec"
            }),
        "SpreadSheet" : backend:url("spreadsheet-for-component", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec",
            eliminate: "true"
            }),
        "ReportElements" :backend:url("report-elements", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec"
            }),
        "ModelStructure" : backend:url("modelstructure-for-component", {
            token: $config:test-token,
            aid: "0000021344-13-000039",
            format: "json",
            role: "http://www.thecoca-colacompany.com/role/DocumentAndEntityInformation",
            profile-name: "sec"
            })
      },
      { NoArrayOrder: true }
    )
})


Update spreadsheet-for-report.jq

Update spreadsheet-for-report.jq

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

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

import module namespace config = "http://apps.28.io/config";
import module namespace session = "http://apps.28.io/session";
import module namespace api = "http://apps.28.io/api";
import module namespace request = "http://www.28msec.com/modules/http-request";

declare option rest:response "first-item";

(: 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;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriodType  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                 variable $validate      as boolean external := false;
declare  %rest:case-insensitive                 variable $eliminate     as boolean external := false;
declare  %rest:case-insensitive                 variable $elimination-threshold as integer external := 0;
declare  %rest:case-insensitive                 variable $report        as string? external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $language      as string  external := "en-US";
declare  %rest:case-insensitive                 variable $debug         as boolean external := false;

(: 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 $fiscalPeriodType as string* := api:preprocess-fiscal-period-types($fiscalPeriodType)
let $tag as string* := api:preprocess-tags($tag)

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 $report-id as string? := $report
let $report as object? := reports:reports($report)

return
if(empty($report))
then
{
      { status: 404 },
      session:error("report with id '" || $report-id || "' does not exist.", $format)
} else

    (: Fact resolution :)
    let $filter-override as object? :=
        if($profile-name eq "sec")
        then fiscal-core:filter-override(
            $entities,
            $fiscalYear,
            $fiscalPeriod,
            $fiscalPeriodType,
            $aid)
        else components:filter-override($entities, $aid)

    (: Fact resolution :)
    let $hypercube := hypercubes:hypercubes-for-components($report)[1]
    let $filtered-aspects := values($hypercube.Aspects)[exists(($$.Members, $$.DomainRestriction))]
    let $spreadsheet as object? :=
        if(count($filtered-aspects) lt $config:filtered-aspects and not exists(($filter-override)))
        then {
              { status: 403 },
              session:error("The report filters are too weak, which leads to too big an output.", $format)
        } else
            components:spreadsheet(
                $report,
                {|
                    { FilterOverride: $filter-override}[exists($filter-override)],
                    {
                        FlattenRows: true,
                        Eliminate: $eliminate,
                        EliminationThreshold: double($elimination-threshold) div 100,
                        Validate: $validate,
                        Language: $language,
                        Debug: $debug
                    }
                |}
            )
    let $results :=
        if($format eq "html")
        then {
          {
            status: 302,
            content-type: "text/html",
            serialization: { "method" : "html" },
            headers: { "Location" : "http://rendering.secxbrl.info/#?url=" || encode-for-uri("http://" || request:server-name() || ":" || request:server-port() || replace(request:uri(), "format=html", "format=json")) }
          },
          <html/>
        }
        else
        {
            { serialization: { method: "json", indent : true } },
            $spreadsheet
        }
    return if($entities-not-found)
           then api:not-found("entity")
           else api:check-and-return-results($token, $results, $format)

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

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

import module namespace config = "http://apps.28.io/config";
import module namespace session = "http://apps.28.io/session";
import module namespace api = "http://apps.28.io/api";
import module namespace request = "http://www.28msec.com/modules/http-request";

declare option rest:response "first-item";

(: 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;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriodType  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                 variable $validate      as boolean external := false;
declare  %rest:case-insensitive                 variable $eliminate     as boolean external := false;
declare  %rest:case-insensitive                 variable $elimination-threshold as integer external := 0;
declare  %rest:case-insensitive                 variable $report        as string? external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $language      as string  external := "en-US";
declare  %rest:case-insensitive                 variable $debug         as boolean external := false;

(: 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 $fiscalPeriodType as string* := api:preprocess-fiscal-period-types($fiscalPeriodType)
let $tag as string* := api:preprocess-tags($tag)

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 $report-id as string? := $report
let $report as object? := reports:reports($report)

return
if(empty($report))
then
{
      { status: 404 },
      session:error("report with id '" || $report-id || "' does not exist.", $format)
} else

    (: Fact resolution :)
    let $filter-override as object? :=
        if($profile-name eq "sec")
        then fiscal-core:filter-override(
            $entities,
            $fiscalYear,
            $fiscalPeriod,
            $fiscalPeriodType,
            $aid)
        else components:filter-override($entities, $aid)

    (: Fact resolution :)
    let $hypercube := hypercubes:hypercubes-for-components($report)[1]
    let $filtered-aspects := values($hypercube.Aspects)[exists(($$.Members, $$.DomainRestriction))]
    let $spreadsheet as object? :=
        if(count($filtered-aspects) lt $config:filtered-aspects and not exists(($filter-override)))
        then {
              { status: 403 },
              session:error("The report filters are too weak, which leads to too big an output.", $format)
        } else
            components:spreadsheet(
                $report,
                {|
                    { FilterOverride: $filter-override}[exists($filter-override)],
                    {
                        FlattenRows: true,
                        Eliminate: $eliminate,
                        EliminationThreshold: double($elimination-threshold) div 100,
                        Validate: $validate,
                        Language: $language,
                        Debug: $debug
                    }
                |}
            )
    let $results :=
        if($format eq "html")
        then {
          {
            status: 301,
            content-type: "text/html",
            serialization: { "method" : "html" },
            headers: { "Location" : "http://rendering.secxbrl.info/#?url=" || encode-for-uri("http://" || request:server-name() || ":" || request:server-port() || replace(request:uri(), "format=html", "format=json")) }
          },
          <html/>
        }
        else
        {
            { serialization: { method: "json", indent : true } },
            $spreadsheet
        }
    return if($entities-not-found)
           then api:not-found("entity")
           else api:check-and-return-results($token, $results, $format)