diff --git a/src/Knp/Menu/Renderer/BreadcrumbRenderer.php b/src/Knp/Menu/Renderer/BreadcrumbRenderer.php
new file mode 100644
index 00000000..a7aab600
--- /dev/null
+++ b/src/Knp/Menu/Renderer/BreadcrumbRenderer.php
@@ -0,0 +1,160 @@
+defaultOptions = array_merge(array(
+ 'current_as_link' => true,
+ 'current_class' => 'current',
+ 'additional_path' => null,
+ 'compressed' => false,
+ 'allow_safe_labels' => false,
+ 'root_attributes' => array(),
+ ), $defaultOptions);
+
+ parent::__construct($charset);
+ }
+
+ /**
+ * Renders a menu with the specified renderer.
+ *
+ * @param \Knp\Menu\ItemInterface $item
+ * @param array $options
+ * @return string
+ */
+ public function render(ItemInterface $item, array $options = array())
+ {
+ $options = array_merge($this->defaultOptions, $options);
+
+ $breadcrumb = $item->getBreadcrumbsArray($options['additional_path']);
+
+ if (empty($breadcrumb)) {
+ return '';
+ }
+
+ return $this->renderBreadcrumb($breadcrumb, $options);
+ }
+
+ /**
+ * Renders the breadcrumb
+ *
+ * @param array $breadcrumb
+ * @param array $options
+ * @return string
+ */
+ protected function renderBreadcrumb(array $breadcrumb, array $options)
+ {
+ $html = $this->format('
renderHtmlAttributes($options['root_attributes']).'>', 'ul', 0, $options);
+ $html .= $this->renderList($breadcrumb, $options);
+ $html .= $this->format('
', 'ul', 0, $options);
+
+ return $html;
+ }
+
+ /**
+ * Renders the breadcrumb list
+ *
+ * @param array $breadcrumb
+ * @param array $options
+ * @return string
+ */
+ protected function renderList(array $breadcrumb, array $options)
+ {
+ $html = '';
+ foreach ($breadcrumb as $element) {
+ $element = array_replace(array('label' => null, 'uri' => null, 'item' => null), $element);
+ $html .= $this->renderItem($element['label'], $element['uri'], $options, $element['item']);
+ }
+
+ return $html;
+ }
+
+ /**
+ * @param string $label
+ * @param string $uri
+ * @param array $options
+ * @param \Knp\Menu\ItemInterface|null $item
+ * @return string
+ */
+ protected function renderItem($label, $uri, array $options, ItemInterface $item = null)
+ {
+ $isCurrent = null !== $item && $item->isCurrent();
+ $attributes = $isCurrent ? array('class' => $options['current_class']) : array();
+
+ // opening li tag
+ $html = $this->format('renderHtmlAttributes($attributes).'>', 'li', 1, $options);
+
+ // render the text/link inside the li tag
+ if (null === $uri || ($isCurrent && !$options['current_as_link'])) {
+ $content = $this->renderLabel($label, $options, $item);
+ } else {
+ $content = sprintf('%s', $this->escape($uri), $this->renderLabel($label, $options, $item));
+ }
+ $html .= $this->format($content, 'link', 1, $options);
+
+ // closing li tag
+ $html .= $this->format('', 'li', 1, $options);
+
+ return $html;
+ }
+
+ /**
+ * @param string $label
+ * @param array $options
+ * @param \Knp\Menu\ItemInterface|null $item
+ * @return string
+ */
+ protected function renderLabel($label, array $options, ItemInterface $item = null)
+ {
+ if ($options['allow_safe_labels'] && null !== $item && $item->getExtra('safe_label', false)) {
+ return $item->getLabel();
+ }
+
+ return $this->escape($label);
+ }
+
+ /**
+ * If $this->renderCompressed is on, this will apply the necessary
+ * spacing and line-breaking so that the particular thing being rendered
+ * makes up its part in a fully-rendered and spaced menu.
+ *
+ * @param string $html The html to render in an (un)formatted way
+ * @param string $type The type [ul,link,li] of thing being rendered
+ * @param integer $level
+ * @param array $options
+ * @return string
+ */
+ protected function format($html, $type, $level, array $options)
+ {
+ if ($options['compressed']) {
+ return $html;
+ }
+
+ switch ($type) {
+ case 'ul':
+ case 'link':
+ $spacing = $level * 4;
+ break;
+
+ case 'li':
+ $spacing = $level * 4 - 2;
+ break;
+ }
+
+ return str_repeat(' ', $spacing).$html."\n";
+ }
+}
diff --git a/tests/Knp/Menu/Tests/Renderer/BreadcrumbRendererTest.php b/tests/Knp/Menu/Tests/Renderer/BreadcrumbRendererTest.php
new file mode 100644
index 00000000..f01b5f24
--- /dev/null
+++ b/tests/Knp/Menu/Tests/Renderer/BreadcrumbRendererTest.php
@@ -0,0 +1,115 @@
+ true));
+
+ $expected = '';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testAdditionalPath()
+ {
+ $renderer = new BreadcrumbRenderer(array('compressed' => true));
+
+ $expected = '- Root li
- Parent 1
- Child 1
- Foo
';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1, array('additional_path' => array('Foo' => 'http://example.com'))));
+ }
+
+ public function testCurrentLink()
+ {
+ $this->pt1->setUri('foobar')->setCurrent(true);
+
+ $renderer = new BreadcrumbRenderer(array('compressed' => true));
+
+ $expected = '';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testCurrentNoLink()
+ {
+ $this->pt1->setUri('foobar')->setCurrent(true);
+
+ $renderer = new BreadcrumbRenderer(array('compressed' => true, 'current_as_link' => false));
+
+ $expected = '';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testCurrentCustomClass()
+ {
+ $this->pt1->setCurrent(true);
+
+ $renderer = new BreadcrumbRenderer(array('compressed' => true, 'current_class' => 'foo'));
+
+ $expected = '';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testEscapedLabel()
+ {
+ $this->pt1->setExtra('safe_label', true)->setLabel('Foo');
+
+ $renderer = new BreadcrumbRenderer(array('compressed' => true));
+
+ $expected = '- Root li
- <strong>Foo</strong>
- Child 1
';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testUnsafeLabel()
+ {
+ $this->pt1->setLabel('Foo');
+
+ $renderer = new BreadcrumbRenderer(array('compressed' => true, 'allow_safe_labels' => true));
+
+ $expected = '- Root li
- <strong>Foo</strong>
- Child 1
';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testSafeLabel()
+ {
+ $this->pt1->setExtra('safe_label', true)->setLabel('Foo');
+
+ $renderer = new BreadcrumbRenderer(array('compressed' => true, 'allow_safe_labels' => true));
+
+ $expected = '';
+
+ $this->assertEquals($expected, $renderer->render($this->ch1));
+ }
+
+ public function testPrettyRendering()
+ {
+ $renderer = new BreadcrumbRenderer();
+
+ $rendered = <<
+
+ Root li
+
+
+ Parent 1
+
+
+ Child 1
+
+
+
+HTML;
+
+ $this->assertEquals($rendered, $renderer->render($this->ch1));
+ }
+}