Downwards compatibility, and a surprising find

I've just released a new version of WWW::Mechanize::Firefox, appropriating a new function of WWW::Mechanize, ->click_button. I used that occassion to also change the XPath queries sent by W:M:F to Firefox to be somewhat slimmer by changing queries for the element type from a global "OR" to a local test for the node name:

The old query to look for a <BUTTON> or <INPUT type="submit"> was

//input[@type = "submit" ]

The new query is

//*[local-name(.) = "button" or (local-name(.) = "input" and @type="submit")]

The advantage of the new type is that the query engine does not need to search the DOM tree twice, and more importantly, that I can now easily ask for the "third button in this form", which I cannot do with two separate queries, as they don't necessarily return the buttons in the relative order in which they appear in the HTML+DOM.

All would be well if it weren't for differing Firefox versions. local-name(.) returns the element name. Except that in versions before 3.6, the element name for HTML pages is in ALL UPPERCASE, while since 3.6 onwards, it is in lower case. Of course, this is no real hindrance, as we can just lower-case() the element name. Except that Firefox does not implement lower-case(). It only implements translate(), which is uneasily similar to the tr/// operator in Perl. As you can imagine in your horror-filled dreams, the generated queries now look like this:

//*[translate(local-name(.), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")="button"
or (translate(local-name(.), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")="input" and @type="submit")]

... at least until I ditch support for older Firefox versions.

While I was copying the test suite of WWW::Mechanize, click_button.t, I saw that it launches a local HTTP server to test against that. Digging deeper, I found that it actually and actively uses a server that I once wrote to test WWW::Mechanize::Shell. This has really surprised me and made me happy. If people don't use my modules, at least my test infrastructure gets used by other modules in the same problem space, so I must do something right.


Does this work?

//*[self::button or (self::input and @type="submit")]

Max, we use your modules. :)

Another point: even if that query doesn’t work and you need the original form, you shouldn’t need such bulky transliterations:

//*[translate(local-name(.), "BUTON", "buton")="button"
or (translate(local-name(.), "INPUT", "input")="input" and @type="submit")]

After all, it doesn’t matter whether you compare “ADDRESS” or “address” with “input”, it’s gonna fail either way.

Leave a comment

About Max Maischein

user-pic I'm the Treasurer for the Frankfurt Perlmongers e.V. . I have organized Perl events including 9 German Perl Workshops and one YAPC::Europe.