I believe that there is a fundamental flaw in the DTDParser
's attribute use parsing. Using various test DTDs, it appears that attributes with #IMPLIED
default declarations are assigned attribute use USE_NORMAL
. Vice versa, attributes with a declared default value (and neither #IMPLIED
, #REQUIRED
nor #FIXED
designation) do appear as USE_IMPLIED
.
The following test case, I believe, demonstrates this issue with a very simple DTD:
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import com.sun.xml.dtdparser.DTDParser;
import com.sun.xml.dtdparser.DTDHandlerBase;
import com.sun.xml.dtdparser.DTDEventListener;
import org.junit.Test;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import static org.junit.Assert.assertEquals;
public class AttributeUseTest
{
Map<String, Short> attributes = new HashMap<>();
public AttributeUseTest() throws Exception
{
String dtd =
"""
<!ELEMENT root EMPTY>
<!ATTLIST root
normal CDATA "default"
implied CDATA #IMPLIED
required CDATA #REQUIRED
fixed CDATA #FIXED "default"
>
""";
DTDEventListener handler = new DTDHandlerBase()
{
@Override
public void attributeDecl(String element, String name, String type, String[] enums, short use, String defaultValue) throws SAXException
{
attributes.put(name, use);
}
};
InputSource input = new InputSource(new StringReader(dtd));
DTDParser parser = new DTDParser();
parser.setDtdHandler(handler);
parser.parse(input);
}
@Test
public void testNormalAttribute()
{
assertEquals(DTDEventListener.USE_NORMAL, attributes.get("normal").shortValue());
}
@Test
public void testImpliedAttribute()
{
assertEquals(DTDEventListener.USE_IMPLIED, attributes.get("implied").shortValue());
}
@Test
public void testRequiredAttribute()
{
assertEquals(DTDEventListener.USE_REQUIRED, attributes.get("required").shortValue());
}
@Test
public void testFixedAttribute()
{
assertEquals(DTDEventListener.USE_FIXED, attributes.get("fixed").shortValue());
}
}
The test cases for #REQUIRED
and #FIXED
pass as expected, but the two other test cases both fail.
As I ran this in the debugger, I noticed some strange looking code in DTDParser.maybeAttlistDecl()
:
|
} else if (!peek("#IMPLIED")) { |
|
attributeUse = DTDEventListener.USE_IMPLIED; |
Intuitively, the negation !peek("#IMPLIED")
(instead of just peek("#IMPLIED")
) seems incorrect here, since it would select USE_IMPLIED
specifically if #IMPLIED
was not present. This explains why attributes without any default declarations end up being recognized as implied.
Further down, USE_NORMAL
is assigned as a fallback value:
|
} else { |
|
// TODO: this looks like an fatal error. |
|
attributeUse = DTDEventListener.USE_NORMAL; |
This explains why #IMPLIED
attributes (which do not get recognized further up, due to the negation) are designated as USE_NORMAL
.