diff --git a/CHANGELOG.md b/CHANGELOG.md index 55aeeda408d972df3642507a50cb6949e95dc915..1a0318f9a6edb2805a3a975b39b6253849cf78b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added (for new features, dependecies etc.) +* Support for deeply nested selectors in SELECT queries. * edit_mode supports reference properties when inserting/updating properties (See [#83](https://gitlab.com/caosdb/caosdb-webui/-/issues/83) and [#55](https://gitlab.com/caosdb/caosdb-webui/-/issues/55)). diff --git a/misc/select_query_test_data.py b/misc/select_query_test_data.py new file mode 100755 index 0000000000000000000000000000000000000000..3ecf0a47a56641c1c2a05ea3346e9a4ef2ff0e35 --- /dev/null +++ b/misc/select_query_test_data.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header + +import caosdb + + +d = caosdb.execute_query("FIND Test*") +if len(d) > 0: + d.delete() + +rt_house = caosdb.RecordType("TestHouse").insert() +rt_house.description = "A House" +caosdb.RecordType("TestWindow").insert() +rt_person = caosdb.RecordType("TestPerson").insert() +caosdb.RecordType("TestParty").insert() +caosdb.Property("TestHeight", datatype=caosdb.DOUBLE, unit="ft").insert() +caosdb.Property("TestDate", datatype=caosdb.DATETIME).insert() + +window = caosdb.Record().add_parent("TestWindow") +window.add_property("TestHeight", 20.5, unit="ft") +window.insert() + +owner = caosdb.Record("The Queen").add_parent("TestPerson").insert() + +house = caosdb.Record("Buckingham Palace") +house.description = "A rather large house" +house.add_parent("TestHouse") +house.add_property(rt_person, name="TestOwner", value=owner) +house.add_property("TestWindow", window).insert() + +g1 = caosdb.Record().add_parent("TestPerson").insert() +g2 = caosdb.Record().add_parent("TestPerson").insert() +g3 = caosdb.Record().add_parent("TestPerson").insert() + +party = caosdb.Record("Diamond Jubilee of Elizabeth II").add_parent("TestParty") +party.add_property(rt_house, name="Location", value=house) +party.add_property("TestDate", "2012-02-06") +party.add_property(rt_person, datatype=caosdb.LIST(rt_person), name="Guests", + value = [g1, g2, g3]) +party.insert() diff --git a/src/core/js/ext_references.js b/src/core/js/ext_references.js index 8676bed637fdf8c8134cc2a3a2aba1e914e2807b..39e16d27817f721d9933bfff8fa37ca06bae321b 100644 --- a/src/core/js/ext_references.js +++ b/src/core/js/ext_references.js @@ -651,7 +651,7 @@ var resolve_references = new function () { summary_field.dispatchEvent( resolve_references .summary_ready_event - ); logger.error("here");}) + );}) .catch((err) => { logger.error(err); }) diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index 816d826c0a4d282f6b81f841aad7baa81cdebbbd..299b1dac37a1029e5bc7c48008efa3735d61037b 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -334,6 +334,19 @@ </xsl:with-param> </xsl:call-template> </xsl:for-each> + <xsl:for-each select="Record|RecordType|File|Property"> + <xsl:call-template name="single-value"> + <xsl:with-param name="reference"> + <xsl:value-of select="'true'"/> + </xsl:with-param> + <xsl:with-param name="value"> + <xsl:value-of select="@id"/> + </xsl:with-param> + <xsl:with-param name="boolean"> + <xsl:value-of select="'false'"/> + </xsl:with-param> + </xsl:call-template> + </xsl:for-each> </xsl:element> </div> </xsl:template> @@ -377,17 +390,38 @@ </xsl:when> <!-- hence, this is no collection --> <xsl:otherwise> - <xsl:call-template name="single-value"> - <xsl:with-param name="value"> - <xsl:value-of select="text()"/> - </xsl:with-param> - <xsl:with-param name="reference"> - <xsl:value-of select="not(contains('+INTEGER+DOUBLE+TEXT+BOOLEAN+DATETIME+',concat('+',@datatype,'+')))"/> - </xsl:with-param> - <xsl:with-param name="boolean"> - <xsl:value-of select="@datatype='BOOLEAN'"/> - </xsl:with-param> - </xsl:call-template> + <xsl:choose> + <!-- the referenced entities have been returned. --> + <xsl:when test="*[@id]"> + <xsl:for-each select="*[@id]"> + <xsl:call-template name="single-value"> + <xsl:with-param name="reference"> + <xsl:value-of select="'true'"/> + </xsl:with-param> + <xsl:with-param name="value"> + <xsl:value-of select="@id"/> + </xsl:with-param> + <xsl:with-param name="boolean"> + <xsl:value-of select="'false'"/> + </xsl:with-param> + </xsl:call-template> + </xsl:for-each> + </xsl:when> + <xsl:otherwise> + <!-- only the ids are available --> + <xsl:call-template name="single-value"> + <xsl:with-param name="value"> + <xsl:value-of select="text()"/> + </xsl:with-param> + <xsl:with-param name="reference"> + <xsl:value-of select="not(contains('+INTEGER+DOUBLE+TEXT+BOOLEAN+DATETIME+',concat('+',@datatype,'+')))"/> + </xsl:with-param> + <xsl:with-param name="boolean"> + <xsl:value-of select="@datatype='BOOLEAN'"/> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> </xsl:otherwise> </xsl:choose> <!-- unit --> diff --git a/src/core/xsl/query.xsl b/src/core/xsl/query.xsl index 8b6ae132b7b98118b03882c6bdeec259019c190b..be49ed7d889e920cfee87e723c4c5c3b8efb27b2 100644 --- a/src/core/xsl/query.xsl +++ b/src/core/xsl/query.xsl @@ -169,6 +169,7 @@ </span> </a> </xsl:template> + <xsl:template name="select-table-row"> <xsl:param name="entity-id"/> <tr> @@ -180,7 +181,7 @@ <xsl:with-param name="entity-id" select="$entity-id"/> </xsl:call-template> </td> - <xsl:for-each select="/Response/Query/Selection/Selector[@name!='id']"> + <xsl:for-each select="/Response/Query/Selection/Selector"> <xsl:call-template name="select-table-cell"> <xsl:with-param name="entity-id" select="$entity-id"/> <xsl:with-param name="field-name" select="translate(@name, $uppercase, $lowercase)"/> @@ -188,6 +189,7 @@ </xsl:for-each> </tr> </xsl:template> + <xsl:template name="select-table-cell"> <xsl:param name="entity-id"/> <xsl:param name="field-name"/> @@ -196,18 +198,90 @@ <xsl:value-of select="$field-name"/> </xsl:attribute> <div class="caosdb-f-property-value caosdb-v-property-value"> - <xsl:choose> - <xsl:when test="/Response/*[@id=$entity-id]/@*[translate(name(),$uppercase, $lowercase)=$field-name]"> - <xsl:value-of select="/Response/*[@id=$entity-id]/@*[translate(name(), $uppercase, $lowercase)=$field-name]"/> - </xsl:when> - <xsl:when test="/Response/*[@id=$entity-id]/Property[translate(@name, $uppercase, $lowercase)=$field-name]"> - <xsl:apply-templates mode="property-value" select="/Response/*[@id=$entity-id]/Property[translate(@name, $uppercase, $lowercase)=$field-name]"/> - </xsl:when> - </xsl:choose> + <xsl:apply-templates select="/Response/*[@id=$entity-id]" mode="walk-select-segments"> + <xsl:with-param name="first-segment"> + <xsl:value-of select="substring-before(concat($field-name, '.'), '.')"/> + </xsl:with-param> + <xsl:with-param name="next-segments"> + <xsl:value-of select="substring-after($field-name, '.')"/> + </xsl:with-param> + </xsl:apply-templates> </div> </td> </xsl:template> + + <xsl:template match="Property" mode="walk-select-segments"> + <!-- handle properties --> + <xsl:param name="next-segments"/> + + <xsl:choose> + <xsl:when test="$next-segments='value'"> + <!--handle value--> + <xsl:apply-templates mode="property-value" select="."/> + </xsl:when> + + <xsl:when test="translate($next-segments, $uppercase, $lowercase)='unit'"> + <!--handle unit--> + <xsl:call-template name="single-value"> + <xsl:with-param name="value"> + <xsl:value-of select="@unit"/> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + + <xsl:when test="$next-segments!=''"> + <!--walk to next level of nested entities--> + <xsl:apply-templates select="*[@id]" mode="walk-select-segments"> + <xsl:with-param name="first-segment"> + <xsl:value-of select="substring-before(concat($next-segments, '.'), '.')"/> + </xsl:with-param> + <xsl:with-param name="next-segments"> + <xsl:value-of select="substring-after($next-segments, '.')"/> + </xsl:with-param> + </xsl:apply-templates> + </xsl:when> + + <xsl:otherwise> + <!--next is empty. handle complete property--> + <xsl:apply-templates mode="property-value" select="."/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="*" mode="walk-select-segments"> + <!-- handle anything but attributes and properties --> + <xsl:param name="first-segment"/> + <xsl:param name="next-segments"/> + + <xsl:choose> + <xsl:when test="@*[translate($first-segment, $uppercase, $lowercase)=name()]"> + <!--handle attributes--> + <xsl:call-template name="single-value"> + <xsl:with-param name="value"> + <xsl:value-of select="@*[translate(name(), $uppercase, $lowercase)=$first-segment]"/> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + + <xsl:when test="$next-segments"> + <!-- when there is a next-segmenst --> + <xsl:apply-templates select="Property[translate(@name, $uppercase, $lowercase)=$first-segment]" mode="walk-select-segments"> + <xsl:with-param name="next-segments"> + <xsl:value-of select="$next-segments"/> + </xsl:with-param> + </xsl:apply-templates> + </xsl:when> + + + <xsl:otherwise> + <!-- otherwise, this is the final segment and the reference can be printed. --> + <xsl:value-of select="@id"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template name="caosdb-query-panel"> + <!-- query panel, this is the area which contains the query form and other related stuff (e.g. query short cuts). --> <div class="container caosdb-query-panel"> <form class="panel" id="caosdb-query-form" method="GET"> <xsl:attribute name="action">