Implement AbsolutePath and IEConditionalComments filters. Add an xc namespace (urn...
[xhtml-compiler.git] / XHTMLCompiler / FilterManager.php
blob59bdd57f2fde68beadf43db8ccc74f2a4b34f13b
1 <?php
3 /**
4 * Manages various filters in a document, and performs text processing
5 */
6 class XHTMLCompiler_FilterManager
9 protected $preTextFilters = array();
10 protected $postTextFilters = array();
11 protected $DOMFilters = array();
12 protected $xcAttr = array();
14 protected $errors = array();
16 /**
17 * Adds a pre-processing text filter to the queue.
18 * @note Filters added here are run before the document is
19 * parsed into a DOM. Suggested use is for transforming
20 * non-XML style specialized markup.
21 * @param $filter XHTMLCompiler_TextFilter
23 public function addPreTextFilter($filter) {
24 $filter = $this->loadFilter($filter, 'TextFilter');
25 $n = $filter->getName();
26 if (isset($this->preTextFilters[$n])) {
27 throw new Exception('Cannot overload pre text filter ' .
28 $filter->getName());
30 return $this->preTextFilters[$n] = $filter;
33 /**
34 * Adds a post-processing text filter to the queue.
35 * @note Filters added here are run after the document has been
36 * parsed into a DOM and then serialized back. Suggested use
37 * is for fixing cosmetic issues with the source.
38 * @warning Anything done on this step will not have its
39 * well-formedness corrected, so be careful.
40 * @param $filter XHTMLCompiler_TextFilter
42 public function addPostTextFilter($filter) {
43 $filter = $this->loadFilter($filter, 'TextFilter');
44 $n = $filter->getName();
45 if (isset($this->postTextFilters[$n])) {
46 throw new Exception('Cannot overload post text filter ' .
47 $filter->getName());
49 return $this->postTextFilters[$n] = $filter;
52 /**
53 * Adds a DOM-processing filter to the queue
54 * @param $filter XHTMLCompiler_DOMFilter
56 public function addDOMFilter($filter) {
57 $filter = $this->loadFilter($filter, 'DOMFilter');
58 $n = $filter->getName();
59 if (isset($this->DOMFilters[$n])) {
60 throw new Exception('Cannot overload DOM filter ' .
61 $filter->getName());
63 $attributes = $filter->getXCAttributesDefined();
64 foreach ($attributes as $attribute) {
65 if (isset($this->xcAttr[$attribute])) {
66 throw new Exception('Duplicate attribute definition for '.
67 'xc:' . $attribute);
69 $this->xcAttr[$attribute] = true;
71 return $this->DOMFilters[$n] = $filter;
74 /**
75 * If filter is string, load the filter based on a few guesses
76 * @param $filter String or object filter
78 protected function loadFilter($filter, $subclass) {
79 if (is_string($filter)) {
80 $class = "XHTMLCompiler_{$subclass}_$filter";
81 if (class_exists($class)) {
82 $filter = new $class;
83 } elseif (class_exists($filter)) {
84 $filter = new $filter;
85 } else {
86 require "$subclass/$filter.php";
87 $filter = new $class;
90 return $filter;
93 /**
94 * Accepts a page's text and processes it.
95 * @param $text String text to be processed
96 * @param $page XHTMLCompiler_Page representing currently processed page
98 public function process($text, $page) {
100 // do pre-text processing
101 foreach ($this->preTextFilters as $filter) {
102 $text = $filter->process($text, $page);
105 // setup XML catalog to improve speed
106 $catalog = str_replace(array(' ', '\\'), array('%20', '/'),
107 dirname(__FILE__)) . '/../catalog/catalog.xml';
108 if ($catalog[1] == ':') $catalog = substr($catalog, 2); // remove drive
109 putenv('XML_CATALOG_FILES=' . $catalog);
111 // configure DOMDocument
112 $dom = new DOMDocument();
113 $dom->preserveWhiteSpace = false;
114 $dom->formatOutput = true;
115 $dom->resolveExternals = true;
117 $dom->loadXML($text);
118 set_error_handler(array($this, 'muteErrorHandler'));
119 $dom->validate();
120 restore_error_handler();
122 $dom->encoding = 'UTF-8'; // override document encoding
124 // perform includes
125 $dom->xinclude();
127 // run DOM filters
128 foreach ($this->DOMFilters as $filter) {
129 $filter->setup($dom);
130 $filter->process($dom, $page);
133 // translate back to text
134 $text = $dom->saveXML();
136 // remove all non-default namespace declarations
137 $text = preg_replace('/ xmlns:.+?=".+?"/', '', $text);
138 foreach ($this->postTextFilters as $filter) {
139 $text = $filter->process($text, $page);
142 // okay, now finally do validation, and let the errors get
143 // spit out if there are some
144 // collect parse errors
145 set_error_handler(array($this, 'validationErrorHandler'));
146 $dom->loadXML($text);
147 $status = $dom->validate();
148 restore_error_handler();
150 if (!$status || !empty($this->errors)) {
151 $body = $dom->getElementsByTagName('body')->item(0);
152 if (!$body) {
153 $dom->appendChild($html = $dom->createElement('html'));
154 $html->appendChild($body = $dom->createElement('body'));
156 $warning = $dom->createElement('div');
157 $warning->setAttribute('class', 'warning');
158 $warning->appendChild($dom->createElement('h2', 'Warning: Errors'));
159 $warning->appendChild($dom->createElement('p', 'This document has validation errors:'));
160 $list = $dom->createElement('ul');
161 foreach ($this->errors as $error) {
162 // strip-tags removes HTML tags to make the plaintext output
163 // more friendly, IS NOT for security reasons
164 $list->appendChild($dom->createElement('li', strip_tags($error)));
166 $warning->appendChild($list);
167 $body->insertBefore($warning, $body->childNodes->item(0));
168 $text = $dom->saveXML();
171 // scrub out XML declaration for Internet Explorer
172 $text = str_replace('<?xml version="1.0" encoding="UTF-8"?>'."\n", '', $text);
174 return $text;
178 * Temporary error handler to use when validating a document
180 public function validationErrorHandler($n, $text) {
181 $this->errors[] = $text;
185 * Handler that mutes all errors
187 public function muteErrorHandler($n, $t) {}