View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.jcabi.xml;
6   
7   import com.jcabi.matchers.XhtmlMatchers;
8   import com.yegor256.Together;
9   import java.util.concurrent.ExecutorService;
10  import java.util.concurrent.Executors;
11  import java.util.concurrent.TimeUnit;
12  import org.apache.commons.lang3.StringUtils;
13  import org.hamcrest.MatcherAssert;
14  import org.hamcrest.Matchers;
15  import org.junit.jupiter.api.Assertions;
16  import org.junit.jupiter.api.RepeatedTest;
17  import org.junit.jupiter.api.Test;
18  
19  /**
20   * Test case for {@link XSLDocument}.
21   * @since 0.1
22   * @checkstyle AbbreviationAsWordInNameCheck (5 lines)
23   */
24  @SuppressWarnings({"PMD.TooManyMethods", "PMD.JUnitAssertionsShouldIncludeMessage"})
25  final class XSLDocumentTest {
26  
27      @Test
28      void makesXslTransformations() {
29          final XSL xsl = new XSLDocument(
30              StringUtils.join(
31                  "<xsl:stylesheet",
32                  " xmlns:xsl='http://www.w3.org/1999/XSL/Transform'",
33                  " version='2.0'>",
34                  "<xsl:template match='/'><done/>",
35                  "</xsl:template></xsl:stylesheet>"
36              )
37          );
38          MatcherAssert.assertThat(
39              xsl.transform(new XMLDocument("<a/>")),
40              XhtmlMatchers.hasXPath("/done")
41          );
42          MatcherAssert.assertThat(
43              xsl.transform(new XMLDocument("<a></a>")),
44              XhtmlMatchers.hasXPath("/done ")
45          );
46      }
47  
48      @Test
49      @SuppressWarnings({"PMD.DoNotUseThreads", "PMD.CloseResource"})
50      void makesXslTransformationsInThreads() throws Exception {
51          final int loop = 50;
52          final int timeout = 30;
53          final XSL xsl = new XSLDocument(
54              StringUtils.join(
55                  "<xsl:stylesheet  ",
56                  " xmlns:xsl='http://www.w3.org/1999/XSL/Transform' ",
57                  " version='2.0' >",
58                  "<xsl:template match='/'><works/>",
59                  "</xsl:template> </xsl:stylesheet>"
60              )
61          );
62          final Runnable runnable = () -> MatcherAssert.assertThat(
63              xsl.transform(new XMLDocument("<test/>")),
64              XhtmlMatchers.hasXPath("/works")
65          );
66          final ExecutorService service = Executors.newFixedThreadPool(5);
67          for (int count = 0; count < loop; count += 1) {
68              service.submit(runnable);
69          }
70          service.shutdown();
71          MatcherAssert.assertThat(
72              service.awaitTermination(timeout, TimeUnit.SECONDS),
73              Matchers.is(true)
74          );
75          service.shutdownNow();
76      }
77  
78      @Test
79      void transformsWithImports() {
80          final XSL xsl = new XSLDocument(
81              this.getClass().getResourceAsStream("first.xsl")
82          ).with(new ClasspathSources(this.getClass()));
83          MatcherAssert.assertThat(
84              xsl.transform(new XMLDocument("<simple-test/>")),
85              XhtmlMatchers.hasXPath("/result[.=6]")
86          );
87      }
88  
89      @Test
90      void transformsWithImportsFromUrl() {
91          final XSL xsl = XSLDocument.make(
92              this.getClass().getResource("first.xsl")
93          ).with(new ClasspathSources(this.getClass()));
94          MatcherAssert.assertThat(
95              xsl.transform(new XMLDocument("<simple-test/>")),
96              XhtmlMatchers.hasXPath("/result[.=6]")
97          );
98      }
99  
100     @Test
101     void transformsIntoText() {
102         final XSL xsl = new XSLDocument(
103             StringUtils.join(
104                 "<xsl:stylesheet ",
105                 " xmlns:xsl='http://www.w3.org/1999/XSL/Transform'  ",
106                 " version='2.0'><xsl:output method='text'/>",
107                 "<xsl:template match='/'>hello</xsl:template></xsl:stylesheet>"
108             )
109         );
110         MatcherAssert.assertThat(
111             xsl.applyTo(new XMLDocument("<something/>")),
112             Matchers.equalTo("hello")
113         );
114     }
115 
116     @Test
117     void stripsXml() {
118         MatcherAssert.assertThat(
119             XSLDocument.STRIP.transform(
120                 new XMLDocument("<a>   <b/>  </a>")
121             ).toString(),
122             Matchers.containsString("<a>\n")
123         );
124     }
125 
126     @Test
127     void transformsIntoTextWithParams() {
128         final XSL xsl = new XSLDocument(
129             StringUtils.join(
130                 "<xsl:stylesheet   ",
131                 " xmlns:xsl='http://www.w3.org/1999/XSL/Transform'    ",
132                 " xmlns:xs='http://www.w3.org/2001/XMLSchema'",
133                 " version='2.0'><xsl:output method='text'  />",
134                 "<xsl:param name='boom' />",
135                 "<xsl:template match='/'>[<xsl:value-of select='$boom'/>]",
136                 "</xsl:template>   </xsl:stylesheet>"
137             )
138         );
139         MatcherAssert.assertThat(
140             xsl.with("boom", "Donny").applyTo(new XMLDocument("<ehe/>")),
141             Matchers.equalTo("[Donny]")
142         );
143     }
144 
145     @Test
146     void transformsIntoTextWithIntegerParams() {
147         final XSL xsl = new XSLDocument(
148             StringUtils.join(
149                 "<xsl:stylesheet     ",
150                 " xmlns:xsl='http://www.w3.org/1999/XSL/Transform'       ",
151                 " version='2.0'><xsl:output method='text'    />",
152                 "<xsl:param name='faa' select='5'/>",
153                 "<xsl:template match='/'>+<xsl:value-of select='$faa'/>+",
154                 "</xsl:template>   </xsl:stylesheet>  "
155             )
156         );
157         MatcherAssert.assertThat(
158             xsl.with("faa", 1).applyTo(new XMLDocument("<r0/>")),
159             Matchers.equalTo("+1+")
160         );
161     }
162 
163     @Test
164     void catchesXslErrorMessages() {
165         MatcherAssert.assertThat(
166             Assertions.assertThrows(
167                 RuntimeException.class,
168                 () -> new XSLDocument(
169                     StringUtils.join(
170                         " <xsl:stylesheet",
171                         "  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'",
172                         "  version='2.0'><xsl:template match='/'>",
173                         "<xsl:message terminate='yes'>",
174                         "<xsl:text>oopsie...</xsl:text>",
175                         " </xsl:message></xsl:template></xsl:stylesheet>"
176                     ),
177                     "foo"
178                 ).with(new ClasspathSources()).transform(new XMLDocument("<zz1/>"))
179             ).getLocalizedMessage(),
180             Matchers.allOf(
181                 Matchers.containsString("Processing terminated by xsl:message"),
182                 Matchers.containsString("by xsl:message at line 1 in foo")
183             )
184         );
185     }
186 
187     @Test
188     void catchesXslWarningsDuringCompilation() {
189         MatcherAssert.assertThat(
190             Assertions.assertThrows(
191                 RuntimeException.class,
192                 () ->
193                     new XSLDocument(this.getClass().getResourceAsStream("multiple-imports.xsl"))
194                         .with(new ClasspathSources(this.getClass()))
195                         .transform(new XMLDocument("<f/>")),
196                 "Warnings was not thrown, but they should"
197             ).getLocalizedMessage(),
198             Matchers.allOf(
199                 Matchers.containsString(
200                     "Stylesheet module file:/second.xsl is included or imported more than once"
201                 )
202             )
203         );
204     }
205 
206     @Test
207     void printsSystemIdInErrorMessages() {
208         MatcherAssert.assertThat(
209             Assertions.assertThrows(
210                 RuntimeException.class,
211                 () -> new XSLDocument(
212                     StringUtils.join(
213                         " <xsl:stylesheet",
214                         "  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'",
215                         "  version='2.0'><xsl:template match='/'>",
216                         "<xsl:value-of select='$xx'/></xsl:template>",
217                         "  </xsl:stylesheet>"
218                     ),
219                     "some-fake-systemId"
220                 ).with(new ClasspathSources()).transform(new XMLDocument("<ooo/>"))
221             ).getLocalizedMessage(),
222             Matchers.containsString(
223                 "Failed to create transformer by net.sf.saxon.TransformerFactoryImpl"
224             )
225         );
226     }
227 
228     @Test
229     void catchesSaxonWarnings() {
230         new XSLDocument(
231             StringUtils.join(
232                 " <xsl:stylesheet",
233                 "  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'",
234                 "  version='2.0'>",
235                 "<xsl:template match='a'></xsl:template>",
236                 "<xsl:template match='a'></xsl:template>",
237                 "</xsl:stylesheet>"
238             ),
239             "https://example.com/hello.xsl"
240         ).transform(new XMLDocument("<x><a/></x>"));
241     }
242 
243     @RepeatedTest(10)
244     void transformsInManyThreads() throws Exception {
245         final XSL xsl = new XSLDocument(
246             this.getClass().getResourceAsStream("first.xsl")
247         ).with(new ClasspathSources());
248         final XML xml = new XMLDocument(
249             this.getClass().getResourceAsStream("simple.xml")
250         );
251         final int total = 50;
252         MatcherAssert.assertThat(
253             new Together<>(
254                 total,
255                 t -> xsl.transform(xml)
256             ).asList().size(),
257             Matchers.equalTo(total)
258         );
259     }
260 
261 }