问题
How do I group based on whether there's a match to [0-9]
for digits with a tumbling window
?
desired output:
...
<record>
<name>joe</name>
<data>phone1</data>
<data>phone2</data>
</record>
...
current output, not grouped:
<xml>
<record>
<person key="$s" data="name">phone1</person>
</record>
<record>
<person key="$s" data="name">phone2</person>
</record>
<record>
<person key="$s" data="name">phone3sue</person>
</record>
<record>
<person key="$s" data="name">cell4</person>
</record>
<record>
<person key="$s" data="name">home5alice</person>
</record>
<record>
<person key="$s" data="name">atrib6</person>
</record>
<record>
<person key="$s" data="name">x7</person>
</record>
<record>
<person key="$s" data="name">y9</person>
</record>
<record>
<person key="$s" data="name">z10</person>
</record>
</xml>
input:
<text>
<line>people</line>
<line>joe</line>
<line>phone1</line>
<line>phone2</line>
<line>phone3</line>
<line>sue</line>
<line>cell4</line>
<line>home5</line>
<line>alice</line>
<line>atrib6</line>
<line>x7</line>
<line>y9</line>
<line>z10</line>
</text>
The notion is that each "person" will have a name (no digits) and perhaps additional data. So looking to read in each line and then group based on where the names are found.
code:
xquery version "3.0";
<xml>
{
for tumbling window $line in db:open("foo.txt")//text()
start $s when matches($s, '[0-9]')
return
<record>
<person key='$s' data="name">{$line}</person>
</record>
}
</xml>
Looking at the output, "phone3sue" is obviously doing some matching and grouping, although not exactly as desired because "phone3" should be in its own element, nested within "joe" rather than "sue". But, still, there's some matching happening there.
from the saxon mailing list:
On Wed, Feb 19, 2020 at 10:31:37AM -0800, thufir scripsit:
I'll re-read the section on windowing; my impression was that it was more for display or report purposes.
Windowing is how you take chunks out of a stream of data.
What you've got is effectively a stream of line elements; you can identify the "name" lines, but you don't now how far part they are/how much data is between any particular pair of names.
Windows lets you say "I want the chunk of this stream that starts with a name line and continues up to (but not including) the next name line".
Would you elaborate on what you mean by two steps, a bit more concretely?
You're trying to take some input XML and turn it into different output XML.
If this is pure transformation -- change all of the elements named FOO to element named BAZ -- XQuery's not the best tool choice. Use XSLT if you can. They're computationally the same but the languages have different biases and XSLT does transforms more naturally.
If the output XML is a representation of an abstraction of your input -- morally some sort of report -- it helps a lot to have the abstraction, and then present it.
So in your case, what you have is a stream containing an implicit association between names and data. (It's a stream of lines; the only way you know these data lines go with that name line is position. So implicit.) If you turn that into an explicit mapping between names and data -- such as by creating a map variable where the keys are the contents of the name line (with spaces handled somehow) and the entries for each key are the data lines associated with that name -- you have done the abstraction part.
You can then take that map and produce the XML output you want from it, which is much simpler than trying to combine the "create new XML" and "do the abstraction steps". The last thing I posted has an example of turning a map into elements, but as a pattern it's just
map:keys($map) ! {.}{$map(.)}
(it gets more complicated if you've got nodes or a sequence in the entry, but not much more.)
That make something a little closer to sense?
-- Graydon
_______________________________________________ saxon-help mailing list archived at http://saxon.markmail.org/ saxon-help@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/saxon-help
回答1:
The following tries to use a tumbling window
which starts with any line
not containing any ASCII digit (the name
of the person
) followed by any line containing at least one ASCII digit (i.e. the data
lines):
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method 'xml';
declare option output:indent 'yes';
<xml>
{
for tumbling window $person in text/line
start $name next $data when matches($name, '^[^0-9]+$') and matches($data, '[0-9]')
return
<person>
{
<name>{ data($name) }</name>,
tail($person) ! <data>{data()}</data>
}
</person>
}
</xml>
https://xqueryfiddle.liberty-development.net/gWmuPs1
Output there is
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<person>
<name>joe</name>
<data>phone1</data>
<data>phone2</data>
<data>phone3</data>
</person>
<person>
<name>sue</name>
<data>cell4</data>
<data>home5</data>
</person>
<person>
<name>alice</name>
<data>atrib6</data>
<data>x7</data>
<data>y9</data>
<data>z10</data>
</person>
</xml>
回答2:
If you're using 3.0 or 3.1, then use the "window" clause of the FLWOR expression. Something like
for tumbling window $w in line
start $s when matches($s, '[a-z]')
return <group key="$s">{$w}</group>
Not tested and probably will need correction or adaptation.
回答3:
This is probably quite close:
thufir@dur:~/flwor/foo.txt.database$
thufir@dur:~/flwor/foo.txt.database$ basex dennis.xq
<person name="joe">
<person id="3" x="0" numerical="true">phone1</person>
<person id="4" x="0" numerical="true">phone2</person>
<person id="5" x="0" numerical="true">phone3</person>
</person>
<person name="sue">
<person id="7" x="0" numerical="true">cell4</person>
<person id="8" x="0" numerical="true">home5</person>
</person>
<person name="alice">
<person id="10" x="0" numerical="true">atrib6</person>
<person id="11" x="0" numerical="true">x7</person>
<person id="12" x="0" numerical="true">y9</person>
<person id="13" x="0" numerical="true">z10</person>
</person>thufir@dur:~/flwor/foo.txt.database$
code:
xquery version "3.0";
declare variable $XML := <xml>
<person id="1" x="0" numerical="false">people</person>
<person id="2" x="0" numerical="false">joe</person>
<person id="3" x="0" numerical="true">phone1</person>
<person id="4" x="0" numerical="true">phone2</person>
<person id="5" x="0" numerical="true">phone3</person>
<person id="6" x="0" numerical="false">sue</person>
<person id="7" x="0" numerical="true">cell4</person>
<person id="8" x="0" numerical="true">home5</person>
<person id="9" x="0" numerical="false">alice</person>
<person id="10" x="0" numerical="true">atrib6</person>
<person id="11" x="0" numerical="true">x7</person>
<person id="12" x="0" numerical="true">y9</person>
<person id="13" x="0" numerical="true">z10</person>
</xml> ;
for $P in $XML/person
where $P[@numerical="true"]
let $PREV := $P/preceding-sibling::person[@numerical="false"][1]
group by $PREV
return <person name="{$PREV}" > { $P } </person>
来源:https://stackoverflow.com/questions/60237739/how-to-use-tumbling-window-to-group-xml-elements-by-content