Skip to content

Commit

Permalink
[Config] Create XML Reference Dumper
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterj authored and fabpot committed Sep 18, 2013
1 parent 1b2ef74 commit 05e9ca7
Show file tree
Hide file tree
Showing 8 changed files with 645 additions and 200 deletions.
Expand Up @@ -11,7 +11,8 @@

namespace Symfony\Bundle\FrameworkBundle\Command;

use Symfony\Component\Config\Definition\ReferenceDumper;
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -21,6 +22,7 @@
* A console command for dumping available configuration reference
*
* @author Kevin Bond <kevinbond@gmail.com>
* @author Wouter J <waldio.webdesign@gmail.com>
*/
class ConfigDumpReferenceCommand extends ContainerDebugCommand
{
Expand All @@ -32,7 +34,8 @@ protected function configure()
$this
->setName('config:dump-reference')
->setDefinition(array(
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle or extension alias')
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle or extension alias'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The format, either yaml or xml', 'yaml'),
))
->setDescription('Dumps default configuration for an extension')
->setHelp(<<<EOF
Expand All @@ -47,6 +50,9 @@ protected function configure()
or
<info>php %command.full_name% FrameworkBundle</info>
With the <info>format</info> option specifies the format of the configuration, this is either yaml
or xml. When the option is not provided, yaml is used.
EOF
)
;
Expand Down Expand Up @@ -118,7 +124,17 @@ protected function execute(InputInterface $input, OutputInterface $output)

$output->writeln($message);

$dumper = new ReferenceDumper();
$output->writeln($dumper->dump($configuration));
switch ($input->getOption('format')) {
case 'yaml':
$dumper = new YamlReferenceDumper();
break;
case 'xml':
$dumper = new XmlReferenceDumper();
break;
default:
throw new \InvalidArgumentException('Only the yaml and xml formats are supported.');
}

$output->writeln($dumper->dump($configuration), $extension->getNamespace());
}
}
10 changes: 10 additions & 0 deletions src/Symfony/Component/Config/Definition/ArrayNode.php
Expand Up @@ -105,6 +105,16 @@ public function setXmlRemappings(array $remappings)
$this->xmlRemappings = $remappings;
}

/**
* Gets the xml remappings that should be performed.
*
* @return array $remappings an array of the form array(array(string, string))
*/
public function getXmlRemappings()
{
return $this->xmlRemappings;
}

/**
* Sets whether to add default values for this array if it has not been
* defined in any of the configuration files.
Expand Down
300 changes: 300 additions & 0 deletions src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php
@@ -0,0 +1,300 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Config\Definition\Dumper;

use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;

/**
* Dumps a XML reference configuration for the given configuration/node instance.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
class XmlReferenceDumper
{
private $reference;

public function dump(ConfigurationInterface $configuration, $namespace = null)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
}

public function dumpNode(NodeInterface $node, $namespace = null)
{
$this->reference = '';
$this->writeNode($node, 0, true, $namespace);
$ref = $this->reference;
$this->reference = null;

return $ref;
}

/**
* @param NodeInterface $node
* @param integer $depth
* @param boolean $root If the node is the root node
* @param string $namespace The namespace of the node
*/
private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null)
{
$rootName = ($root ? 'config' : $node->getName());
$rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));

// xml remapping
if ($node->getParent()) {
$remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
return $rootName === $mapping[1];
});

if (count($remapping)) {
list($singular, $plural) = current($remapping);
$rootName = $singular;
}
}
$rootName = str_replace('_', '-', $rootName);

$rootAttributes = array();
$rootAttributeComments = array();
$rootChildren = array();
$rootComments = array();

if ($node instanceof ArrayNode) {
$children = $node->getChildren();

// comments about the root node
if ($rootInfo = $node->getInfo()) {
$rootComments[] = $rootInfo;
}

if ($rootNamespace) {
$rootComments[] = 'Namespace: '.$rootNamespace;
}

// render prototyped nodes
if ($node instanceof PrototypedArrayNode) {
array_unshift($rootComments, 'prototype');

if ($key = $node->getKeyAttribute()) {
$rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
}

$prototype = $node->getPrototype();

if ($prototype instanceof ArrayNode) {
$children = $prototype->getChildren();
} else {
if ($prototype->hasDefaultValue()) {
$prototypeValue = $prototype->getDefaultValue();
} else {
switch (get_class($prototype)) {
case 'Symfony\Component\Config\Definition\ScalarNode':
$prototypeValue = 'scalar value';
break;

case 'Symfony\Component\Config\Definition\FloatNode':
case 'Symfony\Component\Config\Definition\IntegerNode':
$prototypeValue = 'numeric value';
break;

case 'Symfony\Component\Config\Definition\BooleanNode':
$prototypeValue = 'true|false';
break;

case 'Symfony\Component\Config\Definition\EnumNode':
$prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
break;

default:
$prototypeValue = 'value';
}
}
}
}

// get attributes and elements
foreach ($children as $child) {
if (!$child instanceof ArrayNode) {
// get attributes

// metadata
$name = str_replace('_', '-', $child->getName());
$value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world

// comments
$comments = array();
if ($info = $child->getInfo()) {
$comments[] = $info;
}

if ($example = $child->getExample()) {
$comments[] = 'Example: '.$example;
}

if ($child->isRequired()) {
$comments[] = 'Required';
}

if ($child instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
}

if (count($comments)) {
$rootAttributeComments[$name] = implode(";\n", $comments);
}

// default values
if ($child->hasDefaultValue()) {
$value = $child->getDefaultValue();
}

// append attribute
$rootAttributes[$name] = $value;
} else {
// get elements
$rootChildren[] = $child;
}
}
}

// render comments

// root node comment
if (count($rootComments)) {
foreach ($rootComments as $comment) {
$this->writeLine('<!-- '.$comment.' -->', $depth);
}
}

// attribute comments
if (count($rootAttributeComments)) {
foreach ($rootAttributeComments as $attrName => $comment) {
$commentDepth = $depth + 4 + strlen($attrName) + 2;
$commentLines = explode("\n", $comment);
$multiline = (count($commentLines) > 1);
$comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);

if ($multiline) {
$this->writeLine('<!--', $depth);
$this->writeLine($attrName.': '.$comment, $depth + 4);
$this->writeLine('-->', $depth);
} else {
$this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
}
}
}

// render start tag + attributes
$rootIsVariablePrototype = isset($prototypeValue);
$rootIsEmptyTag = (0 === count($rootChildren) && !$rootIsVariablePrototype);
$rootOpenTag = '<'.$rootName;
if (1 >= ($attributesCount = count($rootAttributes))) {
if (1 === $attributesCount) {
$rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
}

$rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';

if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}

$this->writeLine($rootOpenTag, $depth);
} else {
$this->writeLine($rootOpenTag, $depth);

$i = 1;

foreach ($rootAttributes as $attrName => $attrValue) {
$attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));

$this->writeLine($attr, $depth + 4);

if ($attributesCount === $i++) {
$this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);

if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
}
}
}

// render children tags
foreach ($rootChildren as $child) {
$this->writeLine('');
$this->writeNode($child, $depth + 4);
}

// render end tag
if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
$this->writeLine('');

$rootEndTag = '</'.$rootName.'>';
$this->writeLine($rootEndTag, $depth);
}
}

/**
* Outputs a single config reference line
*
* @param string $text
* @param int $indent
*/
private function writeLine($text, $indent = 0)
{
$indent = strlen($text) + $indent;
$format = '%'.$indent.'s';

$this->reference .= sprintf($format, $text)."\n";
}

/**
* Renders the string conversion of the value.
*
* @param mixed $value
*
* @return string
*/
private function writeValue($value)
{
if ('%%%%not_defined%%%%' === $value) {
return '';
}

if (is_string($value) || is_numeric($value)) {
return $value;
}

if (false === $value) {
return 'false';
}

if (true === $value) {
return 'true';
}

if (null === $value) {
return 'null';
}

if (empty($value)) {
return '';
}

if (is_array($value)) {
return implode(',', $value);
}
}
}

0 comments on commit 05e9ca7

Please sign in to comment.