Skip to content

Commit 13e1765

Browse files
committed
[FEATURE] Add MigrateEmailFlagToEmailTypeFlexFormFractor
1 parent 1cfb9ee commit 13e1765

File tree

17 files changed

+639
-41
lines changed

17 files changed

+639
-41
lines changed

fractor-xml/src/AbstractXmlFractor.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
abstract class AbstractXmlFractor implements DomNodeVisitor, XmlFractor
99
{
10-
public function beforeTraversal(\DOMNode $rootNode): void
10+
public function beforeTraversal(\DOMDocument $rootNode): void
1111
{
1212
// no-op for now
1313
}
@@ -26,7 +26,7 @@ public function leaveNode(\DOMNode $node): void
2626
// no-op for now
2727
}
2828

29-
public function afterTraversal(\DOMNode $rootNode): void
29+
public function afterTraversal(\DOMDocument $rootNode): void
3030
{
3131
// no-op for now
3232
}

fractor-xml/src/Contract/DomNodeVisitor.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
interface DomNodeVisitor
1111
{
12-
public function beforeTraversal(\DOMNode $rootNode): void;
12+
public function beforeTraversal(\DOMDocument $rootNode): void;
1313

1414
/**
1515
* @return \DOMNode|DomDocumentIterator::*
@@ -18,5 +18,5 @@ public function enterNode(\DOMNode $node): \DOMNode|int;
1818

1919
public function leaveNode(\DOMNode $node): void;
2020

21-
public function afterTraversal(\DOMNode $rootNode): void;
21+
public function afterTraversal(\DOMDocument $rootNode): void;
2222
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorXml\Helper;
6+
7+
trait FlexFormHelperTrait
8+
{
9+
protected function extractDomElementByKey(?\DOMElement $element, string $key): ?\DOMElement
10+
{
11+
if (!$element instanceof \DOMElement) {
12+
return null;
13+
}
14+
15+
foreach ($element->childNodes as $childNode) {
16+
if (!$childNode instanceof \DOMElement) {
17+
continue;
18+
}
19+
20+
$itemKey = $childNode->nodeName;
21+
if ($key === $itemKey) {
22+
return $childNode;
23+
}
24+
}
25+
26+
return null;
27+
}
28+
29+
protected function hasKey(?\DOMElement $columnItemConfigurationArray, string $configKey): bool
30+
{
31+
if (!$columnItemConfigurationArray instanceof \DOMElement) {
32+
return false;
33+
}
34+
35+
foreach ($columnItemConfigurationArray->childNodes as $item) {
36+
if (!$item instanceof \DOMElement) {
37+
continue;
38+
}
39+
40+
if ($this->isValue($item->nodeName, $configKey)) {
41+
return true;
42+
}
43+
}
44+
45+
return false;
46+
}
47+
48+
private function isConfigType(?\DOMElement $columnItemConfigurationArray, string $type): bool
49+
{
50+
if (!$columnItemConfigurationArray instanceof \DOMElement) {
51+
return false;
52+
}
53+
54+
return $this->hasKeyValuePair($columnItemConfigurationArray, 'type', $type);
55+
}
56+
57+
private function configIsOfRenderType(?\DOMElement $columnItemConfigurationArray, string $expectedRenderType): bool
58+
{
59+
if (!$columnItemConfigurationArray instanceof \DOMElement) {
60+
return false;
61+
}
62+
63+
return $this->hasKeyValuePair($columnItemConfigurationArray, 'renderType', $expectedRenderType);
64+
}
65+
66+
private function hasRenderType(\DOMElement $columnItemConfigurationArray): bool
67+
{
68+
$renderTypeItem = $this->extractDomElementByKey($columnItemConfigurationArray, 'renderType');
69+
return $renderTypeItem !== null;
70+
}
71+
72+
private function hasKeyValuePair(\DOMElement $configValueArray, string $configKey, string $expectedValue): bool
73+
{
74+
foreach ($configValueArray->childNodes as $configItemValue) {
75+
if (!$configItemValue instanceof \DOMElement) {
76+
continue;
77+
}
78+
79+
if ($this->isValue($configItemValue->nodeName, $configKey)
80+
&& $this->isValue($configItemValue->textContent, $expectedValue)
81+
) {
82+
return true;
83+
}
84+
}
85+
86+
return false;
87+
}
88+
89+
private function removeChildElementFromDomElementByKey(\DOMElement $configValueArray, string $configKey): bool
90+
{
91+
$arrayItemToRemove = $this->extractDomElementByKey($configValueArray, $configKey);
92+
if ($arrayItemToRemove instanceof \DOMElement && $arrayItemToRemove->parentNode instanceof \DOMElement) {
93+
$arrayItemToRemove->parentNode->removeChild($arrayItemToRemove);
94+
return true;
95+
}
96+
97+
return false;
98+
}
99+
100+
private function isValue(string $element, string $value): bool
101+
{
102+
return $element === $value;
103+
}
104+
105+
/**
106+
* @see https://stackoverflow.com/a/21885789
107+
*/
108+
private function changeTagName(\DOMDocument $domDocument, \DOMElement $node, string $elementName): void
109+
{
110+
$newNode = $domDocument->createElement($elementName);
111+
foreach ($node->childNodes as $child) {
112+
$newNode->appendChild($domDocument->importNode($child, false));
113+
}
114+
115+
// We don't need to copy the attributes because TCA doesn't have attributes
116+
117+
if (!$node->parentNode instanceof \DOMNode) {
118+
return;
119+
}
120+
121+
$node->parentNode->replaceChild($newNode, $node);
122+
}
123+
124+
private function changeTcaType(\DOMDocument $domDocument, \DOMElement $configElement, string $newType): void
125+
{
126+
$toChangeItem = $this->extractDomElementByKey($configElement, 'type');
127+
if ($toChangeItem instanceof \DOMElement) {
128+
$toChangeItem->nodeValue = '';
129+
$toChangeItem->appendChild($domDocument->createTextNode($newType));
130+
}
131+
}
132+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorXml\Printer;
6+
7+
/**
8+
* @source https://github.com/shanethehat/pretty-xml/blob/master/src/PrettyXml/Formatter.php
9+
*/
10+
class PrettyXMLPrinter
11+
{
12+
private int $depth;
13+
14+
private int $indent = 4;
15+
16+
private string $padChar = ' ';
17+
18+
private bool $preserveWhitespace = false;
19+
20+
public function setIndentSize(int $indent): void
21+
{
22+
$this->indent = $indent;
23+
}
24+
25+
public function setIndentCharacter(string $indentCharacter): void
26+
{
27+
$this->padChar = $indentCharacter;
28+
}
29+
30+
public function format(string $xml): string
31+
{
32+
$output = '';
33+
$this->depth = 0;
34+
35+
$parts = $this->getXmlParts($xml);
36+
37+
if (str_starts_with($parts[0], '<?xml')) {
38+
$output = array_shift($parts) . PHP_EOL;
39+
}
40+
41+
foreach ($parts as $key => $part) {
42+
$element = preg_replace('/<([a-zA-Z0-9\-_]+).*/', "$1", $part);
43+
44+
if ($element && isset($parts[$key + 1]) && preg_replace('~</(.*)>~', "$1", $parts[$key + 1]) === $element) {
45+
$output .= $this->getOutputForPart($part, '');
46+
} else {
47+
$output .= $this->getOutputForPart($part);
48+
}
49+
}
50+
51+
return trim((string)preg_replace('~>' . $this->padChar . '+<~', '><', $output));
52+
}
53+
54+
/**
55+
* @return string[]
56+
*/
57+
private function getXmlParts(string $xml): array
58+
{
59+
$withNewLines = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", trim($xml));
60+
return explode("\n", (string)$withNewLines);
61+
}
62+
63+
private function getOutputForPart(string $part, string $eol = PHP_EOL): string
64+
{
65+
$output = '';
66+
$this->runPre($part);
67+
68+
if ($this->preserveWhitespace) {
69+
$output .= $part . $eol;
70+
} else {
71+
$part = trim($part);
72+
$output .= $this->getPaddedString($part) . $eol;
73+
}
74+
75+
$this->runPost($part);
76+
77+
return $output;
78+
}
79+
80+
private function runPre(string $part): void
81+
{
82+
if ($this->isClosingTag($part)) {
83+
$this->depth--;
84+
}
85+
}
86+
87+
private function runPost(string $part): void
88+
{
89+
if ($this->isOpeningCdataTag($part) && $this->isClosingCdataTag($part)) {
90+
return;
91+
}
92+
if ($this->isOpeningTag($part)) {
93+
$this->depth++;
94+
}
95+
if ($this->isClosingCdataTag($part)) {
96+
$this->preserveWhitespace = false;
97+
}
98+
if ($this->isOpeningCdataTag($part)) {
99+
$this->preserveWhitespace = true;
100+
}
101+
}
102+
103+
private function getPaddedString(string $part): string
104+
{
105+
return str_pad($part, strlen($part) + ($this->depth * $this->indent), $this->padChar, STR_PAD_LEFT);
106+
}
107+
108+
private function isOpeningTag(string $part): bool
109+
{
110+
return (bool) preg_match('/^<[^\/]*>$/', $part);
111+
}
112+
113+
private function isClosingTag(string $part): bool
114+
{
115+
return (bool) preg_match('/^\s*<\//', $part);
116+
}
117+
118+
private function isOpeningCdataTag(string $part): bool
119+
{
120+
return str_contains($part, '<![CDATA[');
121+
}
122+
123+
private function isClosingCdataTag(string $part): bool
124+
{
125+
return str_contains($part, ']]>');
126+
}
127+
}

fractor-xml/src/XmlFileProcessor.php

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@
66
use a9f\Fractor\Application\ValueObject\File;
77
use a9f\Fractor\Exception\ShouldNotHappenException;
88
use a9f\FractorXml\Contract\XmlFractor;
9+
use a9f\FractorXml\Printer\PrettyXMLPrinter;
910
use a9f\FractorXml\ValueObjectFactory\DomDocumentFactory;
10-
use DOMDocument;
1111
use Webmozart\Assert\Assert;
1212

1313
final readonly class XmlFileProcessor implements FileProcessor
1414
{
1515
/**
1616
* @param XmlFractor[] $rules
1717
*/
18-
public function __construct(private iterable $rules, private DomDocumentFactory $domDocumentFactory)
19-
{
18+
public function __construct(
19+
private iterable $rules,
20+
private DomDocumentFactory $domDocumentFactory,
21+
private PrettyXMLPrinter $prettyXMLPrinter
22+
) {
2023
Assert::allIsInstanceOf($this->rules, XmlFractor::class);
2124
}
2225

@@ -37,6 +40,8 @@ public function handle(File $file): void
3740
$iterator->traverseDocument($document);
3841

3942
$newFileContent = $this->saveXml($document);
43+
// TODO make the indentation configurable in fractor config
44+
$newFileContent = $this->prettyXMLPrinter->format($newFileContent);
4045

4146
$file->changeFileContent($newFileContent);
4247
}
@@ -46,7 +51,7 @@ public function allowedFileExtensions(): array
4651
return ['xml'];
4752
}
4853

49-
private function saveXml(DOMDocument $document): string
54+
private function saveXml(\DOMDocument $document): string
5055
{
5156
$xml = $document->saveXML();
5257
if ($xml === false) {

fractor-xml/tests/DomDocumentIteratorTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ public function __construct(
228228
) {
229229
}
230230

231-
public function beforeTraversal(\DOMNode $rootNode): void
231+
public function beforeTraversal(\DOMDocument $rootNode): void
232232
{
233233
$this->calls[] = sprintf('%s:beforeTraversal:%s', $this->visitorName, $rootNode->nodeName);
234234
}
@@ -244,7 +244,7 @@ public function leaveNode(\DOMNode $node): void
244244
$this->calls[] = sprintf('%s:leaveNode:%s', $this->visitorName, $node->nodeName);
245245
}
246246

247-
public function afterTraversal(\DOMNode $rootNode): void
247+
public function afterTraversal(\DOMDocument $rootNode): void
248248
{
249249
$this->calls[] = sprintf('%s:afterTraversal:%s', $this->visitorName, $rootNode->nodeName);
250250
}

fractor/src/Helper/ArrayUtility.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Helper;
6+
7+
class ArrayUtility
8+
{
9+
/**
10+
* @return string[]
11+
*/
12+
public static function trimExplode(
13+
string $delimiter,
14+
string $string,
15+
bool $removeEmptyValues = false,
16+
int $limit = 0
17+
): array {
18+
if ($delimiter === '') {
19+
throw new \InvalidArgumentException('Please define a correct delimiter');
20+
}
21+
22+
$result = explode($delimiter, $string);
23+
24+
if ($removeEmptyValues) {
25+
$temp = [];
26+
foreach ($result as $value) {
27+
if (trim($value) !== '') {
28+
$temp[] = $value;
29+
}
30+
}
31+
32+
$result = $temp;
33+
}
34+
35+
if ($limit > 0 && count($result) > $limit) {
36+
$lastElements = array_splice($result, $limit - 1);
37+
$result[] = implode($delimiter, $lastElements);
38+
} elseif ($limit < 0) {
39+
$result = array_slice($result, 0, $limit);
40+
}
41+
42+
return array_map('trim', $result);
43+
}
44+
}

0 commit comments

Comments
 (0)