Skip to content

Commit

Permalink
Find definition for external declared entity
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#706

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed May 15, 2020
1 parent a382694 commit 4e1056b
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import java.util.Map;
import java.util.Set;

import org.apache.xerces.impl.XMLEntityManager.ScannedEntity;
import org.apache.xerces.impl.dtd.DTDGrammar;
import org.apache.xerces.impl.dtd.XMLDTDLoader;
import org.apache.xerces.impl.dtd.XMLEntityDecl;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.XMLString;
import org.apache.xerces.xni.XNIException;
Expand Down Expand Up @@ -90,6 +90,63 @@ public String getComment(String attrName) {
}
}

public static class ScannedDTDEntityDecl extends DTDEntityDecl {

private final String entityName;
private final String systemId;

private final String value;

private final int startNameLineNumber;

private final int startNameColumnNumber;

public ScannedDTDEntityDecl(String name, String value, ScannedEntity scannedEntity) {
super(-1, -1);
this.entityName = name;
this.systemId = scannedEntity.entityLocation.getExpandedSystemId();
this.value = value;
this.startNameLineNumber = scannedEntity.lineNumber - 1;
this.startNameColumnNumber = getEntityNameStartColumnNumber(entityName, scannedEntity);
}

@Override
public String getNodeName() {
return entityName;
}

@Override
public String getNotationName() {
return value;
}

@Override
public String getSystemId() {
return systemId;
}

public int getStartNameColumnNumber() {
return startNameColumnNumber;
}

public int getStartNameLineNumber() {
return startNameLineNumber;
}

private static int getEntityNameStartColumnNumber(String name, ScannedEntity scannedEntity) {
int endEntityIndex = scannedEntity.position;
int startLineIndex = endEntityIndex - scannedEntity.columnNumber + 1;
char[] ch = scannedEntity.ch;
for (int i = startLineIndex; i < endEntityIndex; i++) {
char c = ch[i];
if (c == name.charAt(0)) {
return i - startLineIndex;
}
}
return scannedEntity.columnNumber;
}
}

private final String uri;

private Map<String, DTDElementInfo> hierarchiesMap;
Expand All @@ -102,14 +159,15 @@ public String getComment(String attrName) {
private Map<String, DTDNodeInfo> attributes;
private DTDNodeInfo nodeInfo;

private List<Entity> entities;
private final List<Entity> entities;

public CMDTDDocument() {
this(null);
}

public CMDTDDocument(String uri) {
this.uri = uri;
this.entities = new ArrayList<>();
}

@Override
Expand Down Expand Up @@ -175,6 +233,13 @@ private CMElementDeclaration findElementDeclaration(String tag, String namespace
return null;
}

@Override
public void internalEntityDecl(String name, XMLString text, XMLString nonNormalizedText, Augmentations augs)
throws XNIException {
super.internalEntityDecl(name, text, nonNormalizedText, augs);
entities.add(new ScannedDTDEntityDecl(name, text.toString(), fEntityManager.getCurrentEntity()));
}

@Override
public void startContentModel(String elementName, Augmentations augs) throws XNIException {
if (hierarchiesMap == null) {
Expand Down Expand Up @@ -304,54 +369,6 @@ public boolean isDirty() {

@Override
public List<Entity> getEntities() {
if (entities == null) {
entities = computeEntities();
}
return entities;
}

private synchronized List<Entity> computeEntities() {
if (entities != null) {
return entities;
}
List<Entity> entities = new ArrayList<>();
fillEntities(fDTDGrammar, entities);
return entities;
}

/**
* Collect entities declared in the DTD grammar.
*
* @param grammar the DTD grammar.
* @param entities list to fill.
*/
private static void fillEntities(DTDGrammar grammar, List<Entity> entities) {
int index = 0;
XMLEntityDecl entityDecl = new XMLEntityDecl() {

@Override
public void setValues(String name, String publicId, String systemId, String baseSystemId, String notation,
String value, boolean isPE, boolean inExternal) {
if (inExternal && !isPE) {
// Only external entities (entities declared in the DTD) and not entity with %
// must be collected.
Entity entity = new DTDEntityDecl(0, 0) {
@Override
public String getNodeName() {
return name;
}

@Override
public String getNotationName() {
return value;
}
};
entities.add(entity);
}
};
};
while (grammar.getEntityDecl(index, entityDecl)) {
index++;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
import org.eclipse.lemminx.dom.DTDEntityDecl;
import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.dtd.contentmodel.CMDTDDocument.ScannedDTDEntityDecl;
import org.eclipse.lemminx.services.extensions.AbstractDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.IDefinitionRequest;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.w3c.dom.Entity;
Expand Down Expand Up @@ -117,9 +119,19 @@ private static void searchInExternalEntities(String entityName, Range entityRang
List<Entity> entities = cmDocument.getEntities();
for (Entity entity : entities) {
if (entityName.equals(entity.getNodeName())) {
// TODO : retrieve location from the external entity?
ScannedDTDEntityDecl entityDecl = (ScannedDTDEntityDecl) entity;
locations.add(createLocationLink(entityRange, entityDecl));
}
}
}
}

public static LocationLink createLocationLink(Range originSelectionRange, ScannedDTDEntityDecl entityDecl) {
Range targetRange = new Range(
new Position(entityDecl.getStartNameLineNumber(), entityDecl.getStartNameColumnNumber()),
new Position(entityDecl.getStartNameLineNumber(),
entityDecl.getStartNameColumnNumber() + entityDecl.getNodeName().length()));
Range targetSelectionRange = targetRange;
return new LocationLink(entityDecl.getSystemId(), targetRange, targetSelectionRange, originSelectionRange);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import static org.eclipse.lemminx.XMLAssert.r;
import static org.eclipse.lemminx.XMLAssert.testDefinitionFor;

import org.apache.xerces.impl.XMLEntityManager;
import org.apache.xerces.util.URI.MalformedURIException;
import org.eclipse.lemminx.commons.BadLocationException;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -73,4 +75,21 @@ public void insideText() throws BadLocationException {
testDefinitionFor(xml, "test.xml", ll("test.xml", r(5, 7, 5, 12), r(2, 11, 2, 16)));
}

// Test for external entities
@Test
public void external() throws BadLocationException, MalformedURIException {
String dtdFileURI = getDTDFileURI("src/test/resources/dtd/base.dtd");
String xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n" + //
"<!DOCTYPE root-element SYSTEM \"src/test/resources/dtd/base.dtd\" [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root-element>\r\n" + //
"\r\n &f|oo" + //
"</root-element>";
testDefinitionFor(xml, "test.xml", ll(dtdFileURI, r(6, 2, 6, 5), r(2, 9, 2, 12)));
}

private static String getDTDFileURI(String dtdURI) throws MalformedURIException {
return XMLEntityManager.expandSystemId(dtdURI, "test.xml", true).replace("///", "/");
}
}

0 comments on commit 4e1056b

Please sign in to comment.