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 19, 2020
1 parent a382694 commit a34ec14
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
*******************************************************************************/
package org.eclipse.lemminx.dom;

import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Range;

/**
* DTDDeclParameter
*/
public class DTDDeclParameter implements DOMRange {
public class DTDDeclParameter implements DOMRange, TargetRange {

private final DTDDeclNode ownerNode;

Expand Down Expand Up @@ -81,4 +84,14 @@ public boolean equals(Object obj) {
return start == temp.start && end == temp.end;
}

@Override
public Range getTargetRange() {
return XMLPositionUtility.createRange(this);
}

@Override
public String getTargetURI() {
return getOwnerDocument().getDocumentURI();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2020 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*/
package org.eclipse.lemminx.dom;

import org.eclipse.lsp4j.Range;

/**
* Target range API.
*
*/
public interface TargetRange {

/**
* Returns the target range.
*
* @return the target range.
*/
Range getTargetRange();

/**
* Returns the target URI.
*
* @return the target URI.
*/
String getTargetURI();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,26 @@
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;
import org.apache.xerces.xni.grammars.Grammar;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.dom.DTDEntityDecl;
import org.eclipse.lemminx.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument;
import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lemminx.extensions.contentmodel.model.FilesChangedTracker;
import org.eclipse.lemminx.extensions.dtd.utils.DTDUtils;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.w3c.dom.Entity;

/**
Expand Down Expand Up @@ -90,6 +93,72 @@ public String getComment(String attrName) {
}
}

private static class ScannedDTDEntityDecl extends DTDEntityDecl {

private final String entityName;
private final String value;
private final DTDDeclParameter nameParameter;

public ScannedDTDEntityDecl(String name, String value, ScannedEntity scannedEntity) {
super(-1, -1);
this.entityName = name;
this.value = value;
this.nameParameter = createNameParameter(name, scannedEntity);
}

@Override
public DTDDeclParameter getNameParameter() {
return nameParameter;
}

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

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

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

private static DTDDeclParameter createNameParameter(String name, ScannedEntity scannedEntity) {
String systemId = scannedEntity.entityLocation.getExpandedSystemId();
int lineNumber = scannedEntity.lineNumber - 1;
int startNameColumnNumber = getEntityNameStartColumnNumber(name, scannedEntity);
return new DTDDeclParameter(null, -1, -1) {

@Override
public Range getTargetRange() {
return new Range(new Position(lineNumber, startNameColumnNumber),
new Position(lineNumber, startNameColumnNumber + name.length()));
};

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

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 +171,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 +245,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 +381,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 @@ -21,8 +21,8 @@
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.dom.DTDEntityDecl;
import org.eclipse.lemminx.dom.TargetRange;
import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.services.extensions.AbstractDefinitionParticipant;
Expand Down Expand Up @@ -92,10 +92,7 @@ private static void searchInInternalEntities(String entityName, Range entityRang
for (int i = 0; i < entities.getLength(); i++) {
cancelChecker.checkCanceled();
DTDEntityDecl entity = (DTDEntityDecl) entities.item(i);
if (entityName.equals(entity.getName())) {
DTDDeclParameter name = entity.getNameParameter();
locations.add(XMLPositionUtility.createLocationLink(entityRange, name));
}
fillEntityLocation(entity, entityName, entityRange, locations);
}
}

Expand All @@ -116,10 +113,17 @@ private static void searchInExternalEntities(String entityName, Range entityRang
if (cmDocument != null) {
List<Entity> entities = cmDocument.getEntities();
for (Entity entity : entities) {
if (entityName.equals(entity.getNodeName())) {
// TODO : retrieve location from the external entity?
}
fillEntityLocation((DTDEntityDecl) entity, entityName, entityRange, locations);
}
}
}

private static void fillEntityLocation(DTDEntityDecl entity, String entityName, Range entityRange,
List<LocationLink> locations) {
if (entityName.equals(entity.getName())) {
TargetRange name = entity.getNameParameter();
locations.add(XMLPositionUtility.createLocationLink(entityRange, name));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.eclipse.lemminx.dom.DTDDeclNode;
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.dom.DTDElementDecl;
import org.eclipse.lemminx.dom.TargetRange;
import org.eclipse.lemminx.dom.parser.Scanner;
import org.eclipse.lemminx.dom.parser.TokenType;
import org.eclipse.lemminx.dom.parser.XMLScanner;
Expand Down Expand Up @@ -773,12 +774,35 @@ public static LocationLink createLocationLink(DOMRange origin, DOMRange target)
return createLocationLink(originSelectionRange, target);
}

public static LocationLink createLocationLink(Range originSelectionRange, DOMRange target) {
/**
* Returns the location link for the given <code>origin</code> and
* <code>target</code> nodes.
*
* @param origin the origin node.
* @param target the target node.
* @return the location link for the given <code>origin</code> and
* <code>target</code> nodes.
*/
public static LocationLink createLocationLink(Range origin, DOMRange target) {
Range targetRange = XMLPositionUtility.createRange(target);
Range targetSelectionRange = targetRange;
DOMDocument targetDocument = target.getOwnerDocument();
return new LocationLink(targetDocument.getDocumentURI(), targetRange, targetSelectionRange,
originSelectionRange);
return new LocationLink(targetDocument.getDocumentURI(), targetRange, targetSelectionRange, origin);
}

/**
* Returns the location link for the given <code>origin</code> and
* <code>target</code> nodes.
*
* @param origin the origin node.
* @param target the target node.
* @return the location link for the given <code>origin</code> and
* <code>target</code> nodes.
*/
public static LocationLink createLocationLink(Range origin, TargetRange target) {
Range targetRange = target.getTargetRange();
Range targetSelectionRange = targetRange;
return new LocationLink(target.getTargetURI(), targetRange, targetSelectionRange, origin);
}

/**
Expand Down
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("///", "/");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void testUnavailableCache() throws Exception {
public void testAvailableCache() throws Exception {
FileServer server = new FileServer();
server.start();
String uri = server.getUri("/dtd/web-app_2_3.dtd");
String uri = server.getUri("/dtd/base.dtd");
try {
cacheResourcesManager.getResource(uri);
fail("cacheResourcesManager should be busy downloading the url");
Expand Down

0 comments on commit a34ec14

Please sign in to comment.