Skip to content

Commit a8d0532

Browse files
committed
[MNG-5862] XML entities support
1 parent 1a71435 commit a8d0532

File tree

9 files changed

+442
-49
lines changed

9 files changed

+442
-49
lines changed

maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import javax.annotation.PreDestroy;
2222
import javax.inject.Named;
2323
import javax.inject.Singleton;
24+
import javax.xml.stream.XMLInputFactory;
25+
import javax.xml.stream.XMLResolver;
2426
import javax.xml.stream.XMLStreamException;
2527
import javax.xml.stream.XMLStreamReader;
2628

@@ -36,6 +38,7 @@
3638
import java.util.concurrent.CopyOnWriteArraySet;
3739
import java.util.function.BiConsumer;
3840

41+
import com.ctc.wstx.api.WstxInputProperties;
3942
import org.apache.maven.feature.Features;
4043
import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
4144
import org.apache.maven.model.building.TransformerContext;
@@ -186,6 +189,18 @@ static InputStream transform(Path pomFile, TransformerContext context) throws IO
186189
try (InputStream input = Files.newInputStream(pomFile)) {
187190
XMLInputFactory2 factory = new com.ctc.wstx.stax.WstxInputFactory();
188191
factory.configureForRoundTripping();
192+
XMLResolver resolver = (String publicID, String systemID, String baseURI, String namespace) -> {
193+
if (systemID != null && systemID.startsWith("file:")) {
194+
return pomFile.resolveSibling(systemID.substring("file:".length()))
195+
.toFile();
196+
}
197+
return null;
198+
};
199+
factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
200+
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
201+
factory.setProperty(WstxInputProperties.P_TREAT_CHAR_REFS_AS_ENTS, false);
202+
factory.setProperty(WstxInputProperties.P_ENTITY_RESOLVER, resolver);
203+
factory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, null);
189204
XMLStreamReader reader = factory.createXMLStreamReader(input);
190205
reader = new RawToConsumerPomXMLFilterFactory(new DefaultBuildPomXMLFilterFactory(context, true))
191206
.get(reader, pomFile);

maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.inject.Singleton;
2424
import javax.xml.stream.Location;
2525
import javax.xml.stream.XMLInputFactory;
26+
import javax.xml.stream.XMLResolver;
2627
import javax.xml.stream.XMLStreamException;
2728
import javax.xml.stream.XMLStreamReader;
2829

@@ -35,6 +36,7 @@
3536
import java.util.Map;
3637
import java.util.Objects;
3738

39+
import com.ctc.wstx.api.WstxInputProperties;
3840
import org.apache.maven.model.InputSource;
3941
import org.apache.maven.model.Model;
4042
import org.apache.maven.model.building.ModelSourceTransformer;
@@ -104,16 +106,26 @@ private TransformerContext getTransformerContext(Map<String, ?> options) {
104106

105107
private Model read(InputStream input, Path pomFile, Map<String, ?> options) throws IOException {
106108
try {
109+
InputSource source = getSource(options);
110+
boolean strict = isStrict(options);
107111
XMLInputFactory factory = new com.ctc.wstx.stax.WstxInputFactory();
108-
factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
112+
XMLResolver resolver = (String publicID, String systemID, String baseURI, String namespace) -> {
113+
if (systemID != null && systemID.startsWith("file:") && pomFile != null) {
114+
return pomFile.resolveSibling(systemID.substring("file:".length()))
115+
.toFile();
116+
}
117+
return null;
118+
};
119+
factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
120+
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
121+
factory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, resolver);
122+
factory.setProperty(WstxInputProperties.P_ENTITY_RESOLVER, resolver);
109123
XMLStreamReader parser = factory.createXMLStreamReader(input);
110124

111125
TransformerContext context = getTransformerContext(options);
112126
XMLStreamReader transformingParser =
113127
context != null ? transformer.transform(parser, pomFile, context) : parser;
114128

115-
InputSource source = getSource(options);
116-
boolean strict = isStrict(options);
117129
if (source != null) {
118130
return readModelEx(transformingParser, source, strict);
119131
} else {

maven-model-transform/src/main/java/org/apache/maven/model/transform/stax/BufferingParser.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
import java.util.Objects;
3131
import java.util.regex.Pattern;
3232

33+
import org.codehaus.stax2.DTDInfo;
3334
import org.codehaus.stax2.XMLStreamReader2;
35+
import org.codehaus.stax2.validation.DTDValidationSchema;
3436

35-
public class BufferingParser implements XMLStreamReader {
37+
public class BufferingParser implements XMLStreamReader, DTDInfo {
3638

3739
private static final Pattern WHITESPACE_REGEX = Pattern.compile("[ \r\t\n]+");
3840

@@ -90,6 +92,8 @@ public String toString() {
9092
case COMMENT:
9193
case SPACE:
9294
return "Event{event=" + TYPES[event] + ", text='" + text + "'}";
95+
case DTD:
96+
return "Event{event=" + TYPES[event] + ", text='" + text + "'}";
9397
case START_ELEMENT:
9498
return "Event{" + "event=START_TAG"
9599
+ ", name='"
@@ -396,6 +400,36 @@ public int getEventType() {
396400
return current != null ? current.event : delegate.getEventType();
397401
}
398402

403+
@Override
404+
public Object getProcessedDTD() {
405+
return delegate instanceof DTDInfo ? ((DTDInfo) delegate).getProcessedDTD() : null;
406+
}
407+
408+
@Override
409+
public String getDTDRootName() {
410+
return delegate instanceof DTDInfo ? ((DTDInfo) delegate).getDTDRootName() : null;
411+
}
412+
413+
@Override
414+
public String getDTDSystemId() {
415+
return delegate instanceof DTDInfo ? ((DTDInfo) delegate).getDTDSystemId() : null;
416+
}
417+
418+
@Override
419+
public String getDTDPublicId() {
420+
return delegate instanceof DTDInfo ? ((DTDInfo) delegate).getDTDPublicId() : null;
421+
}
422+
423+
@Override
424+
public String getDTDInternalSubset() {
425+
return delegate instanceof DTDInfo ? ((DTDInfo) delegate).getDTDInternalSubset() : null;
426+
}
427+
428+
@Override
429+
public DTDValidationSchema getProcessedDTDSchema() {
430+
return delegate instanceof DTDInfo ? ((DTDInfo) delegate).getProcessedDTDSchema() : null;
431+
}
432+
399433
@Override
400434
public int next() throws XMLStreamException {
401435
while (true) {
@@ -476,13 +510,20 @@ protected Event bufferEvent() throws XMLStreamException {
476510
event.name = pp.getLocalName();
477511
event.namespace = pp.getNamespaceURI();
478512
event.prefix = pp.getPrefix();
479-
// event.text = pp.getText();
480513
break;
481514
case CHARACTERS:
482515
case COMMENT:
483516
case SPACE:
517+
event.text = pp.getText();
518+
break;
519+
case DTD:
520+
event.text = pp.getText();
521+
break;
484522
case CDATA:
523+
event.text = pp.getText();
524+
break;
485525
case ENTITY_REFERENCE:
526+
event.name = pp.getLocalName();
486527
event.text = pp.getText();
487528
break;
488529
default:

maven-model-transform/src/main/java/org/apache/maven/model/transform/stax/XmlUtils.java

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.apache.maven.model.transform.stax;
2020

21+
import javax.xml.stream.XMLInputFactory;
2122
import javax.xml.stream.XMLOutputFactory;
2223
import javax.xml.stream.XMLStreamConstants;
2324
import javax.xml.stream.XMLStreamException;
@@ -101,20 +102,17 @@ public static void copy(XMLStreamReader reader, XMLStreamWriter writer, boolean
101102
elementCount = countStack.pop();
102103
}
103104
break;
104-
case XMLStreamConstants.CHARACTERS:
105-
writer.writeCharacters(normalize(reader.getText()));
105+
case XMLStreamConstants.PROCESSING_INSTRUCTION:
106+
writer.writeProcessingInstruction(reader.getPITarget(), reader.getPIData());
106107
break;
107-
case XMLStreamConstants.SPACE:
108+
case XMLStreamConstants.CHARACTERS:
108109
writer.writeCharacters(normalize(reader.getText()));
109110
break;
110-
case XMLStreamConstants.ENTITY_REFERENCE:
111-
writer.writeEntityRef(reader.getLocalName());
112-
break;
113111
case XMLStreamConstants.COMMENT:
114112
writer.writeComment(normalize(reader.getText()));
115113
break;
116-
case XMLStreamConstants.CDATA:
117-
writer.writeCData(normalize(reader.getText()));
114+
case XMLStreamConstants.SPACE:
115+
writer.writeCharacters(normalize(reader.getText()));
118116
break;
119117
case XMLStreamConstants.START_DOCUMENT:
120118
if (reader.getVersion() != null) {
@@ -124,6 +122,38 @@ public static void copy(XMLStreamReader reader, XMLStreamWriter writer, boolean
124122
case XMLStreamConstants.END_DOCUMENT:
125123
writer.writeEndDocument();
126124
return;
125+
case XMLStreamConstants.ENTITY_REFERENCE:
126+
if (!((Boolean) reader.getProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES))) {
127+
writer.writeEntityRef(reader.getLocalName());
128+
}
129+
break;
130+
case XMLStreamConstants.ATTRIBUTE:
131+
throw new IllegalStateException();
132+
case XMLStreamConstants.DTD:
133+
/*
134+
if (reader instanceof DTDInfo) {
135+
DTDInfo dtd = (DTDInfo) reader;
136+
String name = dtd.getDTDRootName();
137+
String pid = dtd.getDTDPublicId();
138+
String sid = dtd.getDTDSystemId();
139+
String is = reader.getText();
140+
if (pid != null) {
141+
writer.writeDTD("<!DOCTYPE " + name + " PUBLIC " + pid + " " + sid + " [" + is + "]>");
142+
} else if (sid != null) {
143+
writer.writeDTD("<!DOCTYPE " + name + " SYSTEM " + sid + " [" + is + "]>");
144+
} else {
145+
writer.writeDTD("<!DOCTYPE " + name + " [" + is + "]>");
146+
}
147+
}
148+
*/
149+
break;
150+
case XMLStreamConstants.CDATA:
151+
writer.writeCData(normalize(reader.getText()));
152+
break;
153+
case XMLStreamConstants.NAMESPACE:
154+
case XMLStreamConstants.NOTATION_DECLARATION:
155+
case XMLStreamConstants.ENTITY_DECLARATION:
156+
throw new IllegalStateException();
127157
default:
128158
break;
129159
}

maven-model-transform/src/test/java/org/apache/maven/model/transform/ConsumerPomXMLFilterTest.java

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,25 @@
1818
*/
1919
package org.apache.maven.model.transform;
2020

21+
import javax.xml.stream.XMLInputFactory;
22+
import javax.xml.stream.XMLResolver;
23+
import javax.xml.stream.XMLStreamException;
2124
import javax.xml.stream.XMLStreamReader;
2225

26+
import java.io.ByteArrayOutputStream;
27+
import java.io.StringReader;
2328
import java.nio.file.Path;
2429
import java.nio.file.Paths;
2530
import java.util.Optional;
2631
import java.util.function.BiFunction;
2732
import java.util.function.Function;
2833

34+
import com.ctc.wstx.api.WstxInputProperties;
35+
import com.ctc.wstx.stax.WstxInputFactory;
36+
import org.apache.maven.model.transform.stax.XmlUtils;
2937
import org.junit.jupiter.api.Test;
3038

39+
import static org.junit.jupiter.api.Assertions.assertEquals;
3140
import static org.xmlunit.assertj.XmlAssert.assertThat;
3241

3342
class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests {
@@ -98,7 +107,7 @@ void aggregatorWithCliFriendlyVersion() throws Exception {
98107
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
99108
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
100109
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n"
101-
+ " http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
110+
+ " https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
102111
+ " <modelVersion>4.0.0</modelVersion>\n"
103112
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
104113
+ " <artifactId>parent</artifactId>\n"
@@ -120,7 +129,7 @@ void aggregatorWithCliFriendlyVersion() throws Exception {
120129
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
121130
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
122131
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n"
123-
+ " http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
132+
+ " https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
124133
+ " <modelVersion>4.0.0</modelVersion>\n"
125134
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
126135
+ " <artifactId>parent</artifactId>\n"
@@ -163,7 +172,7 @@ void licenseHeader() throws Exception {
163172
+ "\n"
164173
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
165174
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
166-
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
175+
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
167176
+ " <modelVersion>4.0.0</modelVersion>\n"
168177
+ " <parent>\n"
169178
+ " <groupId>org.apache.maven</groupId>\n"
@@ -226,4 +235,40 @@ void lexicalHandler() throws Exception {
226235
String actual = transform(input);
227236
assertThat(actual).and(expected).areIdentical();
228237
}
238+
239+
@Test
240+
void testWithEntities() throws Exception {
241+
String input = "<!DOCTYPE foo [\n"
242+
+ " <!ENTITY desc SYSTEM \"file:desc.xml\">\n"
243+
+ "]>\n"
244+
+ "<project>"
245+
+ "&desc;"
246+
+ "</project>";
247+
String expected = "<project>" + "<description>foo</description>" + "</project>";
248+
249+
XMLResolver resolver = new XMLResolver() {
250+
@Override
251+
public Object resolveEntity(String publicID, String systemID, String baseURI, String namespace)
252+
throws XMLStreamException {
253+
if ("file:desc.xml".equals(systemID)) {
254+
return "<?xml version='1.0' encoding='UTF-8'?><description>foo</description>";
255+
}
256+
return null;
257+
}
258+
};
259+
WstxInputFactory factory = new WstxInputFactory();
260+
factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
261+
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
262+
factory.setProperty(WstxInputProperties.P_TREAT_CHAR_REFS_AS_ENTS, false);
263+
factory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, resolver);
264+
factory.setProperty(WstxInputProperties.P_ENTITY_RESOLVER, resolver);
265+
XMLStreamReader parser = factory.createXMLStreamReader(new StringReader(input));
266+
XMLStreamReader filter = getFilter(parser);
267+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
268+
XmlUtils.writeDocument(filter, baos);
269+
String actual = baos.toString();
270+
271+
System.out.println(actual);
272+
assertEquals(expected, actual);
273+
}
229274
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.model.transform.stax;
20+
21+
import javax.xml.stream.XMLInputFactory;
22+
import javax.xml.stream.XMLStreamReader;
23+
24+
import java.io.ByteArrayOutputStream;
25+
import java.io.StringReader;
26+
27+
import com.ctc.wstx.api.WstxInputProperties;
28+
import org.codehaus.stax2.XMLInputFactory2;
29+
import org.junit.jupiter.api.Test;
30+
31+
import static org.junit.jupiter.api.Assertions.assertEquals;
32+
33+
public class XmlUtilsTest {
34+
35+
@Test
36+
void testRoundtrip() throws Exception {
37+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<project>"
38+
+ "<?foo?>"
39+
+ "<name>&oelig;</name>"
40+
+ "</project>";
41+
XMLInputFactory2 factory = new com.ctc.wstx.stax.WstxInputFactory();
42+
factory.configureForRoundTripping();
43+
factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
44+
factory.setProperty(WstxInputProperties.P_TREAT_CHAR_REFS_AS_ENTS, false);
45+
XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
46+
ByteArrayOutputStream output = new ByteArrayOutputStream();
47+
XmlUtils.writeDocument(reader, output);
48+
assertEquals(xml, output.toString());
49+
}
50+
}

0 commit comments

Comments
 (0)