Xpath: Select node but not specific child elements - xpath

I have a structure similar to the following:
<page id='1'>
<title>Page 1</title>
<page id='2'>
<title>Sub Page 1</title>
</page>
<page id='3'>
<title>Sub Page 2</title>
</page>
</page>
<page id='4'>
<title>Page 2</title>
</page>
I need to select a page by Id but if that page has descendant pages I don't want to return those elements, but I do want the other elements of that page. If I select Page 1 I want to return title but not the child pages...
//page[#id=1]
The above gets me page 1, but how do I exclude the sub pages? Also, There could be any arbitrary number of elements in a page.
//page[#id=1]/*[not(self::page)]
I have found that this gets me the data I want. However, that data comes back as an array of objects with one object per element and apparently excludes the element names???. I am using PHP SimpleXML for what it is worth.

Use:
//page[#id=$yourId]/node()[not(self::page)]
This selects all nodes that are not page and that are children of any page in the document, the string value of whose id attribute is equal to the string contained in $yourId (most probably you would substitute $yourId above with a specific, desired string, such as '1').
Here is a simple XSLT-based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pId" select="3"/>
<xsl:template match="/">
<xsl:copy-of select="//page[#id=$pId]/node()[not(self::page)]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (wrapped in a single top node to make it well-formed):
<pages>
<page id='1'>
<title>Page 1</title>
<page id='2'>
<title>Sub Page 1</title>
</page>
<page id='3'>
<title>Sub Page 2</title>
</page>
</page>
<page id='4'>
<title>Page 2</title>
</page>
</pages>
the wanted, correct result is produced:
<title>Sub Page 2</title>
Do note: One assumption made is that an id value uniquely identifies a page. If this is not so, the proposed XPath expression will select all page elements whose id attribute has a string valu of $yourId.
If this is the case and only one page element must be selected, the OP must specify which one of the many page elements with this id should be selected.
For example, it may be the first:
(//page[#id=$yourId]/node()[not(self::page)])[1]
or the last:
(//page[#id=$yourId]/node()[not(self::page)])[last()]
or ...

If you're only interested in the title element, this would work:
//page[#id=1]/title
If however you need other sub elements of page, I'm not sure XPath is the right tool for you.
Sounds more like something that an XSLT would be suited for, since what you are really doing is transforming your data.

If the page always has a title:
//page[#id='1']/*[not(boolean(./title))]

Related

Put some elements from component into head tag

I want that some tags from component to go to head tag.
I want to put in the slot named head.
How could achieve this ?
layout.astro
<html>
<head>
<slot name="head" />
</head>
<body>
<slot />
<mycomponent />
</body>
</html>
mycomponent.astro
<link .... slot="head">
<div>
...
</div>
Solution
It is possible to place a tag, but only from where you call a Layout where the single <head> is placed, using slots like this
in the Layout.astro you create a
default slot and
a named slot e.g. name="head" but you could use any name
<head>
<title>{title}</title>
<slot name="head"/>
</head>
<body>
<slot />
</body>
then in your page or in the Component that is calling the Layout
<Layout title="Welcome to Astro.">
<link slot="head" rel="icon" type="image/svg+xml" href="/favicon.svg" />
<main>
<h1>Astro</h1>
</main>
</Layout>
Clarifications
The slot concept is independent from the Layout and head
The Layout component can have any name and can be used from any component
In Astro there can be only one single <head> element per page, all other used <head> tags will stay where they are and will not be moved by the compiler to the main top <head>
A page and all of its children components can use the slot concept to fill it tags inside the single parent <head> tag via slots
This only works if the component directly includes the component e.g. Layout that is providing the slots
References
Note : The reference below from the Astro Documentation website recommends to "place the single <head> and its contents in a layout component."
https://docs.astro.build/en/guides/troubleshooting/#using-head-in-a-component
named slots :https://docs.astro.build/en/core-concepts/astro-components/#named-slots

Find element name via case-insensitive search

Given the following three pieces of XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<body>
<customerSearchRequest>
<id>1234</id>
</customerSearchRequest>
</body>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<BODY>
<userSearchRequest>
<id>5678</id>
</userSearchRequest>
</BODY>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<Body>
<orderSearchRequest>
<id>9101</id>
</orderSearchRequest>
</Body>
</root>
I need to extract the name of the first-child of body (i.e. customerSearchRequest, userSearchRequest and orderSearchRequest), which I am currently doing as follows:
name(//SOAP-ENV:body/*[1])
The problem is, this only works for the first request as body is case-sensitive. How do I make the path case-insensitive?
Thanks for any pointers.
I would go with this :
name(//SOAP-ENV:*[translate(local-name(),'BODY','body')='body']/*[1])
The following part first finds elements in namespace of SOAP-ENV, and then filter to those with local-name equals 'body', case-insensitive :
//SOAP-ENV:*[translate(local-name(),'BODY','body')='body']
If you have XPath 2.0 available, it can be done in a cleaner way using lower-case(), upper-case(), or regex based functions like matches() : case-insensitive matching in xpath?

GSA use connector for databases do not have metadata?

We are working on a GSA project. We are using GSA version 7.2 and Connector database adapter 3.2.4.
That SQL as
SELECT EMPLOYEE_ID,
FIRST_NAME,
LAST_NAME,
EMAIL,
PHONE_NUMBER,
HIRE_DATE,
JOB_ID,
SALARY,
COMMISSION_PCT,
MANAGER_ID,
DEPARTMENT_ID
FROM HR.EMPLOYEES ;
How to write "Stylesheet for Serving Results" for show all metadata from SQL.
You must customize stylesheet as follow:
`<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<head>
<xsl:for-each select="pland_connector"> <!-- name of connector -->
<!-- now for every single field from the database you want to be able to filter upon or show in your result, add a meta-field -->
<title><xsl:value-of select="LAST_NAME"/></title> <!-- don't forget the title -> that is the default title in the GSA result -->
<meta name="EMPLOYEE_ID"><xsl:attribute name="content"><xsl:value-of select="EMPLOYEE_ID"/></xsl:attribute></meta>
<!-- mind the capital sensitivity of XML, dependend on the notation in you db view or table the value-of should be in capitals or not -->
<meta name="LAST_NAME"><xsl:attribute name="content"><xsl:value-of select="LAST_NAME"/></xsl:attribute></meta>
<meta name="FIRST_NAME"><xsl:attribute name="content"><xsl:value-of select="FIRST_NAME"/></xsl:attribute></meta>
<meta name="EMAIL"><xsl:attribute name="content"><xsl:value-of select="EMAIL"/></xsl:attribute></meta>
<meta name="PHONE_NUMBER"><xsl:attribute name="content"><xsl:value-of select="PHONE_NUMBER"/></xsl:attribute></meta>
<!-- Etc..... -->
</xsl:for-each>
<!-- You can add static metadata to -->
<meta name="test" content="Person"/>
</head>
<body>
<!-- Just see what data you want in the content-field -->
<xsl:for-each select="pland_connector"> <!-- Name of database -->
<h1><xsl:value-of select="NAME"/></h1></br>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>`
I think you will completed.

In Symphony CMS, how do I call an image into 'get-article.xsl'?

In my 'get-article.xsl', how do I call an image? I added a field in the article editor page to include a file upload, which uploads the image file to workspace/uploads. The code that I am trying to use to call the image is this, but I am not sure if it is correct.
<xsl:call-template name="article-images/entry">
<xsl:with-param name="entry-id" select="#id"/>
</xsl:call-template>
I have not changed anything in the default 'get-images.xsl', as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="get-images">
<xsl:param name="entry-id"/>
<xsl:if test="/data/article-images/entry[article/item/#id = $entry-id]">
<div class="article-images">
<xsl:apply-templates select="/data/article-images/entry[article/item/#id = $entry-id]"/>
</div>
</xsl:if>
</xsl:template>
<xsl:template match="article-images/entry">
<a href="{$workspace}/uploads/{image/filename}">
<xsl:if test="position() mod 4 = 0">
<xsl:attribute name="class">last-column</xsl:attribute>
</xsl:if>
<img title="{description}">
<xsl:attribute name="src">
<xsl:value-of select="$root"/>
<xsl:text>/image/2/133/88/2/uploads/</xsl:text>
<xsl:value-of select="image/filename"/>
</xsl:attribute>
</img>
</a>
</xsl:template>
</xsl:stylesheet>
I assume there's you already have this Article Images DS and get-images.xsl (Utilities). If so, then you must add this Article Images DS in your Article "PAGE".
Once you added the Article Images. Check your get-article.xsl utilities if there is this kind of code:
<xsl:call-template name="get-images">
<xsl:with-param name="entry-id" select="#id"/>
</xsl:call-template>
If you can see it there. Go to your admin panel and add article or edit an existing one.
On the edit or create article page (where you can see text fields and areas). There's a button "Show Associates" at the upper right. Click that button, and another field where you can add images will appear. Just upload an image and it will automatically added in your article.

Working With Nested XPath Predicates ... Refined

All great answers! But the question deserves refinement ...
I've got the following sample XML ...
<objects>
<object objectId="1123" ... />
<properties refObjectId="1123" ... />
<properties refObjectId="1123" refPropertyId="2311" ... />
<properties refObjectId="1123" refPropertyId="4611" ... />
<object objectId="2123" ... />
<properties refObjectId="2123" refPropertyId="4311" ... />
<properties refObjectId="2123" refPropertyId="8611" ... />
....
</objects>
... and the following XPath query ...
//object[//properties[#refObjectId=#objectId and not(#refPropertyId)]]
I thought this query would return all object nodes where there is a properties node that has a refObjectId attribute that equals the objectId attribute of the object node and no 'refPropertyId' attribute ... namely object 1123 only, not object 2123 ... but it doesn't. It seems the #objectId in the nested predicate does not refer to the objectId attribute of the object node.
Any ideas? I know the XML structure isn't nested as you would expect, but there are reasons for this structure.
Generally you should avoid using // where you can. I'd consider rephrasing:
//object[../properties/#refObjectId=#objectId]
In the expression provided, your nested predicate is actually checking for
//properties/#refObjectId=//properties/#objectId
of which there are none.
I hope this helps!
EDIT: Since the question has been updated here is an updated response:
You added "It seems the #objectId in the nested predicate does not refer to the objectId attribute of the object node." You're absolutely right! So let's fix it!!
//object[../properties[not(#refPropertyId)]/#refObjectId=#objectId]
This should be closer to what you're after!
Try this:
//objects[object/#objectId = properties/#refObjectId]/object
This should work:
//objects/object[#objectId = ../properties/#refObjectId]
I am not sure how your xml is. However, if it is in the following format:
<objects>
<object objectId="1111" />
<properties refObjectId="1111" />
<object objectId="2111" />
<properties refObjectId="3111" />
<object objectId="4111" />
<properties refObjectId="5111" />
<object objectId="6111" />
<properties refObjectId="4111" />
<object objectId="7111" />
<properties refObjectId="7111" />
</objects>
Then you should use the following xpath to get only objects 1111 and 7111. The result should not include 4111 because the properties where refObjectId = 4111 does not immediately follow the object whose objectId=4111.
//objects/properties[#refObjectId = preceding::object[1]/#objectId]/preceding::object[1]
Assuming that all <properties> nodes that belong to a given <object> actually follow that object (your input seems to imply that), you could do:
/objects/properties[
#refObjectId = preceding-sibling::object[1]/#objectId
and
not(#refPropertyId)
]/preceding-sibling::object[1]
This should perform pretty well.
If you happen to be in XSLT, things get a lot simpler:
<xsl:key name="kPropertiesByObjectId" match="properties" use="#refObjectId" />
and
<xsl:template match="object">
<!-- This tests for an empty node-set. Non-empty node sets can only happen
for objects with at least one <properties> node without #refPropertyId -->
<xsl:if test="key('kPropertiesByObjectId', #objectId)[not(#refPropertyId)]">
<xsl:copy-of select="." />
</xsl:if>
</xsl:template>
In the XSLT case, the order of object and proerties nodes becomes irrelevant.

Resources