mirror of https://github.com/rectorphp/rector.git
147 lines
5.1 KiB
PHP
147 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare (strict_types=1);
|
|
namespace Rector\FileFormatter\Formatter;
|
|
|
|
use RectorPrefix20210808\Nette\Utils\Strings;
|
|
use Rector\Core\ValueObject\Application\File;
|
|
use Rector\FileFormatter\Contract\Formatter\FileFormatterInterface;
|
|
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
|
|
use Rector\FileFormatter\ValueObject\Indent;
|
|
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
|
|
/**
|
|
* @see \Rector\Tests\FileFormatter\Formatter\XmlFileFormatter\XmlFileFormatterTest
|
|
*/
|
|
final class XmlFileFormatter implements \Rector\FileFormatter\Contract\Formatter\FileFormatterInterface
|
|
{
|
|
/**
|
|
* @see https://regex101.com/r/uTmMcr/1
|
|
* @var string
|
|
*/
|
|
private const XML_PARTS_REGEX = '#(>)(<)(\\/*)#';
|
|
/**
|
|
* @see https://regex101.com/r/hSG1JT/1
|
|
* @var string
|
|
*/
|
|
private const IS_OPENING_TAG_REGEX = '#^<[^\\/]*>$#';
|
|
/**
|
|
* @see https://regex101.com/r/ywS62K/1
|
|
* @var string
|
|
*/
|
|
private const IS_CLOSING_TAG_REGEX = '#^\\s*<\\/#';
|
|
/**
|
|
* @var int|null
|
|
*/
|
|
private $depth;
|
|
/**
|
|
* @var int
|
|
*/
|
|
private $indent = 4;
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $padChar = ' ';
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private $preserveWhitespace = \false;
|
|
/**
|
|
* @param \Rector\Core\ValueObject\Application\File $file
|
|
*/
|
|
public function supports($file) : bool
|
|
{
|
|
$smartFileInfo = $file->getSmartFileInfo();
|
|
return $smartFileInfo->getExtension() === 'xml';
|
|
}
|
|
/**
|
|
* @param \Rector\Core\ValueObject\Application\File $file
|
|
* @param \Rector\FileFormatter\ValueObject\EditorConfigConfiguration $editorConfigConfiguration
|
|
*/
|
|
public function format($file, $editorConfigConfiguration) : void
|
|
{
|
|
$this->padChar = $editorConfigConfiguration->getIndentStyleCharacter();
|
|
$this->indent = $editorConfigConfiguration->getIndentSize();
|
|
$newFileContent = $this->formatXml($file->getFileContent(), $editorConfigConfiguration);
|
|
$newFileContent .= $editorConfigConfiguration->getFinalNewline();
|
|
$file->changeFileContent($newFileContent);
|
|
}
|
|
public function createDefaultEditorConfigConfigurationBuilder() : \Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder
|
|
{
|
|
$editorConfigConfigurationBuilder = new \Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder();
|
|
$editorConfigConfigurationBuilder->withIndent(\Rector\FileFormatter\ValueObject\Indent::createTab());
|
|
return $editorConfigConfigurationBuilder;
|
|
}
|
|
private function formatXml(string $xml, \Rector\FileFormatter\ValueObject\EditorConfigConfiguration $editorConfigConfiguration) : string
|
|
{
|
|
$output = '';
|
|
$this->depth = 0;
|
|
$parts = $this->getXmlParts($xml);
|
|
if (\strncmp($parts[0], '<?xml', \strlen('<?xml')) === 0) {
|
|
$output = \array_shift($parts) . $editorConfigConfiguration->getNewline();
|
|
}
|
|
foreach ($parts as $part) {
|
|
$output .= $this->getOutputForPart($part, $editorConfigConfiguration);
|
|
}
|
|
return \trim($output);
|
|
}
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
private function getXmlParts(string $xml) : array
|
|
{
|
|
$withNewLines = \RectorPrefix20210808\Nette\Utils\Strings::replace(\trim($xml), self::XML_PARTS_REGEX, "\$1\n\$2\$3");
|
|
return \explode("\n", $withNewLines);
|
|
}
|
|
private function getOutputForPart(string $part, \Rector\FileFormatter\ValueObject\EditorConfigConfiguration $editorConfigConfiguration) : string
|
|
{
|
|
$output = '';
|
|
$this->runPre($part);
|
|
if ($this->preserveWhitespace) {
|
|
$output .= $part . $editorConfigConfiguration->getNewline();
|
|
} else {
|
|
$part = \trim($part);
|
|
$output .= $this->getPaddedString($part) . $editorConfigConfiguration->getNewline();
|
|
}
|
|
$this->runPost($part);
|
|
return $output;
|
|
}
|
|
private function runPre(string $part) : void
|
|
{
|
|
if ($this->isClosingTag($part)) {
|
|
--$this->depth;
|
|
}
|
|
}
|
|
private function runPost(string $part) : void
|
|
{
|
|
if ($this->isOpeningTag($part)) {
|
|
++$this->depth;
|
|
}
|
|
if ($this->isClosingCdataTag($part)) {
|
|
$this->preserveWhitespace = \false;
|
|
}
|
|
if ($this->isOpeningCdataTag($part)) {
|
|
$this->preserveWhitespace = \true;
|
|
}
|
|
}
|
|
private function getPaddedString(string $part) : string
|
|
{
|
|
return \str_pad($part, \strlen($part) + $this->depth * $this->indent, $this->padChar, \STR_PAD_LEFT);
|
|
}
|
|
private function isOpeningTag(string $part) : bool
|
|
{
|
|
return (bool) \RectorPrefix20210808\Nette\Utils\Strings::match($part, self::IS_OPENING_TAG_REGEX);
|
|
}
|
|
private function isClosingTag(string $part) : bool
|
|
{
|
|
return (bool) \RectorPrefix20210808\Nette\Utils\Strings::match($part, self::IS_CLOSING_TAG_REGEX);
|
|
}
|
|
private function isOpeningCdataTag(string $part) : bool
|
|
{
|
|
return \strpos($part, '<![CDATA[') !== \false;
|
|
}
|
|
private function isClosingCdataTag(string $part) : bool
|
|
{
|
|
return \strpos($part, ']]>') !== \false;
|
|
}
|
|
}
|