Skip to content

Commit 7a32bb9

Browse files
authored
[java-source-utils] Transform XML using XSLT (#924)
I've seen inconsistent XML output from java-source-utils when running on macOS vs. Windows, or JDK 8 vs. JDK 11. On Windows I've seen extra carriage returns in CDATA blocks, and when using JDK 9+ we've seen additional new lines added between XML elements. An XSL style sheet can be used to improve the formatting consistency of the XML that is produced. This should prevent us from generating inconsistent API docs across macOS and Windows. There is still [an outstanding issue][0] concerning CDATA blocks between JDK 8 and JDK 11 that I have not yet been able to sort out. This issue has seemingly been ["fixed" in JDK 14][1]. [0]: https://stackoverflow.com/questions/55853220/handling-change-in-newlines-by-xml-transformation-for-cdata-from-java-8-to-java [1]: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8223291
1 parent 7f55b2d commit 7a32bb9

File tree

8 files changed

+80
-17
lines changed

8 files changed

+80
-17
lines changed

build-tools/automation/azure-pipelines.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,5 @@ jobs:
179179
- template: templates\core-tests.yaml
180180
parameters:
181181
runNativeTests: true
182-
runJavaTests: true
183182

184183
- template: templates\fail-on-issue.yaml

build-tools/automation/templates/core-tests.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
parameters:
22
condition: succeeded()
33
runNativeTests: false
4-
runJavaTests: false
54

65
steps:
76
- task: DotNetCoreCLI@2
@@ -110,15 +109,13 @@ steps:
110109

111110
- task: DotNetCoreCLI@2
112111
displayName: 'Tests: java-source-utils'
113-
condition: eq('${{ parameters.runJavaTests }}', 'true')
114112
inputs:
115113
command: build
116114
arguments: -c $(Build.Configuration) tools/java-source-utils/java-source-utils.csproj -t:RunTests
117115
continueOnError: true
118116

119117
- task: PublishTestResults@2
120118
displayName: Publish JUnit Test Results
121-
condition: eq('${{ parameters.runJavaTests }}', 'true')
122119
inputs:
123120
testResultsFormat: JUnit
124121
testResultsFiles: 'tools/java-source-utils/build/test-results/**/TEST-*.xml'

build-tools/automation/templates/publish-test-results.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ steps:
99

1010
- task: PublishTestResults@2
1111
displayName: Publish JUnit Test Results
12-
condition: ne('$(Agent.OS)', 'Windows')
1312
inputs:
1413
testResultsFormat: JUnit
1514
testResultsFiles: '**/TEST-*.xml'

build-tools/scripts/RunNUnitTests.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
ContinueOnError="ErrorAndContinue"
3131
/>
3232
<MSBuild
33-
Condition=" !$([MSBuild]::IsOSPlatform ('windows')) "
33+
Condition=" '$(SkipJSUTests)' != 'true' "
3434
Projects="$(MSBuildThisFileDirectory)..\..\tools\java-source-utils\java-source-utils.csproj"
3535
Targets="RunTests"
3636
/>

tools/java-source-utils/src/main/java/com/microsoft/android/JavadocXmlGenerator.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.io.File;
44
import java.io.FileNotFoundException;
5+
import java.io.InputStream;
6+
import java.io.InputStreamReader;
57
import java.io.IOException;
68
import java.io.PrintStream;
79
import java.io.UnsupportedEncodingException;
@@ -17,6 +19,7 @@
1719
import javax.xml.transform.TransformerFactory;
1820
import javax.xml.transform.dom.DOMSource;
1921
import javax.xml.transform.stream.StreamResult;
22+
import javax.xml.transform.stream.StreamSource;
2023

2124
import org.w3c.dom.Document;
2225
import org.w3c.dom.Element;
@@ -65,10 +68,10 @@ private void startApi() throws ParserConfigurationException {
6568
}
6669

6770
public void close() throws TransformerException {
71+
InputStream is = getClass().getClassLoader().getResourceAsStream("transform-style.xsl");
72+
InputStreamReader isr = new InputStreamReader(is);
6873
Transformer transformer = TransformerFactory.newInstance()
69-
.newTransformer();
70-
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
71-
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
74+
.newTransformer(new StreamSource (isr));
7275
transformer.transform(new DOMSource(document), new StreamResult(output));
7376

7477
if (output != System.out) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<xsl:stylesheet version="1.0"
2+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
3+
xmlns:xalan="http://xml.apache.org/xalan">
4+
5+
<xsl:output method="xml"
6+
encoding="UTF-8"
7+
indent="yes"
8+
xalan:indent-amount="2"
9+
standalone="no"
10+
cdata-section-elements="javadoc"/>
11+
12+
<xsl:strip-space elements="*"/>
13+
14+
<xsl:template match="node()|@*">
15+
<xsl:copy>
16+
<xsl:apply-templates select="node()|@*"/>
17+
</xsl:copy>
18+
</xsl:template>
19+
20+
21+
<!-- Remove extra carriage returns from `<javadoc/>` CDATA elements to help standardize output across Unix and Windows -->
22+
<xsl:template match="javadoc/text()">
23+
<xsl:call-template name="removeCarriageReturn"/>
24+
</xsl:template>
25+
26+
<xsl:template name="removeCarriageReturn">
27+
<xsl:param name="pText" select="."/>
28+
29+
<xsl:if test="string-length($pText) >0">
30+
<xsl:choose>
31+
<xsl:when test="not(contains($pText,'&#13;'))">
32+
<xsl:value-of select="$pText"/>
33+
</xsl:when>
34+
35+
<xsl:otherwise>
36+
<xsl:value-of select="substring-before($pText, '&#13;')"/>
37+
<xsl:call-template name="removeCarriageReturn">
38+
<xsl:with-param name="pText" select="substring-after($pText, '&#13;')"/>
39+
</xsl:call-template>
40+
</xsl:otherwise>
41+
</xsl:choose>
42+
</xsl:if>
43+
</xsl:template>
44+
45+
</xsl:stylesheet>

tools/java-source-utils/src/test/java/com/microsoft/android/JavadocXmlGeneratorTest.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import javax.xml.parsers.ParserConfigurationException;
1111
import javax.xml.transform.TransformerException;
1212

13+
import org.junit.Assume;
1314
import org.junit.Test;
1415
import static org.junit.Assert.*;
1516

@@ -19,7 +20,14 @@
1920
public final class JavadocXmlGeneratorTest {
2021
@Test(expected = FileNotFoundException.class)
2122
public void init_invalidFileThrows() throws FileNotFoundException, ParserConfigurationException, TransformerException, UnsupportedEncodingException {
22-
try (JavadocXmlGenerator g = new JavadocXmlGenerator("/this/file/does/not/exist")) {
23+
String invalidFilePath = "/this/file/does/not/exist";
24+
String osName = System.getProperty("os.name");
25+
if (osName.startsWith("Windows")) {
26+
invalidFilePath = System.getenv("ProgramFiles") + "\\this\\file\\does\\not\\exist";
27+
// Ignore if running on an Azure Pipelines Microsoft hosted agent by only running when %AGENT_NAME% is not set.
28+
Assume.assumeTrue(System.getenv("AGENT_NAME") == null);
29+
}
30+
try (JavadocXmlGenerator g = new JavadocXmlGenerator(invalidFilePath)) {
2331
}
2432
}
2533

@@ -40,9 +48,11 @@ public void testWritePackages_noPackages() throws ParserConfigurationException,
4048
generator.writePackages(packages);
4149
generator.close();
4250

43-
final String expected =
51+
final String expected = (
4452
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
45-
"<api api-source=\"java-source-utils\"/>\n";
53+
"<api api-source=\"java-source-utils\"/>\n"
54+
).replace("\n", System.lineSeparator());
55+
4656
assertEquals("no packages", expected, bytes.toString());
4757
}
4858

@@ -53,7 +63,7 @@ public void testWritePackages_demo() throws ParserConfigurationException, Transf
5363
final JavadocXmlGenerator generator = new JavadocXmlGenerator(new PrintStream(bytes));
5464
final JniPackagesInfo packages = JniPackagesInfoTest.createDemoInfo();
5565

56-
final String expected =
66+
final String expected = (
5767
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
5868
"<api api-source=\"java-source-utils\">\n" +
5969
" <package jni-name=\"\" name=\"\">\n" +
@@ -94,7 +104,8 @@ public void testWritePackages_demo() throws ParserConfigurationException, Transf
94104
" </method>\n" +
95105
" </interface>\n" +
96106
" </package>\n" +
97-
"</api>\n";
107+
"</api>\n"
108+
).replace("\n", System.lineSeparator());
98109

99110
generator.writePackages(packages);
100111
generator.close();

tools/java-source-utils/src/test/java/com/microsoft/android/ParameterNameGeneratorTest.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.io.UnsupportedEncodingException;
88
import java.util.Arrays;
99

10+
import org.junit.Assume;
1011
import org.junit.Test;
1112
import static org.junit.Assert.*;
1213

@@ -17,7 +18,14 @@ public class ParameterNameGeneratorTest {
1718

1819
@Test(expected = FileNotFoundException.class)
1920
public void init_invalidFileThrows() throws FileNotFoundException, UnsupportedEncodingException {
20-
new ParameterNameGenerator("/this/file/does/not/exist");
21+
String invalidFilePath = "/this/file/does/not/exist";
22+
String osName = System.getProperty("os.name");
23+
if (osName.startsWith("Windows")) {
24+
invalidFilePath = System.getenv("ProgramFiles") + "\\this\\file\\does\\not\\exist";
25+
// Ignore if running on an Azure Pipelines Microsoft hosted agent by only running when %AGENT_NAME% is not set.
26+
Assume.assumeTrue(System.getenv("AGENT_NAME") == null);
27+
}
28+
new ParameterNameGenerator(invalidFilePath);
2129
}
2230

2331
@Test(expected = IllegalArgumentException.class)
@@ -45,7 +53,7 @@ public void testWritePackages_demo() {
4553
ParameterNameGenerator generator = new ParameterNameGenerator(new PrintStream(bytes));
4654
JniPackagesInfo packages = JniPackagesInfoTest.createDemoInfo();
4755

48-
final String expected =
56+
final String expected = (
4957
";---------------------------------------\n" +
5058
" class A\n" +
5159
" #ctor(int one, java.lang.String two)\n" +
@@ -60,7 +68,8 @@ public void testWritePackages_demo() {
6068
";---------------------------------------\n" +
6169
" interface Exampleable\n" +
6270
" example(java.lang.String e)\n" +
63-
"";
71+
""
72+
).replace("\n", System.lineSeparator());
6473

6574
generator.writePackages(packages);
6675
assertEquals("global package + example packages", expected, bytes.toString());

0 commit comments

Comments
 (0)