In the play (http://www.ibiblio.org/xml/examples/shakespeare/as_you.xml), I am looking to get the lines spoken by SPEAKERS, that contain the word love in it. (i.e. it love as in loved, should not be displayed). In the output, CHARLES is the name of the speaker, PLAY is the title of the play, followed by the line containing the word love in it, spoken by that person.
==============================
<line speaker="CHARLES" play="As You Like It">for your love, I would be loath to foil him, as I</line>
<line speaker="CHARLES" play="As You Like It">out of my love to you, I came hither to acquaint you</line>
<line speaker="OLIVER" play="As You Like It">Charles, I thank thee for thy love to me, which</line>
<line speaker="CELIA" play="As You Like It">that I love thee. If my uncle, thy banished father,</line>
...
==============================
Any guidance will be be appreciated.
The following should work:
declare function local:escape-for-regex
( $arg ) as xs:string {
replace($arg,
'(\.|\[|\]|\\|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\\$1')
} ;
declare function local:contains-word
($arg as xs:string,
$word as xs:string) as xs:boolean {
matches(
upper-case($arg),
concat('^(.*\W)?',
upper-case(local:escape-for-regex($word)),
'(\W.*)?$'
))
};
for $play in doc('http://www.ibiblio.org/xml/examples/shakespeare/as_you.xml')/PLAY
for $l in $play//LINE
where $l[local:contains-word(., 'love')]
return <line speaker="{$l/preceding-sibling::SPEAKER}" play="{$play/TITLE}">{$l/text()}</line>
The function local:contains-word() is directly taken from the excellent FunctX library, see http://www.xqueryfunctions.com/xq/functx_contains-word.html for more information. Of course you can also simply import the complete library and use the given function.
Related
I have a listPers.xml (TEI List containing persons, obviously ) . I want to write a function to update the listPers.xml
My function looks like this:
declare function app:addPerson($node as node(), $model as map(*)) {
let $person := "<person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></person>"
let $list := doc(concat($config:app-root, '/resources/listPers_test.xml'))
return
update insert $person into $list//tei:listPerson
};
And the listPerson.xml
looks more or less like a typical list with person-entries
I have a tei:header (here omitted) followed by
<text>
<body>
<listPerson xml:id="person">
<person xml:id="abbadie_jacques">
<persName ref="http://d-nb.info/gnd/100002307">
<forename>Jacques</forename>
<surname>Abbadie</surname>
</persName>
<note>Prediger der französisch-reformierten Gemeinde in <rs type="place" ref="#berlin">Berlin</rs>
</note>
</person>
</body>
</text>
</TEI>
(sorry for ruining indentions, it's just an excerpt )
I do not get an error, which means that my app:addPerson should be fine, right?
I want the listPers_test to look like this:
<text>
<body>
<listPerson xml:id="person">
<person xml:id="abbadie_jacques">
<persName ref="http://d-nb.info/gnd/100002307">
<forename>Jacques</forename>
<surname>Abbadie</surname>
</persName>
<note>Prediger der französisch-reformierten Gemeinde in <rs type="place" ref="#berlin">Berlin</rs>
</note>
</person>
<!-- here comes the output that I wish to have :-) -->
<person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></person>
</body>
</text>
</TEI>
In the long run, I aim for an html-form that allows users to input names etc., where ids are generated using sth like
to-lowercase(concat($surname, "_", $forename));
But I will not get into my questions regarding forms and xquery, as I have barely done a quick Google-trip regarding html forms and xquery!
Can anyone hint me at why I do not get the listPers_test.xml file updated with the second value? :-)
All the best and thanks in advance to everyone,
K
Alright, I have a solution for anyone interested in it:
My first snippet $person:= ... contains a STRING, not an element.Changing the line
let $person := "<person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></person>"
to this one actually solves the issue:
let $person := <tei:person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></tei:person>
I have a movie database and want to search for actors with last and/or first name. The goal is to get as list the actors name and the title as well as the role name the actor played in the movie.
The XML movie database looks like this:
<movies>
<movie>
<title>A History of Violence</title>
<year>2005</year>
<country>USA</country>
<genre>Crime</genre>
<summary>Tom Stall, a humble family man and owner of a
popular neighborhood restaurant, lives a quiet but
fulfilling existence in the Midwest. One night Tom
foils a crime at his place of business and, to his
chagrin, is plastered all over the news for his
heroics. Following this, mysterious people follow
the Stalls' every move, concerning Tom more than
anyone else. As this situation is confronted, more
lurks out over where all these occurrences have
stemmed from compromising his marriage, family
relationship and the main characters' former
relations in the process.</summary>
<director>
<last_name>Cronenberg</last_name>
<first_name>David</first_name>
<birth_date>1943</birth_date>
</director>
<actor>
<first_name>Vigo</first_name>
<last_name>Mortensen</last_name>
<birth_date>1958</birth_date>
<role>Tom Stall</role>
</actor>
<actor>
<first_name>Maria</first_name>
<last_name>Bello</last_name>
<birth_date>1967</birth_date>
<role>Eddie Stall</role>
</actor>
<actor>
<first_name>Ed</first_name>
<last_name>Harris</last_name>
<birth_date>1950</birth_date>
<role>Carl Fogarty</role>
</actor>
<actor>
<first_name>William</first_name>
<last_name>Hurt</last_name>
<birth_date>1950</birth_date>
<role>Richie Cusack</role>
</actor>
</movie>
Actually I have the following code and it works so far but for example for the query with last_name=Dunst I get as result:
1. Dunst, Kirsten
movie title as role
2. Dunst, Kirsten
movie title as role
but I want to have the actor just one time so I tried to add distinct-values() but it doesn´t work :(
I would like to have the output like this:
1. Dunst, Kirsten
movie title as role
movie title as role
Here is the code:
xquery version "3.0";
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";
let $last_name := distinct-values(request:get-parameter('last_name', ''))
let $first_name := distinct-values(request:get-parameter('first_name', ''))
let $movies := collection('/db/Movie/data')/movies/movie/actor[if(not($last_name)) then xs:boolean(1) else equals(last_name, $last_name)][if(not($first_name)) then xs:boolean(1) else equals(first_name, $first_name)]
return
<html>
<head>
</head>
<body>
<h1>Search results for actor {$last_name} {$first_name}:</h1>
<ol>{
for $movie in $movies
let $title := $movie/../title/text()
let $role := $movie/role/text()
return
<li>{$movie/last_name/text()}, {$movie/first_name/text()} <p> In the movie <i>{$title}</i> as role <i>{$role}</i> </p></li>
}</ol>
</body>
</html>
Hope someone can help me ;)
Thanks in advance!
The variable $movies is bound to a sequence of actor elements, which causes some confusion.
If you only intend to use this for one actor at a time, you can simply put the actor's name prior to the FLWOR expression, and get your intended output:
<ol>
<li>
<p>{ $movies[1]/last_name }, { $movies[1]/first_name}</p>
{
for $movie in $movies
let $title := $movie/../title
let $role := $movie/role
return
<p>In the movie <i>{$title}</i> as role <i>{$role}</i></p>
}</li>
</ol>
Note: the text() path selector is unnecessary in this case, and occasionally confusing as it can return a sequence of text nodes. If you need to ensure a type constraint, consider using fn:string() instead.
i have asked regarding the questions about travelling,
byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).
byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).
byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(losAngeles,auckland).
and i have amended to this:
connect(X,Y,byCar):- byCar(X,Y) ; byCar(Y,X).
connect(X,Y,byTrain):- byTrain(X,Y) ; byTrain(Y,X).
connect(X,Y,byPlane):- byPlane(X,Y) ; byPlane(Y,X).
travel(X,Y):-X\=Y,travelLoop(X,Y,Passed).
travelLoop(X,Y,Passed):-connect(X,Thru,byCar);
connect(X,Thru,byTrain);
connect(X,Thru,byPlane);
\+memberchk(Thru,Passed),travelLoop([Thru|Passed],Y,Thru).
travelLoop(X,Y,Path):- X=Y.
travel(X,Y):-travel_Path(X,Y,[X]),
travel_Path(Y,X,[Y]) -> true ; wrong.
wrong:- writeln('Wrong travel input, please try again.'), nl.
it works well. Can i just ask one more favour, which is to be able to show all alternative travelling path, to and fro. Just managed to get it for one way only.
(c)
travel(X,Y,go(X,Y)):-byCar(X,Y).
travel(X,Y,go(X,Y)):-byTrain(X,Y).
travel(X,Y,go(X,Y)):-byPlane(X,Y).
travel(X,Y,go(X,Z,W)):-travel(X,Z,go(X,Z)),travel(Z,Y,W).
findall(X,travel(A,B,X),List),nl,
writelist(List).
writelist([]).
writelist([L|Lt]):-write(L),nl,writelist(Lt).
(d)
travel1(X,Y,go(X,Y,car)):-byCar(X,Y).
travel1(X,Y,go(X,Y,train)):-byTrain(X,Y).
travel1(X,Y,go(X,Y,plane)):-byPlane(X,Y).
travel1(X,Y,go(X,Z,V,W)):-travel1(X,Z,go(X,Z,V)),travel1(Z,Y,W).
findall(Y,travel1(A,B,Y),List1),nl,
writelist(List1).
writelist([]).
writelist([L|Lt]):-write(L),nl,writelist(Lt).
any suggestions please?
The output for (c), if i enter from=valmont, to=paris, it gives this:
go(valmont,saarbruecken,go(saarbruecken,paris))
go(valmont,metz,go(metz,paris))
Same situation for (d).
go(valmont,saarbruecken,car,go(saarbruecken,paris,train))
go(valmont,metz,car,go(metz,paris,train))
A chapter-metadata.xml store in each book isbn folder(there are 100 isbn folder so there is 100 chapter-metadata.xml) which store in marklogic database server and chapter-metadata. Xml either contain data of one chapter or empty. If chapter-metadata.xml contain only one chapter information then I want to add more chapter information(my chapter infomation is common for all chapter) under chapter element with attribute and value of that chapter up to how many chapter store in book isbn folder(that I can fetch and store in a variable $chapter_sequence like ch001 ch002 ch003 ch004..) or if chapter-metadata.xml does not have any chaper information then it will create chapter element with attribute and value of chapter number and add my information, below I have put some xml structure if there is one chapter information and my information is from element keywordset
<?xml version="1.0" encoding="UTF-8" ?>
<chaptermetadata>
<bookisbn>isbn number</bookisbn>
<booktitle>Copyright</booktitle>
<chapter id="ch001"">
<keywordset>
<keyword role="primary">context</keyword>
<keyword role="secondary">Copyright</keyword>
<keyword role="tertiary">subject</keyword>
</keywordset>
</chapter>
</chaptermetadata>
I want like below:
<?xml version="1.0" encoding="UTF-8" ?>
<chaptermetadata>
<bookisbn>isbn number</bookisbn>
<booktitle>Copyright</booktitle>
<chapter id="ch001"">
<keywordset>
<keyword role="primary">context</keyword>
<keyword role="secondary">Copyright</keyword>
<keyword role="tertiary">subject</keyword>
</keywordset>
</chapter>
<chapter id="ch002"">
<keywordset>
<keyword role="primary">context</keyword>
<keyword role="secondary">Copyright</keyword>
<keyword role="tertiary">subject</keyword>
</keywordset>
</chapter>
so on to last chapter which I store in veriable
</chaptermetadata>
thanks,
raj
The question is hard to follow, but start with http://docs.marklogic.com/xdmp:directory and a FLWOR expression. Let's say you put this into a function. I'll handwave a few helper functions that you would also have to implement, but the function might look something like this:
declare function chaptermetadata($isbn as xs:string)
as element(chaptermetadata) {
<chaptermetadata>
{
<bookisbn>{ $isbn }</bookisbn>
<booktitle>{ title($isbn) }</booktitle>
for $chapter in xdmp:directory(isbn-uri($isbn), 'infinity')
return element { fn:node-name($chapter) } {
$chapter/#*,
$chapter/keywordset }
}
<chaptermetadata>
};
Now, this code won't help much unless you understand everything that it's doing so you can modify it to suit your needs. This is a variation on one of the XQuery use cases, so you might find helpful to work through and understand those: http://www.w3.org/TR/xquery-use-cases/
Hi I am new to marklogic and in Xquery world. I am not able to think of starting point to write the following logic in Marklogic Xquery. I would be thankful if somebody can give me idea/sample so I can achieve the following:
I want to Query A.XML based on a word lookup in B.XML. Query should produce C.XML. The logic should be as follows:
A.XML
<root>
<content> The state passed its first ban on using a handheld cellphone while driving in 2004 Nokia Vodafone Nokia Growth Recession Creicket HBO</content>
</root>
B.XML
<WordLookUp>
<companies>
<company name="Vodafone">Vodafone</company>
<company name="Nokia">Nokia</company>
</companies>
<topics>
<topic group="Sports">Cricket</topic>
<topic group="Entertainment">HBO</topic>
<topic group="Finance">GDP</topic>
</topics>
<moods>
<mood number="4">Growth</mood>
<mood number="-5">Depression</mood>
<mood number="-3">Recession</mood>
</moods>
C.XML (Result XML)
<root>
<content> The state passed its first ban on using a handheld cellphone while driving in 2004 Nokia Vodafone Nokia Growth Recession Creicket HBO</content>
<updatedElement>
<companies>
<company count="1">Vodafone</company>
<company count="2">Nokia</company>
</companies>
<mood>1</mood>
<topics>
<topic count="1">Sports</topic>
<topic count="1">Entertainment</topic>
</topics>
<word-count>22</word-count>
</updatedElement>
</root>
Search each company/text() of A.xml in B.xml, if match found create tag:
TAG {company count="Number of occurrence of that word"}company/#name
{/company}
Search each topic/text() of A.xml in B.xml, if match found create tag
TAG {topic topic="Number of occurrences of that word"}topic/#group{/topic}
Search each mood/text() of A.xml in B.xml, if match found
[occurrences of first word * {/mood[first word]/#number}] + [occurrences of second word * {/mood[second word]/#number})]....
get the word count of element.
This was a fun one, and I learned a few things in the process. Thanks!
Note: to get the results you wanted, I fixed a typo in A.xml ("Creicket" -> "Cricket").
The following solution uses two MarkLogic-specific functions:
cts:highlight (for replacing matching text with nodes which you can then count)
cts:tokenize (for breaking up a given string into word, space, and punctuation parts)
It also includes some powerful magic specific to those two functions, respectively:
the dynamic binding of the special variable $cts:text (which isn't really necessary for this particular use case, but I digress), and
the data model extension which adds these subtypes of xs:string:
cts:word,
cts:space, and
cts:punctuation.
Enjoy!
xquery version "1.0-ml";
(: Generic function using MarkLogic's ability to find query matches within a single node :)
declare function local:find-matches($content, $search-text) {
cts:highlight($content, $search-text, <MATCH>{$cts:text}</MATCH>)
//MATCH
};
(: Generic function using MarkLogic's ability to tokenize text into words, punctuation, and spaces :)
declare function local:get-words($text) {
cts:tokenize($text)[. instance of cts:word]
};
(: The rest of this is pure XQuery :)
let $content := doc("A.xml")/root/content,
$lookup := doc("B.xml")/WordLookUp
return
<root>
{$content}
<updatedElement>
<companies>{
for $company in $lookup/companies/company
let $results := local:find-matches($content, string($company))
where exists($results)
return
<company count="{count($results)}">{string($company/#name)}</company>
}</companies>
<mood>{
sum(
for $mood in $lookup/moods/mood
let $results := local:find-matches($content, string($mood))
return count($results) * $mood/#number
)
}</mood>
<topics>{
for $topic in $lookup/topics/topic
let $results := local:find-matches($content, string($topic))
where exists($results)
return
<topic count="{count($results)}">{string($topic/#group)}</topic>
}</topics>
<word-count>{
count(local:get-words($content))
}</word-count>
</updatedElement>
</root>
Let me know if you have any follow-up questions about how all the above works. At first, I was inclined to use cts:search or cts:contains, which are the bread and butter for search in MarkLogic. But I realized that this example wasn't so much about search (finding documents) as it was about looking up matching text within an already-given document. If you needed to extend this somehow to aggregate across a large number of documents, then you'd want to look into the additional use of cts:search or cts:contains.
One final caveat: if you think your content might have <MATCH> elements already, you'll want to use a different element name when calling cts:highlight (a name which you can guarantee won't conflict with your content's existing element names). Otherwise, you'll potentially get the wrong number of results (higher than the accurate count).
ADDENDUM:
I was curious if this could be done without cts:highlight, given that cts:tokenize already breaks up the text into all the words for you. The same result is produced using this alternative implementation of local:find-matches (provided you swap the order of the function declarations because one depends on the other):
(: Find word matches by comparing them one-by-one :)
declare function local:find-matches($content, $search-text) {
local:get-words($content)[cts:stem(.) = cts:stem($search-text)]
};
It uses cts:stem to normalize the given word to its stem, so, for example searching for "pass" will match "passed", etc. However, this still won't work for multi-word (phrase) searches. So to be safe, I'd stick with using cts:highlight, which, like cts:search and cts:contains, can handle any cts:query you give it (including simple word/phrase searches like we do above).
Might make sense to step back and ask if you might be better served modeling your data and or documents for use with a document oriented database instead of an rdbms
This is simpler/shorter and fully compliant XQuery not containing any implementation extensions, which make it work with any compliant XQuery 1.0 processor:
let $content := doc('file:///c:/temp/delete/A.xml')/*/*,
$lookup := doc('file:///c:/temp/delete/B.xml')/*,
$words := tokenize($content, '\W+')[.]
return
<root>
{$content}
<updatedElement>
<companies>
{for $c in $lookup/companies/*,
$occurs in count(index-of($words, $c))
return
if($occurs)
then
<company count="{$occurs}">
{$c/text()}
</company>
else ()
}
</companies>
<mood>
{
sum($lookup/moods/*[false or index-of($words, data(.))]/#number)
}
</mood>
<topics>
{for $t in $lookup/topics/*,
$occurs in count(index-of($words, $t))
return
if($occurs)
then
<topic count="{$occurs}">
{data($t/#group)}
</topic>
else ()
}
</topics>
<word-count>{count($words)}</word-count>
</updatedElement>
</root>
When applied on the provided files A.xml and B.XML (contained in the local directory c:/temp/delete), the wanted, correct result is produced:
<root>
<content> The state passed its first ban on using a handheld cellphone while driving in 2004 Nokia Vodafone Nokia Growth Recession Cricket HBO</content>
<updatedElement>
<companies>
<company count="1">Vodafone</company>
<company count="2">Nokia</company>
</companies>
<mood>1</mood>
<topics>
<topic count="1">Sports</topic>
<topic count="1">Entertainment</topic>
</topics>
<word-count>22</word-count>
</updatedElement>
</root>