diff --git a/pmd/pom.xml b/pmd/pom.xml
index 12bdf4844c..93221522a7 100644
--- a/pmd/pom.xml
+++ b/pmd/pom.xml
@@ -634,7 +634,7 @@
xercesImpl
2.9.1
jar
- runtime
+ compile
net.java.dev.javacc
diff --git a/pmd/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java b/pmd/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java
index 13381244ca..6f6ab3b4f9 100644
--- a/pmd/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java
+++ b/pmd/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java
@@ -17,10 +17,12 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.Stack;
import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.ast.RootNode;
@@ -28,12 +30,27 @@ import net.sourceforge.pmd.lang.ast.xpath.Attribute;
import net.sourceforge.pmd.lang.xml.XmlParserOptions;
import net.sourceforge.pmd.util.CompoundIterator;
+import org.apache.xerces.dom.CoreDocumentImpl;
+import org.apache.xerces.dom.EntityImpl;
+import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.Entity;
+import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.DefaultHandler2;
public class XmlParser {
protected final XmlParserOptions parserOptions;
@@ -46,20 +63,23 @@ public class XmlParser {
protected Document parseDocument(Reader reader) throws ParseException {
nodeCache.clear();
try {
- DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
- documentBuilderFactory.setCoalescing(parserOptions.isCoalescing());
- documentBuilderFactory.setExpandEntityReferences(parserOptions.isExpandEntityReferences());
- documentBuilderFactory.setIgnoringComments(parserOptions.isIgnoringComments());
- documentBuilderFactory.setIgnoringElementContentWhitespace(parserOptions.isIgnoringElementContentWhitespace());
- documentBuilderFactory.setNamespaceAware(parserOptions.isNamespaceAware());
- documentBuilderFactory.setValidating(parserOptions.isValidating());
- documentBuilderFactory.setXIncludeAware(parserOptions.isXincludeAware());
-
+ SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
+ saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ saxParserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ saxParserFactory.setNamespaceAware(parserOptions.isNamespaceAware());
+ saxParserFactory.setValidating(parserOptions.isValidating());
+ saxParserFactory.setXIncludeAware(parserOptions.isXincludeAware());
+ SAXParser saxParser = saxParserFactory.newSAXParser();
- DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
- documentBuilder.setEntityResolver(parserOptions.getEntityResolver());
- Document document = documentBuilder.parse(new InputSource(reader));
- return document;
+ LineNumberAwareSaxHandler handler = new LineNumberAwareSaxHandler(parserOptions);
+ XMLReader xmlReader = saxParser.getXMLReader();
+ xmlReader.setContentHandler(handler);
+ xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
+ xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", handler);
+ xmlReader.setEntityResolver(parserOptions.getEntityResolver());
+
+ xmlReader.parse(new InputSource(reader));
+ return handler.getDocument();
} catch (ParserConfigurationException e) {
throw new ParseException(e);
} catch (SAXException e) {
@@ -69,6 +89,224 @@ public class XmlParser {
}
}
+ /**
+ * SAX Handler to build a DOM Document with line numbers.
+ * @see http://eyalsch.wordpress.com/2010/11/30/xml-dom-2/
+ */
+ private static class LineNumberAwareSaxHandler extends DefaultHandler2 {
+ public static final String BEGIN_LINE = "pmd:beginLine";
+ public static final String BEGIN_COLUMN = "pmd:beginColumn";
+ public static final String END_LINE = "pmd:endLine";
+ public static final String END_COLUMN = "pmd:endColumn";
+
+ private Stack nodeStack = new Stack();
+ private StringBuilder text = new StringBuilder();
+ private int beginLineText = -1;
+ private int beginColumnText = -1;
+ private Locator locator;
+ private final DocumentBuilder documentBuilder;
+ private final Document document;
+ private boolean cdataEnded = false;
+
+ private boolean coalescing;
+ private boolean expandEntityReferences;
+ private boolean ignoringComments;
+ private boolean ignoringElementContentWhitespace;
+ private boolean namespaceAware;
+
+ public LineNumberAwareSaxHandler(XmlParserOptions options) throws ParserConfigurationException {
+ // uses xerces directly, so that we can build a DTD / entities section
+ this.documentBuilder = new DocumentBuilderFactoryImpl().newDocumentBuilder();
+
+ this.document = this.documentBuilder.newDocument();
+ this.coalescing = options.isCoalescing();
+ this.expandEntityReferences = options.isExpandEntityReferences();
+ this.ignoringComments = options.isIgnoringComments();
+ this.ignoringElementContentWhitespace = options.isIgnoringElementContentWhitespace();
+ this.namespaceAware = options.isNamespaceAware();
+ }
+
+ public Document getDocument() {
+ return document;
+ }
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ addTextIfNeeded(false);
+
+ Element element;
+ if (namespaceAware) {
+ element = document.createElementNS(uri, qName);
+ } else {
+ element = document.createElement(qName);
+ }
+
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String attQName = attributes.getQName(i);
+ String attNamespaceURI = attributes.getURI(i);
+ String attValue = attributes.getValue(i);
+ Attr a;
+ if (namespaceAware) {
+ a = document.createAttributeNS(attNamespaceURI, attQName);
+ element.setAttributeNodeNS(a);
+ } else {
+ a = document.createAttribute(attQName);
+ element.setAttributeNode(a);
+ }
+ a.setValue(attValue);
+ }
+
+ element.setUserData(BEGIN_LINE, locator.getLineNumber(), null);
+ element.setUserData(BEGIN_COLUMN, locator.getColumnNumber(), null);
+
+ nodeStack.push(element);
+ }
+ private void addTextIfNeeded(boolean alwaysAdd) {
+ if (text.length() > 0) {
+ addTextNode(text.toString(), cdataEnded || alwaysAdd);
+ text.setLength(0);
+ cdataEnded = false;
+ }
+ }
+ private void addTextNode(String s, boolean alwaysAdd) {
+ if (alwaysAdd || !ignoringElementContentWhitespace || s.trim().length() > 0) {
+ Text textNode = document.createTextNode(s);
+ textNode.setUserData(BEGIN_LINE, beginLineText, null);
+ textNode.setUserData(BEGIN_COLUMN, beginColumnText, null);
+ textNode.setUserData(END_LINE, locator.getLineNumber(), null);
+ textNode.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ appendChild(textNode);
+ }
+ }
+ @Override
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ this.characters(ch, start, length);
+ }
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (text.length() == 0) {
+ beginLineText = locator.getLineNumber();
+ beginColumnText = locator.getColumnNumber();
+ }
+ text.append(ch, start, length);
+ }
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ addTextIfNeeded(false);
+ Node element = nodeStack.pop();
+ element.setUserData(END_LINE, locator.getLineNumber(), null);
+ element.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ appendChild(element);
+ }
+ @Override
+ public void startDocument() throws SAXException {
+ document.setUserData(BEGIN_LINE, locator.getLineNumber(), null);
+ document.setUserData(BEGIN_COLUMN, locator.getColumnNumber(), null);
+ }
+ @Override
+ public void endDocument() throws SAXException {
+ addTextIfNeeded(false);
+ document.setUserData(END_LINE, locator.getLineNumber(), null);
+ document.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ }
+ @Override
+ public void startCDATA() throws SAXException {
+ if (!coalescing) {
+ addTextIfNeeded(true);
+ }
+ }
+ @Override
+ public void endCDATA() throws SAXException {
+ if (!coalescing) {
+ CDATASection cdataSection = document.createCDATASection(text.toString());
+ cdataSection.setUserData(BEGIN_LINE, beginLineText, null);
+ cdataSection.setUserData(BEGIN_COLUMN, beginColumnText, null);
+ cdataSection.setUserData(END_LINE, locator.getLineNumber(), null);
+ cdataSection.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ appendChild(cdataSection);
+ text.setLength(0);
+ cdataEnded = true;
+ }
+ }
+ @Override
+ public void comment(char[] ch, int start, int length) throws SAXException {
+ if (!ignoringComments) {
+ addTextIfNeeded(false);
+ Comment comment = document.createComment(new String(ch, start, length));
+ comment.setUserData(BEGIN_LINE, locator.getLineNumber(), null);
+ comment.setUserData(BEGIN_COLUMN, locator.getColumnNumber(), null);
+ comment.setUserData(END_LINE, locator.getLineNumber(), null);
+ comment.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ appendChild(comment);
+ }
+ }
+ @Override
+ public void startDTD(String name, String publicId, String systemId) throws SAXException {
+ DocumentType docType = documentBuilder
+ .getDOMImplementation()
+ .createDocumentType(name, publicId, systemId);
+ docType.setUserData(BEGIN_LINE, locator.getLineNumber(), null);
+ docType.setUserData(BEGIN_COLUMN, locator.getColumnNumber(), null);
+ document.appendChild(docType);
+ }
+ @Override
+ public void startEntity(String name) throws SAXException {
+ if (!expandEntityReferences) {
+ addTextIfNeeded(false);
+ }
+ }
+ @Override
+ public void endEntity(String name) throws SAXException {
+ if (!expandEntityReferences) {
+ EntityReference entity = document.createEntityReference(name);
+ entity.setUserData(BEGIN_LINE, beginLineText, null);
+ entity.setUserData(BEGIN_COLUMN, beginColumnText, null);
+ entity.setUserData(END_LINE, locator.getLineNumber(), null);
+ entity.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ appendChild(entity);
+ text.setLength(0); // throw the expanded entity text away
+ }
+ }
+ @Override
+ public void endDTD() throws SAXException {
+ DocumentType doctype = document.getDoctype();
+ doctype.setUserData(END_LINE, locator.getLineNumber(), null);
+ doctype.setUserData(END_COLUMN, locator.getColumnNumber(), null);
+ }
+ @Override
+ public void internalEntityDecl(String name, String value) throws SAXException {
+ Entity entity = new ChangeableEntity(document, name);
+ entity.appendChild(document.createTextNode(value));
+
+ NamedNodeMap entities = document.getDoctype().getEntities();
+ entities.setNamedItem(entity);
+ }
+ @Override
+ public void processingInstruction(String target, String data) throws SAXException {
+ ProcessingInstruction pi = document.createProcessingInstruction(target, data);
+ appendChild(pi);
+ }
+ private void appendChild(Node node) {
+ if (nodeStack.isEmpty()) {
+ document.appendChild(node);
+ } else {
+ nodeStack.peek().appendChild(node);
+ }
+ }
+ private static class ChangeableEntity extends EntityImpl {
+ public ChangeableEntity(Document document, String name) {
+ super((CoreDocumentImpl)document, name);
+ flags = (short) (flags & ~READONLY); // make it changeable again, so that we can add a text node as child
+ }
+ }
+ }
+
+
public XmlNode parse(Reader reader) {
Document document = parseDocument(reader);
return createProxy(document);
@@ -165,13 +403,13 @@ public class XmlParser {
return new CompoundIterator(iterators.toArray(new Iterator[iterators.size()]));
} else if ("getBeginLine".equals(method.getName())) {
- return Integer.valueOf(-1);
+ return getUserData(LineNumberAwareSaxHandler.BEGIN_LINE);
} else if ("getBeginColumn".equals(method.getName())) {
- return Integer.valueOf(-1);
+ return getUserData(LineNumberAwareSaxHandler.BEGIN_COLUMN);
} else if ("getEndLine".equals(method.getName())) {
- return Integer.valueOf(-1);
+ return getUserData(LineNumberAwareSaxHandler.END_LINE);
} else if ("getEndColumn".equals(method.getName())) {
- return Integer.valueOf(-1);
+ return getUserData(LineNumberAwareSaxHandler.END_COLUMN);
} else if ("getNode".equals(method.getName())) {
return node;
} else if ("getUserData".equals(method.getName())) {
@@ -179,6 +417,8 @@ public class XmlParser {
} else if ("setUserData".equals(method.getName())) {
userData = args[0];
return null;
+ } else if ("isFindBoundary".equals(method.getName())) {
+ return false;
}
throw new UnsupportedOperationException("Method not supported for XmlNode: " + method);
}
@@ -193,5 +433,12 @@ public class XmlParser {
return result;
}
}
+
+ private Integer getUserData(String key) {
+ if (node.getUserData(key) != null) {
+ return (Integer)node.getUserData(key);
+ }
+ return Integer.valueOf(-1);
+ }
}
}
diff --git a/pmd/src/site/markdown/changelog.md b/pmd/src/site/markdown/changelog.md
index 082aba77ed..06f039699d 100644
--- a/pmd/src/site/markdown/changelog.md
+++ b/pmd/src/site/markdown/changelog.md
@@ -42,6 +42,8 @@
* Fixed [bug 881]: private final without setter is flagged
* Fixed [bug 1059]: Change rule name "Use Singleton" should be "Use Utility class"
* Fixed [bug 1106]: PMD 5.0.4 fails with NPE on parsing java enum with inner class instance creation
+* Fixed [bug 1045]: //NOPMD not working (or not implemented) with ECMAscript
+* Fixed [bug 1054]: XML Rules ever report a line -1 and not the line/column where the error occurs
* Fixed [bug 1115]: commentRequiredRule in pmd 5.1 is not working properly
* Fixed [bug 1120]: equalsnull false positive
* Fixed [bug 1121]: NullPointerException when invoking XPathCLI
@@ -58,7 +60,6 @@
* Fixed [bug 1141]: ECMAScript: getFinallyBlock() is buggy.
* Fixed [bug 1142]: ECMAScript: getCatchClause() is buggy.
* Fixed [bug 1144]: CPD encoding argument has no effect
-* Fixed [bug 1045]: //NOPMD not working (or not implemented) with ECMAscript
* Fixed [bug 1146]: UseArrayListInsteadOfVector false positive when using own Vector class
* Fixed [bug 1147]: EmptyMethodInAbstractClassShouldBeAbstract false positives
* Fixed [bug 1150]: "EmptyExpression" for valid statements!
@@ -69,6 +70,8 @@
[bug 881]: https://sourceforge.net/p/pmd/bugs/881
[bug 1059]: https://sourceforge.net/p/pmd/bugs/1059
+[bug 1045]: https://sourceforge.net/p/pmd/bugs/1045
+[bug 1054]: https://sourceforge.net/p/pmd/bugs/1054
[bug 1106]: https://sourceforge.net/p/pmd/bugs/1106
[bug 1115]: https://sourceforge.net/p/pmd/bugs/1115
[bug 1120]: https://sourceforge.net/p/pmd/bugs/1120
@@ -86,7 +89,6 @@
[bug 1141]: https://sourceforge.net/p/pmd/bugs/1141
[bug 1142]: https://sourceforge.net/p/pmd/bugs/1142
[bug 1144]: https://sourceforge.net/p/pmd/bugs/1144
-[bug 1045]: https://sourceforge.net/p/pmd/bugs/1045
[bug 1146]: https://sourceforge.net/p/pmd/bugs/1146
[bug 1147]: https://sourceforge.net/p/pmd/bugs/1147
[bug 1150]: https://sourceforge.net/p/pmd/bugs/1150
diff --git a/pmd/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java b/pmd/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java
new file mode 100644
index 0000000000..b15e845127
--- /dev/null
+++ b/pmd/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java
@@ -0,0 +1,423 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+package net.sourceforge.pmd.lang.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.util.Iterator;
+
+import net.sourceforge.pmd.lang.Language;
+import net.sourceforge.pmd.lang.LanguageVersionHandler;
+import net.sourceforge.pmd.lang.Parser;
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.ast.xpath.Attribute;
+import net.sourceforge.pmd.lang.xml.ast.XmlNode;
+import net.sourceforge.pmd.lang.xml.ast.XmlParser;
+import net.sourceforge.pmd.util.StringUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit test for the {@link XmlParser}.
+ */
+public class XmlParserTest {
+
+ private static final String XML_TEST =
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "]\n" +
+ ">\n" +
+ "\n" +
+ " \n" +
+ " entity: &pmd;\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+
+ private static final String XML_NAMESPACE_TEST =
+ "\n" +
+ "\n" +
+ " \n" +
+ " entity: &\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+
+ private static final String XML_INVALID_WITH_DTD =
+ "\n" +
+ "\n" +
+ "\n" +
+ "]\n" +
+ ">\n" +
+ "\n" +
+ " \n" +
+ "";
+
+ /**
+ * See bug #1054:
+ * XML Rules ever report a line -1 and not the line/column where the error occurs
+ * @throws Exception any error
+ */
+ @Test
+ public void testLineNumbers() throws Exception {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ Parser parser = xmlVersionHandler.getParser(xmlVersionHandler.getDefaultParserOptions());
+ Node document = parser.parse(null, new StringReader(XML_TEST));
+
+ assertNode(document, "document", 2);
+ assertLineNumbers(document, 1, 1, 19, 15);
+ Node dtdElement = document.jjtGetChild(0);
+ assertNode(dtdElement, "rootElement", 0);
+ assertLineNumbers(dtdElement, 3, 1, 10, 1);
+ Node rootElement = document.jjtGetChild(1);
+ assertNode(rootElement, "rootElement", 7);
+ assertLineNumbers(rootElement, 12, 14, 19, 15);
+ assertTextNode(rootElement.jjtGetChild(0), "\\n ");
+ assertLineNumbers(rootElement.jjtGetChild(0), 13, 5, 13, 30);
+ assertNode(rootElement.jjtGetChild(1), "comment", 0);
+ assertLineNumbers(rootElement.jjtGetChild(1), 13, 30, 13, 30);
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ assertLineNumbers(rootElement.jjtGetChild(2), 14, 5, 14, 22);
+ Node child1 = rootElement.jjtGetChild(3);
+ assertNode(child1, "child1", 1, "test", "1");
+ assertLineNumbers(child1, 14, 22, 15, 14);
+ assertTextNode(child1.jjtGetChild(0), "entity: Copyright: PMD\\n ");
+ assertLineNumbers(child1.jjtGetChild(0), 14, 30, 15, 14);
+ assertTextNode(rootElement.jjtGetChild(4), "\\n ");
+ assertLineNumbers(rootElement.jjtGetChild(4), 16, 5, 16, 13);
+ Node child2 = rootElement.jjtGetChild(5);
+ assertNode(child2, "child2", 3);
+ assertLineNumbers(child2, 16, 13, 18, 14);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertLineNumbers(child2.jjtGetChild(0), 17, 7, 17, 16);
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertLineNumbers(child2.jjtGetChild(1), 17, 33, 17, 34);
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ assertLineNumbers(child2.jjtGetChild(2), 18, 5, 18, 14);
+ assertTextNode(rootElement.jjtGetChild(6), "\\n");
+ assertLineNumbers(rootElement.jjtGetChild(6), 19, 1, 19, 15);
+ }
+
+ /**
+ * Verifies the default parsing behavior of the XML parser.
+ */
+ @Test
+ public void testDefaultParsing() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ Parser parser = xmlVersionHandler.getParser(xmlVersionHandler.getDefaultParserOptions());
+ Node document = parser.parse(null, new StringReader(XML_TEST));
+
+ assertNode(document, "document", 2);
+ Node dtdElement = document.jjtGetChild(0);
+ assertNode(dtdElement, "rootElement", 0);
+ Node rootElement = document.jjtGetChild(1);
+ assertNode(rootElement, "rootElement", 7);
+ assertTextNode(rootElement.jjtGetChild(0), "\\n ");
+ assertNode(rootElement.jjtGetChild(1), "comment", 0);
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ Node child1 = rootElement.jjtGetChild(3);
+ assertNode(child1, "child1", 1, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: Copyright: PMD\\n ");
+ assertTextNode(rootElement.jjtGetChild(4), "\\n ");
+ Node child2 = rootElement.jjtGetChild(5);
+ assertNode(child2, "child2", 3);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ assertTextNode(rootElement.jjtGetChild(6), "\\n");
+ }
+
+ /**
+ * Verifies the parsing behavior of the XML parser with coalescing enabled.
+ */
+ @Test
+ public void testParsingCoalescingEnabled() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ XmlParserOptions parserOptions = new XmlParserOptions();
+ parserOptions.setCoalescing(true);
+ Parser parser = xmlVersionHandler.getParser(parserOptions);
+ Node document = parser.parse(null, new StringReader(XML_TEST));
+
+ assertNode(document, "document", 2);
+ Node dtdElement = document.jjtGetChild(0);
+ assertNode(dtdElement, "rootElement", 0);
+ Node rootElement = document.jjtGetChild(1);
+ assertNode(rootElement, "rootElement", 7);
+ assertTextNode(rootElement.jjtGetChild(0), "\\n ");
+ assertNode(rootElement.jjtGetChild(1), "comment", 0);
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ Node child1 = rootElement.jjtGetChild(3);
+ assertNode(child1, "child1", 1, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: Copyright: PMD\\n ");
+ assertTextNode(rootElement.jjtGetChild(4), "\\n ");
+ Node child2 = rootElement.jjtGetChild(5);
+ assertNode(child2, "child2", 1);
+ assertTextNode(child2.jjtGetChild(0), "\\n cdata section \\n ");
+ assertTextNode(rootElement.jjtGetChild(6), "\\n");
+ }
+
+ /**
+ * Verifies the parsing behavior of the XML parser if entities are not expanded.
+ */
+ @Test
+ public void testParsingDoNotExpandEntities() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ XmlParserOptions parserOptions = new XmlParserOptions();
+ parserOptions.setExpandEntityReferences(false);
+ Parser parser = xmlVersionHandler.getParser(parserOptions);
+ Node document = parser.parse(null, new StringReader(XML_TEST));
+
+ assertNode(document, "document", 2);
+ Node dtdElement = document.jjtGetChild(0);
+ assertNode(dtdElement, "rootElement", 0);
+ Node rootElement = document.jjtGetChild(1);
+ assertNode(rootElement, "rootElement", 7);
+ assertTextNode(rootElement.jjtGetChild(0), "\\n ");
+ assertNode(rootElement.jjtGetChild(1), "comment", 0);
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ Node child1 = rootElement.jjtGetChild(3);
+ assertNode(child1, "child1", 3, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: ");
+ assertNode(child1.jjtGetChild(1), "pmd", 1);
+ assertTextNode(child1.jjtGetChild(1).jjtGetChild(0), "Copyright: PMD");
+ assertTextNode(child1.jjtGetChild(2), "\\n ");
+ assertTextNode(rootElement.jjtGetChild(4), "\\n ");
+ Node child2 = rootElement.jjtGetChild(5);
+ assertNode(child2, "child2", 3);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ assertTextNode(rootElement.jjtGetChild(6), "\\n");
+ }
+
+ /**
+ * Verifies the parsing behavior of the XML parser if ignoring comments.
+ */
+ @Test
+ public void testParsingIgnoreComments() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ XmlParserOptions parserOptions = new XmlParserOptions();
+ parserOptions.setIgnoringComments(true);
+ Parser parser = xmlVersionHandler.getParser(parserOptions);
+ Node document = parser.parse(null, new StringReader(XML_TEST));
+
+ assertNode(document, "document", 2);
+ Node dtdElement = document.jjtGetChild(0);
+ assertNode(dtdElement, "rootElement", 0);
+ Node rootElement = document.jjtGetChild(1);
+ assertNode(rootElement, "rootElement", 5);
+ assertTextNode(rootElement.jjtGetChild(0), "\\n \\n ");
+ Node child1 = rootElement.jjtGetChild(1);
+ assertNode(child1, "child1", 1, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: Copyright: PMD\\n ");
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ Node child2 = rootElement.jjtGetChild(3);
+ assertNode(child2, "child2", 3);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ assertTextNode(rootElement.jjtGetChild(4), "\\n");
+ }
+
+ /**
+ * Verifies the parsing behavior of the XML parser if ignoring whitespaces in elements.
+ */
+ @Test
+ public void testParsingIgnoreElementContentWhitespace() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ XmlParserOptions parserOptions = new XmlParserOptions();
+ parserOptions.setIgnoringElementContentWhitespace(true);
+ Parser parser = xmlVersionHandler.getParser(parserOptions);
+ Node document = parser.parse(null, new StringReader(XML_TEST));
+
+ assertNode(document, "document", 2);
+ Node dtdElement = document.jjtGetChild(0);
+ assertNode(dtdElement, "rootElement", 0);
+ Node rootElement = document.jjtGetChild(1);
+ assertNode(rootElement, "rootElement", 3);
+ assertNode(rootElement.jjtGetChild(0), "comment", 0);
+ Node child1 = rootElement.jjtGetChild(1);
+ assertNode(child1, "child1", 1, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: Copyright: PMD\\n ");
+ Node child2 = rootElement.jjtGetChild(2);
+ assertNode(child2, "child2", 3);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ }
+
+ /**
+ * Verifies the default parsing behavior of the XML parser with namespaces.
+ */
+ @Test
+ public void testDefaultParsingNamespaces() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ Parser parser = xmlVersionHandler.getParser(xmlVersionHandler.getDefaultParserOptions());
+ Node document = parser.parse(null, new StringReader(XML_NAMESPACE_TEST));
+
+ assertNode(document, "document", 1);
+ Node rootElement = document.jjtGetChild(0);
+ assertNode(rootElement, "pmd:rootElement", 7);
+ Assert.assertEquals("http://pmd.sf.net", ((XmlNode)rootElement).getNode().getNamespaceURI());
+ Assert.assertEquals("pmd", ((XmlNode)rootElement).getNode().getPrefix());
+ Assert.assertEquals("rootElement", ((XmlNode)rootElement).getNode().getLocalName());
+ Assert.assertEquals("pmd:rootElement", ((XmlNode)rootElement).getNode().getNodeName());
+ assertTextNode(rootElement.jjtGetChild(0), "\\n ");
+ assertNode(rootElement.jjtGetChild(1), "comment", 0);
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ Node child1 = rootElement.jjtGetChild(3);
+ assertNode(child1, "pmd:child1", 1, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: &\\n ");
+ assertTextNode(rootElement.jjtGetChild(4), "\\n ");
+ Node child2 = rootElement.jjtGetChild(5);
+ assertNode(child2, "pmd:child2", 3);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ assertTextNode(rootElement.jjtGetChild(6), "\\n");
+ }
+
+ /**
+ * Verifies the default parsing behavior of the XML parser with namespaces but not namespace aware.
+ */
+ @Test
+ public void testParsingNotNamespaceAware() {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ XmlParserOptions parserOptions = new XmlParserOptions();
+ parserOptions.setNamespaceAware(false);
+ Parser parser = xmlVersionHandler.getParser(parserOptions);
+ Node document = parser.parse(null, new StringReader(XML_NAMESPACE_TEST));
+
+ assertNode(document, "document", 1);
+ Node rootElement = document.jjtGetChild(0);
+ assertNode(rootElement, "pmd:rootElement", 7, "xmlns:pmd", "http://pmd.sf.net");
+ Assert.assertNull(((XmlNode)rootElement).getNode().getNamespaceURI());
+ Assert.assertNull(((XmlNode)rootElement).getNode().getPrefix());
+ Assert.assertNull(((XmlNode)rootElement).getNode().getLocalName());
+ Assert.assertEquals("pmd:rootElement", ((XmlNode)rootElement).getNode().getNodeName());
+ assertTextNode(rootElement.jjtGetChild(0), "\\n ");
+ assertNode(rootElement.jjtGetChild(1), "comment", 0);
+ assertTextNode(rootElement.jjtGetChild(2), "\\n ");
+ Node child1 = rootElement.jjtGetChild(3);
+ assertNode(child1, "pmd:child1", 1, "test", "1");
+ assertTextNode(child1.jjtGetChild(0), "entity: &\\n ");
+ assertTextNode(rootElement.jjtGetChild(4), "\\n ");
+ Node child2 = rootElement.jjtGetChild(5);
+ assertNode(child2, "pmd:child2", 3);
+ assertTextNode(child2.jjtGetChild(0), "\\n ");
+ assertTextNode(child2.jjtGetChild(1), " cdata section ", "cdata-section");
+ assertTextNode(child2.jjtGetChild(2), "\\n ");
+ assertTextNode(rootElement.jjtGetChild(6), "\\n");
+ }
+
+ /**
+ * Verifies the parsing behavior of the XML parser with validation on.
+ * @throws UnsupportedEncodingException error
+ */
+ @Test
+ public void testParsingWithValidation() throws UnsupportedEncodingException {
+ LanguageVersionHandler xmlVersionHandler = Language.XML.getDefaultVersion().getLanguageVersionHandler();
+ XmlParserOptions parserOptions = new XmlParserOptions();
+ parserOptions.setValidating(true);
+ Parser parser = xmlVersionHandler.getParser(parserOptions);
+ PrintStream oldErr = System.err;
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(bos));
+ Node document = parser.parse(null, new StringReader(XML_INVALID_WITH_DTD));
+ Assert.assertNotNull(document);
+ String output = bos.toString("UTF-8");
+ Assert.assertTrue(output.contains("Element type \"invalidChild\" must be declared."));
+ Assert.assertTrue(output.contains("The content of element type \"rootElement\" must match \"(child)\"."));
+ Assert.assertEquals(2, document.jjtGetNumChildren());
+ Assert.assertEquals("invalidChild", String.valueOf(document.jjtGetChild(1).jjtGetChild(1)));
+ } finally {
+ System.setErr(oldErr);
+ }
+ }
+
+ /**
+ * Asserts a single node inclusive attributes.
+ * @param node the node
+ * @param toString the to String representation to expect
+ * @param childs number of childs
+ * @param atts attributes - each object pair forms one attribute: first name, then value.
+ */
+ private void assertNode(Node node, String toString, int childs, Object ... atts) {
+ Assert.assertEquals(toString, String.valueOf(node));
+ Assert.assertEquals(childs, node.jjtGetNumChildren());
+ Iterator attributeIterator = ((XmlNode)node).getAttributeIterator();
+ if (atts != null) {
+ for (int i = 0; i < atts.length; i += 2) {
+ Assert.assertTrue(attributeIterator.hasNext());
+ String name = String.valueOf(atts[i]);
+ Object value = atts[i + 1];
+ Attribute attribute = attributeIterator.next();
+ Assert.assertEquals(name, attribute.getName());
+ Assert.assertEquals(value, attribute.getValue());
+ }
+ }
+ Assert.assertFalse(attributeIterator.hasNext());
+ }
+
+ /**
+ * Assert a single text node.
+ * @param node the node to check
+ * @param text the text to expect
+ */
+ private void assertTextNode(Node node, String text) {
+ assertTextNode(node, text, "text");
+ }
+
+ /**
+ * Assert a single text node.
+ *
+ * @param node the node to check
+ * @param text the text to expect
+ * @param toString the to string representation
+ */
+ private void assertTextNode(Node node, String text, String toString) {
+ Assert.assertEquals(toString, String.valueOf(node));
+ Assert.assertEquals(0, node.jjtGetNumChildren());
+ Assert.assertEquals(text, StringUtil.escapeWhitespace(node.getImage()));
+ Iterator attributeIterator = ((XmlNode)node).getAttributeIterator();
+ Assert.assertTrue(attributeIterator.hasNext());
+ Attribute attribute = attributeIterator.next();
+ Assert.assertEquals("Image", attribute.getName());
+ Assert.assertEquals(text, StringUtil.escapeWhitespace(attribute.getValue()));
+ Assert.assertFalse(attributeIterator.hasNext());
+ }
+
+ /**
+ * Assert the line numbers of a node.
+ *
+ * @param node the node
+ * @param beginLine the begin line
+ * @param beginColumn the begin column
+ * @param endLine the end line
+ * @param endColumn the end column
+ */
+ private void assertLineNumbers(Node node, int beginLine, int beginColumn, int endLine, int endColumn) {
+ Assert.assertEquals("begin line wrong", beginLine, node.getBeginLine());
+ Assert.assertEquals("begin column wrong", beginColumn, node.getBeginColumn());
+ Assert.assertEquals("end line wrong", endLine, node.getEndLine());
+ Assert.assertEquals("end column wrong", endColumn, node.getEndColumn());
+ }
+}
diff --git a/pmd/src/test/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRuleTest.java b/pmd/src/test/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRuleTest.java
index 2931e05466..537292c8a0 100644
--- a/pmd/src/test/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRuleTest.java
+++ b/pmd/src/test/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRuleTest.java
@@ -76,8 +76,10 @@ public class AbstractDomXmlRuleTest {
// assertEquals(0, visited.size());
visited = rule.visitedNodes.get("EntityReference");
- assertEquals(1, visited.size());
- assertEquals("entity", ((EntityReference) visited.get(0)).getNodeName());
+ assertEquals(3, visited.size());
+ assertEquals("gt", ((EntityReference) visited.get(0)).getNodeName());
+ assertEquals("entity", ((EntityReference) visited.get(1)).getNodeName());
+ assertEquals("lt", ((EntityReference) visited.get(2)).getNodeName());
// TODO Figure out how to trigger this.
// visited = rule.visitedNodes.get("Notation");
@@ -89,11 +91,9 @@ public class AbstractDomXmlRuleTest {
((ProcessingInstruction) visited.get(0)).getTarget());
visited = rule.visitedNodes.get("Text");
- assertEquals(4, visited.size());
+ assertEquals(2, visited.size());
assertEquals("TEXT", ((Text) visited.get(0)).getData());
- assertEquals(">", ((Text) visited.get(1)).getData());
- assertEquals("e", ((Text) visited.get(2)).getData());
- assertEquals("<", ((Text) visited.get(3)).getData());
+ assertEquals("e", ((Text) visited.get(1)).getData());
}
@Test