Skip to content

Commit

Permalink
Add support for LexicalHandler.startEntity and endEntity (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippn authored Aug 23, 2024
1 parent 45bf1b8 commit a2ac3ef
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 1 deletion.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ SAX2 and Stax2 APIs
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
5 changes: 5 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,8 @@ Stanimir Stamenkov (@stanio)

* Reported, provided fix for #204: Non-conformant `XMLEventFactory.setLocation(null)`
(7.0.0)

Philipp Nanz (@philippn)

* Contributed #209: SAXParser: Add support for `LexicalHandler.startEntity()` and `.endEntity()`
(7.1.0)
5 changes: 5 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Project: woodstox
=== Releases ===
------------------------------------------------------------------------

Not yet released:

#209: SAXParser: Add support for `LexicalHandler.startEntity()` and `.endEntity()`
(contributed by Philipp N)

7.0.0 (21-Jun-2024)

#134: Increase JDK baseline to Java 8
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/com/ctc/wstx/sax/WstxSAXParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,12 @@ private final void fireAuxEvent(int type, boolean inTree)
/* Only occurs in non-entity-expanding mode; so effectively
* we are skipping the entity?
*/
if (mContentHandler != null) {
if (mLexicalHandler != null) {
String text = mScanner.getText();
mLexicalHandler.startEntity(mScanner.getLocalName());
mContentHandler.characters(text.toCharArray(), 0, text.length());
mLexicalHandler.endEntity(mScanner.getLocalName());
} else if (mContentHandler != null) {
mContentHandler.skippedEntity(mScanner.getLocalName());
}
break;
Expand Down
126 changes: 126 additions & 0 deletions src/test/java/wstxtest/sax/TestLexicalHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package wstxtest.sax;

import com.ctc.wstx.sax.WstxSAXParserFactory;
import com.ctc.wstx.stax.WstxInputFactory;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DefaultHandler2;
import wstxtest.BaseWstxTest;

import javax.xml.parsers.SAXParser;
import java.net.URL;

/**
* Unit tests that verify handling of entity references during parsing.
*/
public class TestLexicalHandler extends BaseWstxTest {

public void testReplaceEntityRefs() throws Exception {
WstxSAXParserFactory spf = new WstxSAXParserFactory();
SAXParser sp = spf.newSAXParser();
EventListener listener = Mockito.mock(EventListener.class);
sp.parse(getInputSource("eyephone.xml"), new EventListenerHandler(listener));

InOrder orderVerifier = Mockito.inOrder(listener);
orderVerifier.verify(listener).startElement("prodname");
orderVerifier.verify(listener).characters("eyePhone© 2.0");
orderVerifier.verify(listener).endElement("prodname");
}

public void testWithoutReplaceEntityRefs() throws Exception {
WstxInputFactory staxFactory = new WstxInputFactory();
staxFactory.getConfig().doReplaceEntityRefs(false);
WstxSAXParserFactory spf = new WstxSAXParserFactory(staxFactory);
SAXParser sp = spf.newSAXParser();
EventListener listener = Mockito.mock(EventListener.class);
sp.parse(getInputSource("eyephone.xml"), new EventListenerHandler(listener));

InOrder orderVerifier = Mockito.inOrder(listener);
orderVerifier.verify(listener).startElement("prodname");
orderVerifier.verify(listener).characters("eyePhone");
orderVerifier.verify(listener).skippedEntity("copyright");
orderVerifier.verify(listener).characters(" 2.0");
orderVerifier.verify(listener).endElement("prodname");
}

public void testWithoutReplaceEntityRefsAndWithLexicalHandler() throws Exception {
WstxInputFactory staxFactory = new WstxInputFactory();
staxFactory.getConfig().doReplaceEntityRefs(false);
WstxSAXParserFactory spf = new WstxSAXParserFactory(staxFactory);
SAXParser sp = spf.newSAXParser();
EventListener listener = Mockito.mock(EventListener.class);
sp.setProperty("http://xml.org/sax/properties/lexical-handler", new EventListenerHandler(listener));
sp.parse(getInputSource("eyephone.xml"), new EventListenerHandler(listener));

InOrder orderVerifier = Mockito.inOrder(listener);
orderVerifier.verify(listener).startElement("prodname");
orderVerifier.verify(listener).characters("eyePhone");
orderVerifier.verify(listener).startEntity("copyright");
orderVerifier.verify(listener).characters("©");
orderVerifier.verify(listener).endEntity("copyright");
orderVerifier.verify(listener).characters(" 2.0");
orderVerifier.verify(listener).endElement("prodname");
}

private InputSource getInputSource(String resource) {
URL url = TestLexicalHandler.class.getResource(resource);
return new InputSource(url.toString());
}

private static class EventListenerHandler extends DefaultHandler2 {

private final EventListener eventListener;

private EventListenerHandler(EventListener eventListener) {
this.eventListener = eventListener;
}

@Override
public void startEntity(String name) throws SAXException {
eventListener.startEntity(name);
}

@Override
public void endEntity(String name) throws SAXException {
eventListener.endEntity(name);
}

@Override
public void skippedEntity(String name) throws SAXException {
eventListener.skippedEntity(name);
}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
eventListener.characters(String.copyValueOf(ch, start, length));
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
eventListener.startElement(qName);
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
eventListener.endElement(qName);
}
}

private interface EventListener {

void startElement(String name);

void endElement(String name);

void startEntity(String name);

void endEntity(String name);

void skippedEntity(String name);

void characters(String content);
}
}
18 changes: 18 additions & 0 deletions src/test/resources/wstxtest/sax/eyephone.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE product-info PUBLIC "-//Woodstox//DTD Sample V1.0//EN" "productinfo_v1.dtd">
<product-info>
<meta>
<prodname>eyePhone&copyright; 2.0</prodname>
<company>MomCorp</company>
<prodtype>mobile phone</prodtype>
<source>http://theinfosphere.org/EyePhone</source>
</meta>
<!-- This is a comment -->
<description>
<para>The <b>eyePhone</b> is a new type of mobile phones released by MomCorp and one of the
products of Mom Store in 3010. It is called an eyePhone because it is located in the
eyes of its users and displays a screen in front of them. The eyePhone has a lot of
applications, such as Twit and can even make phone calls. Soon, everyone became addicted
to their new eyePhones, and Mom activated a Twit-worm so that she could have between 1
and 2 million zombies buying the machine's "upgraded" version, the eyePhone 2.0.</para>
</description>
</product-info>
23 changes: 23 additions & 0 deletions src/test/resources/wstxtest/sax/productinfo_v1.dtd
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml encoding="UTF-8"?>

<!-- The DTD for the sample documents -->

<!ENTITY copyright "&#169;">

<!ELEMENT product-info (meta,description)>

<!ELEMENT meta (prodname,company,prodtype,source)>

<!ELEMENT description (para)>

<!ELEMENT prodname (#PCDATA)>

<!ELEMENT company (#PCDATA)>

<!ELEMENT prodtype (#PCDATA)>

<!ELEMENT source (#PCDATA)>

<!ELEMENT para (#PCDATA|b)*>

<!ELEMENT b (#PCDATA)*>

0 comments on commit a2ac3ef

Please sign in to comment.