I am working with table entry elements and want to get all preceding entry elements in the same table that pass the following test:
parent::row/preceding-sibling::row/entry
[@morerows >= count(parent::row/following-sibling::row
[not(preceding-sibling::row/entry
[generate-id() = $id])])]
[count(preceding-sibling::entry
[not(@morerows)]) + 1 = count(current()/preceding-sibling::entry) + 1]
The XPath gives me the desired result, but it is painfully slow..... I would like to use a key but am having problems.
I defined a key as follows:
<xsl:key name="moreRowsEntry" match="entry[@morerows]" use="."/>
While the key does retrieve all entry elements with the morerows attribute, I actually only need it to retrieve those within the same ancestor table. As well, I am at a loss as to how to test the morerows value. I have tried things such as :
<xsl:for-each select="key('moreRowsEntry', (@morerows >= count(parent::row/following-sibling::row[not(preceding-sibling::row/entry[generate-id() = $id])])) and (count(preceding-sibling::entry[not(@morerows)]) + 1 = count(current()/preceding-sibling::entry) + 1))">
I have to do this using XSL 1.0. Any and all help is appreciated.
Some further information:
I am converting a CALS table to OOXML. For each entry in the CALS table that is within a row in which I know there are cells missing, I need to add those additional cells. For these entry elements I have an $id
that is the generate-id() value of the element.
I then have the XPath from above, which tests that any entry elements in the preceding rows of the table (parent::row/preceding-sibling::row/entry
) that have a morerows attribute that is greater than or equal to the number of rows between itself and the entry with the id of $id
, and that are in the correct position in which the empty cell should be inserted (count(preceding-sibling::entry[not(@morerows)]) + 1 = count(current()/preceding-sibling::entry) + 1
)
Simplified Sample Input:
<table>
<tbody>
<row>
<entry morerows="2">A</entry>
<entry morerows="1">B</entry>
<entry>C</entry>
<entry>D</entry>
</row>
<row>
<entry>E</entry>
<entry>F</entry>
</row>
<row>
<entry>G</entry>
<entry>H</entry>
<entry>I</entry>
</row>
<row>
<entry>J</entry>
<entry>K</entry>
<entry>L</entry>
<entry>M</entry>
</row>
</tbody>
</table>
Simplified Sample Output:
<w:tbl>
<w:tr>
<w:tc>
<w:p>
<w:r>
<w:t>A</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>B</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>C</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>D</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>E</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>F</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>G</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>H</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>I</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:p>
<w:r>
<w:t>J</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>K</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>L</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>M</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
The input uses the morerows attribute to specify spanned rows. The output requires that actual cells be inserted with the <w:vMerge/>
element for each spanned cell.
Another sample:
In this sample, there are also merged columns (specified by the namest and nameend attributes) that must also be taken in to account:
<table>
<tgroup cols="7">
<colspec colname="col1"/>
<colspec colname="col2"/>
<colspec colname="col3"/>
<colspec colname="col4"/>
<colspec colname="col5"/>
<colspec colname="col6"/>
<colspec colname="col7"/>
<tbody>
<row>
<entry morerows="5">A</entry>
<entry morerows="1">B</entry>
<entry morerows="1">C</entry>
<entry>D</entry>
<entry>E</entry>
<entry>F</entry>
<entry>G</entry>
</row>
<row>
<entry>2D</entry>
<entry>2E</entry>
<entry>2F</entry>
<entry>2G</entry>
</row>
<row>
<entry morerows="1">3B</entry>
<entry morerows="1">3C</entry>
<entry>3D</entry>
<entry>3E</entry>
<entry>3F</entry>
<entry>3G</entry>
</row>
<row>
<entry>4D</entry>
<entry>4E</entry>
<entry>4F</entry>
<entry>4G</entry>
</row>
<row>
<entry morerows="1" nameend="col6" namest="col2">3G - 4G</entry>
<entry morerows="1">5G</entry>
</row>
</tbody>
</tgroup>
</table>
Output:
<w:tbl>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>A</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>B</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>C</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>D</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>E</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>F</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>G</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>2D</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>2E</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>2F</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>2G</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>3B</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>3C</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>3D</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>3E</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>3F</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>3G</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>4D</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>4E</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>4F</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>4G</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:tr>
<w:tc>
<w:tcPr>
<w:vMerge/>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc>
<w:tcPr>
<w:gridSpan w:val="5"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>3G - 4G</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r>
<w:t>5G</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
Off the top of my head, I would first use a key that matches entries by their ancestor table:
Hopefully you can use that to narrow down the set of nodes that you have to process in XPath. But I confess I haven't really understood the condition you're trying to filter by, so I'm not positive that the above is applicable.
Update
Re: "I can't figure out how to include my XPath condition and get a result." I often get frustrated in XSLT 1.0 at the awkwardness of trying to combine a key with a preceding:: or following:: axis. It can't be done succinctly.
You could work it in by inserting the following predicate where needed:
where $table-id is the id of the current() node's ancestor table:
But that may be slower than not using a key at all:
You could attach this predicate to each
entry
node test in you XPath expression. But again, I'm not sure it would help with performance.Alternative
Instead, I would suggest a two-stage transformation. Does your processing context allow that? For that, you can either run two stylesheets, with the output of the first piped to the input of the second; or use the common
node-set()
extension function to convert the output of one template to a node set that can serve as input to another template.In the first transformation, you could add attributes to each table cell to help with the computation, such as...
The idea would be amortization (if I'm applying that term correctly)... If these values are computed once per entry, in the first stage, instead of many times, in the complicated XPath expression, that could make things quite a bit faster. Maybe there are other attributes that would be more helpful.
You could remove (not copy) these helper attributes in the second transformation.
This is a sketchy suggestion, but maybe it will help point toward a good solution.
If your processor can do the
exslt:node-set
function or an equivalent such as that provided by msxml then you could attack this procedurally, using a tail-recursive template. Ugly, I know, and completely against what we usually recommend for XSLT, but in this case I think the only way to do this efficiently is to pass information from one row to the next. Assuming that the first row of the table will always have anentry
for every column, then how about something like this:(Wow, that ended up much more complex than I expected when I started it, but I've tested it out and it seems to work correctly). The idea here is that we build a structure that encodes the number of rows that each column still needs to span over at the point where that row is processed. So for the first row we'll have
then for the second it'll be
the third
etc. For each row we then iterate over this structure rather than over the
entry
elements themselves.Edit: now you've changed the question so you need to account for the possibility of column spans as well as row spans it gets much much messier. Since we're committed to using a node-set function, I would think about a two-step approach as hinted at by LarsH, where you first expand out the column spanning entries into real
entry
elements (with some attribute to identify them as such) and then process the expanded version of the XML in place of the original.