If its available, the PHP5 DOM extension will be much faster than simple html dom library which is implemented in PHP userspace (and uses a lot of regular expressions and recursion).
Attached is a patch which uses the extension and falls back to simplehtmldom when its disabled. This gives a noticeable improvement (about 25% faster execution).
I benchmarked first with apachebench and APC enabled:
DOM Extension
Concurrency Level: 5
Time taken for tests: 29.678 seconds
Complete requests: 20
Failed requests: 15
(Connect: 0, Receive: 0, Length: 15, Exceptions: 0)
Write errors: 0
Total transferred: 1246361 bytes
HTML transferred: 1239681 bytes
Requests per second: 0.67 [#/sec] (mean)
Time per request: 7419.618 [ms] (mean)
Time per request: 1483.924 [ms] (mean, across all concurrent requests)
Transfer rate: 41.01 [Kbytes/sec] received
Simple HTML DOM
Concurrency Level: 5
Time taken for tests: 37.680 seconds
Complete requests: 20
Failed requests: 11
(Connect: 0, Receive: 0, Length: 11, Exceptions: 0)
Write errors: 0
Total transferred: 1247604 bytes
HTML transferred: 1240924 bytes
Requests per second: 0.53 [#/sec] (mean)
Time per request: 9420.020 [ms] (mean)
Time per request: 1884.004 [ms] (mean, across all concurrent requests)
Transfer rate: 32.33 [Kbytes/sec] received
I also profiled with xhprof and got pretty astounding results.
Dom Extension
Overall Summary
Total Incl. Wall Time (microsec): 3,472,353 microsecs
Total Incl. CPU (microsecs): 3,143,128 microsecs
Total Incl. MemUse (bytes): 159,020,696 bytes
Total Incl. PeakMemUse (bytes): 164,224,728 bytes
Number of Function Calls: 201,392
Simple HTML DOM
Overall Summary
Total Incl. Wall Time (microsec): 4,548,397 microsecs
Total Incl. CPU (microsecs): 4,183,060 microsecs
Total Incl. MemUse (bytes): 159,131,592 bytes
Total Incl. PeakMemUse (bytes): 164,343,360 bytes
Number of Function Calls: 304,683
Comments
Comment #1
omerida commentedThe above patch fails on PHP 5.2 systems, the DOMDocument::saveHTML method in that version does not accept any arguments. Attached is a reworked patch that works on both PHP5.2 and PHP5.3
Comment #2
omerida commentedAttached is a reworked patch that fixes some issues i ran into when testing this with more content, particularly nodes with empty bodies or bodies without text nodes (ie all images or other non-text markup).
It applies cleanly to the latest 6.x-3.x git branch.
Comment #3
sp3boy commentedI have just started trying to implement Glossify and this patch is very relevant as I'll need all the performance boost I can get.
However on applying #2 I immediately got WSOD errors due to a couple of problems:
_fetch_possible_keywordsin a couple of places.I am working on several bug fixes/enhancements for other issues with the module in general but it looks like they would best be applied to the changes proposed by this patch, so I am planning to take that approach, hence more comments will follow when I have devised them.
By way of preview, the issues I've found so far with the unpatched 6.x-3.x-dev are:
strpos()is always used to test the presence of a match.Comment #4
sp3boy commentedAs previously advised I have spent several days sorting out some problems with both the original 6.x-3.x-dev code and the above patch. I offer a somewhat reworked patch with which I believe I have advanced things in general by the following changes and fixes:
preg_match()statements in above patch did not apply unicode mode or case-insensitivity even if selected by configuration).preg_replace()so that the linked or hoverable text created by a case-insensitive match is not changed to the case of the matching term.glossify_theme_comment_view()has been introduced, so this could be an issue if your theme already overrides theme_comment_view._keyword_table().$text_nodeinstead of$nodein some functions to avoid confusion between DOM text nodes and Drupal node object.replaceChild()has happened - otherwise any attempt to loop round the node set a second time appeared to causes WSOD error on re-referencing the replaced text node?$node->contentarray item "glossify_all_hovertips". This can be used instead of the separate$node->content['glossify_hovertip'][ ]array to theme the node's hovertips from a single template variable (useful in any node template that does not use the$contentvariable but themes a node field-by-field)._fetch_possible_keywords()as that can be called more than once for the same config and node id combination when processing different styles etc.I have tested Links, Reference and Hovertips successfully but I realise there may be scenarios I did not hit.
Comment #5
sp3boy commentedVersion changed and category changed in view of the fact that I believe I have fixed some bugs.
Comment #6
sp3boy commentedA problem was discovered in using appendXML() if the text being processed had unescaped ampersands, which I think is possible if using the "non-filter" mode or perhaps if the Glossify filter were sequenced before the HTML filter, which it is possible to do.
Revised patch attached.
Comment #7
omerida commentedmaybe it should be
Otherwise & will get turned into &&
Comment #8
sp3boy commentedHmm... do you mean an existing "&" might get turned into "&&" ?
Comment #9
omerida commentedYes.
Comment #10
sp3boy commentedI attach another revision of the patch. It contains significant differences from the previous one. The main reason for this is that when testing "first-only" mode, I discovered that on certain versions of PHP and/or libxml, the sequence in which the xpath "//text()" query retrieved the text nodes was not consistent once the DOM had been changed. After a hovertip replacement had been performed for a keyword match (which creates child span elements), the next xpath query on the revised document (to perform a link replacement) was encountering the keyword (now within the new child) out of sequence, causing a second occurance of the keyword in a following text node to be replaced with a link instead.
I was not able to find a documented bug report for this libxml behaviour (guessing that libxml may be to blame), so my solution for this was to replace the main xpath usage with a routine to traverse the document using DOM functions. There is a new function to recurse down the DOM tree, although I retained the initial xpath query to see whether there are any text nodes to process at all. Note: in DOM mode I moved the test for iframe and object elements to a point in this traversal code so that all children of these elements are ignored. Previously these elements were just tacked on to the include / exclude functionality, which doesn't necessarily make much sense as they are unlikely to be direct parents of text nodes. This anomaly is still the case for simplehtmldom mode though, and I haven't got unlimited time to sort that out too.
Along the way I also noticed another issue with the include / exclude functionality. Briefly, to be able to enforce it correctly if performing hovertip and link replacement (which is one of my requirements), it is necessary to identify the "true parent" element of a text node, i.e. when a keyword has been given a span parent to achieve hovertip functionality, the span should not be used to check include / exclude. The test should use whatever was the parent of the text node before any replacement happened. Again this has only been factored in for DOM mode.
I have also tried to improve the check in comment 7 (above) with a negative lookahead assertion in the regex. I tested it as best I could and I think it's effective.
So hopefully this patch fulfils my aim which was to make all the declared functionality work fully, at least in DOM mode, while getting the performance benefits initiated by omerida.
I plan to be rolling out Glossify with this patch live on a UK commercial site within a day or so, because with these fix-ups, it's the best solution for the requirement.
Comment #11
omerida commentedThis is great, I've been using the original patch without a hiccup on production since I created it. The performance increase helps a ton. I hope your fixes to get this working with the other linking modes get merged into another release soon. I will try to test your patch on my system soon.
Comment #12
sp3boy commentedThanks, I've put my patched version live yesterday (see an example where the bold+italic links have been inserted by Glossify). It appears to be working OK with the desired settings (match first-only, generate hovertip and link, case insensitivity, unicode compatibility, don't break words, exclude a, b and strong tags).
Fortunately my boss is tolerant of the time it's taken to get it happening :)
Comment #13
sp3boy commentedAn less-extensive change to the patch: I found that if a text DOM node in a body field had following siblings, they were being ignored if that text node was updated to process a keyword match. A typical scenario would be if first-only mode is disabled and a page contains two <p> elements both containing a matching keyword. If the first <p> has an <a> link before the occurrance of the keyword, the text containing the keyword was being skipped, even though matching text in the second <p> would be picked up.
This version now saves any following sibling of a text node before any replacement happens, and passes it back to the calling code in that situation. The calling code checks whether it should use that in preference.
Comment #14
Ivo.Radulovski commentedguys, would be great if you'd like to co-maintain the module because currently Im concentrated on other projects...
Comment #15
sp3boy commentedI have thought about the above suggestion but despite using and developing with Drupal for 4 years I have yet to get into that and I am not sure I can commit the time to (co-)maintaining as I realise that means folowing-up all issues in a timely manner, not just the ones I'm personally interested in.
Comment #16
WorldFallz commentedI like this alot-- and want to get rid of the simplehtmldom dependency anyway. I'm still familiarizing myself with the code, but it's a safe bet this will be one of the first things rolled into the current dev.
Comment #18
WorldFallz commentedpatch applied to 6.x-3.x-dev branch, let the bug fixing begin.