InterchangeBuilder.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <?php
  2. class HTMLPurifier_ConfigSchema_InterchangeBuilder
  3. {
  4. /**
  5. * Used for processing DEFAULT, nothing else.
  6. * @type HTMLPurifier_VarParser
  7. */
  8. protected $varParser;
  9. /**
  10. * @param HTMLPurifier_VarParser $varParser
  11. */
  12. public function __construct($varParser = null)
  13. {
  14. $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
  15. }
  16. /**
  17. * @param string $dir
  18. * @return HTMLPurifier_ConfigSchema_Interchange
  19. */
  20. public static function buildFromDirectory($dir = null)
  21. {
  22. $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
  23. $interchange = new HTMLPurifier_ConfigSchema_Interchange();
  24. return $builder->buildDir($interchange, $dir);
  25. }
  26. /**
  27. * @param HTMLPurifier_ConfigSchema_Interchange $interchange
  28. * @param string $dir
  29. * @return HTMLPurifier_ConfigSchema_Interchange
  30. */
  31. public function buildDir($interchange, $dir = null)
  32. {
  33. if (!$dir) {
  34. $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
  35. }
  36. if (file_exists($dir . '/info.ini')) {
  37. $info = parse_ini_file($dir . '/info.ini');
  38. $interchange->name = $info['name'];
  39. }
  40. $files = array();
  41. $dh = opendir($dir);
  42. while (false !== ($file = readdir($dh))) {
  43. if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') {
  44. continue;
  45. }
  46. $files[] = $file;
  47. }
  48. closedir($dh);
  49. sort($files);
  50. foreach ($files as $file) {
  51. $this->buildFile($interchange, $dir . '/' . $file);
  52. }
  53. return $interchange;
  54. }
  55. /**
  56. * @param HTMLPurifier_ConfigSchema_Interchange $interchange
  57. * @param string $file
  58. */
  59. public function buildFile($interchange, $file)
  60. {
  61. $parser = new HTMLPurifier_StringHashParser();
  62. $this->build(
  63. $interchange,
  64. new HTMLPurifier_StringHash($parser->parseFile($file))
  65. );
  66. }
  67. /**
  68. * Builds an interchange object based on a hash.
  69. * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
  70. * @param HTMLPurifier_StringHash $hash source data
  71. * @throws HTMLPurifier_ConfigSchema_Exception
  72. */
  73. public function build($interchange, $hash)
  74. {
  75. if (!$hash instanceof HTMLPurifier_StringHash) {
  76. $hash = new HTMLPurifier_StringHash($hash);
  77. }
  78. if (!isset($hash['ID'])) {
  79. throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID');
  80. }
  81. if (strpos($hash['ID'], '.') === false) {
  82. if (count($hash) == 2 && isset($hash['DESCRIPTION'])) {
  83. $hash->offsetGet('DESCRIPTION'); // prevent complaining
  84. } else {
  85. throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace');
  86. }
  87. } else {
  88. $this->buildDirective($interchange, $hash);
  89. }
  90. $this->_findUnused($hash);
  91. }
  92. /**
  93. * @param HTMLPurifier_ConfigSchema_Interchange $interchange
  94. * @param HTMLPurifier_StringHash $hash
  95. * @throws HTMLPurifier_ConfigSchema_Exception
  96. */
  97. public function buildDirective($interchange, $hash)
  98. {
  99. $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
  100. // These are required elements:
  101. $directive->id = $this->id($hash->offsetGet('ID'));
  102. $id = $directive->id->toString(); // convenience
  103. if (isset($hash['TYPE'])) {
  104. $type = explode('/', $hash->offsetGet('TYPE'));
  105. if (isset($type[1])) {
  106. $directive->typeAllowsNull = true;
  107. }
  108. $directive->type = $type[0];
  109. } else {
  110. throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
  111. }
  112. if (isset($hash['DEFAULT'])) {
  113. try {
  114. $directive->default = $this->varParser->parse(
  115. $hash->offsetGet('DEFAULT'),
  116. $directive->type,
  117. $directive->typeAllowsNull
  118. );
  119. } catch (HTMLPurifier_VarParserException $e) {
  120. throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
  121. }
  122. }
  123. if (isset($hash['DESCRIPTION'])) {
  124. $directive->description = $hash->offsetGet('DESCRIPTION');
  125. }
  126. if (isset($hash['ALLOWED'])) {
  127. $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED')));
  128. }
  129. if (isset($hash['VALUE-ALIASES'])) {
  130. $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES'));
  131. }
  132. if (isset($hash['ALIASES'])) {
  133. $raw_aliases = trim($hash->offsetGet('ALIASES'));
  134. $aliases = preg_split('/\s*,\s*/', $raw_aliases);
  135. foreach ($aliases as $alias) {
  136. $directive->aliases[] = $this->id($alias);
  137. }
  138. }
  139. if (isset($hash['VERSION'])) {
  140. $directive->version = $hash->offsetGet('VERSION');
  141. }
  142. if (isset($hash['DEPRECATED-USE'])) {
  143. $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE'));
  144. }
  145. if (isset($hash['DEPRECATED-VERSION'])) {
  146. $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION');
  147. }
  148. if (isset($hash['EXTERNAL'])) {
  149. $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL')));
  150. }
  151. $interchange->addDirective($directive);
  152. }
  153. /**
  154. * Evaluates an array PHP code string without array() wrapper
  155. * @param string $contents
  156. */
  157. protected function evalArray($contents)
  158. {
  159. return eval('return array(' . $contents . ');');
  160. }
  161. /**
  162. * Converts an array list into a lookup array.
  163. * @param array $array
  164. * @return array
  165. */
  166. protected function lookup($array)
  167. {
  168. $ret = array();
  169. foreach ($array as $val) {
  170. $ret[$val] = true;
  171. }
  172. return $ret;
  173. }
  174. /**
  175. * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
  176. * object based on a string Id.
  177. * @param string $id
  178. * @return HTMLPurifier_ConfigSchema_Interchange_Id
  179. */
  180. protected function id($id)
  181. {
  182. return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
  183. }
  184. /**
  185. * Triggers errors for any unused keys passed in the hash; such keys
  186. * may indicate typos, missing values, etc.
  187. * @param HTMLPurifier_StringHash $hash Hash to check.
  188. */
  189. protected function _findUnused($hash)
  190. {
  191. $accessed = $hash->getAccessed();
  192. foreach ($hash as $k => $v) {
  193. if (!isset($accessed[$k])) {
  194. trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE);
  195. }
  196. }
  197. }
  198. }
  199. // vim: et sw=4 sts=4