diff --git a/.gitattributes b/.gitattributes
index 8dd540441f5e2c4c8d5140075e5dd4d67a5082ab..aade0b366ecc9490fc7029cbb3b083f417fbb253 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -507,6 +507,7 @@ ecrire/inc/syndic.php -text
 ecrire/inc/tourner.php -text
 ecrire/inc/traduire.php -text
 ecrire/inc/utils.php -text
+ecrire/inc/validateur.php -text
 ecrire/inc/vieilles_defs.php -text
 ecrire/inc/virtualiser.php -text
 ecrire/inc/xml.php -text
diff --git a/ecrire/inc/sax.php b/ecrire/inc/sax.php
index 8ca87552f2b6d39c4cb2ad28fcfd39fc85533986..30ada8f661b0fb062b6764bf178f71c80ff7244c 100644
--- a/ecrire/inc/sax.php
+++ b/ecrire/inc/sax.php
@@ -10,7 +10,6 @@
  *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
-
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
 include_spip('inc/filtres');
@@ -22,6 +21,10 @@ class PhraseurXML {
 function debutElement($parser, $name, $attrs)
 {
   global $phraseur_xml;
+
+  if ($phraseur_xml->elements)
+    validerElement($parser, $name);
+
   $depth = &$phraseur_xml->depth;
   $contenu = &$phraseur_xml->contenu;
   $ouvrant = &$phraseur_xml->ouvrant;
@@ -42,6 +45,8 @@ function debutElement($parser, $name, $attrs)
   $att = '';
   $sep = ' ';
   foreach ($attrs as $k => $v) {
+	if ($phraseur_xml->attributs)
+	  validerAttribut($parser, $k, $v, $name);
 	$delim = strpos($v, "'") === false ? "'" : '"';
 	$val = entites_html($v);
 	$att .= $sep .  $k . "=" . $delim
@@ -159,7 +164,9 @@ function xml_parsestring($xml_parser, $data)
 	      _L(" ligne ") .
 	      $phraseur_xml->reperes[$phraseur_xml->depth]));
 
-	} else $r = $phraseur_xml->res;
+	} else if ($phraseur_xml->err)
+	  $r = join(', ', $phraseur_xml->err);
+	else $r = $phraseur_xml->res;
 
 	return $r;
 }
@@ -169,8 +176,14 @@ function xml_parsestring($xml_parser, $data)
  var $contenu = array();
  var $ouvrant = array();
  var $reperes = array();
+ var $elements = array();
+ var $entites = array();
+ var $attributs = array();
+ var $err = array();
 }
 
+
+
 // http://doc.spip.org/@inc_sax_dist
 function inc_sax_dist($page, $apply=false) {
 	global $phraseur_xml, $xml_parser;
@@ -191,7 +204,8 @@ function inc_sax_dist($page, $apply=false) {
 		$page = ob_get_contents();
 		ob_end_clean();
 	}
-
+	if ($validateur = charger_fonction('validateur', 'inc', true))
+		$validateur($page);
 	$res = $phraseur_xml->xml_parsestring($xml_parser, $page);
 	xml_parser_free($xml_parser);
 	if ($res[0] != '<')
diff --git a/ecrire/inc/validateur.php b/ecrire/inc/validateur.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e2c94ea6881f43dbf1041e5de796c2f3465de9e
--- /dev/null
+++ b/ecrire/inc/validateur.php
@@ -0,0 +1,99 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2006                                                *
+ *  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;
+
+define('_REGEXP_DOCTYPE',
+	'/^\s*<!DOCTYPE\s+(\w+)\s+(\w+)\s+(.)([^\3>]*)\3\s+(.)([^\5>]*)\5[^>]*>/');
+
+function inc_validateur_dist($data)
+{
+  global $phraseur_xml;
+
+	if (!preg_match(_REGEXP_DOCTYPE, $data, $r))
+		return array();
+
+	list(,$ns, $type, $s, $nom, $s2, $grammaire) = $r;
+
+	include_spip('inc/distant');
+	$dtd = recuperer_page($grammaire);
+	preg_match_all('/<!ELEMENT\s+(\w+)[^>]*>/', $dtd, $r);
+	$phraseur_xml->elements = $r[1];
+
+	$res = array();
+	// on ignore les entites publiques. A ameliorer a terme
+	if (preg_match_all('/<!ENTITY\s+%\s+([.\w]+)\s+"([^"]*)"\s*>/', $dtd, $r, PREG_SET_ORDER)) {
+	  foreach($r as $m) {
+	    list(,$nom, $val) = $m;
+	    if (preg_match_all('/%([.\w]+);/', $val, $r2, PREG_SET_ORDER)) {
+	      foreach($r2 as $m2)
+		$val = str_replace($m2[0], $res[$m2[1]], $val);
+	    }
+	    $res[$nom] = $val;
+	  }
+	}
+	$phraseur_xml->entites = $res;
+
+	$res = array();
+	if (preg_match_all('/<!ATTLIST\s+(\S+)\s+([^>]*)>/', $dtd, $r, PREG_SET_ORDER)) {
+	  foreach($r as $m) {
+	    list(,$nom, $val) = $m;
+	    if (preg_match_all('/%([.\w]+);/', $val, $r2, PREG_SET_ORDER)) {
+		foreach($r2 as $m2)
+	  // parfois faux suite au non chargement des entites publiques
+		  if ($x = $phraseur_xml->entites[$m2[1]])
+		    $val = str_replace($m2[0], $x, $val);
+	    }
+	    $att = array();
+	    if (preg_match_all("/\s*(\S+)\s+(([(][^)]*[)])|(\S+))\s+(\S+)(\s*'[^']*')?/", $val, $r2, PREG_SET_ORDER)) {
+	      foreach($r2 as $m2)
+		$att[$m2[1]] = $m2[5];
+	    }
+	    $res[$nom] = $att;
+	  }
+	}
+	$phraseur_xml->attributs = $res;
+}
+
+function validerElement($parser, $name)
+{
+  global $phraseur_xml;
+
+	if ($phraseur_xml->elements 
+	AND !in_array($name, $phraseur_xml->elements))
+
+		$phraseur_xml->err[]= $name 
+		. '&nbsp;:&nbsp;'
+		. _L('balise inconnue ')
+		. _L('ligne ')
+		. xml_get_current_line_number($parser);
+}
+
+
+function validerAttribut($parser, $name, $val, $bal)
+{
+  global $phraseur_xml;
+
+	if ($a = $phraseur_xml->attributs[$bal] 
+	    AND !isset($a[$name]))
+
+		$phraseur_xml->err[]= $name 
+		. '&nbsp;:&nbsp;'
+		. _L('attribut inconnu de ')
+		. $bal 
+		. _L(' ligne ')
+		. xml_get_current_line_number($parser);
+}
+
+
+?>