diff --git a/.gitattributes b/.gitattributes
index 661528261702adc95fba8a07162e26674698fc42..b326fdb8010fa5e40ab7eee72d85fd93964d4033 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -566,8 +566,10 @@ ecrire/safehtml/readme.txt -text
 ecrire/urls/page.php -text
 ecrire/urls/propres-qs.php -text
 ecrire/win_png.htc -text
+ecrire/xml/analyser_dtd.php -text
 ecrire/xml/atom.php -text
 ecrire/xml/ical.php -text
+ecrire/xml/interfaces.php -text
 ecrire/xml/rss.php -text
 /inc-public.php3 -text
 /index.php -text
diff --git a/ecrire/inc/valider_xml.php b/ecrire/inc/valider_xml.php
index c5a5719289adf6e5c38f20ef6dddfcdc8f64251b..ccd7bedcd9473c5ca04f764d01508837701f8818 100644
--- a/ecrire/inc/valider_xml.php
+++ b/ecrire/inc/valider_xml.php
@@ -13,31 +13,7 @@
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
 include_spip('inc/sax');
-
-define('_REGEXP_DOCTYPE',
-	'/^\s*(<[?][^>]*>\s*)?<!DOCTYPE\s+(\w+)\s+(\w+)\s*([^>]*)>/');
-
-define('_SUB_REGEXP_SYMBOL', '[A-Za-z_][\w_:.-]*');
-
-define('_REGEXP_ID', '/^'  . _SUB_REGEXP_SYMBOL . '$/');
-
-define('_REGEXP_ENTITY_NAME', '/' . _SUB_R2EGEXP_SYMBOL . '/');
-define('_REGEXP_ENTITY_USE', '/%('  . _SUB_REGEXP_SYMBOL . ');/');
-define('_REGEXP_ENTITY_DEF', '/^%('  . _SUB_REGEXP_SYMBOL . ');/');
-define('_REGEXP_ENTITY_DECL', '/^<!ENTITY\s+(%?)\s*(' .
-		_SUB_REGEXP_SYMBOL .
-		';?)\s+(PUBLIC|SYSTEM|INCLUDE|IGNORE)?\s*"([^"]*)"\s*("([^"]*)")?\s*>\s*(.*)$/s');
-
-// Document Type Compilation
-
-class DTC {
-	var	$macros = array();
-	var 	$elements = array();
-	var 	$peres = array();
-	var 	$attributs = array();
-	var	$entites = array();
-	var	$regles = array();
-}
+include_spip('xml/interfaces');
 
 // http://doc.spip.org/@analyser_doctype
 function analyser_doctype($data)
@@ -64,267 +40,6 @@ function analyser_doctype($data)
 	return array($topelement, $avail, $grammaire, $rotlvl);
 }
 
-// http://doc.spip.org/@charger_dtd
-function charger_dtd($data)
-{
-	$r = analyser_doctype($data);
-	if (!$r) return array();
-
-	list ($topelement, $avail, $grammaire, $rotlvl) = $r;
-	spip_log("Racine $topelement dans $grammaire ($rotlvl)");
-	$dtc = new DTC;
-	analyser_dtd($grammaire, $avail, $dtc);
-
-	// tri final pour presenter les suggestions de corrections
-	foreach ($dtc->peres as $k => $v) {
-		asort($v);
-		$dtc->peres[$k] = $v;
-	} 
-
-#	$r = $dtc->regles; ksort($r);foreach($r as $l => $v) echo "<b>$l</b> '$v' ", join (', ',array_keys($dtc->attributs[$l])), "<br />\n";exit;
-	return $dtc;
-}
-
-// Compiler une regle de production en une Regexp qu'on appliquera sur la
-// suite des noms de balises separes par des espaces. Du coup:
-// supprimer #PCDATA etc, ca ne sert pas pour le controle des balises;
-// supprimer les virgules (les sequences sont implicites dans une Regexp)
-// conserver | + * ? ( ) qui ont la meme signification en DTD et en Regexp;
-// faire suivre chaque nom d'un espace (et supprimer les autres) ...
-// et parentheser le tout pour que  | + * ? s'applique dessus.
-
-// http://doc.spip.org/@compilerRegle
-function compilerRegle($val)
-{
-	$x = str_replace('\s*()\s*','',
-		preg_replace('/\s*,\s*/','',
-		preg_replace('/(\w+)\s*/','(\1 )',
-		preg_replace('/\s*([(+*|])\s*/','\1',
-		preg_replace('/\s*#\w+\s*[,|]?\s*/','', $val)))));
-	return $x;
-}
-
-
-// http://doc.spip.org/@analyser_dtd
-function analyser_dtd($loc, $avail, &$dtc)
-{
-	if ($avail == 'SYSTEM')
-	  $file = $loc;
-	else {
-	  $file = sous_repertoire(_DIR_DTD);
-	  $file .= preg_replace('/[^\w.]/','_', $loc);
-	}
-
-	$dtd = '';
-	if (@is_readable($file)) {
-		lire_fichier($file, $dtd);
-	} else {
-		if ($avail == 'PUBLIC') {
-			include_spip('inc/distant');
-			if ($dtd = recuperer_page($loc))
-				ecrire_fichier($file, $dtd); 
-		}
-	}
-
-	if (!$dtd = ltrim($dtd)) {
-		spip_log("DTD $loc inaccessible");
-		return array();
-	}
-
-	while ($dtd) {
-		if ($dtd[0] != '<')
-			$r = analyser_dtd_lexeme($dtd, $dtc, $loc);
-		elseif ($dtd[1] != '!')
-			$r = analyser_dtd_pi($dtd, $dtc, $loc);
-		else switch ($dtd[3]) {
-	  case '%' : $r = analyser_dtd_data($dtd, $dtc, $loc); break;
-	  case 'T' : $r = analyser_dtd_attlist($dtd, $dtc, $loc);break;
-	  case 'L' : $r = analyser_dtd_element($dtd, $dtc, $loc);break;
-	  case 'N' : $r = analyser_dtd_entity($dtd, $dtc, $loc);break;
-	  case 'O' : $r = analyser_dtd_notation($dtd, $dtc, $loc);break;
-	  case '-' : $r = analyser_dtd_comment($dtd, $dtc, $loc); break;
-	  default: $r = -1;
-	  }
-
-	  if (!is_string($r)) {
-	    spip_log("erreur $r dans la DTD  " . substr($dtd,0,80) . ".....");
-	    return array();
-	  }
-	  $dtd = $r;
- 
-	}
-	spip_log("DTD $avail $loc " . count($dtc->macros)  . ' macros, ' . count($dtc->elements)  . ' elements, ' . count($dtc->attributs) . " listes d'attributs, " . count($dtc->entites) . " entites");
-}
-
-// http://doc.spip.org/@analyser_dtd_comment
-function analyser_dtd_comment($dtd, &$dtc, $grammaire){
-	// ejecter les commentaires, surtout quand ils contiennent du code.
-	// Option /s car sur plusieurs lignes parfois
-
-	if (!preg_match('/^<!--.*?-->\s*(.*)$/s',$dtd, $m))
-		return -6;
-	return $m[1];
-}
-
-// http://doc.spip.org/@analyser_dtd_pi
-function analyser_dtd_pi($dtd, &$dtc, $grammaire){
-	if (!preg_match('/^<\?.*?>\s*(.*)$/s', $dtd, $m))
-		return -10;
-	return $m[1];
-}
-
-// http://doc.spip.org/@analyser_dtd_lexeme
-function analyser_dtd_lexeme($dtd, &$dtc, $grammaire){
-	if (!preg_match(_REGEXP_ENTITY_DEF,$dtd, $m))
-		return -9;
-
-	list(,$s) = $m;
-	$n = $dtc->macros[$s];
-	if (is_array($n)) {
-	    // en cas d'inclusion, l'espace de nom est le meme
-		analyser_dtd($n[1], $n[0], $dtc);
-	}
-	
-	return ltrim(substr($dtd,strlen($m[0])));
-}
-
-// il faudrait prevoir plusieurs niveaux d'inclusion.
-// (Ruby en utilise mais l'erreur est transparente. Scandaleux coup de pot)
-
-// http://doc.spip.org/@analyser_dtd_data
-function analyser_dtd_data($dtd, &$dtc, $grammaire){
-	if (!preg_match('/^<!\[%([^;]*);\s*\[\s*(.*?)\]\]>\s*(.*)$/s',$dtd, $m))
-		return -6;
-	if ($dtc->macros[$m[1]] == 'INCLUDE')
-		$retour = $m[2] . $m[3];
-	else $retour = $m[3]; 
-	return $retour;
-}
-
-// http://doc.spip.org/@analyser_dtd_notation
-function analyser_dtd_notation($dtd, &$dtc, $grammaire){
-	if (!preg_match('/^<!NOTATION.*?>\s*(.*)$/s',$dtd, $m))
-		return -8;
-	spip_log("analyser_dtd_notation a ecrire");
-	return $m[1];
-}
-
-// http://doc.spip.org/@analyser_dtd_entity
-function analyser_dtd_entity($dtd, &$dtc, $grammaire)
-{
-	if (!preg_match(_REGEXP_ENTITY_DECL, $dtd, $m))
-		return -2;
-
-	list($t, $term, $nom, $type, $val, $q, $alt, $dtd) = $m;
-
-	if (isset($dtc->macros[$nom]) AND $dtc->macros[$nom])
-		return $dtd;
-	if (isset($dtc->entites[$nom]))
-		spip_log("redefinition de l'entite $nom");
-	if  (!$term)
-		$dtc->entites[$nom] = expanserEntite($val, $dtc->macros);
-	elseif (!$type)
-		$dtc->macros[$nom] = expanserEntite($val, $dtc->macros);
-	elseif (!$alt)
-		$dtc->macros[$nom] = expanserEntite($val, $dtc->macros);
-	else {
-		if (strpos($alt, '/') === false)
-			$alt = preg_replace(',/[^/]+$,', '/', $grammaire)
-			. $alt ;
-		$dtc->macros[$nom] = array($type, $alt);
-	} 
-	return $dtd;
-}
-
-// Dresser le tableau des filles potentielles de l'element
-// pour traquer tres vite les illegitimes.
-// Si la regle a au moins une sequence (i.e. une virgule)
-// ou n'est pas une itération (i.e. se termine par * ou +)
-// en faire une RegExp qu'on appliquera aux balises rencontrees.
-// Sinon, conserver seulement le type de l'iteration car la traque
-// aura fait l'essentiel du controle sans memorisation des balises.
-// Fin du controle en finElement
-
-// http://doc.spip.org/@analyser_dtd_element
-function analyser_dtd_element($dtd, &$dtc, $grammaire)
-{
-	if (!preg_match('/^<!ELEMENT\s+(\S+)\s+([^>]*)>\s*(.*)$/s', $dtd, $m))
-		return -3;
-
-	list(,$nom, $val, $dtd) = $m;
-	$nom = expanserEntite($nom, $dtc->macros);
-	$val = compilerRegle(expanserEntite($val, $dtc->macros));
-	if (isset($dtc->elements[$nom])) {
-		spip_log("redefinition de l'element $nom dans la DTD");
-		return -4;
-	}
-	$filles = array();
-	if ($val == '(EMPTY )')
-		$dtc->regles[$nom] = 'EMPTY';
-	elseif  ($val == '(ANY )') 
-		$dtc->regles[$nom] = 'ANY';
-	else {
-		$last = substr($val,-1);
-		if (preg_match('/ \w/', $val)
-		OR strpos('*+', $last) === false)
-			$dtc->regles[$nom] = "/^$val$/";
-		else
-			$dtc->regles[$nom] = $last;
-			$filles = array_values(preg_split('/\W+/', $val,-1, PREG_SPLIT_NO_EMPTY));
-
-			foreach ($filles as $k) {
-				if (!isset($dtc->peres[$k]))
-				  $dtc->peres[$k] = array();
-				if (!in_array($nom, $dtc->peres[$k]))
-					$dtc->peres[$k][]= $nom;
-			}
-	}
-	$dtc->elements[$nom]= $filles;
-	return $dtd;
-}
-
-
-// http://doc.spip.org/@analyser_dtd_attlist
-function analyser_dtd_attlist($dtd, &$dtc, $grammaire)
-{
-	if (!preg_match('/^<!ATTLIST\s+(\S+)\s+([^>]*)>\s*(.*)/s', $dtd, $m))
-		return -5;
-
-	list(,$nom, $val, $dtd) = $m;
-	$nom = expanserEntite($nom, $dtc->macros);
-	$val = expanserEntite($val, $dtc->macros);
-	if (!isset($dtc->attributs[$nom]))
-		$dtc->attributs[$nom] = array();
-
-	if (preg_match_all("/\s*(\S+)\s+(([(][^)]*[)])|(\S+))\s+([^\s']*)(\s*'[^']*')?/", $val, $r2, PREG_SET_ORDER)) {
-		foreach($r2 as $m2) {
-			$v = preg_match('/^\w+$/', $m2[2]) ? $m2[2]
-			  : ('/^' . preg_replace('/\s+/', '', $m2[2]) . '$/');
-			$m21 = expanserEntite($m2[1], $dtc->macros);
-			$m25 = expanserEntite($m2[5], $dtc->macros);
-			$dtc->attributs[$nom][$m21] = array($v, $m25);
-		}
-	}
-
-	return $dtd;
-}
-
-
-// http://doc.spip.org/@expanserEntite
-function expanserEntite($val, $macros)
-{
-	if (preg_match_all(_REGEXP_ENTITY_USE, $val, $r, PREG_SET_ORDER)){
-	  foreach($r as $m) {
-		  $ent = $m[1];
-		  // il peut valoir ""
-			if (isset($macros[$ent]))
-				$val = str_replace($m[0], $macros[$ent], $val);
-	  }
-	}
-	return trim(preg_replace('/\s+/', ' ', $val));
-}
-
-
 // http://doc.spip.org/@validerElement
 function validerElement($phraseur, $name, $attrs)
 {
@@ -336,7 +51,7 @@ function validerElement($phraseur, $name, $attrs)
 		. _L(' balise inconnue ')
 		.  coordonnees_erreur($phraseur);
 	else {
-	// controler les filles illegitimes, ca suffut 
+	// controler les filles illegitimes, ca suffit 
 	  $depth = $phraseur_xml->depth;
 	  $ouvrant = $phraseur_xml->ouvrant;
 	  if (isset($ouvrant[$depth])) {
@@ -592,7 +307,18 @@ function defautElement($phraseur, $data)
 // http://doc.spip.org/@phraserTout
 function phraserTout($phraseur, $data)
 { 
-	$this->dtc = charger_dtd($data);
+	$r = analyser_doctype($data);
+	if ($r) {
+		list ($topelement, $avail, $grammaire, $rotlvl) = $r;
+		$file = _DIR_DTD . preg_replace('/[^\w.]/','_', $rotlvl) . '.gz';
+		if (lire_fichier($file, $r))
+		  $this->dtc = unserialize($r);
+		else {
+		  include_spip('xml/analyser_dtd');
+		  $this->dtc = charger_dtd($grammaire, $avail);
+		  ecrire_fichier($file, serialize($this->dtc));
+		}
+	}
 
   // bug de SAX qui ne dit pas si une Entite est dans un attribut ou non
   // ==> eliminer toutes les entites
@@ -625,8 +351,10 @@ function phraserTout($phraseur, $data)
 // http://doc.spip.org/@inc_valider_xml_dist
 function inc_valider_xml_dist($page, $apply=false)
 {
+	spip_timer('valider');
 	$sax = charger_fonction('sax', 'inc');
-	return $sax($page, $apply, $GLOBALS['phraseur_xml'] = new ValidateurXML());
-
+	$sax = $sax($page, $apply, $GLOBALS['phraseur_xml'] = new ValidateurXML());
+	spip_log("validation : " . spip_timer('valider'));
+	return $sax;
 }
 ?>
diff --git a/ecrire/xml/analyser_dtd.php b/ecrire/xml/analyser_dtd.php
new file mode 100644
index 0000000000000000000000000000000000000000..09b3f343bf5345f782385900e471a5a057652b2b
--- /dev/null
+++ b/ecrire/xml/analyser_dtd.php
@@ -0,0 +1,274 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2007                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('xml/interfaces');
+
+function charger_dtd($grammaire, $avail)
+{
+	spip_timer('dtd');
+	$dtc = new DTC;
+	analyser_dtd($grammaire, $avail, $dtc);
+
+	// tri final pour presenter les suggestions de corrections
+	foreach ($dtc->peres as $k => $v) {
+		asort($v);
+		$dtc->peres[$k] = $v;
+	  } 
+	  
+	spip_log("Analyser DTD $avail $grammaire (" . spip_timer('dtd') . ") " . count($dtc->macros)  . ' macros, ' . count($dtc->elements)  . ' elements, ' . count($dtc->attributs) . " listes d'attributs, " . count($dtc->entites) . " entites");
+#	$r = $dtc->regles; ksort($r);foreach($r as $l => $v) echo "<b>$l</b> '$v' ", join (', ',array_keys($dtc->attributs[$l])), "<br />\n";exit;
+	return $dtc;
+}
+
+// Compiler une regle de production en une Regexp qu'on appliquera sur la
+// suite des noms de balises separes par des espaces. Du coup:
+// supprimer #PCDATA etc, ca ne sert pas pour le controle des balises;
+// supprimer les virgules (les sequences sont implicites dans une Regexp)
+// conserver | + * ? ( ) qui ont la meme signification en DTD et en Regexp;
+// faire suivre chaque nom d'un espace (et supprimer les autres) ...
+// et parentheser le tout pour que  | + * ? s'applique dessus.
+
+// http://doc.spip.org/@compilerRegle
+function compilerRegle($val)
+{
+	$x = str_replace('\s*()\s*','',
+		preg_replace('/\s*,\s*/','',
+		preg_replace('/(\w+)\s*/','(\1 )',
+		preg_replace('/\s*([(+*|])\s*/','\1',
+		preg_replace('/\s*#\w+\s*[,|]?\s*/','', $val)))));
+	return $x;
+}
+
+
+// http://doc.spip.org/@analyser_dtd
+function analyser_dtd($loc, $avail, &$dtc)
+{
+	if ($avail == 'SYSTEM')
+	  $file = $loc;
+	else {
+	  $file = sous_repertoire(_DIR_DTD);
+	  $file .= preg_replace('/[^\w.]/','_', $loc);
+	}
+
+	$dtd = '';
+	if (@is_readable($file)) {
+		lire_fichier($file, $dtd);
+	} else {
+		if ($avail == 'PUBLIC') {
+			include_spip('inc/distant');
+			if ($dtd = recuperer_page($loc))
+				ecrire_fichier($file, $dtd); 
+		}
+	}
+
+	if (!$dtd = ltrim($dtd)) {
+		spip_log("DTD $loc inaccessible");
+		return array();
+	}
+
+	while ($dtd) {
+		if ($dtd[0] != '<')
+			$r = analyser_dtd_lexeme($dtd, $dtc, $loc);
+		elseif ($dtd[1] != '!')
+			$r = analyser_dtd_pi($dtd, $dtc, $loc);
+		else switch ($dtd[3]) {
+	  case '%' : $r = analyser_dtd_data($dtd, $dtc, $loc); break;
+	  case 'T' : $r = analyser_dtd_attlist($dtd, $dtc, $loc);break;
+	  case 'L' : $r = analyser_dtd_element($dtd, $dtc, $loc);break;
+	  case 'N' : $r = analyser_dtd_entity($dtd, $dtc, $loc);break;
+	  case 'O' : $r = analyser_dtd_notation($dtd, $dtc, $loc);break;
+	  case '-' : $r = analyser_dtd_comment($dtd, $dtc, $loc); break;
+	  default: $r = -1;
+	  }
+
+	  if (!is_string($r)) {
+	    spip_log("erreur $r dans la DTD  " . substr($dtd,0,80) . ".....");
+	    return array();
+	  }
+	  $dtd = $r;
+ 
+	}
+}
+
+// http://doc.spip.org/@analyser_dtd_comment
+function analyser_dtd_comment($dtd, &$dtc, $grammaire){
+	// ejecter les commentaires, surtout quand ils contiennent du code.
+	// Option /s car sur plusieurs lignes parfois
+
+	if (!preg_match('/^<!--.*?-->\s*(.*)$/s',$dtd, $m))
+		return -6;
+	return $m[1];
+}
+
+// http://doc.spip.org/@analyser_dtd_pi
+function analyser_dtd_pi($dtd, &$dtc, $grammaire){
+	if (!preg_match('/^<\?.*?>\s*(.*)$/s', $dtd, $m))
+		return -10;
+	return $m[1];
+}
+
+// http://doc.spip.org/@analyser_dtd_lexeme
+function analyser_dtd_lexeme($dtd, &$dtc, $grammaire){
+	if (!preg_match(_REGEXP_ENTITY_DEF,$dtd, $m))
+		return -9;
+
+	list(,$s) = $m;
+	$n = $dtc->macros[$s];
+	if (is_array($n)) {
+	    // en cas d'inclusion, l'espace de nom est le meme
+		analyser_dtd($n[1], $n[0], $dtc);
+	}
+	
+	return ltrim(substr($dtd,strlen($m[0])));
+}
+
+// il faudrait prevoir plusieurs niveaux d'inclusion.
+// (Ruby en utilise mais l'erreur est transparente. Scandaleux coup de pot)
+
+// http://doc.spip.org/@analyser_dtd_data
+function analyser_dtd_data($dtd, &$dtc, $grammaire){
+	if (!preg_match('/^<!\[%([^;]*);\s*\[\s*(.*?)\]\]>\s*(.*)$/s',$dtd, $m))
+		return -6;
+	if ($dtc->macros[$m[1]] == 'INCLUDE')
+		$retour = $m[2] . $m[3];
+	else $retour = $m[3]; 
+	return $retour;
+}
+
+// http://doc.spip.org/@analyser_dtd_notation
+function analyser_dtd_notation($dtd, &$dtc, $grammaire){
+	if (!preg_match('/^<!NOTATION.*?>\s*(.*)$/s',$dtd, $m))
+		return -8;
+	spip_log("analyser_dtd_notation a ecrire");
+	return $m[1];
+}
+
+// http://doc.spip.org/@analyser_dtd_entity
+function analyser_dtd_entity($dtd, &$dtc, $grammaire)
+{
+	if (!preg_match(_REGEXP_ENTITY_DECL, $dtd, $m))
+		return -2;
+
+	list($t, $term, $nom, $type, $val, $q, $alt, $dtd) = $m;
+
+	if (isset($dtc->macros[$nom]) AND $dtc->macros[$nom])
+		return $dtd;
+	if (isset($dtc->entites[$nom]))
+		spip_log("redefinition de l'entite $nom");
+	if  (!$term)
+		$dtc->entites[$nom] = expanserEntite($val, $dtc->macros);
+	elseif (!$type)
+		$dtc->macros[$nom] = expanserEntite($val, $dtc->macros);
+	elseif (!$alt)
+		$dtc->macros[$nom] = expanserEntite($val, $dtc->macros);
+	else {
+		if (strpos($alt, '/') === false)
+			$alt = preg_replace(',/[^/]+$,', '/', $grammaire)
+			. $alt ;
+		$dtc->macros[$nom] = array($type, $alt);
+	} 
+	return $dtd;
+}
+
+// Dresser le tableau des filles potentielles de l'element
+// pour traquer tres vite les illegitimes.
+// Si la regle a au moins une sequence (i.e. une virgule)
+// ou n'est pas une itération (i.e. se termine par * ou +)
+// en faire une RegExp qu'on appliquera aux balises rencontrees.
+// Sinon, conserver seulement le type de l'iteration car la traque
+// aura fait l'essentiel du controle sans memorisation des balises.
+// Fin du controle en finElement
+
+// http://doc.spip.org/@analyser_dtd_element
+function analyser_dtd_element($dtd, &$dtc, $grammaire)
+{
+	if (!preg_match('/^<!ELEMENT\s+(\S+)\s+([^>]*)>\s*(.*)$/s', $dtd, $m))
+		return -3;
+
+	list(,$nom, $val, $dtd) = $m;
+	$nom = expanserEntite($nom, $dtc->macros);
+	$val = compilerRegle(expanserEntite($val, $dtc->macros));
+	if (isset($dtc->elements[$nom])) {
+		spip_log("redefinition de l'element $nom dans la DTD");
+		return -4;
+	}
+	$filles = array();
+	if ($val == '(EMPTY )')
+		$dtc->regles[$nom] = 'EMPTY';
+	elseif  ($val == '(ANY )') 
+		$dtc->regles[$nom] = 'ANY';
+	else {
+		$last = substr($val,-1);
+		if (preg_match('/ \w/', $val)
+		OR strpos('*+', $last) === false)
+			$dtc->regles[$nom] = "/^$val$/";
+		else
+			$dtc->regles[$nom] = $last;
+			$filles = array_values(preg_split('/\W+/', $val,-1, PREG_SPLIT_NO_EMPTY));
+
+			foreach ($filles as $k) {
+				if (!isset($dtc->peres[$k]))
+				  $dtc->peres[$k] = array();
+				if (!in_array($nom, $dtc->peres[$k]))
+					$dtc->peres[$k][]= $nom;
+			}
+	}
+	$dtc->elements[$nom]= $filles;
+	return $dtd;
+}
+
+
+// http://doc.spip.org/@analyser_dtd_attlist
+function analyser_dtd_attlist($dtd, &$dtc, $grammaire)
+{
+	if (!preg_match('/^<!ATTLIST\s+(\S+)\s+([^>]*)>\s*(.*)/s', $dtd, $m))
+		return -5;
+
+	list(,$nom, $val, $dtd) = $m;
+	$nom = expanserEntite($nom, $dtc->macros);
+	$val = expanserEntite($val, $dtc->macros);
+	if (!isset($dtc->attributs[$nom]))
+		$dtc->attributs[$nom] = array();
+
+	if (preg_match_all("/\s*(\S+)\s+(([(][^)]*[)])|(\S+))\s+([^\s']*)(\s*'[^']*')?/", $val, $r2, PREG_SET_ORDER)) {
+		foreach($r2 as $m2) {
+			$v = preg_match('/^\w+$/', $m2[2]) ? $m2[2]
+			  : ('/^' . preg_replace('/\s+/', '', $m2[2]) . '$/');
+			$m21 = expanserEntite($m2[1], $dtc->macros);
+			$m25 = expanserEntite($m2[5], $dtc->macros);
+			$dtc->attributs[$nom][$m21] = array($v, $m25);
+		}
+	}
+
+	return $dtd;
+}
+
+
+// http://doc.spip.org/@expanserEntite
+function expanserEntite($val, $macros)
+{
+	if (preg_match_all(_REGEXP_ENTITY_USE, $val, $r, PREG_SET_ORDER)){
+	  foreach($r as $m) {
+		  $ent = $m[1];
+		  // il peut valoir ""
+			if (isset($macros[$ent]))
+				$val = str_replace($m[0], $macros[$ent], $val);
+	  }
+	}
+	return trim(preg_replace('/\s+/', ' ', $val));
+}
+
+
+
+?>
diff --git a/ecrire/xml/interfaces.php b/ecrire/xml/interfaces.php
new file mode 100644
index 0000000000000000000000000000000000000000..c8ac1bd0b8796b0a20b296a20d3578c395e40572
--- /dev/null
+++ b/ecrire/xml/interfaces.php
@@ -0,0 +1,40 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2007                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/sax');
+
+define('_REGEXP_DOCTYPE',
+	'/^\s*(<[?][^>]*>\s*)?<!DOCTYPE\s+(\w+)\s+(\w+)\s*([^>]*)>/');
+
+define('_SUB_REGEXP_SYMBOL', '[A-Za-z_][\w_:.-]*');
+
+define('_REGEXP_ID', '/^'  . _SUB_REGEXP_SYMBOL . '$/');
+
+define('_REGEXP_ENTITY_USE', '/%('  . _SUB_REGEXP_SYMBOL . ');/');
+define('_REGEXP_ENTITY_DEF', '/^%('  . _SUB_REGEXP_SYMBOL . ');/');
+define('_REGEXP_ENTITY_DECL', '/^<!ENTITY\s+(%?)\s*(' .
+		_SUB_REGEXP_SYMBOL .
+		';?)\s+(PUBLIC|SYSTEM|INCLUDE|IGNORE)?\s*"([^"]*)"\s*("([^"]*)")?\s*>\s*(.*)$/s');
+
+// Document Type Compilation
+
+class DTC {
+	var	$macros = array();
+	var 	$elements = array();
+	var 	$peres = array();
+	var 	$attributs = array();
+	var	$entites = array();
+	var	$regles = array();
+}
+?>