Replace text node with element if text match in the document BaseX - xquery

I am trying to replace text node with element if document matches that text, below query I have tried but it is giving error "Target is not an element, text, attribute, comment or pi" below is my query.
inputXML:
<book>
<p>Isn't it lovely here? Very smart. We'll be like three queens when you've finished with us,
Edie. You doing well then?</p>
<p>
<name type="person">April De Angelis</name>’ plays include <title type="work">Positive
Hour</title> (Out of Joint) <title type="work">Playhouse Creatures</title> (<name
type="org">Sphinx Theatre Company</name>), <title type="work">Hush</title> (<name
type="org">Royal Court</name>), <title type="work">Soft Vengeance</title>, <title
type="work">The Life and Times of Fanny Hill</title> (adapted from the <name type="org"
>John Cleland novel</name>) and <title type="work">Ironmistress</title>. Her work for
radio includes <title>The Outlander</title> (<name type="org">Radio 5</name>), which won the
<name type="org">Writers’ Guild Award</name> (<date>1992</date>), and, for opera, <title
type="work">Flight</title> with composer <name type="person">Jonathan Dove</name> (<name
type="place">Glyndebourne</name>, <date>1998</date>).</p>
</book>
Expected output:
<book>
<p>Isn't it lovely here? Very smart. We'll be like three <highlight>>queens</highlight> when
you've finished with us, Edie. You doing well then?</p>
<p>
<name type="person">April De Angelis</name>’ plays <highlight>include</highlight>
<title type="work">Positive Hour</title> (Out of Joint) <title type="work">Playhouse
Creatures</title> (<name type="org">Sphinx Theatre Company</name>), <title type="work"
>Hush</title> (<name type="org">Royal Court</name>), <title type="work">Soft
Vengeance</title>, <title type="work">The Life and Times of Fanny Hill</title> (adapted
from the <name type="org">John Cleland novel</name>) and <title type="work"
>Ironmistress</title>. Her work for radio includes <title>The Outlander</title> (<name
type="org">Radio 5</name>), which won the <name type="org">Writers’ Guild Award</name>
(<date>1992</date>), and, for opera, <title type="work">Flight</title> with composer
<name type="person">Jonathan Dove</name> (<name type="place">Glyndebourne</name>,
<date>1998</date>).</p>
</book>
I am using BaseX version 9.5.1 below is the code.
let $body := <indexedterms>
<content>
<terms>
<term>include</term>
<term>Queens</term>
</terms>
<uri>/IEEE/IEEE/test.xml</uri>
</content>
</indexedterms>
for $contents in $body/content
let $uri := $contents/uri
let $doc := fn:doc($uri)
for $selectedterm in $contents/terms/term/string()
let $Modifieddoc := copy $c := $doc
modify
(
for $nodes in $c//*//text()[fn:matches(.,$selectedterm)]/parent::*
return
if($nodes/node()[fn:matches(.,$selectedterm)]/parent::*:highlight)
then ()
else
replace node $nodes/$selectedterm with <highlight>{$selectedterm}</highlight>
)
return $c
return
db:replace('IEEE',substring-after($uri,'/IEEE'),$Modifieddoc)
Previously I was using the "replace node $nodes/node()[fn:contains(.,$selectedterm)] with {$selectedterm} " instead of "replace node $nodes/$selectedterm with {$selectedterm}" it was doing the work but where terms like steam e.g.(include, includes) so it was matching the both words which is not correct so I have changed the code to "replace node "$nodes/$selectedterm with {$selectedterm}"

$nodes/$selectedterm is probably the culprit and most likely not what you want as the $selectedterm variable is a sequence of string values (you bind for $selectedterm in $contents/terms/term/string()). It might help us understand what you want to achieve if you show us a sample document you load with the doc function and the update you want to do on that with BaseX, for instance, for the two sample terms you have shown in your code snippet.
Your task of identifying and wrapping search terms in your text contents can be done nicely in XSLT 3 or 3 which you can run with BaseX if you put Saxon 9.9 or 10 or 11 on the class path:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:param name="terms" as="xs:string*" select="'include', 'Queens'"/>
<xsl:output method="xml" indent="no"/>
<xsl:template match="p//text()">
<xsl:apply-templates select="analyze-string(., string-join($terms, '|'), 'i')/node()"/>
</xsl:template>
<xsl:template match="fn:match">
<highlight>{.}</highlight>
</xsl:template>
<xsl:template match="fn:non-match">
<xsl:apply-templates/>
</xsl:template>
<xsl:mode on-no-match="shallow-copy"/>
</xsl:stylesheet>
As the used analyze-string function exists also in BaseX/XQuery you should also be able to use XQuery update on the result calling that function, i.e. by replacing fn:match elements with highlight elements.

Related

Edit a xml file using c# asp.net

Here is my xml file :
I want to edit this using c# and my code is like that
XmlDocument xml = new XmlDocument();
xml.Load(Server.MapPath("xyz.XML"));
foreach (XmlElement element in xml.SelectNodes("//table"))
{
foreach (XmlElement element1 in element)
{
if (element.SelectSingleNode("//cell").InnerText == "Amit Pate")
{
XmlNode newname = xml.CreateElement("Name");
newname.InnerText = Name.Text;
element.ReplaceChild(newname, element1);
//xml.Save(Server.MapPath("xyz.XML"));
}
if (element.SelectSingleNode("//cell").InnerText == "SSE")
{
XmlNode newdsg = xml.CreateElement("Designation");
newdsg.InnerText = Designation.Text;
element.ReplaceChild(newdsg, element1);
//xml.Save(Server.MapPath("xyz.XML"));
}
if (element.SelectSingleNode("//cell").InnerText == "asp.net")
{
XmlNode newskill = xml.CreateElement("Skill");
newskill.InnerText = Skill.Text;
element.ReplaceChild(newskill, element1);
//xml.Save(Server.MapPath("xyz.XML"));
}
xml.Save(Server.MapPath("xyz.XML"));
}
}
It's not working properly. It always updated only the first node of the xml file. Please tell me what I'm missing or any other better way to edit the xml file.
Thank you
While I know nothing of C# or ASP.Net, but do know XML, you may have to modify your inner for loop and if logic to explicitly search each <cell> under each <row>:
foreach (XmlElement rows in xml.SelectNodes("//table"))
{
foreach (XmlElement row in rows.SelectNodes("//row"))
{
if (row.SelectSingleNode("cell").InnerText ...
Right now, it seems you are searching all <cell> nodes under <table> since you use the double forward XPath expression on the outer loop's element variable and then select only one of them (always the first) with SelectSingleNode() (same method used in VB and VBA which I know a bit).
With that said, consider using XSLT, a special-purpose, declarative language and sibling to XPath, designed specifically to edit a XML file. In doing so, you avoid any looping and conditional logic. Practically all general purpose languages carry an XSLT 1.0 processor including C#, ASP, VB, Java, PHP, Python, Perl, etc. and these same languages can call external command lines to dedicated XSLT processors like Saxon or Xalan, even XSLT 2.0 and 3.0 processors.
Below is an example to replicate your objective in what appears to be renaming <cell> nodes:
XML Input
<document>
<page>
<table rows="3" cols="1">
<row>
<cell>Amit Pate</cell>
</row>
<row>
<cell>SSE</cell>
</row>
<row>
<cell>asp.net</cell>
</row>
</table>
</page>
</document>
XSLT Script (save as .xsl --special well-formed .xml file-- and read in like any other .xml)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/document|page">
<xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="table">
<xsl:copy>
<row><Name><xsl:value-of select="row[1]"/></Name></row>
<row><Designation><xsl:value-of select="row[2]"/></Designation></row>
<row><Skill><xsl:value-of select="row[3]"/></Skill></row>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML Output
<?xml version="1.0"?>
<document>
<page>
<table>
<row>
<Name>Amit Pate</Name>
</row>
<row>
<Designation>SSE</Designation>
</row>
<row>
<Skill>asp.net</Skill>
</row>
</table>
</page>
</document>
References
C#/VB - XSLT Transformations with the XslTransform Class
Using XSLT within ASP

XQuery: Return value of attribute if it contains another spesific attribute

I want to return a result on all the books which contain the "databases" attribute in inside of "field".
for $e in //testbook[field="databases"]
return $e/title
Sample of the .xml file:
<?xml version="1.0" encoding="UTF-?>
<testbook>
<book>
<author> AuthorGuy </author>
<title> theBook</title>
<field> databases </field>
</book>
</testbook>
There are two issues:
<title/> and <field/> elements are contained in a <book/> element, and are not direct children of <testbook/>, fix the path expressions.
The <field/> tag contains the field wrapped in whitespace, use contains($string, $needle) instead of a simple comparison.
A working example:
let $document := document{<testbook>
<book>
<author> AuthorGuy </author>
<title> theBook</title>
<field> databases </field>
</book>
</testbook>}
for $e in $document//testbook[contains(book/field, "databases")]
return $e/book/title

How to output an IMG tag from XML using XSLT

Here is my XML:
<?xml version="1.0" encoding="UTF-8"?>
<Collection>
<Content>
<ID>2779</ID>
<Type>Content</Type>
<Title>Article One</Title>
<QuickLink>/template.aspx?id=2779</QuickLink>
<Teaser />
<Html>
<root>
<NewsArticle>
<artTitle>The Comprehensive Breast Center: Quality Care on the Fast Track</artTitle>
<artThumb>
<img alt="Thumb Article One" src="/uploadedImages/Test/News/artOne.png?n=5954" />
</artThumb>
<artFull />
<releaseDate />
<contactName />
<contactPhone />
<contactEmail />
<artTeaser>The National Cancer Institute estimates that a woman in the United States has a 1 in 8 chance of developing invasive breast cancer</artTeaser>
<artText>
<p>The Comprehensive Breast Center: Quality Care on
the Fast Track</p>
<p>
How do I display the IMG tag from my XML above to an HTML document using XSLT
Something like this should do the trick:
<xsl:template match="/">
<xsl:for-each select="Collection/Content">
<xsl:copy-of select="Html/root/NewsArticle/artThumb/node()"/>
</xsl:for-each>
</xsl:template>
I should note that this assumes you're getting this from an Ektron collection -- assumption made based on your tagging of this question. This will display the image from each content block in the collection. If you want just the image from the first content block of the collection, you could remove the for-each and instead use something like this:
<xsl:template match="/">
<xsl:copy-of select="Collection/Content/Html/root/NewsArticle/artThumb/node()"/>
</xsl:template>
Also, it works either way, but i removed the slash from the front of the select on the for-each. Seemed redundant since the code is in a template that already matches on "/".
UPDATE
Some of that can be done in the workarea -- it allows you to set the css class, though I'm not sure if you can set the title attribute of an image. Here's how you could do that via XSLT. In this case, you can't copy the node whole-sale:
<xsl:template match="/">
<xsl:for-each select="Collection/Content">
<xsl:variable name="imageSrc" select="Html/root/NewsArticle/artThumb/img/#src" />
<xsl:variable name="imageId">
<xsl:text>NewsArticle_</xsl:text>
<xsl:value-of select="ID" />
<xsl:text>_image</xsl:text>
</xsl:variable>
<xsl:variable name="contentTitle" select="Html/root/NewsArticle/artTitle" />
<img id="{ $imageId }" class="myCssClass" title="{ $contentTitle }" alt="{ $contentTitle }" src="{ $imageSrc }" />
</xsl:for-each>
</xsl:template>
(Updated again - appears i misread your comment. Thanks, #MathiasMüller!)
When assigning ids to elements like this, I prefer to use a little more than just the content id. In this case, by using "NewsArticle_{Content ID}image", I allow for a container div to use an id "NewsArticle{Content Id}" if it is needed in the future without colliding with the image ids.
How do i assign a title and an alt from artTitle and also a class and id?
Building upon the answer given by #BrianOliver, this is how you output an img element whose "title" attribute reflects the content of artTitle from your input XML - the same for ID.
I assume that by "an alt from artTitle" you mean that the text content of img/#alt should also come from the artTitle element.
<xsl:template match="/">
<xsl:for-each select="Collection/Content">
<xsl:variable name="imageSrc" select="Html/root/NewsArticle/artThumb/img/#src" />
<!--xsl:variable name="imageAlt" select="Html/root/NewsArticle/artThumb/img/#alt" /-->
<xsl:variable name="imageId" select="ID"/>
<xsl:variable name="imageTitle" select="Html/root/NewsArticle/artTitle"/>
<img id="{$imageId}" class="myCssClass" title="{$imageTitle}" alt="{ $imageTitle}" src="{$imageSrc}"/>
</xsl:for-each>
</xsl:template>
However, I am not sure where the class attribute should come from.

processing css stylesheet with xml document

I am new to XML (a couple of days now...)and everything is going good, however, I can't seem to get the xml processed with css style
here is my perl...
#!/usr/bin/perl -w
use strict;
use warnings;
use diagnostics;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 });
my $ifile="elementarray.csv";
my $ofile="elementarray.xml";
open my $fh, "<", $ifile or die $ifile.": $!";
open my $out, "> ".$ofile or die "Cannot write ".$ofile.": $!";
print $out <<EOT;
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="elementarray.xsl"?>
<?xml-stylesheet href="elementarray.css" title="Default style"?>
<EMAILCOMMENTS>
EOT
# First record contains list of fieldnames
#my $fields = $csv->getline($fh);
#print #$fields;
while (my $row = $csv->getline($fh)) {
last unless $row && #$row;
# Encode "<" characters as "<" and "&" as "&" in all fields
foreach (#$row) {
s/&/&/g;
s/</</g;
}
# Create hash of fields using hash slice
my %row;
#row{"Subject","Body","From: (Name)","From: (Address)","To: (Name)","To:(Address)","Date","Time"} = #$row;
print $out <<EOT;
<EMAIL>
<HEADER>
<ORIGNAME>$row{"From: (Name)"}</ORIGNAME>
<ORIGADDRESS>$row{"From: (Address)"}</ORIGADDRESS>
<DESTNAME>$row{"To: (Name)"}</DESTNAME>
<DESTADDRESS>$row{"To: (Address)"}</DESTADDRESS>
<SUBJECT>$row{"Subject"}</SUBJECT>
</HEADER>
<BODY>$row{"Body"}</BODY>
<DATE id="date">$row{"Date"}</DATE>
<TIME id="time">$row{"Time"}</TIME>
</EMAIL>
EOT
}
print $out "</EMAILCOMMENTS>\n";
$csv->eof or $csv->error_diag;
close $fh or die $ifile.": $!";
close $out or die $ofile.": $!";
Here is my xsl...
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="html"/>
<xsl:template match="/"><!-- a 'pattern', / matches the root node -->
<html>
<head>
<title>E-mail</title>
</head>
<body>
<xsl:apply-templates/><!-- an instruction -->
</body>
</html>
</xsl:template>
<xsl:template match="HEADER">
<span STYLE="color:red; font-weight:bold; font-style:italic">
<xsl:value-of select="SUBJECT"/>
</span>
From: <xsl:call-template name="formatEmail">
<xsl:with-param name="address" select="ORIGADDRESS"/>
</xsl:call-template>
To: <xsl:call-template name="formatEmail">
<xsl:with-param name="address" select="DESTADDRESS"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="EMAIL">
<xsl:apply-templates select="HEADER"/>
<pre>
<xsl:value-of select="BODY"/>
</pre>
<div>
<span>
<xsl:attribute name="id"><xsl:value-of select="date"/></xsl:attribute>
<!-- --><xsl:value-of select="DATE"/><!-- --></span>
<span>
<xsl:attribute name="id"><xsl:value-of select="time"/></xsl:attribute>
<!-- --><xsl:value-of select="TIME"/><!-- --></span>
</div>
<br />
</xsl:template>
<xsl:template name="formatEmail">
<xsl:param name="address"/>
<a>
<xsl:attribute name="href"><xsl:value-of select="concat('mailto:',$address)"/></xsl:attribute>
<xsl:value-of select="$address"/>
</a>
</xsl:template>
</xsl:stylesheet>
here is my xml (1 record)...
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="elementarray.xsl"?>
<?xml-stylesheet href="elementarray.css" title="Default style"?>
<EMAILCOMMENTS>
<EMAIL>
<HEADER>
<ORIGNAME>William Holt</ORIGNAME>
<ORIGADDRESS><x#gmail.com></ORIGADDRESS>
<DESTNAME>X</DESTNAME>
<DESTADDRESS>bill#elementarray.com</DESTADDRESS>
<SUBJECT>welcome to the neighborhood</SUBJECT>
</HEADER>
<BODY>just thought i'd say hello</BODY>
<DATE id="date">07/18/2013</DATE>
<TIME id="time">11:53</TIME>
</EMAIL>
</EMAILCOMMENTS>
and here is css ( style for just to see if it is working, and it isn't :( )...
DATE{background-color:red;}
TIME{background-color:green;}
#date{background-color:yellow;}
#time{background-color:blue;}
BODY{background-color:grey;}
Sorry for the length of the post but I think you need all these files...
Although you can indeed style XML with CSS, using the xml-stylesheet processing instruction you have included in your XML, you can't transform it with XSLT at the same time. That is to say you should only really have one stylesheet processing instruction in your XML. Either you simply style your XML with CSS (which is not really a common thing to do), or your transform it to HTML with XSLT and style the HTML output with CSS.
In the latter case, remove the <?xml-stylesheet href="elementarray.css"?> instruction from your XML document, and instead output a reference to the CSS document in your first template.
<xsl:template match="/">
<html>
<head>
<title>E-mail</title>
<link href="elementarray.css" type="text/css" rel="stylesheet" />
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
Now, in your CSS, I see you already have selectors for the date and time ids, so you need to ensure the relevant elements in your HTML have these ids.
At the moment, you are doing this in your XSLT
<span>
<xsl:attribute name="id"><xsl:value-of select="date"/></xsl:attribute>
<!-- --><xsl:value-of select="DATE"/><!-- --></span>
<span>
But this is setting the id attribute to have the value of the date element in your XML, but such an element does not exist! I think you meaning to use the literal string here. Now, you could do this...
<xsl:attribute name="id"><xsl:value-of select="'date'"/></xsl:attribute>
But this is verbose. You can just do this:
<xsl:attribute name="id">date</xsl:attribute>
But better still, just write it out as an attribute in the normal way:
<span id="date">
<xsl:value-of select="DATE"/>
</span>
And similarly for 'time'. This should ensure the two span tags get styled using your CSS in the HTML you output.

Select/Output unique values

I need to loop through an XML document (no problem over there) and check if a value that i find is already in a (a) tag in a div in my XSL document that i am generating, only if the value is not in that (a) tag i should create a new (a) tag for it and put in in the div that i am checking...
Any one knows how to do it dynamically in XSLT?
<div id="tags"><span class="l_cap"> </span>
all
<xsl:for-each select="root/nodes/node/data/genres">
<xsl:for-each select="value">
**<xsl:if test="not(contains())">**
<xsl:value-of select="current()"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
 
sorry for before, what i am trying to do is: in the if statement, check if the current value is already exist in the div if not, add it, if is, don't do anything...
10x again
It sounds like you're trying to create a distinct list of all of the "genres" in your list.
Assuming a data structure which looks a bit like this:
<root>
<nodes>
<node>
<data>
<genres>
<value>One</value>
<value>Two</value>
<value>Two</value>
<value>Three</value>
<value>Two</value>
</genres>
</data>
</node>
</nodes>
</root>
And a stylesheet which looks a bit like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="genres" match="value" use="."/>
<xsl:template match="/">
<div>
<xsl:for-each select="/root/nodes/node/data/genres/value">
<xsl:if test="generate-id(.) = generate-id(key('genres', .)[1])">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
Then you will end up with something like this:
<div>
One
Two
Three
</div>
This is a fairly standard XSLT 1.0 technique. It uses keys (described here: http://www.xml.com/pub/a/2002/02/06/key-lookups.html ) to create a sort of index of all the /root/nodes/node/data/genres/value entries. Then it loops through all of the entries, but only prints the first one of each type. The end result is that each value will only be output once.

Resources