diff --git a/library/ZendAfi/View/Helper/ListeNotices/Tableau.php b/library/ZendAfi/View/Helper/ListeNotices/Tableau.php
index 0de54e98e0faab3d31bf1308f9878eef36aa685f..652e435e8676a1326c71933a678fcbb4366801db 100644
--- a/library/ZendAfi/View/Helper/ListeNotices/Tableau.php
+++ b/library/ZendAfi/View/Helper/ListeNotices/Tableau.php
@@ -18,71 +18,93 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
 class ZendAfi_View_Helper_ListeNotices_Tableau extends ZendAfi_View_Helper_ListeNotices_Abstract {
   public function listeNotices_Tableau($data, $preferences=[]) {
-    $champs = isset($preferences['liste_codes'])
-      ? $preferences['liste_codes']
-      : 'TANE';
-
-    // Entête
-    $html='<table cellspacing="0" cellpadding="3" border="0" width="100%">';
-    $html.='<tr><td class="listeTitre" width="26px">&nbsp;</td>';
-    for($i=0; $i < strlen($champs); $i++)
-      {
-        $champ=Class_Codification::getInstance()->getNomChamp($champs[$i]);
-        $html.='<td class="listeTitre">'.$champ.'</td>';
-      }
-    $html.='</tr>';
-
-    // Notices
-    $lig=0;
+    $preferences = $this->_updatePreferences($preferences);
+    $codes = $this->_filterCodes($preferences['liste_codes']);
+
+    return $this->_tag('table',
+                       $this->_header($codes)
+                       . $this->_body($data, $codes),
+                       ['cellspacing' => '0',
+                        'cellpadding' => '3',
+                        'border' => '0',
+                        'width' => '100%']);
+  }
+
+
+  protected function _filterCodes($codes) {
+    return array_filter(str_split($codes),
+                        function($code) { return !in_array($code, [';', '-']); });
+  }
+
 
+  protected function _header($codes) {
+    $columns = ['<td class="listeTitre" width="26px">&nbsp;</td>'];
+    $codifications = Class_Codification::getInstance();
+    foreach($codes as $code)
+      $columns[] = $this->_tag('td', $codifications->getNomChamp($code),
+                               ['class' => 'listeTitre']);
+
+    return $this->_tag('tr', implode($columns));
+  }
+
+
+  protected function _body($data, $codes) {
+    $lig=0;
+    $rows = [];
     foreach ($data as $notice) {
-        if($lig % 2) $style_css="listeImpaire"; else $style_css="listePaire";
-        $html.='<tr>';
-        $html.=sprintf('<td class="%s" style="text-align:center">%s</td>',
-                       $style_css,
-                       $this->view->iconeSupport($notice->getTypeDoc()));
-
-        for($i=0; $i < strlen($champs); $i++)
-          {
-            $champ=$champs[$i];
-
-            if($champ=="J")
-              $html.= sprintf('<td class="%s"><a href="%s">%s</a></td>',
-                              $style_css,
-                              $this->view->urlNotice($notice),
-                              $notice->getTitrePrincipal());
-            else if($champ=="A")  {
-              $html.= sprintf('<td class="%s">%s</td>',
-                              $style_css,
-                              $this->view->notice_LienRebondAuteur($notice));
-            }
-            else {
-              if (!$value = $notice->getChampNotice($champ, $notice->getFacettes())) {
-                $html .='<td class="'. $style_css .'"></td>';
-                continue;
-              }
-
-
-              if (is_array($value))
-                $value = $value[0];
-
-              if (is_array($value) && array_key_exists('libelle',$value))
-                $value = $value['libelle'];
-
-              if (is_object($value))
-                $value = $value->renderOn($this->view);
-
-              if($champ == "N") $align='style="text-align:center"'; else $align="";
-              $html.='<td class="'. $style_css .'" '.$align.'>'.$value.'</td>';
-            }
-            $lig++;
-          }
-        $html.='</tr>';
-      }
-    $html.='</table>';
-    return $html;
+      $style = 'liste' . ((0 === $lig % 2) ? 'Impaire' : 'Paire');
+      $rows[] = $this->_record($notice, $codes, $style);
+      $lig++;
+    }
+
+    return implode($rows);
+  }
+
+
+  protected function _record($notice, $codes, $style) {
+    $columns = [$this->_td($this->view->iconeSupport($notice->getTypeDoc()),
+                           $style,
+                           ['style' => 'text-align:center'])];
+
+    foreach ($codes as $code)
+      $columns[] = $this->_field($code, $notice, $style);
+
+    return $this->_tag('tr', implode($columns));
+  }
+
+
+  protected function _field($code, $notice, $style) {
+    if ('J' === $code)
+      return $this->_td($this->_tag('a', $notice->getTitrePrincipal(),
+                                    ['href' => $this->view->urlNotice($notice)]),
+                         $style);
+
+    if ('A' === $code)
+      return $this->_td($this->view->notice_LienRebondAuteur($notice),
+                        $style);
+
+    if (!$value = $notice->getChampNotice($code, $notice->getFacettes()))
+      return $this->_td('', $style);
+
+    if (is_array($value))
+      $value = $value[0];
+
+    if (is_array($value) && array_key_exists('libelle', $value))
+      $value = $value['libelle'];
+
+    if (is_object($value))
+      $value = $value->renderOn($this->view);
+
+    return $this->_td($value, $style,
+                      ('N' === $code) ? ['style' =>  'text-align:center'] : []);
+  }
+
+
+  protected function _td($content, $style, $attribs=[]) {
+    return $this->_tag('td', $content,
+                       array_merge(['class' => $style], $attribs));
   }
 }
-?>
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/ListeNotices/TableauTest.php b/tests/library/ZendAfi/View/Helper/ListeNotices/TableauTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..14efec9c40b7087939d4e3c42b569559a0b12104
--- /dev/null
+++ b/tests/library/ZendAfi/View/Helper/ListeNotices/TableauTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_ListeNotices_TableauTestCase extends ViewHelperTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_html = '',
+    $_helper;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_helper = new ZendAfi_View_Helper_ListeNotices_Tableau();
+    $this->_helper->setView($this->view);
+
+    $data = [$this->fixture('Class_Notice',
+                            ['id' => 42,
+                             'titre_principal' => 'Le grand livre de Beatrix Potter'])];
+
+    $this->_html = $this->_helper->listeNotices_Tableau($data, ['liste_codes' => 'J;A;F;C;N']);
+  }
+
+
+  /** @test */
+  public function pageShouldContainsTitleColumn() {
+    $this->assertXPathContentContains($this->_html, '//td[@class="listeTitre"]', 'Titre');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsBeatrixPotter() {
+    $this->assertXPathContentContains($this->_html, '//td', 'Beatrix Potter');
+  }
+}
\ No newline at end of file