:lipstick: align TotalForConcept... example query.

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace companies = "http://28.io/modules/xbrl/profiles/sec/companies";
import module namespace sec-fiscal = "http://28.io/modules/xbrl/profiles/sec/fiscal/core";
import module namespace csv = "http://zorba.io/modules/json-csv";
import module namespace session = "http://apps.28.io/session";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace request = "http://apps.28.io/request";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace rules = "http://28.io/modules/xbrl/rules";
import module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace labels = "http://28.io/modules/xbrl/labels";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace conversion = "http://28.io/modules/xbrl/conversion";

declare option rest:response "first-item";

declare variable $local:additional-concepts as object* := (
  {
    Name: "sec:DefaultLegalEntity",
    Labels : [ {
        Role : "http://www.xbrl.org/2003/role/label",
        Language : "en-us",
        Value : "Default Legal Entity [Member]"
    } ]
  },
  {
    Name: "xbrl:NonNumeric",
    Labels : [ {
        Role : "http://www.xbrl.org/2003/role/label",
        Language : "en-us",
        Value : "Non Numeric"
    } ]
  }
);

declare function local:to-csv($o as object*) as string
{
    string-join(
        csv:serialize(
            for $o in $o
            let $a := $o.Aspects
            return {|
                for $k in keys($a)
                return { $k : $a.$k },
                { "Unit" :  $o."Unit" },
                { "Value" : $o.Value },
                { "Decimals" : "INF" },
                { "NumReports" : $o.NumReports }
            |},
            { serialize-null-as : "" }
        )
    )
};

declare function local:to-xml($o as object*) as node()*
{
    (
    session:comment("xml"),
    <FactTable NetworkIdentifier="http://xbrl.io/TotalForConceptGroupedByFiscalPeriodForAllEntities"
            TableName="xbrl:TotalForConceptGroupedByFiscalPeriodForAllEntities">{

        for $o in $o
        let $a := $o.Aspects
        return
            <Fact>{
                (<Aspects>{
                    for $k in keys($a)
                    return
                        <Aspect>
                            <Name>{$k}</Name>
                            <Value>{$a.$k}</Value>
                        </Aspect>
                }</Aspects>,
                <Unit>{$o.Unit}</Unit>,
                <Type>{$o.Value}</Type>,
                <Value>{$o.Value}</Value>,
                <Decimals>INF</Decimals>,
                <NumReports>{$o.NumReports}</NumReports>,
                if (exists($o.Debug))
                then
                    <Debug>{
                        for $a in $o.Debug[]
                        return <InstanceURL value="{$a.Value}">{$a.InstanceURL}</InstanceURL>
                    }</Debug>
                else ()
                )
            }</Fact>
    }</FactTable>)
};

(: Query parameters :)
declare  %rest:env                              variable $request-uri       as string  external;
declare  %rest:case-insensitive                 variable $profile-name      as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $format            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 $fiscalPeriodType  as string* external := ("instant", "YTD");
declare  %rest:case-insensitive %rest:distinct  variable $concept           as string  external := "us-gaap:Assets";
declare  %rest:case-insensitive %rest:distinct  variable $cik               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid               as string* external;
declare  %rest:case-insensitive                 variable $debug             as boolean external := false;
declare  %rest:case-insensitive                 variable $map               as string? external;
declare  %rest:case-insensitive                 variable $rule              as string? external;
declare  %rest:case-insensitive                 variable $report            as string? external;
declare  %rest:case-insensitive                 variable $additional-rules  as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $eid               as string* external;

(: Post-processing :)
let $open as boolean := false

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

let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rule as item* :=
    (
        if(exists($report))
        then reports:rules($report)
        else rules:rules($rule),
        if(exists($additional-rules))
        then rules:rules($additional-rules)
        else ()
    )

let $request as object := request:request(
  $profile-name,
  $entities,
  $request-uri,
  $format,
  $concept,
  $fiscalYear,
  $fiscalPeriod,
  $fiscalPeriodType,
  $cik,
  $tag,
  $ticker,
  $sic,
  $aid,
  $open)
let $hypercube := request:hypercube($request, $entities)

(: Object resolution :)
let $json-result :=
    let $facts :=
        let $options := {|
          {
            Hypercube : $hypercube
          },
          { "ConceptMaps" : $map }[exists($map)],
          { "Rules" : [ $rule ] }[exists($rule)],
          { "Concepts" : $report.Concepts }[exists($report) and exists($report.Concepts)]
        |}
        return if($profile-name eq "sec")
        then sec:facts-for($options)
        else facts:facts-for($options)

    let $drilldown-axis as string* := keys($request.dimensions[])[starts-with($$, "us-gaap:")]

    let $archives as string* := distinct-values($facts.Aspects."xbrl28:Archive")
    let $facts :=
      for $fact in $facts
      let $drilldown-member as string? :=
        string-join(
          for $member in ( $drilldown-axis ! $fact.Aspects.$$ )
          order by $member
          return $member, "|")
      group by $fyf := ($fact.Aspects."sec:FiscalYear", $fact.Aspects."fsa:FiscalYear")[1],
               $fpf := ($fact.Aspects."sec:FiscalPeriod", $fact.Aspects."fsa:FiscalPeriod")[1],
               $unit := $fact.Aspects."xbrl:Unit",
               $drilldown-member
      order by $fyf
      return {|
          {
              Aspects : {|
                for $aspect in distinct-values(keys($fact.Aspects))
                where not($aspect = ("sec:Accepted", "xbrl28:Archive", "xbrl:Period"))
                return
                  if($aspect eq "xbrl:Entity")
                  then
                    { "xbrl:Entity": $tag || " Aggregation" }
                  else
                    { $aspect: distinct-values($fact.Aspects.$aspect) }
              |}
          },
          { "Value" : sum($fact.Value[$$ ne null]) },
          { "Type"  : "NumericValue" },
          { "Unit" : $unit },
          { "Decimals"  : "INF" },
          { "NumReports" : count(distinct-values($fact.$facts:ASPECTS."xbrl28:Archive")) },
          { "AuditTrails" : [
              {
                "Type" : "xbrl28:Aggregation",
                "Label" : "Aggregation",
                "Message" : "Aggregation for each year, period, unit and additional drilldown axis",
                "Data" :
                    {|
                      { DrilldownMembers: [ ( $drilldown-axis ! { Axis: $$, Member: distinct-values($fact.Aspects.$$) } ) ] }
                    |}
              },
              $fact.AuditTrails
            ]
          },

          if ($debug)
          then
              {
                  "Debug" : [ $fact !
                              {
                                  "InstanceURL" : archives:archives(distinct-values($$.$facts:ASPECTS."xbrl28:Archive")).InstanceURL,
                                  "Value" : $$.Value,
                                  "Decimals" : $$.Decimals
                              } ]
              }
          else ()
      |}

    let $facts :=
      let $concept-names as string* := (
        distinct-values((keys($facts.Aspects), values($facts.Aspects)[$$ instance of string]))
      )
      let $concepts as object* :=
        (
            concepts:concepts($concept-names, $archives, $concepts:ANY_COMPONENT_LINK_ROLE),
            $report.Concepts[][$$.Name = $concept-names],
            if($profile-name eq "sec") then $local:additional-concepts else ()
        )
      let $language as string := ( $report.$components:DEFAULT-LANGUAGE , $labels:AMERICAN_ENGLISH )[1]
      let $nonFetchedEntities as string* := request:param-values("xbrl:Entity")[not $$ = entities:eid($entities)]
      let $entities as object* := ($entities, entities:entities($nonFetchedEntities))
      for $fact as object in $facts
      return
      {|
        $fact,
        let $concept-labels as object? := labels:labels-for-facts(
          $fact,
          $labels:STANDARD_LABEL_ROLE,
          $language,
          $concepts,
          $entities,
          ()
        )
        return { Labels : $concept-labels }
      |}

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

    return $facts

let $result := {
                    NetworkIdentifier : "http://bizql.io/facts",
                    TableName : "xbrl:Facts",
                    FactTable : [ $json-result ]
               }
let $comment :=
  {|
      { NetworkIdentifier : "http://xbrl.io/TotalForConceptGroupedByFiscalPeriodForAllEntities" },
      { TableName : "xbrl:TotalForConceptGroupedByFiscalPeriodForAllEntities" },
      if($debug)
      then { DebugInfo: { Hypercube: $hypercube,
                          Request: $request } }
      else (),
      session:comment("json")
  |}
let $serializers := {
    to-xml : function($res as object) as node()* {
           <FactTable NetworkIdentifier="http://xbrl.io/TotalForConceptGroupedByFiscalPeriodForAllEntities"
            TableName="xbrl:TotalForConceptGroupedByFiscalPeriodForAllEntities">{
                conversion:facts-to-xml($res.FactTable[], { Caller: "Report"})
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        (conversion:facts-to-csv($res.FactTable[], { Caller: "Report" }), "")[1]
    }
}
let $results := api:serialize($result, $comment, $serializers, $request.format, "facts")
return $results

(:return
    switch ($format)
    case "xml" return {
        {
            serialization: { method: "xml", "omit-xml-declaration" : false, indent : true }
        },
        local:to-xml($json-result)
    }
    case "text" case "csv" return {
        {
            "content-type": "text/csv",
            headers: { "Content-Disposition": "attachment; filename=Total-" || $concept || ".csv" }
        },
        local:to-csv($json-result)
    }
    case "excel" return {
        {
            "content-type": "application/vnd.ms-excel",
            headers: { "Content-Disposition": "attachment; filename=Total-" || $concept || ".csv" }
        },
        local:to-csv($json-result)
    }
    default return {
        { serialization: { method: "json", indent : true } },
        {|
            { NetworkIdentifier : "http://xbrl.io/TotalForConceptGroupedByFiscalPeriodForAllEntities" },
            { TableName : "xbrl:TotalForConceptGroupedByFiscalPeriodForAllEntities" },
            { FactTable : [ $json-result ] },
            if($debug)
            then { DebugInfo: { Hypercube: $hypercube,
                                Request: $request } }
            else (),
            session:comment("json")
        |}
    }
:)

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace companies = "http://28.io/modules/xbrl/profiles/sec/companies";
import module namespace sec-fiscal = "http://28.io/modules/xbrl/profiles/sec/fiscal/core";
import module namespace csv = "http://zorba.io/modules/json-csv";
import module namespace session = "http://apps.28.io/session";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace request = "http://apps.28.io/request";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace rules = "http://28.io/modules/xbrl/rules";
import module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

declare option rest:response "first-item";

declare function local:to-csv($o as object*) as string
{
    string-join(
        csv:serialize(
            for $o in $o
            let $a := $o.Aspects
            return {|
                for $k in keys($a)
                return { $k : $a.$k },
                { "Unit" :  $o."Unit" },
                { "Value" : $o.Value },
                { "Decimals" : "INF" },
                { "NumReports" : $o.NumReports }
            |},
            { serialize-null-as : "" }
        )
    )
};

declare function local:to-xml($o as object*) as node()*
{
    (
    session:comment("xml"),
    <FactTable NetworkIdentifier="http://xbrl.io/TotalForConceptGroupedByFiscalPeriodForAllEntities"
            TableName="xbrl:TotalForConceptGroupedByFiscalPeriodForAllEntities">{

        for $o in $o
        let $a := $o.Aspects
        return
            <Fact>{
                (<Aspects>{
                    for $k in keys($a)
                    return
                        <Aspect>
                            <Name>{$k}</Name>
                            <Value>{$a.$k}</Value>
                        </Aspect>
                }</Aspects>,
                <Unit>{$o.Unit}</Unit>,
                <Type>{$o.Value}</Type>,
                <Value>{$o.Value}</Value>,
                <Decimals>INF</Decimals>,
                <NumReports>{$o.NumReports}</NumReports>,
                if (exists($o.Debug))
                then
                    <Debug>{
                        for $a in $o.Debug[]
                        return <InstanceURL value="{$a.Value}">{$a.InstanceURL}</InstanceURL>
                    }</Debug>
                else ()
                )
            }</Fact>
    }</FactTable>)
};

(: Query parameters :)
declare  %rest:env                              variable $request-uri       as string  external;
declare  %rest:case-insensitive                 variable $profile-name      as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $format            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 $fiscalPeriodType  as string* external := ("instant", "YTD");
declare  %rest:case-insensitive %rest:distinct  variable $concept           as string  external := "us-gaap:Assets";
declare  %rest:case-insensitive %rest:distinct  variable $cik               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid               as string* external;
declare  %rest:case-insensitive                 variable $debug             as boolean external := false;
declare  %rest:case-insensitive                 variable $map               as string? external;
declare  %rest:case-insensitive                 variable $rule              as string? external;
declare  %rest:case-insensitive                 variable $report            as string? external;
declare  %rest:case-insensitive                 variable $additional-rules  as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $eid               as string* external;

(: Post-processing :)
let $open as boolean := false

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

let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rule as item* :=
    (
        if(exists($report))
        then reports:rules($report)
        else rules:rules($rule),
        if(exists($additional-rules))
        then rules:rules($additional-rules)
        else ()
    )

let $request as object := request:request(
  $profile-name,
  $entities,
  $request-uri,
  $format,
  $concept,
  $fiscalYear,
  $fiscalPeriod,
  $fiscalPeriodType,
  $cik,
  $tag,
  $ticker,
  $sic,
  $aid,
  $open)
let $hypercube := request:hypercube($request, $entities)

(: Object resolution :)
let $json-result :=
    let $facts :=
        let $options := {|
          {
            Hypercube : $hypercube
          },
          { "ConceptMaps" : $map }[exists($map)],
          { "Rules" : [ $rule ] }[exists($rule)],
          { "Concepts" : $report.Concepts }[exists($report) and exists($report.Concepts)]
        |}
        return if($profile-name eq "sec")
        then sec:facts-for($options)
        else facts:facts-for($options)

    for $fact in $facts
    group by $fyf := ($fact.Aspects."sec:FiscalYear", $fact.Aspects."fsa:FiscalYear")[1],
             $fpf := ($fact.Aspects."sec:FiscalPeriod", $fact.Aspects."fsa:FiscalPeriod")[1],
             $unit := $fact.Aspects."xbrl:Unit"
    order by $fyf
    return {|
        {
            Aspects : {|
              for $aspect in distinct-values(keys($fact.Aspects))
              return
                { $aspect: distinct-values($fact.Aspects.$aspect) }
            |}
        },
        { "Value" : sum($fact.Value[$$ ne null]) },
        { "Type"  : "NumericValue" },
        { "Unit" : $unit },
        { "Decimals"  : "INF" },
        { "NumReports" : count(distinct-values($fact.$facts:ASPECTS."xbrl28:Archive")) },
        { "AuditTrails" : [
            {
              "Type" : "xbrl28:Aggregation",
              "Label" : "",
              "Message" : "",
              "Data" : ""
            },
            $fact.AuditTrails
          ]
        },

        if ($debug)
        then
            {
                "Debug" : [ $fact !
                            {
                                "InstanceURL" : archives:archives(distinct-values($$.$facts:ASPECTS."xbrl28:Archive")).InstanceURL,
                                "Value" : $$.Value,
                                "Decimals" : $$.Decimals
                            } ]
            }
        else ()
    |}
return
    switch ($format)
    case "xml" return {
        {
            serialization: { method: "xml", "omit-xml-declaration" : false, indent : true }
        },
        local:to-xml($json-result)
    }
    case "text" case "csv" return {
        {
            "content-type": "text/csv",
            headers: { "Content-Disposition": "attachment; filename=Total-" || $concept || ".csv" }
        },
        local:to-csv($json-result)
    }
    case "excel" return {
        {
            "content-type": "application/vnd.ms-excel",
            headers: { "Content-Disposition": "attachment; filename=Total-" || $concept || ".csv" }
        },
        local:to-csv($json-result)
    }
    default return {
        { serialization: { method: "json", indent : true } },
        {|
            { NetworkIdentifier : "http://xbrl.io/TotalForConceptGroupedByFiscalPeriodForAllEntities" },
            { TableName : "xbrl:TotalForConceptGroupedByFiscalPeriodForAllEntities" },
            { FactTable : [ $json-result ] },
            if($debug)
            then { DebugInfo: { Hypercube: $hypercube } }
            else (),
            session:comment("json")
        |}
    }


:green_heart: Fix test.

:green_heart: Fix test.

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

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

declare %an:nondeterministic function local:test-404($params as object) as item
{
    let $endpoint := "facts"
    let $res as object := test:invoke-raw($endpoint, $params)
    return if($res.status eq 404)
           then true
           else {
              url: test:url($endpoint, $params),
              unexpectedResponse: $res
           }
};

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
};

declare %an:nondeterministic function local:test-labels() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: [ "fac:Assets", "us-gaap:CashAndCashEquivalentsAtCarryingValue" ],
        report: "FundamentalAccountingConcepts",
        format: "csv",
        ticker: "ko",
        fiscalYear: 2013,
        fiscalPeriod: "Q3",
        labels: true
    }
    let $res as object := test:invoke-raw($endpoint, $params)
    let $actual := $res.body.content
    let $expectedLines := (
        "SEC Acceptance Date [Axis],Archive [Axis],Fiscal Period [Axis],Fiscal Year [Axis],Fiscal Period Type [Axis],Period [Axis],Reporting Entity [Axis],Concept [Axis],Legal Entity [Axis],Unit,Value,Decimals\r\n20131024121047,0000021344-13-000050,Q3,2013,instant,\"September 27, 2013\",COCA COLA CO,\"Cash and Cash Equivalents, at Carrying Value\",Default Legal Entity [Member],USD,11118000000,-6\r\n20131024121047,0000021344-13-000050,Q3,2013,instant,\"September 27, 2013\",COCA COLA CO,Assets,Default Legal Entity [Member],USD,89432000000,-6\r\n"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        url: test:url($endpoint, $params),
        unexpectedResponse: $res,
        expected: $expectedLines
    }
};

declare %an:nondeterministic function local:test-labels-aids() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: "disc:AdvertisingCostsPolicyTextBlock",
        report: "Disclosures",
        format: "json",
        fiscalYear: 2014,
        fiscalPeriod: "FY",
        labels: true,
        aid: "0000858877-14-000029"
    }
    let $res := test:invoke($endpoint, $params)
    let $actual := [
            for $labels in $res[2].FactTable[].Labels
            return (keys($labels) ! $labels.$$)
        ]
    let $expected := [ "Archive [Axis]", "SEC Acceptance Date [Axis]", "Fiscal Year [Axis]", "Non-Numeric [Member]", "Default Legal Entity [Member]", "Unit [Axis]", "Advertising Costs, Policy", "Fiscal Period [Axis]", "Concept [Axis]", "Reporting Entity [Axis]", "Legal Entity [Axis]", "Fiscal Period Type [Axis]", "Period [Axis]", "From July 28, 2013 to July 26, 2014", "CISCO SYSTEMS, INC." ]
    let $status as integer := $res[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

local:check({
    cocacola-latest:
    (: this test will fail and needs to be updated if a newer report has been filed.
       current latest filing: 2014 :)
    local:test-facttable(462, {
        ticker:"ko",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    cocacola-all: local:test-facttable(468, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant", "YTD", "QTD" ]
    }),
    cocacola-all-q3: local:test-facttable(341, {
        ticker:"ko",
        fiscalYear: 2014,
        fiscalPeriod: [ "Q3" ],
        fiscalPeriodType: [ "instant", "YTD", "QTD" ]
    }),
    cocacola-instant: local:test-facttable(163, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant" ]
    }),
    cocacola-ytd: local:test-facttable(305, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "YTD" ]
    }),
    cocacola-qtd: local:test-facttable(90, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "Q2" ],
        fiscalPeriodType: [ "QTD" ]
    }),
    cocacolaCSVLabels: local:test-labels(),
    ciscoLabelsByAid: local:test-labels-aids(),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-404({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    }),
    quartersfact: local:test-facttable(0, { (: this fact is not reported in the raw XML filing :)
        aid: "0000051143-13-000007",
        concept: "disc:AssetRetirementObligationsPolicy",
        fiscalPeriod: "Q3",
        fiscalYear: "2013",
        map: "Disclosures"
    }),
    quartersfact2: local:test-facttable(1, {
        aid: "0001193125-12-207154",
        concept: "disc:CashFlowOperatingCapitalTableTextBlock",
        fiscalPeriod: "Q1",
        fiscalYear: "2012",
        map: "Disclosures"
    }),
    generic: local:test-facttable(2, {
        "xbrl:Entity":"http://www.sec.gov/CIK 0000021344",
        "sec:Accepted":"20140227132423",
        concept:"us-gaap:Assets",
        profile-name: "generic"
    })
})

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

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

declare %an:nondeterministic function local:test-404($params as object) as item
{
    let $endpoint := "facts"
    let $res as object := test:invoke-raw($endpoint, $params)
    return if($res.status eq 404)
           then true
           else {
              url: test:url($endpoint, $params),
              unexpectedResponse: $res
           }
};

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
};

declare %an:nondeterministic function local:test-labels() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: [ "fac:Assets", "us-gaap:CashAndCashEquivalentsAtCarryingValue" ],
        report: "FundamentalAccountingConcepts",
        format: "csv",
        ticker: "ko",
        fiscalYear: 2013,
        fiscalPeriod: "Q3",
        labels: true
    }
    let $res as object := test:invoke-raw($endpoint, $params)
    let $actual := $res.body.content
    let $expectedLines := (
        "SEC Acceptance Date [Axis],Archive [Axis],Fiscal Period [Axis],Fiscal Year [Axis],Fiscal Period Type [Axis],Period [Axis],Reporting Entity [Axis],Concept [Axis],Legal Entity [Axis],Unit,Value,Decimals\r\n20131024121047,0000021344-13-000050,Q3,2013,instant,\"September 27, 2013\",COCA COLA CO,\"Cash and Cash Equivalents, at Carrying Value\",Default Legal Entity [Member],USD,11118000000,-6\r\n20131024121047,0000021344-13-000050,Q3,2013,instant,\"September 27, 2013\",COCA COLA CO,Assets,Default Legal Entity [Member],USD,89432000000,-6\r\n"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        url: test:url($endpoint, $params),
        unexpectedResponse: $res,
        expected: $expectedLines
    }
};

declare %an:nondeterministic function local:test-labels-aids() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: "disc:AdvertisingCostsPolicyTextBlock",
        report: "Disclosures",
        format: "json",
        fiscalYear: 2014,
        fiscalPeriod: "FY",
        labels: true,
        aid: "0000858877-14-000029"
    }
    let $res := test:invoke($endpoint, $params)
    let $actual := [
            for $labels in $res[2].FactTable[].Labels
            return (keys($labels) ! $labels.$$)
        ]
    let $expected := [ "Archive [Axis]", "SEC Acceptance Date [Axis]", "Fiscal Year [Axis]", "Non-Numeric [Member]", "Default Legal Entity [Member]", "Unit [Axis]", "Advertising Costs, Policy", "Fiscal Period [Axis]", "Concept [Axis]", "Reporting Entity [Axis]", "Legal Entity [Axis]", "Fiscal Period Type [Axis]", "Period [Axis]", "From July 28, 2013 to July 26, 2014", "CISCO SYSTEMS, INC." ]
    let $status as integer := $res[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

local:check({
    cocacola-latest:
    (: this test will fail and needs to be updated if a newer report has been filed.
       current latest filing: 2013 :)
    local:test-facttable(468, {
        ticker:"ko",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    cocacola-all: local:test-facttable(468, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant", "YTD", "QTD" ]
    }),
    cocacola-all-q3: local:test-facttable(341, {
        ticker:"ko",
        fiscalYear: 2014,
        fiscalPeriod: [ "Q3" ],
        fiscalPeriodType: [ "instant", "YTD", "QTD" ]
    }),
    cocacola-instant: local:test-facttable(163, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant" ]
    }),
    cocacola-ytd: local:test-facttable(305, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "YTD" ]
    }),
    cocacola-qtd: local:test-facttable(90, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "Q2" ],
        fiscalPeriodType: [ "QTD" ]
    }),
    cocacolaCSVLabels: local:test-labels(),
    ciscoLabelsByAid: local:test-labels-aids(),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-404({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    }),
    quartersfact: local:test-facttable(0, { (: this fact is not reported in the raw XML filing :)
        aid: "0000051143-13-000007",
        concept: "disc:AssetRetirementObligationsPolicy",
        fiscalPeriod: "Q3",
        fiscalYear: "2013",
        map: "Disclosures"
    }),
    quartersfact2: local:test-facttable(1, {
        aid: "0001193125-12-207154",
        concept: "disc:CashFlowOperatingCapitalTableTextBlock",
        fiscalPeriod: "Q1",
        fiscalYear: "2012",
        map: "Disclosures"
    }),
    generic: local:test-facttable(2, {
        "xbrl:Entity":"http://www.sec.gov/CIK 0000021344",
        "sec:Accepted":"20140227132423",
        concept:"us-gaap:Assets",
        profile-name: "generic"
    })
})


:green_heart: Fix test.

:green_heart: Fix test.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(:~
 :
Retrieves all concepts.

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

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

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

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

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

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

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

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

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

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

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

  return
  (
    $fromLocator,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(:~
 :
Retrieves all concepts.

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

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

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

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

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

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

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

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

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

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

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

  return
  (
    $fromLocator,

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

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

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


A=B or A=C is the same as A=(B, C)

A=B or A=C is the same as A=(B, C)

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

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

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

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

import module namespace mongo = "http://www.28msec.com/modules/mongodb";
import module namespace credentials = "http://www.28msec.com/modules/credentials";

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

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

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

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

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

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

(:~
 : A helper variable holding the default language ("en-US")
 :)
declare variable $concepts:AMERICAN_ENGLISH as xs:string := "en-US";

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

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

(:~
 : The standard label role.
 :)
declare variable $concepts:STANDARD_LABEL_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/label";

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

(:~
 :
Retrieves all concepts.

 :
 : @return all concepts.
 :)
declare function concepts:concepts() as object*
{
  let $conn := concepts:connection()
  return mongo:find($conn, $concepts:col, {})
};

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

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

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

 :
 : @param $concept-names the concepts names.
 : @param $archives the archive numbers.
 : @param $component-roles the component roles.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives as string*,
    $component-roles as string*
  ) as object*
{
  let $conn := concepts:connection()
  where exists($archives)
  return mongo:find($conn, $concepts:col,
    {|
      {
        $concepts:ARCHIVE : { "$in" : [ $archives ] }
      },
      if($component-roles = $concepts:ANY_COMPONENT_LINK_ROLE)
      then ()
      else
          {
            $concepts:ROLE : { "$in" :
              [ $component-roles, "http://www.xbrl.org/2003/role/link" ]
            }
          },
      {
        $concepts:NAME : { "$in" : [ $concept-names ] }
      }[not $concept-names = $concepts:ALL_CONCEPT_NAMES]
    |}
  )
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name matches a given one,
 :  - archive number matches that of a given component,
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $options optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
 declare function concepts:labels-for-components(
     $concept-names as string*,
     $component-or-ids as item*,
     $label-role as string,
     $language as string,
     $options as object?
   ) as string*
{
  let $components := components:components($component-or-ids)
  let $concepts := concepts:concepts-for-components($concept-names, $components)
  return concepts:labels($concept-names, $components.Archive[not $$ instance of null], $components.Role,
      $label-role, $language, $concepts, $options)
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name matches a given one,
 :  - archive number matches that of a given component,
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $options optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
declare function concepts:labels-for-components(
    $concept-names as string*,
    $component-or-ids as item*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $options as object?
  ) as string*
{
  let $components := components:components($component-or-ids)
    return concepts:labels($concept-names, $components.Archive[not $$ instance of null], $components.Role,
      $label-role, $language, $concepts, $options)
};

(:~
 :
Retrieves all the labels with the given label role and language for
 : all concepts used in the fact and matching a concept in the list of
 : concepts. Concepts used in a fact includes not only those from the
 : 'xbrl:Concept' aspect, but also Members of any custom axis.

 :
 :
Matching concepts are those which:
 :  - concept name matches a given one,
 :  - archive number matches that of a given component,
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $facts a sequence of facts.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $options optional parameters to control language matching.
 :
 : @return an object with matching concepts as keys and labels as values.
 :)
declare function concepts:labels-for-facts(
    $facts as object*,
    $component-roles as string*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $options as object?
  ) as object?
{
    let $concept-names as string* :=
        distinct-values(
            for $fact in $facts
            return
                keys($fact.Aspects)[string($fact.Aspects.($$)) = $concepts.Name] ! $fact.Aspects.($$)
        )
    let $archives := $facts.Aspects."sec:Archive"
    return
        {|
            for $name in $concept-names
            return
                {
                    $name: concepts:labels(
                                $name, $archives, $component-roles,
                                $label-role, $language, $concepts, $options)
                },
            for $key in distinct-values(keys($facts.Aspects))
            where not string($facts.Aspects.$key) = $concept-names
            return
                switch (true)
                case ($key eq "dei:LegalEntityAxis" and $facts.Aspects.$key eq "sec:DefaultLegalEntity")
                  return { $facts.Aspects.$key : "Default Legal Entity" }
                default return ()
        |}
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $archives the archive numbers.
 : @param $component-roles the component roles.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $options optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
declare function concepts:labels(
    $concept-names as string*,
    $archives as string*,
    $component-roles as string*,
    $label-role as string,
    $language as string,
    $options as object?
  ) as string*
{
  let $concepts := concepts:concepts($concept-names, $archives, $component-roles)
  return concepts:labels($concept-names, $archives, $component-roles, $label-role, $language,
      $concepts, $options)
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $archives the archive numbers.
 : @param $component-roles the component roles.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $options Optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
declare function concepts:labels(
    $concept-names as string*,
    $archives as string*,
    $component-roles as string*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $options as object?
  ) as string*
{
  let $normalized-language := concepts:normalize-language($language)
  let $concept-labels-groups-for-role := (
      $concepts[
          $$($concepts:NAME)    = $concept-names and
          (: concepts can be defined within an archive or outside of an archive - e.g. in a taxonomy :)
          ($$($concepts:ARCHIVE) = $archives or empty($$($concepts:ARCHIVE)) ) and
          $component-roles = ($concepts:ANY_COMPONENT_LINK_ROLE, $$($concepts:ROLE))]
      )[1]($concepts:LABELS)($label-role)
  for $concept-labels-group in $concept-labels-groups-for-role
  let $perfect-match := $concept-labels-group($normalized-language)
  return
    if ($perfect-match)
    then $concept-labels-group($normalized-language)
    else concepts:approximated-labels-match($concept-labels-group, $normalized-language, $options)
};

declare %private function concepts:approximated-labels-match(
    $concept-labels-group as object,
    $normalized-language as string,
    $options as object?
  ) as string*
{
  if ($options)
  then
    let $keys := keys($concept-labels-group)
    return
    (
      if ($options.MatchDown cast as boolean?)
      then
        for $matching-key in $keys[starts-with($$, $normalized-language)]
        return $concept-labels-group($matching-key)
      else (),
      if ($options.MatchUp cast as boolean?)
      then
        for $matching-key in $keys[starts-with($normalized-language, $$)]
        return $concept-labels-group($matching-key)
      else (),
      if ($options.MatchAnyVariant cast as boolean?)
      then
        if (contains($normalized-language, "-"))
        then
          for $matching-key in $keys[starts-with($$, substring-before($normalized-language, "-") || "-")]
          return $concept-labels-group($matching-key)
        else ()
      else ()
    )
  else ()
};

(:~
 :
Normalizes the language code. This normalized language code can then
 : be used to find the right labels in the concepts collection.

 :
 : @param $language the language identifier.
 :
 : @return the normalized language.
 :)
declare function concepts:normalize-language($language as string) as string
{
  replace(lower-case($language), "_", "-")
};

declare %private %an:strictlydeterministic function concepts:connection() as anyURI
{
  let $credentials :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return if (empty($credentials))
             then error(QName("components:CONNECTION-FAILED"), "no xbrl MongoDB configured")
             else $credentials
  return
    try {
      mongo:connect($credentials)
    } catch mongo:* {
      error(QName("components:CONNECTION-FAILED"), $err:description)
    }
};

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

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

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

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

import module namespace mongo = "http://www.28msec.com/modules/mongodb";
import module namespace credentials = "http://www.28msec.com/modules/credentials";

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

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

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

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

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

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

(:~
 : A helper variable holding the default language ("en-US")
 :)
declare variable $concepts:AMERICAN_ENGLISH as xs:string := "en-US";

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

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

(:~
 : The standard label role.
 :)
declare variable $concepts:STANDARD_LABEL_ROLE as xs:string :=
    "http://www.xbrl.org/2003/role/label";

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

(:~
 :
Retrieves all concepts.

 :
 : @return all concepts.
 :)
declare function concepts:concepts() as object*
{
  let $conn := concepts:connection()
  return mongo:find($conn, $concepts:col, {})
};

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

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

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

 :
 : @param $concept-names the concepts names.
 : @param $archives the archive numbers.
 : @param $component-roles the component roles.
 :
 : @return the matching concepts.
 :)
declare function concepts:concepts(
    $concept-names as string*,
    $archives as string*,
    $component-roles as string*
  ) as object*
{
  let $conn := concepts:connection()
  where exists($archives)
  return mongo:find($conn, $concepts:col,
    {|
      {
        $concepts:ARCHIVE : { "$in" : [ $archives ] }
      },
      if($component-roles = $concepts:ANY_COMPONENT_LINK_ROLE)
      then ()
      else
          {
            $concepts:ROLE : { "$in" :
              [ $component-roles, "http://www.xbrl.org/2003/role/link" ]
            }
          },
      {
        $concepts:NAME : { "$in" : [ $concept-names ] }
      }[not $concept-names = $concepts:ALL_CONCEPT_NAMES]
    |}
  )
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name matches a given one,
 :  - archive number matches that of a given component,
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $options optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
 declare function concepts:labels-for-components(
     $concept-names as string*,
     $component-or-ids as item*,
     $label-role as string,
     $language as string,
     $options as object?
   ) as string*
{
  let $components := components:components($component-or-ids)
  let $concepts := concepts:concepts-for-components($concept-names, $components)
  return concepts:labels($concept-names, $components.Archive[not $$ instance of null], $components.Role,
      $label-role, $language, $concepts, $options)
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name matches a given one,
 :  - archive number matches that of a given component,
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $options optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
declare function concepts:labels-for-components(
    $concept-names as string*,
    $component-or-ids as item*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $options as object?
  ) as string*
{
  let $components := components:components($component-or-ids)
    return concepts:labels($concept-names, $components.Archive[not $$ instance of null], $components.Role,
      $label-role, $language, $concepts, $options)
};

(:~
 :
Retrieves all the labels with the given label role and language for
 : all concepts used in the fact and matching a concept in the list of
 : concepts. Concepts used in a fact includes not only those from the
 : 'xbrl:Concept' aspect, but also Members of any custom axis.

 :
 :
Matching concepts are those which:
 :  - concept name matches a given one,
 :  - archive number matches that of a given component,
 :  - component role matches that of a given component or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $facts a sequence of facts.
 : @param $component-or-ids the CIDs or the components themselves.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $options optional parameters to control language matching.
 :
 : @return an object with matching concepts as keys and labels as values.
 :)
declare function concepts:labels-for-facts(
    $facts as object*,
    $component-roles as string*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $options as object?
  ) as object?
{
    let $concept-names as string* :=
        distinct-values(
            for $fact in $facts
            return
                keys($fact.Aspects)[string($fact.Aspects.($$)) = $concepts.Name] ! $fact.Aspects.($$)
        )
    let $archives := $facts.Aspects."sec:Archive"
    return
        {|
            for $name in $concept-names
            return
                {
                    $name: concepts:labels(
                                $name, $archives, $component-roles,
                                $label-role, $language, $concepts, $options)
                },
            for $key in distinct-values(keys($facts.Aspects))
            where not string($facts.Aspects.$key) = $concept-names
            return
                switch (true)
                case ($key eq "dei:LegalEntityAxis" and $facts.Aspects.$key eq "sec:DefaultLegalEntity")
                  return { $facts.Aspects.$key : "Default Legal Entity" }
                default return ()
        |}
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $archives the archive numbers.
 : @param $component-roles the component roles.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $options optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
declare function concepts:labels(
    $concept-names as string*,
    $archives as string*,
    $component-roles as string*,
    $label-role as string,
    $language as string,
    $options as object?
  ) as string*
{
  let $concepts := concepts:concepts($concept-names, $archives, $component-roles)
  return concepts:labels($concept-names, $archives, $component-roles, $label-role, $language,
      $concepts, $options)
};

(:~
 :
Retrieves all the labels with the given label role and language in the
 : matching concepts.

 :
 :
Matching concepts are those which:
 :  - concept name and archive number match a given one;
 :  - component role matches a given one or is the default
 :    component role.
 :

 :
 :
The set of concepts to search in is specified as a parameter.

 :
 :
Language matching can either be exact, if no options are given,
 : or approximated, if at least one of the following options is given:

 :

 :
MatchDown: whether to match a more specific language, e.g.:
 :       "en" will match labels which language is "en" or "en-US".

 :
MatchUp: whether to match a less specific language, e.g.:
 :       "en-US" will match labels which language is "en-US" or "en".

 :
MatchAnyVariant: whether to match a different variant of the same
 :       language, e.g.: "en-US" will match labels which language is "en-US"
 :       or "en-UK".

 :

 :
 : @param $concept-names the concepts names.
 : @param $archives the archive numbers.
 : @param $component-roles the component roles.
 : @param $label-role the label role.
 : @param $language the label language.
 : @param $concepts the concepts in which the labels will be
 :                  searched.
 : @param $options Optional parameters to control language matching.
 :
 : @return the matching labels.
 :)
declare function concepts:labels(
    $concept-names as string*,
    $archives as string*,
    $component-roles as string*,
    $label-role as string,
    $language as string,
    $concepts as object*,
    $options as object?
  ) as string*
{
  let $normalized-language := concepts:normalize-language($language)
  let $concept-labels-groups-for-role := (
      (: concepts can be defined within an archive :)
      $concepts[
          $$($concepts:NAME)    = $concept-names and
          $$($concepts:ARCHIVE) = $archives and
          ($component-roles = $concepts:ANY_COMPONENT_LINK_ROLE
           or $$($concepts:ROLE)    = $component-roles)],
      (: or outside of an archive - e.g. in a taxonomy :)
      $concepts[
          $$($concepts:NAME)    = $concept-names and
          empty($$($concepts:ARCHIVE)) and
          ($component-roles = $concepts:ANY_COMPONENT_LINK_ROLE
           or $$($concepts:ROLE)    = $component-roles)]
      )[1]($concepts:LABELS)($label-role)
  for $concept-labels-group in $concept-labels-groups-for-role
  let $perfect-match := $concept-labels-group($normalized-language)
  return
    if ($perfect-match)
    then $concept-labels-group($normalized-language)
    else concepts:approximated-labels-match($concept-labels-group, $normalized-language, $options)
};

declare %private function concepts:approximated-labels-match(
    $concept-labels-group as object,
    $normalized-language as string,
    $options as object?
  ) as string*
{
  if ($options)
  then
    let $keys := keys($concept-labels-group)
    return
    (
      if ($options.MatchDown cast as boolean?)
      then
        for $matching-key in $keys[starts-with($$, $normalized-language)]
        return $concept-labels-group($matching-key)
      else (),
      if ($options.MatchUp cast as boolean?)
      then
        for $matching-key in $keys[starts-with($normalized-language, $$)]
        return $concept-labels-group($matching-key)
      else (),
      if ($options.MatchAnyVariant cast as boolean?)
      then
        if (contains($normalized-language, "-"))
        then
          for $matching-key in $keys[starts-with($$, substring-before($normalized-language, "-") || "-")]
          return $concept-labels-group($matching-key)
        else ()
      else ()
    )
  else ()
};

(:~
 :
Normalizes the language code. This normalized language code can then
 : be used to find the right labels in the concepts collection.

 :
 : @param $language the language identifier.
 :
 : @return the normalized language.
 :)
declare function concepts:normalize-language($language as string) as string
{
  replace(lower-case($language), "_", "-")
};

declare %private %an:strictlydeterministic function concepts:connection() as anyURI
{
  let $credentials :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return if (empty($credentials))
             then error(QName("components:CONNECTION-FAILED"), "no xbrl MongoDB configured")
             else $credentials
  return
    try {
      mongo:connect($credentials)
    } catch mongo:* {
      error(QName("components:CONNECTION-FAILED"), $err:description)
    }
};


adapted expected results to new report

adapted expected results to new report

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";

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as string) as atomic
{
    let $actual as integer := count(parse-json(http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facttable-for-report.jq?_method=POST" || $params).body.content).FactTable[])
    return if ($actual eq $expected) then true else "false [Actual="||$actual||", Expected="||$expected ||"]"
};

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-facttable(82, "&report=FundamentalAccountingConcepts&ticker=ko&fiscalYear=2013&fiscalPeriod=Q1"),
    tickerrole: local:test-facttable(82, "&report=FundamentalAccountingConcepts&ticker=ko&fiscalYear=2012&fiscalPeriod=Q1"),
    tickerfyfprole: local:test-facttable(166, "&report=FundamentalAccountingConcepts&ticker=ko&ticker=wmt&fiscalYear=2013&fiscalPeriod=FY")
})

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";

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as string) as atomic
{
    let $actual as integer := count(parse-json(http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facttable-for-report.jq?_method=POST" || $params).body.content).FactTable[])
    return if ($actual eq $expected) then true else "false [Actual="||$actual||", Expected="||$expected ||"]"
};

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-facttable(74, "&report=FundamentalAccountingConcepts&ticker=ko&fiscalYear=2013&fiscalPeriod=Q1"),
    tickerrole: local:test-facttable(74, "&report=FundamentalAccountingConcepts&ticker=ko&fiscalYear=2012&fiscalPeriod=Q1"),
    tickerfyfprole: local:test-facttable(149, "&report=FundamentalAccountingConcepts&ticker=ko&ticker=wmt&fiscalYear=2013&fiscalPeriod=FY")
})


fixed listing of public reports.

fixed listing of public reports.

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

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

try{
    (: ### INIT PARAMS :)
    let $id := request:param-values("_id")
    let $userids := request:param-values("user")
    let $public-read := request:param-values("public-read")
    let $private := request:param-values("private")

    let $authenticated-user := user:get-existing-by-id(session:validate())
    let $users := for $email in $userids return user:get-existing-by-email($email)

    let $query :=
        {|
            switch(true)
            case (exists($id)) return
                { "_id" :  if(count($id) gt 1 ) then { "$in" : [ $id ] } else $id }
            case $private return
                { "$or":
                    [
                        {"Owner": $authenticated-user.email},
                        {"AccessControlList": { "$elemMatch": { "Type": "User", "Grantee" : $authenticated-user.email, "Permission": { "$in": [ "READ", "WRITE", "FULL_CONTROL" ]}}}}
                    ]
                }
            case $public-read and exists($users) return
                {
                    "Owner": {"$in": [$users ! $$.email]},
                    "ACL": { "$elemMatch": { "Type": "Group", "Grantee" : "http://28.io/groups/AllUsers", "Permission": { "$in": [ "READ", "WRITE", "FULL_CONTROL" ]}}}
                }
            case $public-read and empty($users) return
                {
                    "ACL": { "$elemMatch": { "Type": "Group", "Grantee" : "http://28.io/groups/AllUsers", "Permission": { "$in": [ "READ", "WRITE", "FULL_CONTROL" ]}}}
                }
            default return
                {"Owner": $authenticated-user.email}
        |}

    let $reports as object* :=
        for $report in find("reports", $query)
        where reports:has-report-access-permission($report, $authenticated-user.email, "READ")
        return $report
    return
        switch (true)

        (: ### AUTHENTICATION :)
        case not(session:valid()) return {
            response:status-code(401);
            session:error("Unauthorized: Login required", "json")
        }

        (: ### AUTHORIZATION :)
        case not(session:valid("reports_get")) return {
            response:status-code(403);
            session:error("Forbidden: You are not authorized to access the requested resource", "json")
        }

        (: ### BAD REQUEST HANDLING :)
        case (empty($reports) and exists($id))
        return {
            response:status-code(404);
            session:error("report not found", "json")
        }

        (: ### MAIN WORK :)
        default
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            [ $reports ]
        }
} catch session:expired {
    {
        response:status-code(401);
        session:error("Unauthorized: Login required", "json")
    }
} catch api:missing-parameter {
    if(exists($err:value) and $err:value.parameter eq "token")
    then
    {
        response:status-code(401);
        session:error("Unauthorized: Login required", "json")
    }
    else
    {
        response:status-code(400);
        session:error($err:description, "json")
    }
}

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

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

try{
    (: ### INIT PARAMS :)
    let $id := request:param-values("_id")
    let $userids := request:param-values("user")
    let $public-read := request:param-values("public-read")
    let $private := request:param-values("private")

    let $authenticated-user := user:get-existing-by-id(session:validate())
    let $users := for $email in $userids return user:get-existing-by-email($email)

    let $query :=
        {|
            switch(true)
            case (exists($id)) return
                { "_id" :  if(count($id) gt 1 ) then { "$in" : [ $id ] } else $id }
            case $private return
                { "$or":
                    [
                        {"Owner": $authenticated-user.email},
                        {"AccessControlList": { "$elemMatch": { "Type": "User", "Grantee" : $authenticated-user.email, "Permission": { "$in": [ "READ", "WRITE", "FULL_CONTROL" ]}}}}
                    ]
                }
            case $public-read return             {
                    "Owner": if($users) then {"$in": [$users ! $$.email]} else $authenticated-user.email,
                    "AccessControlList": { "$elemMatch": { "Type": "Group", "Grantee" : "http://28.io/groups/AllUsers", "Permission": { "$in": [ "READ", "WRITE", "FULL_CONTROL" ]}}}
                }
            default return
                {"Owner": $authenticated-user.email}
        |}

    let $reports as object* :=
        for $report in find("reports", $query)
        where reports:has-report-access-permission($report, $authenticated-user.email, "READ")
        return $report
    return
        switch (true)

        (: ### AUTHENTICATION :)
        case not(session:valid()) return {
            response:status-code(401);
            session:error("Unauthorized: Login required", "json")
        }

        (: ### AUTHORIZATION :)
        case not(session:valid("reports_get")) return {
            response:status-code(403);
            session:error("Forbidden: You are not authorized to access the requested resource", "json")
        }

        (: ### BAD REQUEST HANDLING :)
        case (empty($reports) and exists($id))
        return {
            response:status-code(404);
            session:error("report not found", "json")
        }

        (: ### MAIN WORK :)
        default
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            [ $reports ]
        }
} catch session:expired {
    {
        response:status-code(401);
        session:error("Unauthorized: Login required", "json")
    }
} catch api:missing-parameter {
    if(exists($err:value) and $err:value.parameter eq "token")
    then
    {
        response:status-code(401);
        session:error("Unauthorized: Login required", "json")
    }
    else
    {
        response:status-code(400);
        session:error($err:description, "json")
    }
}