001/*
002 * Shredzone Commons
003 *
004 * Copyright (C) 2014 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU Library General Public License as
009 * published by the Free Software Foundation, either version 3 of the
010 * License, or (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU Library General Public License
018 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
019 */
020package org.shredzone.commons.xml;
021
022import static java.util.stream.Collectors.toList;
023import static org.hamcrest.Matchers.*;
024import static org.junit.Assert.assertThat;
025
026import java.io.IOException;
027import java.io.InputStreamReader;
028import java.io.Reader;
029import java.util.List;
030
031import org.junit.Before;
032import org.junit.Test;
033
034/**
035 * Unit tests for {@link XQuery}.
036 *
037 * @author Richard "Shred" Körber
038 */
039public class XQueryTest {
040
041    private XQuery xq;
042
043    /**
044     * Sets up a new {@link XQuery} instance.
045     */
046    @Before
047    public void init() throws IOException {
048        xq = XQuery.parse(XQueryTest.class.getResourceAsStream("/test.xml"));
049    }
050
051    /**
052     * Test {@link Reader} based parser.
053     */
054    @Test
055    public void parserReaderTest() throws IOException {
056        try (Reader r = new InputStreamReader(XQueryTest.class.getResourceAsStream("/test.xml"))) {
057            XQuery result = XQuery.parse(r);
058            assertThat(result, is(notNullValue()));
059        }
060    }
061
062    /**
063     * Test {@link String} based parser.
064     */
065    @Test
066    public void parserStringTest() throws IOException {
067        XQuery result = XQuery.parse("<test><foo>bar</foo></test>");
068        assertThat(result, is(notNullValue()));
069    }
070
071    /**
072     * Test parser error on bad XML.
073     */
074    @Test(expected = IOException.class)
075    public void parserFailTest() throws IOException {
076        XQuery.parse(":-(");
077    }
078
079    /**
080     * Does the {@link XQuery#stream()} of the root {@link XQuery} return the root
081     * element?
082     */
083    @Test
084    public void streamTest() throws IOException {
085        List<String> tags = xq.stream().map(XQuery::name).collect(toList());
086
087        assertThat(tags, contains("catalog"));
088    }
089
090    /**
091     * Does the {@link XQuery#stream()} of a subelement return the child elements of that
092     * element?
093     */
094    @Test
095    public void subStreamTest() throws IOException {
096        XQuery book7 = xq.get("//book[@id='bk7']");
097        List<String> tags = book7.stream().map(XQuery::name).collect(toList());
098
099        assertThat(tags, contains("author", "title", "original", "published", "description"));
100    }
101
102    /**
103     * Does {@link XQuery#select(String)} return the correct elements? Does
104     * {@link XQuery#text()} return the text content?
105     */
106    @Test
107    public void selectTest() throws IOException {
108        List<String> titles = xq.select("//book/original").map(XQuery::text).collect(toList());
109
110        assertThat(titles, contains("Le Lotus bleu", "L'Île noire",
111                        "Le Secret de la Licorne", "Objectif Lune", "Tintin au Tibet"));
112    }
113
114    /**
115     * Does {@link XQuery#get(String)} return a single element?
116     */
117    @Test
118    public void getTest() throws IOException {
119        XQuery book11 = xq.get("//book[@id='bk11']");
120        assertThat(book11.attr().get("id"), is("bk11"));
121    }
122
123    /**
124     * Does {@link XQuery#get(String)} throw an exception if there is no such element?
125     */
126    @Test(expected = IllegalArgumentException.class)
127    public void getNoneFailTest() throws IOException {
128        xq.get("//book[@id='bk9']");
129    }
130
131    /**
132     * Does {@link XQuery#get(String)} throw an exception if multiple elements are
133     * found?
134     */
135    @Test(expected = IllegalArgumentException.class)
136    public void getMultipleFailTest() throws IOException {
137        xq.get("//book");
138    }
139
140    /**
141     * Does {@link XQuery#previousSibling()} and {@link XQuery#nextSibling()} return
142     * the correct siblings?
143     */
144    @Test
145    public void siblingTest() throws IOException {
146        XQuery book7 = xq.get("//book[@id='bk7']");
147
148        XQuery book5 = book7.previousSibling().get();
149        assertThat(book5.attr().get("id"), is("bk5"));
150        assertThat(book5.previousSibling().isPresent(), is(false));
151
152        XQuery book11 = book7.nextSibling().get();
153        assertThat(book11.attr().get("id"), is("bk11"));
154
155        XQuery book16 = book11.nextSibling().get();
156        assertThat(book16.attr().get("id"), is("bk16"));
157
158        XQuery book20 = book16.nextSibling().get();
159        assertThat(book20.attr().get("id"), is("bk20"));
160        assertThat(book20.nextSibling().isPresent(), is(false));
161    }
162
163    /**
164     * Does {@link XQuery#exists(String)} return a correct answer.
165     */
166    @Test
167    public void existsTest() throws IOException {
168        assertThat(xq.exists("//book/original"), is(true));
169        assertThat(xq.exists("//catfood/brand"), is(false));
170    }
171
172    /**
173     * Does {@link XQuery#select(String)} fail on bad XPath?
174     */
175    @Test(expected = IllegalArgumentException.class)
176    public void selectFailTest() throws IOException {
177        xq.select(":-(");
178    }
179
180    /**
181     * Does {@link XQuery#value(String)} return the text contents of the matching
182     * elements?
183     */
184    @Test
185    public void valueTest() throws IOException {
186        List<String> titles = xq.value("/catalog/book/title").collect(toList());
187
188        assertThat(titles, contains("The Blue Lotus","The Black Island",
189                        "The Secret of the Unicorn", "Destination Moon",
190                        "Tintin in Tibet"));
191    }
192
193    /**
194     * Does {@link XQuery#allValue(String)} return the text contents of the matching
195     * elements, recursively?
196     */
197    @Test
198    public void allValueTest() throws IOException {
199        List<String> dates = xq.value("/catalog/book/published").map(String::trim).collect(toList());
200        List<String> allDates = xq.allValue("/catalog/book/published").map(String::trim).collect(toList());
201
202        assertThat(dates, contains("", "", "", "", ""));
203        assertThat(allDates, contains("1936\n        1946", "1938\n        1943\n        1966",
204                        "1943", "1953", "1960"));
205    }
206
207    /**
208     * Is {@link XQuery#value(String)} non-recursive, i.e. does it only return the text
209     * of the immediate children?
210     */
211    @Test
212    public void valueNonRecursiveTest() throws IOException {
213        List<String> dates = xq.value("/catalog/book[@id='bk7']/published")
214                        .map(String::trim)
215                        .collect(toList());
216
217        assertThat(dates, contains(""));
218    }
219
220    /**
221     * Does {@link XQuery#select(String)} and {@link XQuery#allText()} return the text
222     * contents of the entire subtree?
223     */
224    @Test
225    public void value3Test() throws IOException {
226        String dates = xq.select("/catalog/book[@id='bk7']/published")
227                        .map(XQuery::allText)
228                        .findFirst()
229                        .get();
230
231        assertThat(dates, is("\n        1938\n        1943\n        1966\n      "));
232    }
233
234    /**
235     * Does {@link XQuery#attr()} return a map of attributes?
236     */
237    @Test
238    public void attrTest() throws IOException {
239        List<String> ids = xq.select("/catalog/book")
240                .map(book -> book.attr().get("id"))
241                .collect(toList());
242
243        assertThat(ids, contains("bk5", "bk7", "bk11", "bk16", "bk20"));
244    }
245
246    /**
247     * Does {@link XQuery#attr()} return a map of multiple attributes?
248     */
249    @Test
250    public void attrManyTest() throws IOException {
251        List<XQuery> albums = xq.select("/catalog/book[@id='bk7']/published/album")
252                .collect(toList());
253
254        assertThat(albums.get(0).attr().size(), is(1));
255        assertThat(albums.get(0).attr().get("type"), is("bw"));
256
257        assertThat(albums.get(1).attr().size(), is(1));
258        assertThat(albums.get(1).attr().get("type"), is("color"));
259
260        assertThat(albums.get(2).attr().size(), is(2));
261        assertThat(albums.get(2).attr().get("type"), is("color"));
262        assertThat(albums.get(2).attr().get("republished"), is("yes"));
263    }
264
265    /**
266     * Does {@link XQuery#attr()} return an empty map of attributes when there are none?
267     */
268    @Test
269    public void attrEmptyTest() throws IOException {
270        xq.select("/catalog/book/author")
271                .forEach(author -> assertThat(author.attr().keySet(), is(empty())));
272        assertThat(xq.attr().keySet(), is(empty()));
273    }
274
275    /**
276     * Are comments ignored?
277     */
278    @Test
279    public void noCommentTest() throws IOException {
280        String text = xq.text("/catalog/book[@id='bk16']/description");
281
282        assertThat(text, is("Tintin's friend Professor Calculus has been secretly commissioned\n"
283            + "      \n"
284            + "      by the Syldavian government to build a rocket ship that will fly from the\n"
285            + "      Earth to the Moon."));
286    }
287
288    /**
289     * Are CDATA elements read as text?
290     */
291    @Test
292    public void cdataTest() throws IOException {
293        String text = xq.text("/catalog/book[@id='bk20']/description");
294
295        assertThat(text, is("While on holiday at a resort in the French Alps with Snowy,\n"
296            + "      Captain Haddock, & Professor Calculus, <Tintin> reads about a plane crash in\n"
297            + "      the Gosain Than Massif in the Himalayas of Tibet."));
298    }
299
300    /**
301     * Does {@link XQuery#parent()} return the correct parent node?
302     */
303    @Test
304    public void parentTest() throws IOException {
305        XQuery title = xq.get("/catalog/book[@id='bk11']/title");
306
307        XQuery parent = title.parent().get();
308        assertThat(parent.name(), is("book"));
309
310        // make sure parent is cached
311        XQuery parent2 = title.parent().get();
312        assertThat(parent2, is(sameInstance(parent)));
313    }
314
315    /**
316     * Does {@link XQuery#parent()} return empty on root?
317     */
318    @Test
319    public void parentOfRootTest() throws IOException {
320        assertThat(xq.parent().isPresent(), is(false));
321    }
322
323    /**
324     * Does {@link XQuery#parent()} return the correct parent node?
325     */
326    @Test
327    public void rootTest() throws IOException {
328        XQuery title = xq.get("/catalog/book[@id='bk11']/title");
329        assertThat(title.isRoot(), is(false));
330        assertThat(title.name(), is("title"));
331
332        XQuery root = title.root();
333        assertThat(root.isRoot(), is(true));
334        assertThat(root.name(), is("#document"));
335    }
336
337    /**
338     * Does {@link XQuery#parent()} return empty on root?
339     */
340    @Test
341    public void rootOfRootTest() throws IOException {
342        assertThat(xq.root(), is(sameInstance(xq)));
343        assertThat(xq.isRoot(), is(true));
344    }
345
346}