<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

use DOMDocument;
use DOMNode;

/**
 * Compares DOMNode instances for equality.
 *
 * @package    Comparator
 * @author     Bernhard Schussek <bschussek@2bepublished.at>
 * @copyright  Sebastian Bergmann <sebastian@phpunit.de>
 * @license    http://www.opensource.org/licenses/BSD-3-Clause  The BSD 3-Clause License
 * @link       http://www.github.com/sebastianbergmann/comparator
 */
class DOMNodeComparator extends ObjectComparator {
	/**
	 * Returns whether the comparator can compare two values.
	 *
	 * @param  mixed $expected The first value to compare
	 * @param  mixed $actual   The second value to compare
	 *
	 * @return boolean
	 */
	public function accepts ( $expected, $actual ) {
		return $expected instanceof DOMNode && $actual instanceof DOMNode;
	}

	/**
	 * Asserts that two values are equal.
	 *
	 * @param  mixed $expected     The first value to compare
	 * @param  mixed $actual       The second value to compare
	 * @param  float $delta        The allowed numerical distance between two values to
	 *                             consider them equal
	 * @param  bool  $canonicalize If set to TRUE, arrays are sorted before
	 *                             comparison
	 * @param  bool  $ignoreCase   If set to TRUE, upper- and lowercasing is
	 *                             ignored when comparing string values
	 *
	 * @throws ComparisonFailure Thrown when the comparison
	 *                           fails. Contains information about the
	 *                           specific errors that lead to the failure.
	 */
	public function assertEquals ( $expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false ) {
		$expectedAsString = $this->nodeToText( $expected, true, $ignoreCase );
		$actualAsString   = $this->nodeToText( $actual, true, $ignoreCase );

		if ( $expectedAsString !== $actualAsString ) {
			if ( $expected instanceof DOMDocument ) {
				$type = 'documents';
			} else {
				$type = 'nodes';
			}

			throw new ComparisonFailure(
				$expected,
				$actual,
				$expectedAsString,
				$actualAsString,
				false,
				sprintf( "Failed asserting that two DOM %s are equal.\n", $type )
			);
		}
	}

	/**
	 * Returns the normalized, whitespace-cleaned, and indented textual
	 * representation of a DOMNode.
	 *
	 * @param  DOMNode $node
	 * @param  boolean $canonicalize
	 * @param  boolean $ignoreCase
	 *
	 * @return string
	 */
	private function nodeToText ( DOMNode $node, $canonicalize, $ignoreCase ) {
		if ( $canonicalize ) {
			$document = new DOMDocument;
			$document->loadXML( $node->C14N() );

			$node = $document;
		}

		if ( $node instanceof DOMDocument ) {
			$document = $node;
		} else {
			$document = $node->ownerDocument;
		}

		$document->formatOutput = true;
		$document->normalizeDocument();

		if ( $node instanceof DOMDocument ) {
			$text = $node->saveXML();
		} else {
			$text = $document->saveXML( $node );
		}

		if ( $ignoreCase ) {
			$text = strtolower( $text );
		}

		return $text;
	}
}
