I have an XML document:
<resultsets>
<row>
<emp_no>10001</emp_no>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
<row>
<emp_no>10002</emp_no>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
<row>
<emp_no>10003</emp_no>
<first_name>Parto</first_name>
<last_name>Bamford</last_name>
</row>
</resultsets>
Currently, my code is as follows:
let $first := doc("db/apps/flowq/index/employees.xml")//first_name
let $last := doc("db/apps/flowq/index/employees.xml")//last_name
My question is, is it possible ONLY use $first and $last to generate the following result?
<row>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
<row>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
<row>
<first_name>Parto</first_name>
<last_name>Bamford</last_name>
</row>
Basically, if we have two lists of nodes same size, how can we merge them one by one? I have tried
($first_name, $last_name) and ($first_name union $last_name) but doesn't work,
thanks!
And of course for FP afficionados there's the basic recursive function:
declare function local:merge($s1, $s2) {
if ($s1 and $s2)
then (<row>{head($s1), head($s2)}</row>,
local:merge(tail($s1), tail($s2)))
else ()
}
It is a bit odd that you first extract the two different sequences from the same document but in general if you want positional merging then using for $item at $pos can help:
let $first := (
<first_name>Georgi</first_name>,
<first_name>Bezalel</first_name>,
<first_name>Parto</first_name>),
$second := (
<last_name>Facello</last_name>,
<last_name>Simmel</last_name>,
<last_name>Bamford</last_name>
)
return for $name at $pos in $first
return
<row>
{$name, $second[$pos]}
</row>
http://xqueryfiddle.liberty-development.net/eiQZDbb
Or use higher-order for-each-pair:
let $first := (
<first_name>Georgi</first_name>,
<first_name>Bezalel</first_name>,
<first_name>Parto</first_name>),
$second := (
<last_name>Facello</last_name>,
<last_name>Simmel</last_name>,
<last_name>Bamford</last_name>
)
return for-each-pair($first, $second, function($f, $s) { <row>{$f, $s}</row>})
Related
I'm currently working with VDA message types that have been convert to xml using a custom xml converter. However each header and line record in the source document is at the same level, as in the sample below:
<root>
<row>
<Record_type>512</Record_type>
<Customer_item_Number>A0528406</Customer_item_Number>
<Supplier_item_number>10962915</Supplier_item_number>
</row>
<row>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</row>
<row>
<Record_type>513</Record_type>
<Date>190306</Date>
<Quantity>97</Quantity>
</row>
<row>
<Record_type>512</Record_type>
<Customer_item_Number>A0528433</Customer_item_Number>
<Supplier_item_number>10962916</Supplier_item_number>
</row>
<row>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</row>
<row>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</row>
<row>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</row>
<row>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</row>
</root>
(512) record types are headers, the following (513) record types are lines for the preceding (512) record above it.
I am struggling to format this message, so that the lines (513) are indented underneath each header (512) record.
i.e. required output, something like this.
<root>
<Header>
<Record_type>512</Record_type>
<Customer_item_Number>A0528406</Customer_item_Number>
<Supplier_item_number>10962915</Supplier_item_number>
<Line>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</Line>
<Line>
<Record_type>513</Record_type>
<Date>190306</Date>
<Quantity>97</Quantity>
</Line>
</Header>
<Header>
<Record_type>512</Record_type>
<Customer_item_Number>A0528433</Customer_item_Number>
<Supplier_item_number>10962916</Supplier_item_number>
<Line>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</Line>
<Line>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</Line>
<Line>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</Line>
<Line>
<Record_type>513</Record_type>
<Date>170306</Date>
<Quantity>115</Quantity>
</Line>
</Header>
</root>
I have had some success using following sibling, but I'm unable to link this with preceding-sibling, to filter out only the required records before the next loop.
I am hoping someone will be able to assist. :)
In XQuery 3.1 you can use a tumbling window https://www.w3.org/TR/xquery-31/#id-tumbling-windows:
<root>
{
for tumbling window $record in root/row
start $s when $s/Record_type = 512
return
<Header>
{
head($record)/*,
tail($record) !
<Line>
{ * }
</Line>
}
</Header>
}
</root>
https://xqueryfiddle.liberty-development.net/gWcDMef
If your XQuery processor supports XQuery 3.0's window clauses, your query is (at least conceptually) very straight forward and efficient:
<root>{
for tumbling window $w in /root/row
start when true()
end next $n when $n/Record_type = '512'
return <Header>{
head($w)/*,
for $line in tail($w)
return <Line>{$line/*}</Line>
}</Header>
}</root>
Otherwise you have to use the preceding-sibling and following-sibling XPath axes as Mads Hansen also shows in his answer:
<root>{
for $header in /root/row[Record_type = '512']
return <Header>{
$header/*,
for $line in $header/following-sibling::row[Record_type = '513']
let $prev-headers := $line/preceding-sibling::row[Record_type = '512']
where $prev-headers[last()] is $header
return <Line>{$line/*}</Line>
}</Header>
}</root>
Here we get all lines after the current header first, and then check for each line if the last header before it is the current one. It is important here to use is instead of = or eq because the latter two work on atomic items only. This means that XML nodes are atomized (i.e., stripped down to just their concatenated text contents) before the comparison is performed. The is operator compares node identity instead.
For every row that has a Record_type of 512, create a Header element.
In order to find the row elements for the relevant group of Line elements, you want to select the row elements that are following-sibling from the 512 who's Record_type = 513 and who's first preceding-sibling is the current header.
for $header in $doc/root/row[Record_type = 512]
let $lines := $header/following-sibling::row[Record_type = 513]
[preceding-sibling::row[Record_type = 512][1] = $header]
return
<Header>{
$header/*,
for $line in $lines
return <Line>{ $line/* }</Line>
}</Header>
I have a XML column and i need help to write the query to display the nodes and their values.
below is the data from my xml column:
<item_content>
<stimulus_reference>
<table_wrapper>
<table frame="all" colsep="1" rowsep="1" pgwide="0">
<tgroup cols="3">
<thead>
<row>
<entry />
<entry align="center">Male</entry>
<entry align="center">Female</entry>
</row>
</thead>
<tbody>
<row>
<entry align="left">Juniors</entry>
<entry align="right">12</entry>
<entry align="right">3</entry>
</row>
<row>
<entry align="left">Seniors</entry>
<entry align="right">9</entry>
<entry align="right">21</entry>
</row>
</tbody>
</tgroup>
</table>
</table_wrapper>
<rationale>This is a rationale paragraph</rationale>
</stimulus_reference>
<task>
<item_stem>
<stem_paragraph>The table above shows the distribution of students that attended a concert, by class and gender.</stem_paragraph>
</item_stem>
<item_response>
<response_choices>
<columnar_choice_list>
<columns align="character" align_character="1">
<choice_row numeric_identifier="1">
CHROW1
<choice_cell>3</choice_cell>
</choice_row>
<choice_row numeric_identifier="2">
CHROW2
<choice_cell>15</choice_cell>
</choice_row>
<choice_row numeric_identifier="3">
CHROW3
<choice_cell>2102</choice_cell>
</choice_row>
<choice_row numeric_identifier="4">
CHROW4
<choice_cell>321</choice_cell>
</choice_row>
ColumnsData
</columns>
</columnar_choice_list>
</response_choices>
</item_response>
</task>
<math_expression>1+2=3</math_expression>
</item_content>
i want the output in the below format
Node_Name Node_val
stimulus_reference
table_wrapper
table
tgroup
thead
row
entry
entry Male
entry Female
tbody
row
entry Juniors
entry 12
entry 3
row
entry Seniors
entry 9
entry 21
task
item_stem
stem_paragraph The table above shows the distribution of students that attended a concert, by class and gender.
item_response
response_choices
columnar_choice_list
columns ColumnsData
choice_row CHROW1
choice_cell 3
choice_row CHROW2
choice_cell 15
choice_row CHROW3
choice_cell 2102
choice_row CHROW4
choice_cell 321
appreciate your help on this.
//* returns all nodes on all levels.
name() returns node name
text() returns node value
If you want only text nodes you have to replace //* with //*[text()]
select * from xmltable('//*' passing xmltype ('
<item_content>
<stimulus_reference>
<table_wrapper>
<table frame="all" colsep="1" rowsep="1" pgwide="0">
<tgroup cols="3">
<thead>
<row>
<entry />
<entry align="center">Male</entry>
<entry align="center">Female</entry>
</row>
</thead>
<tbody>
<row>
<entry align="left">Juniors</entry>
<entry align="right">12</entry>
<entry align="right">3</entry>
</row>
<row>
<entry align="left">Seniors</entry>
<entry align="right">9</entry>
<entry align="right">21</entry>
</row>
</tbody>
</tgroup>
</table>
</table_wrapper>
<rationale>This is a rationale paragraph</rationale>
</stimulus_reference>
<task>
<item_stem>
<stem_paragraph>The table above shows the distribution of students that attended a concert, by class and gender.</stem_paragraph>
</item_stem>
<item_response>
<response_choices>
<columnar_choice_list>
<columns align="character" align_character="1">
<choice_row numeric_identifier="1">
CHROW1
<choice_cell>3</choice_cell>
</choice_row>
<choice_row numeric_identifier="2">
CHROW2
<choice_cell>15</choice_cell>
</choice_row>
<choice_row numeric_identifier="3">
CHROW3
<choice_cell>2102</choice_cell>
</choice_row>
<choice_row numeric_identifier="4">
CHROW4
<choice_cell>321</choice_cell>
</choice_row>
ColumnsData
</columns>
</columnar_choice_list>
</response_choices>
</item_response>
</task>
<math_expression>1+2=3</math_expression>
</item_content>')
columns
node_name varchar2(100) path 'name()',
node_value varchar2(100) path 'text()'
)
This question has been significantly edited to make things a bit clearer.
I am attempting to pull data out of the electronic Code of Federal Regulations XML feed (http://www.gpo.gov/fdsys/bulkdata/CFR/2015/title-15/CFR-2015-title15-vol2.xml) and am having trouble.
Specifically, I'd like to grab data that will be matched by a combination of Node and Attribute. In the following snippet of XML, you can see some of the text I'd like to grab. I would like to obtain the data for each FP node where the attribute FP-2 is present. I would also like to grab the data for each FP node having the attribute FP-1.
<APPENDIX>
<EAR>Pt. 774, Supp. 1</EAR>
<HD SOURCE="HED">Supplement No. 1 to Part 774—The Commerce Control List</HD>
<HD SOURCE="HD1">Category 0—Nuclear Materials, Facilities, and Equipment [and Miscellaneous Items]</HD>
<HD SOURCE="HD1">A. “End Items,” “Equipment,” “Accessories,” “Attachments,” “Parts,” “Components,” and “Systems”</HD>
<FP SOURCE="FP-2">
<E T="02">0A002Power generating or propulsion equipment “specially designed” for use with space, marine or mobile “nuclear reactors”. (These items are “subject to the ITAR.” See 22 CFR parts 120 through 130.)</E>
</FP>
<FP SOURCE="FP-2">
<E T="02">0A018Items on the Wassenaar Munitions List (see List of Items Controlled).</E>
</FP>
<FP SOURCE="FP-1">
<E T="04">License Requirements</E>
</FP>
<FP SOURCE="FP-1">
<E T="03">Reason for Control:</E> NS, AT, UN</FP>
<GPOTABLE CDEF="s50,r50" COLS="2" OPTS="L2">
<BOXHD>
<CHED H="1">Control(s)</CHED>
<CHED H="1">Country Chart (See Supp. No. 1 to part 738)</CHED>
</BOXHD>
<ROW>
<ENT I="01">NS applies to entire entry</ENT>
<ENT>NS Column 1.</ENT>
</ROW>
<ROW>
<ENT I="01">AT applies to entire entry</ENT>
<ENT>AT Column 1.</ENT>
</ROW>
<ROW>
<ENT I="01">UN applies to entire entry</ENT>
<ENT>See § 746.1(b) for UN controls.</ENT>
</ROW>
</GPOTABLE>
<FP SOURCE="FP-1">
<E T="05">List Based License Exceptions (See Part 740 for a description of all license exceptions)</E>
</FP>
<FP SOURCE="FP-1">
<E T="03">LVS:</E> $3,000 for 0A018.b</FP>
<FP SOURCE="FP-1">$1,500 for 0A018.c and .d</FP>
<FP SOURCE="FP-1">
<E T="03">GBS:</E> N/A</FP>
<FP SOURCE="FP-1">
<E T="03">CIV:</E> N/A</FP>
<FP SOURCE="FP-1">
<E T="04">List of Items Controlled</E>
</FP>
<FP SOURCE="FP-1">
<E T="03">Related Controls:</E> (1) See also 0A979, 0A988, and 22 CFR 121.1 Categories I(a), III(b-d), and X(a). (2) See ECCN 0A617.y.1 and .y.2 for items formerly controlled by ECCN 0A018.a. (3) See ECCN 1A613.c for military helmets providing less than NIJ Type IV protection and ECCN 1A613.y.1 for conventional military steel helmets that, immediately prior to July 1, 2014, were classified under 0A018.d and 0A988. (4) See 22 CFR 121.1 Category X(a)(5) and (a)(6) for controls on other military helmets.</FP>
<FP SOURCE="FP-1">
<E T="03">Related Definitions:</E> N/A</FP>
<FP>
<E T="03">Items:</E> a. [Reserved]</FP>
<P>b. “Specially designed” components and parts for ammunition, except cartridge cases, powder bags, bullets, jackets, cores, shells, projectiles, boosters, fuses and components, primers, and other detonating devices and ammunition belting and linking machines (all of which are “subject to the ITAR.” (See 22 CFR parts 120 through 130);</P>
<NOTE>
<HD SOURCE="HED">
<E T="03">Note:</E>
</HD>
<P>
<E T="03">0A018.b does not apply to “components” “specially designed” for blank or dummy ammunition as follows:</E>
</P>
<P>
<E T="03">a. Ammunition crimped without a projectile (blank star);</E>
</P>
</APPENDIX>
To complicate matters, I'm trying to pull this data into Filemaker, but upon edit, I'll stick to simple XSL.
The following XSL grabs all of the FP nodes without differentiation.
<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="//FP">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Modifying this to match on xsl:template match="FP[#SOURCE='FP-1'] allows me to make the necessary match based on the attribute, but I'm still not clear on how to capture the data I need. Thoughts?
A few things:
Your XSLT actually is not an XSLT format
In XPath, to reference an attribute (i.e., SOURCE), it must be prefixed with #.
Finally, there are many FP1s and FP2s but your setup only choose first instances.
Consider the following XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8"/>
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<METADATA>
<FIELD NAME="ECCNFP_2" TYPE="TEXT"/>
<FIELD NAME="ECCNFP_1" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="//FP[#SOURCE = 'FP-2']/E[#T='02']">
<ROW>
<COL>
<DATA><xsl:value-of select="substring(.,1,5)"/></DATA>
</COL>
</ROW>
</xsl:for-each>
<xsl:for-each select="//FP[#SOURCE = 'FP-1']/E[#T='02']">
<ROW>
<COL>
<DATA><xsl:value-of select="substring(.,1,5)"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
Which would output:
<?xml version='1.0' encoding='UTF-8'?>
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<METADATA>
<FIELD NAME="ECCNFP_2" TYPE="TEXT"/>
<FIELD NAME="ECCNFP_1" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<ROW>
<COL>
<DATA>0A002</DATA>
</COL>
</ROW>
<ROW>
<COL>
<DATA>0A018</DATA>
</COL>
</ROW>
</RESULTSET>
</FMPXMLRESULT>
And partial output of full web link xml:
<?xml version='1.0' encoding='UTF-8'?>
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<METADATA>
<FIELD NAME="ECCNFP_2" TYPE="TEXT"/>
<FIELD NAME="ECCNFP_1" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<ROW>
<COL>
<DATA>2A000</DATA>
</COL>
</ROW>
<ROW>
<COL>
<DATA>0A002</DATA>
</COL>
</ROW>
<ROW>
<COL>
<DATA>0A018</DATA>
</COL>
</ROW>
<ROW>
<COL>
<DATA>0A521</DATA>
</COL>
</ROW>
<ROW>
<COL>
<DATA>0A604</DATA>
</COL>
</ROW>
<ROW>
<COL>
<DATA>0A606</DATA>
</COL>
</ROW>
...
In fact, point your XSLT processor to the GPO link and all FP1s and FP2s output. I just did so with Python! Close to 3,000 lines!
Your question is still not clear. If I concentrate on this part:
I would like to obtain the data for each FP node where the attribute
FP-2 is present. I would also like to grab the data for each FP node
having the attribute FP-1.
then you probably want to change this:
<xsl:for-each select="//FP">
to:
<xsl:for-each select="//FP[#SOURCE='FP-1' or #SOURCE='FP-2']">
Note that this returns the value of each FP element where the SOURCE attribute has a value of either 'FP-1' or 'FP-2'. I see no "FP node where the attribute FP-2 is present" in your input.
Note also that the // syntax is expensive in terms of processing power. You will get better performance if you use a full, explicit path.
I have an xml like the following:
<table1>
<row>
<person>person1</person>
<value>10</value>
</row>
<row>
<person>person2</person>
<value>20</value>
</row>
<row>
<person>person1</person>
<value>5</value>
</row>
</table1>
<summaryTable>
<row>
<person>person1</person>
<value_total/>
</row>
<row>
<person>person2</person>
<value_total/>
</row>
</summaryTable>
With XForms 1 (there is no option to switch to XForms 2), using framework betterform, I want to calculate the values in the summary table, by doing the SUM of the rows in 'table1' that have the same person name. To do that I have the following binds:
<xf:bind id="bind_table1"
nodeset="table1" repeatableElement="row">
<xf:bind id="bind_head_table1" nodeset="head" />
<xf:bind id="bind_row_table1" nodeset="row">
<xf:bind id="bind_person" nodeset="person" type="xf:string" />
<xf:bind id="bind_value" nodeset="value" type="xf:integer" />
</xf:bind>
</xf:bind>
<xf:bind id="bind_summaryTable"
nodeset="summaryTable"
repeatableElement="row">
<xf:bind id="bind_head_summaryTable" nodeset="head" />
<xf:bind id="bind_row_summaryTable" nodeset="row">
<xf:bind id="bind_person_name" nodeset="person_name" type="xf:string" readonly="true"/>
<xf:bind id="bind_value_total" nodeset="value_total" type="xf:integer" readonly="true" calculate="SUM(//table1/row[person/text() = ../person_name/text()]/value)"/>
</xf:bind>
</xf:bind>
What I want to have at the end is the value_total for person1 = 15 and value_total for person2 = 20, but using this 'calculate' expression I'm getting 'NaN'. If I replace the calculate expression to compare with a literal String like:
<xf:bind id="bind_value_total" nodeset="value_total" type="xf:integer" readonly="true" calculate="SUM(//table1/row[person/text() = 'person1']/value)"/>
then I get as value_total 15 (the sum is correctly done). So it seems that the error is in the comparison expression person/text() = ../person_name/text() . Does someone have an idea about how should be the correct expression?
Thanks
Try the context() function in the calculate attribute to refer to the current node, like this:
<xf:bind nodeset="summaryTable/row/value_total" calculate="sum(//table1/row[person/text() = context()/../person/text()]/value)"/>
The context function gives you the current context node. If your bind references a nodeset with multiple nodes, it will be evaluated one time for every node, and that node is what context() returns.
It works for me with XSLTForms, maybe your version of betterForm supports it.
I have an XML file that contains authors and editors.
<?xml version="1.0" encoding="UTF-8"?>
<?oxygen RNGSchema="file:textbook.rnc" type="compact"?>
<books xmlns="books">
<book ISBN="i0321165810" publishername="OReilly">
<title>XPath</title>
<author>
<name>
<fname>Priscilla</fname>
<lname>Walmsley</lname>
</name>
</author>
<year>2007</year>
<field>Databases</field>
</book>
<book ISBN="i0321165812" publishername="OReilly">
<title>XQuery</title>
<author>
<name>
<fname>Priscilla</fname>
<lname>Walmsley</lname>
</name>
</author>
<editor>
<name>
<fname>Lisa</fname>
<lname>Williams</lname>
</name>
</editor>
<year>2003</year>
<field>Databases</field>
</book>
<publisher publishername="OReilly">
<web-site>www.oreilly.com</web-site>
<address>
<street_address>hill park</street_address>
<zip>90210</zip>
<state>california</state>
</address>
<phone>400400400</phone>
<e-mail>oreilly#oreilly.com</e-mail>
<contact>
<field>Databases</field>
<name>
<fname>Anna</fname>
<lname>Smith</lname>
</name>
</contact>
</publisher>
</books>
I'm looking for a way to return the person who has been listed the most times as an author and/or editor. The solution should be XQuery 1.0 (XPath 2.0) compatible.
I was thinking about using a FLWOR query to iterate through all authors and editors, then doing a count of unique authors/editors, then returning the author(s)/editor(s) that match the highest count. But I haven't been able to find the proper solution.
Does anyone have any suggestion as to how such a FLWOR query would be written?
Could this be done in a simpler way, using XPath?
This may help:
declare default element namespace 'books';
(for $name in distinct-values($doc/books/*/*/name)
let $entries := $doc/books/*[data(*/name) = $name]
order by count($entries) descending
return $entries/*/name)[1]
Here is a pure XPath 2.0 expression, admittedly not for the faint-hearted:
(for $m in max(for $n in distinct-values(/*/b:book/(b:author | b:editor)
/b:name/concat(b:fname, '|', b:lname)),
$cnt in count(/*/b:book/(b:author | b:editor)
/b:name[$n eq concat(b:fname, '|', b:lname) ])
return $cnt
),
$name in /*/b:book/(b:author | b:editor)/b:name,
$fullName in $name/concat(b:fname, '|', b:lname),
$count in count( /*/b:book/(b:author | b:editor)
/b:name[$fullName eq concat(b:fname, '|', b:lname)])
return
if($count eq $m)
then $name
else ()
)[1]
where the prefix "b:" is associated with the namespace "books".
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="books">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:sequence select=
"(for $m in max(for $n in distinct-values(/*/b:book/(b:author | b:editor)
/b:name/concat(b:fname, '|', b:lname)),
$cnt in count(/*/b:book/(b:author | b:editor)
/b:name[$n eq concat(b:fname, '|', b:lname) ])
return $cnt
),
$name in /*/b:book/(b:author | b:editor)/b:name,
$fullName in $name/concat(b:fname, '|', b:lname),
$count in count( /*/b:book/(b:author | b:editor)
/b:name[$fullName eq concat(b:fname, '|', b:lname)])
return
if($count eq $m)
then $name
else ()
)[1]
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<books xmlns="books">
<book ISBN="i0321165810" publishername="OReilly">
<title>XPath</title>
<author>
<name>
<fname>Priscilla</fname>
<lname>Walmsley</lname>
</name>
</author>
<year>2007</year>
<field>Databases</field>
</book>
<book ISBN="i0321165812" publishername="OReilly">
<title>XQuery</title>
<author>
<name>
<fname>Priscilla</fname>
<lname>Walmsley</lname>
</name>
</author>
<editor>
<name>
<fname>Lisa</fname>
<lname>Williams</lname>
</name>
</editor>
<year>2003</year>
<field>Databases</field>
</book>
<publisher publishername="OReilly">
<web-site>www.oreilly.com</web-site>
<address>
<street_address>hill park</street_address>
<zip>90210</zip>
<state>california</state>
</address>
<phone>400400400</phone>
<e-mail>oreilly#oreilly.com</e-mail>
<contact>
<field>Databases</field>
<name>
<fname>Anna</fname>
<lname>Smith</lname>
</name>
</contact>
</publisher>
</books>
the wanted, correct name element is selected and output:
<name xmlns="books">
<fname>Priscilla</fname>
<lname>Walmsley</lname>
</name>
I've always felt this was an omission in XPath: the max() and min() functions return the highest/lowest value, whereas what you usually want is the object(s) in a collection that have the highest/lowest value for some expression. One solution is to sort the objects on that value and take the first/last from the list, which seems inelegant. Computing the min/max and then selecting the items whose value matches this seems equally unappealing. In Saxon there has long been a pair of higher-order extension functions saxon:highest() and saxon:lowest() which take a sequence and a function, and return the item(s) from the sequence having the lowest or highest values of the function result. The good news is that in XPath 3.0 you can write these functions yourself (in fact, they are given as example user-written functions in the spec).
You are on the right track. The simplest way is to convert the names into strings (separated with a space, for example) and use these: (Note that the following code is untested)
let $names := (//editor | //author)/concat(fname, ' ', lname)
let $distinct-names := distinct-values($names)
let $name-count := for $name in $distinct-names return count($names[. = $name])
for $name at $pos in $distinct-names
where $name-count[$pos] = max($name-count)
return $name
Or, another approach:
(
let $people := (//editor | //author)
for $person in $people
order by count($people[fname = $person/fname and
lname = $person/lname])
return $person
)[last()]