Реализация алгоритма нормализации XML для СМЭВ 3?

Согласно методическим рекомендациям по работе со СМЭВ при подписании XML-фрагментов электронной подписью в форме XMLDSig, обязательно использовать нормализацию urn://smev-gov-ru/xmldsig/transform.

В алгоритме нормализации мне абсолютно непонятны пункты, связанные с namespace'ами, а именно:
  1. Удалить namespace prefix, которые на текущем уровне объявляются, но не используются.
  2. Проверить, что namespace текущего элемента объявлен либо выше по дереву, либо в текущем элементе. Если не объявлен, объявить в текущем элементе.
  3. Namespace prefix элементов и атрибутов должны заменены на автоматически сгенерированные. Сгенерированный префикс состоит из литерала «ns», и порядкового номера сгенерированного префикса в рамках обрабатываемого XML-фрагмента, начиная с единицы. При генерации префиксов должно устраняться их дублирование.


При этом есть пример правильной xml, полученной после нормализации.

Исходная XML*:

<ns:SenderProvidedRequestData Id="SIGNED_BY_CONSUMER" xmlns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" xmlns:ns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" xmlns:ns2="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1">
	<ns:MessageID>db0486d0-3c08-11e5-95e2-d4c9eff07b77</ns:MessageID>
	<ns2:MessagePrimaryContent>
		<ns1:BreachRequest xmlns:ns1="urn://x-artefacts-gibdd-gov-ru/breach/root/1.0"  xmlns:ns2="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0"  xmlns:ns3="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1" Id="PERSONAL_SIGNATURE">
			<ns1:RequestedInformation>
				<ns2:RegPointNum>Т785ЕС57</ns2:RegPointNum>
			</ns1:RequestedInformation>
			<ns1:Governance>
				<ns2:Name>ГИБДД РФ</ns2:Name>
				<ns2:Code>GIBDD</ns2:Code>
				<ns2:OfficialPerson>
					<ns3:FamilyName>Загурский</ns3:FamilyName>
					<ns3:FirstName>Андрей</ns3:FirstName>
					<ns3:Patronymic>Петрович</ns3:Patronymic>
				</ns2:OfficialPerson>
			</ns1:Governance>
		</ns1:BreachRequest>
	</ns2:MessagePrimaryContent>
	<ns:TestMessage/>
</ns:SenderProvidedRequestData>


После нормализации*:

<ns1:SenderProvidedRequestData xmlns:ns1="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" Id="SIGNED_BY_CONSUMER">
	<ns1:MessageID>db0486d0-3c08-11e5-95e2-d4c9eff07b77</ns1:MessageID>
	<ns2:MessagePrimaryContent xmlns:ns2="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1">
		<ns3:BreachRequest xmlns:ns3="urn://x-artefacts-gibdd-gov-ru/breach/root/1.0" Id="PERSONAL_SIGNATURE">
			<ns3:RequestedInformation>
				<ns4:RegPointNum xmlns:ns4="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">Т785ЕС57</ns4:RegPointNum>
			</ns3:RequestedInformation>
			<ns3:Governance>
				<ns5:Name xmlns:ns5="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">ГИБДД РФ</ns5:Name>
				<ns6:Code xmlns:ns6="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">GIBDD</ns6:Code>
				<ns7:OfficialPerson xmlns:ns7="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">
					<ns8:FamilyName xmlns:ns8="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Загурский</ns8:FamilyName>
					<ns9:FirstName xmlns:ns9="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Андрей</ns9:FirstName>
					<ns10:Patronymic xmlns:ns10="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Петрович</ns10:Patronymic>
				</ns7:OfficialPerson>
			</ns3:Governance>
		</ns3:BreachRequest>
	</ns2:MessagePrimaryContent>
	<ns1:TestMessage/>
</ns1:SenderProvidedRequestData>


* Переводы строк и отступы добавлены для удобного просмотра.

Образцовая реализация (образцовая ли она - неизвестно) алгоритма представлена в методических рекомендациях на Java для Apache Santuario.

Кто-нибудь реализовывал данный алгоритм трансформации на PHP?

Или хотя бы смог вникнуть в те правила, согласно которым должна происходить трансформация? Не на языке ГОСТ-овской документации, а на человеческом?
  • Вопрос задан
  • 4021 просмотр
Решения вопроса 2
IvanU7n
@IvanU7n
nothing interesting here
Для будущих искателей: вот такой монстрик у меня получился (PHP 7.1). Он проходит все проверки из МР 3.5, но, возможно, некоторых нюансов не учитывает.
<?php
$in = new DOMDocument();
$in->load($argv[1]);

$out = new XMLWriter();
$out->openMemory();

$index = 0;
$stack = [null, [$in->documentElement, []]];
while (count($stack)) {
	$item = array_pop($stack);
	if ($item === null) {
		$out->text('');
		$out->endElement();
		continue;
	}
	[$node, $nsList] = $item;
	if ($node->nodeType == XML_ELEMENT_NODE) {
		// Let the magic begin! ;)
		// The element itself.
		if ($node->namespaceURI !== null) {
			$thisLevel = false;
			if (($nsList[$node->namespaceURI] ?? null) === null) {
				$thisLevel = true;
				$nsList[$node->namespaceURI] = 'ns' . ++$index;
			}
			$out->startElement("{$nsList[$node->namespaceURI]}:{$node->localName}");
			if ($thisLevel) {
				$out->writeAttribute("xmlns:{$nsList[$node->namespaceURI]}", $node->namespaceURI);
			}
		}
		else {
			$out->startElement($node->localName);
		}
		// Attributes.
		$attrs = iterator_to_array($node->attributes);
		usort($attrs, function($a, $b) {
			if ($a->namespaceURI !== null && $b->namespaceURI === null) return -1;
			else if ($a->namespaceURI === null && $b->namespaceURI !== null) return 1;
			else return strcmp($a->namespaceURI, $b->namespaceURI) ?: strcmp($a->localName, $b->localName);
		});
		foreach ($attrs as $attr) {
			if ($attr->namespaceURI !== null && ($nsList[$attr->namespaceURI] ?? null) === null) {
				$nsList[$attr->namespaceURI] = 'ns' . ++$index;
				$out->writeAttribute("xmlns:{$nsList[$attr->namespaceURI]}", $attr->namespaceURI);
			}
		}
		foreach ($attrs as $attr) {
			if ($attr->namespaceURI !== null) {
				$out->writeAttribute("{$nsList[$attr->namespaceURI]}:{$attr->localName}", $attr->nodeValue);
			}
			else {
				$out->writeAttribute($attr->localName, $attr->nodeValue);
			}
		}
	}
	else if ($node->nodeType == XML_TEXT_NODE && strlen(trim($node->nodeValue))) {
		$out->text($node->nodeValue);
	}

	if ($node->lastChild !== null) {
		$stack[] = null;
		for ($node = $node->lastChild; $node !== null; $node = $node->previousSibling) {
			$stack[] = [$node, $nsList];
		}
	}
}

echo $out->outputMemory(), "\n";

?>
Ответ написан
@Danbka Автор вопроса
Моя реализация алгоритма на PHP:

https://github.com/Danbka/smev-transform
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы