obdurodon / dh_course

Digital Humanities course site
GNU General Public License v3.0
20 stars 6 forks source link

Using variables in XSLT #471

Closed charlietaylor98 closed 3 years ago

charlietaylor98 commented 4 years ago

An XSLT variable like <xsl:variable name="bar_width" select="(state/@elec div sum(//state/@elec))"/> will return different values depending upon which node it's fired on. My question, then, is how variables like this factor into other xpath expressions. In SVG 3, to vary the width of each bar depending on the number of electoral votes, you need to return a different bar width value for each state, and take this variation in width into account when determining the position of the next bar.

To do this, I tried the xpath expression sum(preceding-sibling::state/$bar_width), as in "for each state before this one, find what the width of its bar would be, and add each of those together." XSLT variables must not work the way I think they do, because xpath returned nothing-- in which case, how do they work?

djbpitt commented 4 years ago

@charlietaylor98 Great question!

Declaring a variable in XSLT is not like a macro that gets expanded in place when you use it. Instead, it’s value is determined when it is first declared. The declaration binds the variable name to a specific value (not an instruction for how to compute a value) at the time the variable is created. Your attempt to use the variable as part of a path expression didn’t work because the variable, once evaluated, is stuck with that original value.

Parenthetically: It may look as if we change the value of a variable when we declare it inside a template or a for loop because it has a different value each time. What’s really going on is that you can have a different value for it inside a template or an <xsl:for-each> because each iteration of a template or a loop starts afresh, with no memory of any prior values, so entirely new variables are created on each pass. There is no confusion because when the new pass begins, the variable that was created on the last pass no longer exists. This is why, in XSLT, you can’t do something like:

<xsl:variable name="cuonter" select="$counter + 1"/>

(There is a special counter functionality that was added to XSLT 3.0 to work around this limitation.)

The approach we took in class was to calculate the X position separately for each state by finding the ones that preceding it and doing some arithmetic with them. This does require us to recompute the same values, which is potentially inefficient. For example, if the position of bar 2 depends on bar 1, you have to calculate bar 1 when you’re placing bar 2, and then you calculate it again when you place bar 3, etc. This might be an issue if there were more data, but at this volume it isn’t a problem. With a declarative language like XSLT, we often rely on the processor to optimize our code (for example, to remember and reuse values instead of recomputing them), but that’s sort of a black box, and it doesn’t always work.

If redoing the same work is a problem, there is a way to process the states recursively and pass values computed for one state along to the next, so that they don’t have to be recomputed. You can read about how to do that by looking up <xsl:iterate>; let us know if you’d like to follow up about that. I happened to use <xsl:iterate> (for the first time) at a conference this year; if you’re curious, you can read my paper about it at https://archive.xmlprague.cz/2020/files/xmlprague-2020-proceedings.pdf and watch the presentation at https://www.youtube.com/watch?v=CYue_wCIJoU.