From 048e7dc73d3a9c8b4463fd22bbdcf1d2a2f1357b Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Sun, 25 Mar 2007 22:48:25 +0000 Subject: [PATCH] Add dependency checking for included files. git-svn-id: http://htmlpurifier.org/svnroot@900 48356398-32a2-884e-a903-53898d9a118a --- XHTMLCompiler/DOMFilter.php | 3 ++- XHTMLCompiler/DOMFilter/AbsolutePath.php | 2 +- XHTMLCompiler/DOMFilter/Acronymizer.php | 5 +++- .../DOMFilter/GenerateTableOfContents.php | 2 +- XHTMLCompiler/DOMFilter/IEConditionalComments.php | 2 +- XHTMLCompiler/DOMFilter/MarkLeadParagraphs.php | 2 +- XHTMLCompiler/DOMFilter/Quoter.php | 2 +- XHTMLCompiler/DOMFilter/RSSGenerator.php | 2 +- XHTMLCompiler/FilterManager.php | 30 +++++++++++++++++++--- XHTMLCompiler/Page.php | 24 +++++++++++++---- XHTMLCompiler/TextFilter.php | 3 ++- tests/XHTMLCompiler/FilterManagerTest.php | 13 ++++++---- 12 files changed, 67 insertions(+), 23 deletions(-) diff --git a/XHTMLCompiler/DOMFilter.php b/XHTMLCompiler/DOMFilter.php index 5c8777c..98e947a 100644 --- a/XHTMLCompiler/DOMFilter.php +++ b/XHTMLCompiler/DOMFilter.php @@ -35,8 +35,9 @@ abstract class XHTMLCompiler_DOMFilter extends XHTMLCompiler_Filter * thus edits we make will be globally reflected. * @param $dom DOMDocument to process * @param $page XHTMLCompiler_Page object representing the context + * @param $manager Currently running XHTMLCompiler_FilterManager */ - abstract public function process(DOMDocument $dom, $page); + abstract public function process(DOMDocument $dom, $page, $manager); /** * Performs common initialization of DOM and XPath diff --git a/XHTMLCompiler/DOMFilter/AbsolutePath.php b/XHTMLCompiler/DOMFilter/AbsolutePath.php index 8c8d927..b279536 100644 --- a/XHTMLCompiler/DOMFilter/AbsolutePath.php +++ b/XHTMLCompiler/DOMFilter/AbsolutePath.php @@ -6,7 +6,7 @@ class XHTMLCompiler_DOMFilter_AbsolutePath extends XHTMLCompiler_DOMFilter protected $name = 'AbsolutePath'; protected $xcAttr = array('absolute'); - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { $xc = XHTMLCompiler::getInstance(); $absolute_prefix = $xc->getConf('web_path') . '/'; diff --git a/XHTMLCompiler/DOMFilter/Acronymizer.php b/XHTMLCompiler/DOMFilter/Acronymizer.php index 2d91744..c9361f8 100644 --- a/XHTMLCompiler/DOMFilter/Acronymizer.php +++ b/XHTMLCompiler/DOMFilter/Acronymizer.php @@ -61,12 +61,15 @@ class XHTMLCompiler_DOMFilter_Acronymizer extends XHTMLCompiler_DOMFilter 'WYSIWYM' => 'What You See Is What You Mean', ); - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { $nodes = $this->query("//html:acronym[not(@title)]"); foreach ($nodes as $node) $this->addAdvisoryTitle($node, $this->acronyms); $nodes = $this->query("//html:abbr[not(@title)]"); foreach ($nodes as $node) $this->addAdvisoryTitle($node, $this->abbreviations); + + // add self as dependency; when acronym lists change, so does the page + $manager->addDependency(__FILE__); } protected function addAdvisoryTitle($node, $lookup) { diff --git a/XHTMLCompiler/DOMFilter/GenerateTableOfContents.php b/XHTMLCompiler/DOMFilter/GenerateTableOfContents.php index e7e9e44..11cbab2 100644 --- a/XHTMLCompiler/DOMFilter/GenerateTableOfContents.php +++ b/XHTMLCompiler/DOMFilter/GenerateTableOfContents.php @@ -9,7 +9,7 @@ class XHTMLCompiler_DOMFilter_GenerateTableOfContents extends XHTMLCompiler_DOMF protected $name = 'GenerateTableOfContents'; - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { // test for ToC container, if not present don't bother $container = $this->query("//html:div[@id='toc']")->item(0); diff --git a/XHTMLCompiler/DOMFilter/IEConditionalComments.php b/XHTMLCompiler/DOMFilter/IEConditionalComments.php index 211f1fd..4edc31a 100644 --- a/XHTMLCompiler/DOMFilter/IEConditionalComments.php +++ b/XHTMLCompiler/DOMFilter/IEConditionalComments.php @@ -6,7 +6,7 @@ class XHTMLCompiler_DOMFilter_IEConditionalComments extends XHTMLCompiler_DOMFil protected $name = 'IEConditionalComments'; protected $xcAttr = array('ie-condition'); - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { $nodes = $this->query( "//*[@xc:ie-condition]" ); diff --git a/XHTMLCompiler/DOMFilter/MarkLeadParagraphs.php b/XHTMLCompiler/DOMFilter/MarkLeadParagraphs.php index 96c9e62..7475fa0 100644 --- a/XHTMLCompiler/DOMFilter/MarkLeadParagraphs.php +++ b/XHTMLCompiler/DOMFilter/MarkLeadParagraphs.php @@ -14,7 +14,7 @@ class XHTMLCompiler_DOMFilter_MarkLeadParagraphs extends XHTMLCompiler_DOMFilter if ($class) $this->className = $class; } - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { $nodes = $this->query("//html:p[local-name(preceding-sibling::*[1])!='p']"); foreach ($nodes as $node) { $node->setAttribute('class', $this->className); diff --git a/XHTMLCompiler/DOMFilter/Quoter.php b/XHTMLCompiler/DOMFilter/Quoter.php index 7e95b73..c62400c 100644 --- a/XHTMLCompiler/DOMFilter/Quoter.php +++ b/XHTMLCompiler/DOMFilter/Quoter.php @@ -26,7 +26,7 @@ class XHTMLCompiler_DOMFilter_Quoter extends XHTMLCompiler_DOMFilter if ($right_nest) $this->rightNestedQuote = $right_nest; } - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { // first handle single-quotes $nodes = $this->query("//html:q//html:q"); diff --git a/XHTMLCompiler/DOMFilter/RSSGenerator.php b/XHTMLCompiler/DOMFilter/RSSGenerator.php index de44219..1b951bb 100644 --- a/XHTMLCompiler/DOMFilter/RSSGenerator.php +++ b/XHTMLCompiler/DOMFilter/RSSGenerator.php @@ -9,7 +9,7 @@ class XHTMLCompiler_DOMFilter_RSSGenerator extends XHTMLCompiler_DOMFilter // new namespace defines the following attributes: // for - IDREF to an element where we are to get the RSS info from - public function process(DOMDocument $dom, $page) { + public function process(DOMDocument $dom, $page, $manager) { // attempt to find declarations of the namespace $nodes = $this->query( diff --git a/XHTMLCompiler/FilterManager.php b/XHTMLCompiler/FilterManager.php index 59bdd57..9f17d9e 100644 --- a/XHTMLCompiler/FilterManager.php +++ b/XHTMLCompiler/FilterManager.php @@ -12,6 +12,7 @@ class XHTMLCompiler_FilterManager protected $xcAttr = array(); protected $errors = array(); + protected $deps = array(); /** * Adds a pre-processing text filter to the queue. @@ -90,6 +91,14 @@ class XHTMLCompiler_FilterManager return $filter; } + /** Returns the dependency array accumulated from the filter run */ + public function getDeps() {return $this->deps;} + + /** Adds a file to the dependency list */ + public function addDependency($filename) { + $this->deps[$filename] = filemtime($filename); + } + /** * Accepts a page's text and processes it. * @param $text String text to be processed @@ -99,7 +108,7 @@ class XHTMLCompiler_FilterManager // do pre-text processing foreach ($this->preTextFilters as $filter) { - $text = $filter->process($text, $page); + $text = $filter->process($text, $page, $this); } // setup XML catalog to improve speed @@ -121,13 +130,26 @@ class XHTMLCompiler_FilterManager $dom->encoding = 'UTF-8'; // override document encoding - // perform includes + // XInclude might be better off as a DOMFilter + + // add xincludes to the dependency list + $xpath = new DOMXPath($dom); + $xpath->registerNamespace('xi', $ns = 'http://www.w3.org/2001/XInclude'); + $nodes = $xpath->query('//xi:include'); + foreach ($nodes as $node) { + if (! $node instanceof DOMElement) continue; + if (! $filename = $node->getAttribute('href')) continue; + // doesn't handle second-level includes + $this->addDependency($filename); + } + + // perform includes (we might need to loop to handle nested includes) $dom->xinclude(); // run DOM filters foreach ($this->DOMFilters as $filter) { $filter->setup($dom); - $filter->process($dom, $page); + $filter->process($dom, $page, $this); } // translate back to text @@ -136,7 +158,7 @@ class XHTMLCompiler_FilterManager // remove all non-default namespace declarations $text = preg_replace('/ xmlns:.+?=".+?"/', '', $text); foreach ($this->postTextFilters as $filter) { - $text = $filter->process($text, $page); + $text = $filter->process($text, $page, $this); } // okay, now finally do validation, and let the errors get diff --git a/XHTMLCompiler/Page.php b/XHTMLCompiler/Page.php index 02f43fc..08b6908 100644 --- a/XHTMLCompiler/Page.php +++ b/XHTMLCompiler/Page.php @@ -13,15 +13,19 @@ class XHTMLCompiler_Page */ protected $pathStem; - /** File extension of source files, no period */ + /** File extension of source files (no period) */ protected $sourceExt = 'xhtml'; - /** File extension of cache/served files, no period */ + /** File extension of cache/served files */ protected $cacheExt = 'html'; + /** File extension of dependency files */ + protected $depsExt = 'deps'; - /** Instance of XHTMLCompiler_file for source file */ + /** Instance of XHTMLCompiler_File for source file */ protected $source; - /** Instance of XHTMLCompiler_file for cache file */ + /** Instance of XHTMLCompiler_File for cache file */ protected $cache; + /** Instance of XHTMLCompiler_File for dependency file */ + protected $deps; /** * Constructs a page object, validates filename for correctness @@ -76,6 +80,7 @@ class XHTMLCompiler_Page // setup the files $this->source = new XHTMLCompiler_File($this->pathStem . '.' . $this->sourceExt); $this->cache = new XHTMLCompiler_File($this->pathStem . '.' . $this->cacheExt); + $this->deps = new XHTMLCompiler_File($this->pathStem . '.' . $this->depsExt); if (!$mute && !$this->source->exists()) { // Apache may have redirected to an ErrorDocument which got directed @@ -131,7 +136,14 @@ class XHTMLCompiler_Page does not exist, please call isCacheExistent and take appropriate action with the result'); } - return $this->source->getMTime() > $this->cache->getMTime(); + if ($this->source->getMTime() > $this->cache->getMTime()) return true; + // check dependencies + if (!$this->deps->exists()) return true; // we need a dependency file! + $deps = unserialize($this->deps->get()); + foreach ($deps as $filename => $time) { + if ($time < filemtime($filename)) return true; + } + return false; } /** @@ -171,10 +183,12 @@ class XHTMLCompiler_Page $xc = XHTMLCompiler::getInstance(); $filters = $xc->getFilterManager(); $contents = $filters->process($source, $this); + $deps = $filters->getDeps(); if (empty($contents)) return ''; // don't write, probably an error $contents .= ''; $this->cache->write($contents); $this->cache->chmod(0664); + $this->deps->write(serialize($deps)); return $contents; } diff --git a/XHTMLCompiler/TextFilter.php b/XHTMLCompiler/TextFilter.php index 37dbd4c..f9be7cd 100644 --- a/XHTMLCompiler/TextFilter.php +++ b/XHTMLCompiler/TextFilter.php @@ -10,11 +10,12 @@ abstract class XHTMLCompiler_TextFilter extends XHTMLCompiler_Filter * Defines a filter that processes string text. * @param $text String text to process * @param $page XHTMLCompiler_Page object representing the context + * @param $manager Currently running XHTMLCompiler_FilterManager * @return Processed string text * @note These filters should be used sparingly, only when a DOM * solution would not work. */ - abstract public function process($text, $page); + abstract public function process($text, $page, $manager); } diff --git a/tests/XHTMLCompiler/FilterManagerTest.php b/tests/XHTMLCompiler/FilterManagerTest.php index 1990bfe..94fedfb 100644 --- a/tests/XHTMLCompiler/FilterManagerTest.php +++ b/tests/XHTMLCompiler/FilterManagerTest.php @@ -3,7 +3,7 @@ class XHTMLCompiler_FilterManagerTest_DOMFilter extends XHTMLCompiler_DOMFilter { protected $name = 'Test'; - function process(DOMDocument $dom, $page) { + function process(DOMDocument $dom, $page, $manager) { $dom->getElementById('document')->appendChild($dom->createElement('p', 'DOMFilter')); } } @@ -38,13 +38,16 @@ class XHTMLCompiler_FilterManagerTest extends UnitTestCase $page = new XHTMLCompiler_PageMock(); $input = '1'; + // using new IsAExpectation('XHTMLCompiler_FilterManager') + // since SimpleTest chokes on recursive dependencies + $pre_text_filter = new XHTMLCompiler_TextFilterMock($this); $pre_text_filter->setReturnValue('getName', 'banana-filter'); - $pre_text_filter->expectOnce('process', array('1', $page)); + $pre_text_filter->expectOnce('process', array('1', $page, new IsAExpectation('XHTMLCompiler_FilterManager'))); $pre_text_filter->setReturnValue('process', '2'); $pre_text_filter2 = new XHTMLCompiler_TextFilterMock($this); $pre_text_filter2->setReturnValue('getName', 'apple-filter'); - $pre_text_filter2->expectOnce('process', array('2', $page)); + $pre_text_filter2->expectOnce('process', array('2', $page, new IsAExpectation('XHTMLCompiler_FilterManager'))); $pre_text_filter2->setReturnValue('process', ' @@ -68,11 +71,11 @@ xmlns:proprietary="urn:foobar:foofoofoo"> $post_text_filter = new XHTMLCompiler_TextFilterMock($this); $post_text_filter->setReturnValue('getName', 'Masumune Plus!'); - $post_text_filter->expectOnce('process', array(new PatternExpectation('/Meta.+?DOMFilter/s'), $page)); + $post_text_filter->expectOnce('process', array(new PatternExpectation('/Meta.+?DOMFilter/s'), $page, new IsAExpectation('XHTMLCompiler_FilterManager'))); $post_text_filter->setReturnValue('process', '3'); $post_text_filter2 = new XHTMLCompiler_TextFilterMock($this); $post_text_filter2->setReturnValue('getName', 'Piffle-rockers'); - $post_text_filter2->expectOnce('process', array('3', $page)); + $post_text_filter2->expectOnce('process', array('3', $page, new IsAExpectation('XHTMLCompiler_FilterManager'))); $post_text_filter2->setReturnValue('process', ' -- 2.11.4.GIT