Merge branch '2.7' into 2.8
* 2.7: [HttpFoundation] Use UPSERT for sessions stored in PgSql >= 9.5 [Console] fixed PHPDoc [travis] HHVM 3.12 LTS Fix feature detection for IE [Form] Fixed collapsed choice attributes [Console] added explanation of messages usage in a progress bar force enabling the external XML entity loaders [Yaml] properly count skipped comment lines Conflicts: src/Symfony/Component/Translation/Loader/XliffFileLoader.php
This commit is contained in:
commit
7fa2eab99f
@ -18,7 +18,7 @@ env:
|
||||
matrix:
|
||||
include:
|
||||
# Use the newer stack for HHVM as HHVM does not support Precise anymore since a long time and so Precise has an outdated version
|
||||
- php: hhvm
|
||||
- php: hhvm-3.12
|
||||
sudo: required
|
||||
dist: trusty
|
||||
group: edge
|
||||
|
@ -180,7 +180,7 @@ class DbalSessionHandler implements \SessionHandlerInterface
|
||||
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
$updateStmt->execute();
|
||||
|
||||
// When MERGE is not supported, like in Postgres, we have to use this approach that can result in
|
||||
// When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
|
||||
// duplicate key errors when the same session is written simultaneously. We can just catch such an
|
||||
// error and re-execute the update. This is similar to a serializable transaction with retry logic
|
||||
// on serialization failures but without the overhead and without possible false positives due to
|
||||
@ -224,11 +224,11 @@ class DbalSessionHandler implements \SessionHandlerInterface
|
||||
{
|
||||
$platform = $this->con->getDatabasePlatform()->getName();
|
||||
|
||||
switch ($platform) {
|
||||
case 'mysql':
|
||||
switch (true) {
|
||||
case 'mysql' === $platform:
|
||||
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
|
||||
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)";
|
||||
case 'oracle':
|
||||
case 'oracle' === $platform:
|
||||
// DUAL is Oracle specific dummy table
|
||||
return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
|
||||
@ -239,8 +239,11 @@ class DbalSessionHandler implements \SessionHandlerInterface
|
||||
return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;";
|
||||
case 'sqlite':
|
||||
case 'sqlite' === $platform:
|
||||
return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)";
|
||||
case 'postgresql' === $platform && version_compare($this->con->getServerVersion(), '9.5', '>='):
|
||||
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
|
||||
"ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->timeCol) = (:data, :time) WHERE $this->idCol = :id";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
id="<?php echo $view->escape($id) ?>" name="<?php echo $view->escape($full_name) ?>"
|
||||
<?php if ($disabled): ?>disabled="disabled" <?php endif ?>
|
||||
<?php foreach ($choice_attr as $k => $v): ?>
|
||||
<?php if ($v === true): ?>
|
||||
<?php printf('%s="%s" ', $view->escape($k), $view->escape($k)) ?>
|
||||
<?php elseif ($v !== false): ?>
|
||||
<?php printf('%s="%s" ', $view->escape($k), $view->escape($v)) ?>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
@ -8,6 +8,6 @@ $translatorHelper = $view['translator']; // outside of the loop for performance
|
||||
<?php echo $formHelper->block($form, 'choice_widget_options', array('choices' => $choice)) ?>
|
||||
</optgroup>
|
||||
<?php else: ?>
|
||||
<option value="<?php echo $view->escape($choice->value) ?>" <?php echo $view['form']->block($form, 'attributes', array('attr' => $choice->attr)) ?><?php if ($is_selected($choice->value, $value)): ?> selected="selected"<?php endif?>><?php echo $view->escape(false !== $choice_translation_domain ? $translatorHelper->trans($choice->label, array(), $choice_translation_domain) : $choice->label) ?></option>
|
||||
<option value="<?php echo $view->escape($choice->value) ?>" <?php echo $formHelper->block($form, 'choice_attributes', array('choice_attr' => $choice->attr)) ?><?php if ($is_selected($choice->value, $value)): ?> selected="selected"<?php endif?>><?php echo $view->escape(false !== $choice_translation_domain ? $translatorHelper->trans($choice->label, array(), $choice_translation_domain) : $choice->label) ?></option>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
|
@ -211,7 +211,7 @@
|
||||
var addEventListener;
|
||||
|
||||
var el = document.createElement('div');
|
||||
if (!'addEventListener' in el) {
|
||||
if (!('addEventListener' in el)) {
|
||||
addEventListener = function (element, eventName, callback) {
|
||||
element.attachEvent('on' + eventName, callback);
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ class ProgressBar
|
||||
private $stepWidth;
|
||||
private $percent = 0.0;
|
||||
private $formatLineCount;
|
||||
private $messages;
|
||||
private $messages = array();
|
||||
private $overwrite = true;
|
||||
|
||||
private static $formatters;
|
||||
@ -140,6 +140,16 @@ class ProgressBar
|
||||
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a text with a named placeholder.
|
||||
*
|
||||
* The text is displayed when the progress bar is rendered but only
|
||||
* when the corresponding placeholder is part of the custom format line
|
||||
* (by wrapping the name with %).
|
||||
*
|
||||
* @param string $message The text to associate with the placeholder
|
||||
* @param string $name The name of the placeholder
|
||||
*/
|
||||
public function setMessage($message, $name = 'message')
|
||||
{
|
||||
$this->messages[$name] = $message;
|
||||
|
@ -19,7 +19,6 @@ namespace Symfony\Component\Console\Helper;
|
||||
class TableSeparator extends TableCell
|
||||
{
|
||||
/**
|
||||
* @param string $value
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = array())
|
||||
|
@ -73,21 +73,33 @@ class NullOutput implements OutputInterface
|
||||
return self::VERBOSITY_QUIET;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isQuiet()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isVerbose()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isVeryVerbose()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDebug()
|
||||
{
|
||||
return false;
|
||||
|
@ -94,21 +94,33 @@ abstract class Output implements OutputInterface
|
||||
return $this->verbosity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isQuiet()
|
||||
{
|
||||
return self::VERBOSITY_QUIET === $this->verbosity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isVerbose()
|
||||
{
|
||||
return self::VERBOSITY_VERBOSE <= $this->verbosity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isVeryVerbose()
|
||||
{
|
||||
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDebug()
|
||||
{
|
||||
return self::VERBOSITY_DEBUG <= $this->verbosity;
|
||||
|
@ -338,7 +338,6 @@ class XmlFileLoader extends FileLoader
|
||||
foreach ($definitions as $id => $def) {
|
||||
list($domElement, $file, $wild) = $def;
|
||||
|
||||
|
||||
if (null !== $definition = $this->parseDefinition($domElement, $file)) {
|
||||
$this->container->setDefinition($id, $definition);
|
||||
}
|
||||
@ -508,7 +507,9 @@ $imports
|
||||
EOF
|
||||
;
|
||||
|
||||
$disableEntities = libxml_disable_entity_loader(false);
|
||||
$valid = @$dom->schemaValidateSource($source);
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
foreach ($tmpfiles as $tmpfile) {
|
||||
@unlink($tmpfile);
|
||||
|
@ -84,6 +84,19 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertInstanceOf('DOMDocument', $xml, '->parseFileToDOM() returns an SimpleXMLElement object');
|
||||
}
|
||||
|
||||
public function testLoadWithExternalEntitiesDisabled()
|
||||
{
|
||||
$disableEntities = libxml_disable_entity_loader(true);
|
||||
|
||||
$containerBuilder = new ContainerBuilder();
|
||||
$loader = new XmlFileLoader($containerBuilder, new FileLocator(self::$fixturesPath.'/xml'));
|
||||
$loader->load('services2.xml');
|
||||
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
$this->assertTrue(count($containerBuilder->getParameterBag()->all()) > 0, 'Parameters can be read from the config file.');
|
||||
}
|
||||
|
||||
public function testLoadParameters()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -232,6 +232,68 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceAttributesWithMainAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
|
||||
'choices_as_values' => true,
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'attr' => array('class' => 'bar&baz'),
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')),
|
||||
'/select
|
||||
[@name="name"]
|
||||
[@class="bar&baz form-control"]
|
||||
[not(@required)]
|
||||
[
|
||||
./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
|
||||
]
|
||||
[count(./option)=2]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleExpandedChoiceAttributesWithMainAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
|
||||
'choices_as_values' => true,
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'attr' => array('class' => 'bar&baz'),
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')),
|
||||
'/div
|
||||
[@class="bar&baz"]
|
||||
[
|
||||
./div
|
||||
[@class="radio"]
|
||||
[
|
||||
./label
|
||||
[.=" [trans]Choice&A[/trans]"]
|
||||
[
|
||||
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
|
||||
]
|
||||
]
|
||||
/following-sibling::div
|
||||
[@class="radio"]
|
||||
[
|
||||
./label
|
||||
[.=" [trans]Choice&B[/trans]"]
|
||||
[
|
||||
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
|
||||
]
|
||||
]
|
||||
/following-sibling::input[@type="hidden"][@id="name__token"]
|
||||
]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSelectWithSizeBiggerThanOneCanBeRequired()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
|
||||
|
@ -641,6 +641,55 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceAttributesWithMainAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
|
||||
'choices_as_values' => true,
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'attr' => array('class' => 'bar&baz'),
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')),
|
||||
'/select
|
||||
[@name="name"]
|
||||
[@class="bar&baz"]
|
||||
[not(@required)]
|
||||
[
|
||||
./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::option[@value="&b"][not(@class)][not(@selected)][.="[trans]Choice&B[/trans]"]
|
||||
]
|
||||
[count(./option)=2]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleExpandedChoiceAttributesWithMainAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
|
||||
'choices_as_values' => true,
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'attr' => array('class' => 'bar&baz'),
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')),
|
||||
'/div
|
||||
[@class="bar&baz"]
|
||||
[
|
||||
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
|
||||
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
|
||||
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||
/following-sibling::input[@type="hidden"][@id="name__token"]
|
||||
]
|
||||
[count(./input)=3]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceWithPreferred()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
|
||||
|
@ -347,7 +347,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
$updateStmt->execute();
|
||||
|
||||
// When MERGE is not supported, like in Postgres, we have to use this approach that can result in
|
||||
// When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
|
||||
// duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
|
||||
// We can just catch such an error and re-execute the update. This is similar to a serializable
|
||||
// transaction with retry logic on serialization failures but without the overhead and without possible
|
||||
@ -659,11 +659,11 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
*/
|
||||
private function getMergeSql()
|
||||
{
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
switch (true) {
|
||||
case 'mysql' === $this->driver:
|
||||
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
|
||||
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
|
||||
case 'oci':
|
||||
case 'oci' === $this->driver:
|
||||
// DUAL is Oracle specific dummy table
|
||||
return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
|
||||
@ -674,8 +674,11 @@ class PdoSessionHandler implements \SessionHandlerInterface
|
||||
return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time;";
|
||||
case 'sqlite':
|
||||
case 'sqlite' === $this->driver:
|
||||
return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
|
||||
case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
|
||||
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
|
||||
"ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (:data, :lifetime, :time) WHERE $this->idCol = :id";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,10 +173,16 @@ class XliffFileLoader implements LoaderInterface
|
||||
{
|
||||
$internalErrors = libxml_use_internal_errors(true);
|
||||
|
||||
$disableEntities = libxml_disable_entity_loader(false);
|
||||
|
||||
if (!@$dom->schemaValidateSource($schema)) {
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors))));
|
||||
}
|
||||
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
$dom->normalizeDocument();
|
||||
|
||||
libxml_clear_errors();
|
||||
|
@ -46,6 +46,20 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
libxml_use_internal_errors($internalErrors);
|
||||
}
|
||||
|
||||
public function testLoadWithExternalEntitiesDisabled()
|
||||
{
|
||||
$disableEntities = libxml_disable_entity_loader(true);
|
||||
|
||||
$loader = new XliffFileLoader();
|
||||
$resource = __DIR__.'/../fixtures/resources.xlf';
|
||||
$catalogue = $loader->load($resource, 'en', 'domain1');
|
||||
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
$this->assertEquals('en', $catalogue->getLocale());
|
||||
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
|
||||
}
|
||||
|
||||
public function testLoadWithResname()
|
||||
{
|
||||
$loader = new XliffFileLoader();
|
||||
|
@ -30,17 +30,21 @@ class Parser
|
||||
private $currentLineNb = -1;
|
||||
private $currentLine = '';
|
||||
private $refs = array();
|
||||
private $skippedLineNumbers = array();
|
||||
private $locallySkippedLineNumbers = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $offset The offset of YAML document (used for line numbers in error messages)
|
||||
* @param int|null $totalNumberOfLines The overall number of lines being parsed
|
||||
* @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser
|
||||
*/
|
||||
public function __construct($offset = 0, $totalNumberOfLines = null)
|
||||
public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
|
||||
{
|
||||
$this->offset = $offset;
|
||||
$this->totalNumberOfLines = $totalNumberOfLines;
|
||||
$this->skippedLineNumbers = $skippedLineNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,25 +105,18 @@ class Parser
|
||||
|
||||
// array
|
||||
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new self($c, $this->totalNumberOfLines);
|
||||
$parser->refs = &$this->refs;
|
||||
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
} else {
|
||||
if (isset($values['leadspaces'])
|
||||
&& preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
|
||||
) {
|
||||
// this is a compact notation element, add to next block and parse
|
||||
$c = $this->getRealCurrentLineNb();
|
||||
$parser = new self($c, $this->totalNumberOfLines);
|
||||
$parser->refs = &$this->refs;
|
||||
|
||||
$block = $values['value'];
|
||||
if ($this->isNextLineIndented()) {
|
||||
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
|
||||
}
|
||||
|
||||
$data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
$data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
} else {
|
||||
$data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
|
||||
}
|
||||
@ -175,10 +172,7 @@ class Parser
|
||||
} else {
|
||||
$value = $this->getNextEmbedBlock();
|
||||
}
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new self($c, $this->totalNumberOfLines);
|
||||
$parser->refs = &$this->refs;
|
||||
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
|
||||
if (!is_array($parsed)) {
|
||||
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
@ -226,10 +220,7 @@ class Parser
|
||||
$data[$key] = null;
|
||||
}
|
||||
} else {
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new self($c, $this->totalNumberOfLines);
|
||||
$parser->refs = &$this->refs;
|
||||
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
$value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ($allowOverwrite || !isset($data[$key])) {
|
||||
@ -323,6 +314,24 @@ class Parser
|
||||
return empty($data) ? null : $data;
|
||||
}
|
||||
|
||||
private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
|
||||
{
|
||||
$skippedLineNumbers = $this->skippedLineNumbers;
|
||||
|
||||
foreach ($this->locallySkippedLineNumbers as $lineNumber) {
|
||||
if ($lineNumber < $offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$skippedLineNumbers[] = $lineNumber;
|
||||
}
|
||||
|
||||
$parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
|
||||
$parser->refs = &$this->refs;
|
||||
|
||||
return $parser->parse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line number (takes the offset into account).
|
||||
*
|
||||
@ -330,7 +339,17 @@ class Parser
|
||||
*/
|
||||
private function getRealCurrentLineNb()
|
||||
{
|
||||
return $this->currentLineNb + $this->offset;
|
||||
$realCurrentLineNumber = $this->currentLineNb + $this->offset;
|
||||
|
||||
foreach ($this->skippedLineNumbers as $skippedLineNumber) {
|
||||
if ($skippedLineNumber > $realCurrentLineNumber) {
|
||||
break;
|
||||
}
|
||||
|
||||
++$realCurrentLineNumber;
|
||||
}
|
||||
|
||||
return $realCurrentLineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -432,7 +451,15 @@ class Parser
|
||||
}
|
||||
|
||||
// we ignore "comment" lines only when we are not inside a scalar block
|
||||
if (empty($blockScalarIndentations) && $this->isCurrentLineComment() && false === $this->checkIfPreviousNonCommentLineIsCollectionItem()) {
|
||||
if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
|
||||
// remember ignored comment lines (they are used later in nested
|
||||
// parser calls to determine real line numbers)
|
||||
//
|
||||
// CAUTION: beware to not populate the global property here as it
|
||||
// will otherwise influence the getRealCurrentLineNb() call here
|
||||
// for consecutive comment lines and subsequent embedded blocks
|
||||
$this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -802,44 +829,4 @@ class Parser
|
||||
{
|
||||
return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is a collection item.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isCurrentLineCollectionItem()
|
||||
{
|
||||
$ltrimmedLine = ltrim($this->currentLine, ' ');
|
||||
|
||||
return '' !== $ltrimmedLine && '-' === $ltrimmedLine[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the current comment line is in a collection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function checkIfPreviousNonCommentLineIsCollectionItem()
|
||||
{
|
||||
$isCollectionItem = false;
|
||||
$moves = 0;
|
||||
while ($this->moveToPreviousLine()) {
|
||||
++$moves;
|
||||
// If previous line is a comment, move back again.
|
||||
if ($this->isCurrentLineComment()) {
|
||||
continue;
|
||||
}
|
||||
$isCollectionItem = $this->isCurrentLineCollectionItem();
|
||||
break;
|
||||
}
|
||||
|
||||
// Move parser back to previous line.
|
||||
while ($moves > 0) {
|
||||
$this->moveToNextLine();
|
||||
--$moves;
|
||||
}
|
||||
|
||||
return $isCollectionItem;
|
||||
}
|
||||
}
|
||||
|
@ -656,6 +656,25 @@ EOT;
|
||||
$this->assertSame($expected, $this->parser->parse($yaml));
|
||||
}
|
||||
|
||||
public function testSequenceFollowedByCommentEmbeddedInMapping()
|
||||
{
|
||||
$yaml = <<<EOT
|
||||
a:
|
||||
b:
|
||||
- c
|
||||
# comment
|
||||
d: e
|
||||
EOT;
|
||||
$expected = array(
|
||||
'a' => array(
|
||||
'b' => array('c'),
|
||||
'd' => 'e',
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $this->parser->parse($yaml));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
|
||||
*/
|
||||
|
Reference in New Issue
Block a user