diff --git a/.gitattributes b/.gitattributes
index 2ce318721fb163b54aad36a037e6c0ae93070027..452a19c604e49efdbb6966a16b3c9d80584687bb 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1762,6 +1762,7 @@ library/Class/WebService/Lastfm.php -text
 library/Class/WebService/LibraryThing.php -text
 library/Class/WebService/MappedSoapClient.php -text
 library/Class/WebService/OAI.php -text
+library/Class/WebService/OAI/CatalogueVisitor.php -text
 library/Class/WebService/OAI/Response/Identify.php -text
 library/Class/WebService/OAI/Response/ListSets.php -text
 library/Class/WebService/OAI/Response/Null.php -text
@@ -1821,6 +1822,7 @@ library/Class/WebService/Vignette.php -text
 library/Class/WebService/XMLParser.php -text
 library/Class/Xml.php -text
 library/Class/Xml/Builder.php -text
+library/Class/Xml/Oai/DublinCoreBuilder.php -text
 library/Class/Zone.php -text
 library/Thumbs/GdThumb.inc.php -text
 library/Thumbs/PhpThumb.inc.php -text
@@ -3369,6 +3371,7 @@ scripts/insert_licence.sh -text
 scripts/opac2.sql -text
 /startup.php -text
 tests/.htaccess -text
+tests/TestXPathFactory.php -text
 tests/application/modules/AbstractControllerTestCase.php -text
 tests/application/modules/admin/controllers/AccueilControllerTest.php -text
 tests/application/modules/admin/controllers/AdminAbstractControllerTestCase.php -text
@@ -3486,6 +3489,7 @@ tests/library/Class/UsersTest.php -text
 tests/library/Class/VodeclicLinkTest.php -text
 tests/library/Class/WebService/BabelioTest.php -text
 tests/library/Class/WebService/OAI/Response/IdentifyTest.php -text
+tests/library/Class/WebService/OAI/Response/ListSetsTest.php -text
 tests/library/Class/WebService/OAIIdentify.xml -text
 tests/library/Class/WebService/OAIListIdentifiers.xml -text
 tests/library/Class/WebService/OAIListRecords.xml -text
diff --git a/library/Class/Catalogue.php b/library/Class/Catalogue.php
index 933b9568f0e28e3914b8c2ac0e99874d5d6e5c87..c3d3a01f642d4ee46736c5ffae9a70311bbcfbc4 100644
--- a/library/Class/Catalogue.php
+++ b/library/Class/Catalogue.php
@@ -22,8 +22,27 @@
 // OPAC3 - Catalogues de notices
 ////////////////////////////////////////////////////////////////////////////////
 
-class Class_Catalogue
+class Class_Catalogue extends Storm_Model_Abstract
 {
+	protected $_table_name = 'catalogue';
+	protected $_table_primary = 'ID_CATALOGUE';
+
+	protected $_default_attribute_values = array('oai_spec' => '',
+																							 'description' => '');
+
+	public static function getLoader() {
+		return self::getLoaderFor(__CLASS__);
+	}
+
+
+	public function acceptVisitor($visitor) {
+		$visitor->visitOaiSpec($this->getOaiSpec());
+		$visitor->visitLibelle($this->getLibelle());
+		if ($this->hasDescription())
+			$visitor->visitDescription($this->getDescription());
+	}
+
+
 //------------------------------------------------------------------------------
 // Rend les notices et les stats (test d'un catalogue)
 //------------------------------------------------------------------------------
diff --git a/library/Class/Notice/DublinCoreVisitor.php b/library/Class/Notice/DublinCoreVisitor.php
index bf5fb6be97acad158c0299d1a9b1f7b5e3039636..deeb9a02610fbeca84c9607b5f1d3123318ed5ba 100644
--- a/library/Class/Notice/DublinCoreVisitor.php
+++ b/library/Class/Notice/DublinCoreVisitor.php
@@ -21,38 +21,33 @@
 
 class Class_Notice_DublinCoreVisitor {
 	protected $_xml;
+	protected $_builder;
 
-	public function visit($notice) {
-		$this->_xml = '<oai_dc:dc xmlns:dc="http://purl.org/dc/elements/1.1/"
-									 xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/">';
+	public function __construct() {
+		$this->_builder = new Class_Xml_Oai_DublinCoreBuilder();
+	}
 
+
+	public function visit($notice) {
 		$notice->acceptVisitor($this);
-		$this->_xml .= '</oai_dc:dc>';
-		
 	}
 
 
 	public function xml() {
-		return $this->_xml;
+		return $this->_builder->oai_dc($this->_xml);
 	}
 
 
 	public function visitClefAlpha($clef) {
-		$this->addTag('identifier', sprintf('http://%s%s/recherche/notice/%s',
-																				$_SERVER['SERVER_NAME'],
-																				BASE_URL,
-																				$clef));
+		$this->_xml .= $this->_builder->identifier(sprintf('http://%s%s/recherche/notice/%s',
+																											 $_SERVER['SERVER_NAME'],
+																											 BASE_URL,
+																											 $clef));
 	}
 
 
 	public function visitTitre($titre) {
-		$this->addTag('title', $titre);
-	}
-
-
-	public function addTag($tag, $content) {
-		$this->_xml .= sprintf('<dc:%1$s>%2$s</dc:%1$s>',
-													 $tag, $content);
+		$this->_xml .= $this->_builder->title($titre);
 	}
 }
 
diff --git a/library/Class/WebService/OAI/CatalogueVisitor.php b/library/Class/WebService/OAI/CatalogueVisitor.php
new file mode 100644
index 0000000000000000000000000000000000000000..65b772ddb5c360c8516906d62bd250e5ee1cc3e6
--- /dev/null
+++ b/library/Class/WebService/OAI/CatalogueVisitor.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ */
+
+class Class_WebService_OAI_CatalogueVisitor {
+	protected $_xml;
+	protected $_xmlBuilder;
+
+	public function __construct($builder) {
+		$this->_xmlBuilder = $builder;
+	}
+
+
+	public function xml() {
+		return $this->_xml;
+	}
+
+
+	public function visitCatalogue($catalogue) {
+		$this->_xml = '';
+		$catalogue->acceptVisitor($this);
+		return $this;
+	}
+
+
+	public function visitOaiSpec($oai_spec) {
+		$this->_xml .= $this->_xmlBuilder->setSpec($oai_spec);
+	}
+
+
+	public function visitLibelle($libelle) {
+		$this->_xml .= $this->_xmlBuilder->setName($libelle);
+	}
+
+
+	public function visitDescription($description) {
+		$dcBuilder = new Class_Xml_Oai_DublinCoreBuilder();
+		$dcDescription = $dcBuilder->oai_dc($dcBuilder->description($description));
+		$this->_xml .= $this->_xmlBuilder->setDescription($dcDescription);
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/library/Class/WebService/OAI/Response/ListSets.php b/library/Class/WebService/OAI/Response/ListSets.php
index 59b66ce041d7a56614af97ee8636bd40eb9b26ce..76bc58f5b7da9db02715c8a1a5fa1859a5a1eb6d 100644
--- a/library/Class/WebService/OAI/Response/ListSets.php
+++ b/library/Class/WebService/OAI/Response/ListSets.php
@@ -21,10 +21,38 @@
 
 
 class Class_WebService_OAI_Response_ListSets extends Class_WebService_OAI_Response_Null {
+	protected $_xmlBuilder;
+	protected $_catalogue_visitor;
+
 	public function buildXmlOn($builder) {
+		$this->_xmlBuilder = $builder;
+		$this->_catalog_visitor = new Class_WebService_OAI_CatalogueVisitor($this->_xmlBuilder);
+
 		return 
-			$builder->request(array('verb' => 'ListSets'), 
-												$this->_baseUrl);
+			$this->_xmlBuilder->request(array('verb' => 'ListSets'), 
+																	$this->_baseUrl)
+			. $this->_buildSets();
+	}
+
+
+	protected function _buildSets() {
+		$catalogs = Class_Catalogue::getLoader()
+			->findAllBy(array('where' => 'oai_spec is not null',
+												'where' => 'oai_spec !=\'\'',
+												'order' => 'oai_spec'));
+
+
+		$sets = '';
+		foreach ($catalogs as $catalog)
+			$sets .= $this->xmlForCatalogue($catalog);
+		
+		return $this->_xmlBuilder->ListSets($sets);
+	}
+
+
+	protected function xmlForCatalogue($catalog) {
+		$this->_catalog_visitor->visitCatalogue($catalog);
+		return $this->_xmlBuilder->set($this->_catalog_visitor->xml());
 	}
 }
 
diff --git a/library/Class/WebService/OAI/Response/Null.php b/library/Class/WebService/OAI/Response/Null.php
index e02ee83b29a979d49b58552ff90aafcc64983467..fff17ca425e0b5058070a7fe6bb732251ce43ed8 100644
--- a/library/Class/WebService/OAI/Response/Null.php
+++ b/library/Class/WebService/OAI/Response/Null.php
@@ -38,6 +38,7 @@ class Class_WebService_OAI_Response_Null {
 											 $this->buildXmlOn($builder));
 	}
 
+
 	public function buildXmlOn($builder) {
 		return $builder->error(array('code' => 'badVerb'), 'Illegal OAI verb');
 	}
diff --git a/library/Class/Xml/Builder.php b/library/Class/Xml/Builder.php
index 850911822b59d264d43bebea6c38497753ea21d7..b8919dd3bb979a463007d6312d1bd2c842405248 100644
--- a/library/Class/Xml/Builder.php
+++ b/library/Class/Xml/Builder.php
@@ -30,11 +30,7 @@ class Class_Xml_Builder {
 	public function _tag($tag, $content) {
 		if (!is_array($tag)) 
 			return $this->_xmlString((string)$tag, $content);
-
-		$attributes = '';
-		foreach (current($tag) as $k => $v)
-			$attributes .= ' ' . $k . '="' . $v . '"';
-
+		$attributes = $this->attributesToString(current($tag));
 		return $this->_xmlString(key($tag), $content, $attributes);
 	}
 
@@ -42,6 +38,14 @@ class Class_Xml_Builder {
 	public function _xmlString($name, $content, $attributes = '') {
 		return '<'.$name.$attributes.'>'.$content.'</'.$name.'>';
 	}
+
+	
+	public function attributesToString($attributes) {
+		$attribs = '';
+		foreach ($attributes as $k => $v)
+			$attribs .= ' ' . $k . '="' . $v . '"';
+		return $attribs;
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/library/Class/Xml/Oai/DublinCoreBuilder.php b/library/Class/Xml/Oai/DublinCoreBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..0665eacba5b57c5b5605bc6dc4ae9ff5eed20bc4
--- /dev/null
+++ b/library/Class/Xml/Oai/DublinCoreBuilder.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ */
+
+class Class_Xml_Oai_DublinCoreBuilder extends Class_Xml_Builder {
+	public function oai_dc($content) {
+		return parent::_xmlString('oai_dc:dc', 
+															$content, 
+															$this->attributesToString(array('xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
+																															'xmlns:dc' => 'http://purl.org/dc/elements/1.1')));
+	}
+
+
+	public function _xmlString($name, $content, $attributes = '') {
+		return parent::_xmlString('dc:' . $name, $content, $attributes);
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/TestXPathFactory.php b/tests/TestXPathFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..eed8061d6cc76bc887e39c9d9be8728ce0a05712
--- /dev/null
+++ b/tests/TestXPathFactory.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ */
+
+class TestXPathFactory {
+	public static function newOai() {
+		$xpath = new Storm_Test_XPathXML();
+		$xpath->registerNameSpace('oai', 'http://www.openarchives.org/OAI/2.0/');
+		return $xpath;
+	}
+
+}
+?>
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/OaiControllerTest.php b/tests/application/modules/opac/controllers/OaiControllerTest.php
index fe4d83be6820263550f07a4608b9681b74510562..5407c1449a6b51cc639568181c4744ac7ca1b988 100644
--- a/tests/application/modules/opac/controllers/OaiControllerTest.php
+++ b/tests/application/modules/opac/controllers/OaiControllerTest.php
@@ -82,6 +82,13 @@ class OaiControllerListSetsRequestTest extends OaiControllerRequestTestCase {
 	 
 	public function setUp() {
 		parent::setUp();
+		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Catalogue')
+			->whenCalled('findAllBy')
+			->with(array('where' => 'oai_spec is not null',
+									 'where' => 'oai_spec !=\'\'',
+									 'order' => 'oai_spec'))
+			->answers(array());
+
 		$this->dispatch('/opac/oai/request?verb=ListSets');
 	}
 
diff --git a/tests/library/Class/WebService/OAI/Response/ListSetsTest.php b/tests/library/Class/WebService/OAI/Response/ListSetsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..00f4cc5f904904a195ffd1ecb7a4669e85602b38
--- /dev/null
+++ b/tests/library/Class/WebService/OAI/Response/ListSetsTest.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ */
+
+class ListSetsTest extends Storm_Test_ModelTestCase {
+	protected $_response;
+	protected $_xpath;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->_xpath = TestXPathFactory::newOai();
+
+		$catalogue_bd = Class_Catalogue::getLoader()->newInstanceWithId(1)
+			->setLibelle('BDs')
+			->setOaiSpec('livres:bd');
+		$catalogue_musique = Class_Catalogue::getLoader()->newInstanceWithId(2)
+			->setLibelle('Musique')
+			->setDescription('La musique qui balance')
+			->setOaiSpec('music');
+
+		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Catalogue')
+			->whenCalled('findAllBy')
+			->with(array('where' => 'oai_spec is not null',
+									 'where' => 'oai_spec !=\'\'',
+									 'order' => 'oai_spec'))
+			->answers(array($catalogue_bd, $catalogue_musique));
+
+
+		$this->_response = new Class_WebService_OAI_Response_ListSets('http://afi.fr/oai');
+	}
+
+
+	/** @test */
+	public function setMusiqueShouldContainSpecMusic() {
+		$this->_xpath->assertXpathContentContains($this->_response->xml(),
+																							'//oai:ListSets/oai:set/oai:setSpec',
+																							'music');
+	}
+
+
+	/** @test */
+	public function setMusiqueShouldContainNameMusique() {
+		$this->_xpath->assertXpathContentContains($this->_response->xml(),
+																							'//oai:ListSets/oai:set/oai:setName',
+																							'Musique');
+	}
+
+
+  /** @test */
+	public function setMusiqueShouldContainDescriptionLaMusiqueQuiBalance() {
+		$this->_xpath->assertXpathContentContains($this->_response->xml(),
+																							'//oai:ListSets/oai:set/oai:setDescription',
+																							'La musique qui balance');
+	}
+
+
+
+	/** @test */
+	public function setBdsShouldContainsSpecLivresColonBds() {
+		$this->_xpath->assertXpathContentContains($this->_response->xml(),
+																							'//oai:ListSets/oai:set/oai:setSpec',
+																							'livres:bd');
+	}
+
+
+	/** @test */
+	public function setBdsShouldContainsNameBds() {
+		$this->_xpath->assertXpathContentContains($this->_response->xml(),
+																							'//oai:ListSets/oai:set/oai:setName',
+																							'BDs'); 		
+	}
+
+
+  /** @test */
+	public function setBdsShouldNotContainDescription() {
+		$this->_xpath->assertNotXpath($this->_response->xml(),
+																	'//oai:ListSets/oai:set/oai:setSpec[text()="livres:bd"][following-sibling::oai:setDescription]');
+	}
+
+}
+?>
\ No newline at end of file