xpathxml-parsingxqueryflworshred

How to use tumbling window to group XML elements by content?


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


Solution

  • 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>