001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.util;
018
019import java.io.StringReader;
020import java.util.Map;
021
022import javax.xml.parsers.DocumentBuilder;
023import javax.xml.parsers.DocumentBuilderFactory;
024import javax.xml.transform.Source;
025import javax.xml.transform.dom.DOMSource;
026
027import org.hamcrest.Matcher;
028import org.w3c.dom.Document;
029import org.w3c.dom.Node;
030import org.xml.sax.InputSource;
031import org.xmlunit.builder.DiffBuilder;
032import org.xmlunit.diff.DefaultNodeMatcher;
033import org.xmlunit.diff.Diff;
034import org.xmlunit.diff.ElementSelectors;
035
036import static org.hamcrest.MatcherAssert.assertThat;
037
038/**
039 * A helper class for assertions on XML content.
040 *
041 * @author Rossen Stoyanchev
042 * @since 3.2
043 */
044public class XmlExpectationsHelper {
045
046        /**
047         * Parse the content as {@link Node} and apply a {@link Matcher}.
048         */
049        public void assertNode(String content, Matcher<? super Node> matcher) throws Exception {
050                Document document = parseXmlString(content);
051                assertThat("Body content", document, matcher);
052        }
053
054        private Document parseXmlString(String xml) throws Exception  {
055                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
056                factory.setNamespaceAware(true);
057                DocumentBuilder documentBuilder = factory.newDocumentBuilder();
058                InputSource inputSource = new InputSource(new StringReader(xml));
059                return documentBuilder.parse(inputSource);
060        }
061
062        /**
063         * Parse the content as {@link DOMSource} and apply a {@link Matcher}.
064         * @see <a href="https://github.com/davidehringer/xml-matchers">xml-matchers</a>
065         */
066        public void assertSource(String content, Matcher<? super Source> matcher) throws Exception {
067                Document document = parseXmlString(content);
068                assertThat("Body content", new DOMSource(document), matcher);
069        }
070
071        /**
072         * Parse the expected and actual content strings as XML and assert that the
073         * two are "similar" -- i.e. they contain the same elements and attributes
074         * regardless of order.
075         * <p>Use of this method assumes the
076         * <a href="https://github.com/xmlunit/xmlunit">XMLUnit</a> library is available.
077         * @param expected the expected XML content
078         * @param actual the actual XML content
079         * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers#xpath(String, Object...)
080         * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers#xpath(String, Map, Object...)
081         */
082        public void assertXmlEqual(String expected, String actual) throws Exception {
083                XmlUnitDiff diff = new XmlUnitDiff(expected, actual);
084                if (diff.hasDifferences()) {
085                        AssertionErrors.fail("Body content " + diff.toString());
086                }
087        }
088
089
090        /**
091         * Inner class to prevent hard dependency on XML Unit.
092         */
093        private static class XmlUnitDiff {
094
095                private final Diff diff;
096
097
098                XmlUnitDiff(String expected, String actual) {
099                        this.diff = DiffBuilder.compare(expected).withTest(actual)
100                                        .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
101                                        .ignoreWhitespace().ignoreComments()
102                                        .checkForSimilar()
103                                        .build();
104                }
105
106
107                public boolean hasDifferences() {
108                        return this.diff.hasDifferences();
109                }
110
111                @Override
112                public String toString() {
113                        return this.diff.toString();
114                }
115
116        }
117
118}