Skip to content

Commit

Permalink
feature #54663 [Serializer] Add XmlEncoder::CDATA_WRAPPING_PATTERN
Browse files Browse the repository at this point in the history
…context option (alexpozzi)

This PR was merged into the 7.1 branch.

Discussion
----------

[Serializer] Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | Fix #54155
| License       | MIT

First of all thank you for all your hard work!

This PR adds the ability to configure the CDATA wrapping pattern to give more flexibility on when to wrap values in a CDATA section.
For example, XML validators are not allowing double and single quotes outside of a CDATA section, with this change we could be able to change the pattern from `/[<>&]/` to `/[<>&"\']/` and solve that issue without the need of writing a custom XMLEncoder.

Commits
-------

8ab57d1731 [Serializer] Add XmlEncoder::CDATA_WRAPPING_PATTERN context option
  • Loading branch information
fabpot committed May 2, 2024
2 parents 4f78db5 + 529d358 commit 29dc0b3
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CHANGELOG
* Add `AbstractNormalizer::FILTER_BOOL` context option
* Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option
* Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method)
* Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option

7.0
---
Expand Down
8 changes: 8 additions & 0 deletions Context/Encoder/XmlEncoderContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,12 @@ public function withCdataWrapping(?bool $cdataWrapping): static
{
return $this->with(XmlEncoder::CDATA_WRAPPING, $cdataWrapping);
}

/**
* Configures the pattern used to evaluate if a CDATA section should be added.
*/
public function withCdataWrappingPattern(?string $cdataWrappingPattern): static
{
return $this->with(XmlEncoder::CDATA_WRAPPING_PATTERN, $cdataWrappingPattern);
}
}
4 changes: 3 additions & 1 deletion Encoder/XmlEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
public const TYPE_CAST_ATTRIBUTES = 'xml_type_cast_attributes';
public const VERSION = 'xml_version';
public const CDATA_WRAPPING = 'cdata_wrapping';
public const CDATA_WRAPPING_PATTERN = 'cdata_wrapping_pattern';

private array $defaultContext = [
self::AS_COLLECTION => false,
Expand All @@ -70,6 +71,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
self::ROOT_NODE_NAME => 'response',
self::TYPE_CAST_ATTRIBUTES => true,
self::CDATA_WRAPPING => true,
self::CDATA_WRAPPING_PATTERN => '/[<>&]/',
];

public function __construct(array $defaultContext = [])
Expand Down Expand Up @@ -433,7 +435,7 @@ private function appendNode(\DOMNode $parentNode, mixed $data, string $format, a
*/
private function needsCdataWrapping(string $val, array $context): bool
{
return ($context[self::CDATA_WRAPPING] ?? $this->defaultContext[self::CDATA_WRAPPING]) && preg_match('/[<>&]/', $val);
return ($context[self::CDATA_WRAPPING] ?? $this->defaultContext[self::CDATA_WRAPPING]) && preg_match($context[self::CDATA_WRAPPING_PATTERN] ?? $this->defaultContext[self::CDATA_WRAPPING_PATTERN], $val);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions Tests/Context/Encoder/XmlEncoderContextBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public function testWithers(array $values)
->withTypeCastAttributes($values[XmlEncoder::TYPE_CAST_ATTRIBUTES])
->withVersion($values[XmlEncoder::VERSION])
->withCdataWrapping($values[XmlEncoder::CDATA_WRAPPING])
->withCdataWrappingPattern($values[XmlEncoder::CDATA_WRAPPING_PATTERN])
->toArray();

$this->assertSame($values, $context);
Expand All @@ -67,6 +68,7 @@ public static function withersDataProvider(): iterable
XmlEncoder::TYPE_CAST_ATTRIBUTES => true,
XmlEncoder::VERSION => '1.0',
XmlEncoder::CDATA_WRAPPING => false,
XmlEncoder::CDATA_WRAPPING_PATTERN => '/[<>&"\']/',
]];

yield 'With null values' => [[
Expand All @@ -83,6 +85,7 @@ public static function withersDataProvider(): iterable
XmlEncoder::TYPE_CAST_ATTRIBUTES => null,
XmlEncoder::VERSION => null,
XmlEncoder::CDATA_WRAPPING => null,
XmlEncoder::CDATA_WRAPPING_PATTERN => null,
]];
}
}
52 changes: 46 additions & 6 deletions Tests/Encoder/XmlEncoderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,56 @@ public function testEncodeRootAttributes()
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}

public function testEncodeCdataWrapping()
/**
* @dataProvider encodeCdataWrappingWithDefaultPattern
*/
public function testEncodeCdataWrappingWithDefaultPattern($input, $expected)
{
$array = [
'firstname' => 'Paul & Martha <or Me>',
$this->assertEquals($expected, $this->encoder->encode($input, 'xml'));
}

public static function encodeCdataWrappingWithDefaultPattern()
{
return [
[
['firstname' => 'Paul and Martha'],
'<?xml version="1.0"?>'."\n".'<response><firstname>Paul and Martha</firstname></response>'."\n",
],
[
['lastname' => 'O\'Donnel'],
'<?xml version="1.0"?>'."\n".'<response><lastname>O\'Donnel</lastname></response>'."\n",
],
[
['firstname' => 'Paul & Martha <or Me>'],
'<?xml version="1.0"?>'."\n".'<response><firstname><![CDATA[Paul & Martha <or Me>]]></firstname></response>'."\n",
],
];
}

$expected = '<?xml version="1.0"?>'."\n".
'<response><firstname><![CDATA[Paul & Martha <or Me>]]></firstname></response>'."\n";
/**
* @dataProvider encodeCdataWrappingWithCustomPattern
*/
public function testEncodeCdataWrappingWithCustomPattern($input, $expected)
{
$this->assertEquals($expected, $this->encoder->encode($input, 'xml', ['cdata_wrapping_pattern' => '/[<>&"\']/']));
}

$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
public static function encodeCdataWrappingWithCustomPattern()
{
return [
[
['firstname' => 'Paul and Martha'],
'<?xml version="1.0"?>'."\n".'<response><firstname>Paul and Martha</firstname></response>'."\n",
],
[
['lastname' => 'O\'Donnel'],
'<?xml version="1.0"?>'."\n".'<response><lastname><![CDATA[O\'Donnel]]></lastname></response>'."\n",
],
[
['firstname' => 'Paul & Martha <or Me>'],
'<?xml version="1.0"?>'."\n".'<response><firstname><![CDATA[Paul & Martha <or Me>]]></firstname></response>'."\n",
],
];
}

public function testEnableCdataWrapping()
Expand Down

0 comments on commit 29dc0b3

Please sign in to comment.