Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for LexicalHandler.startEntity and endEntity #210

Merged
merged 2 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)*>
Loading