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 8243a3f
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 70 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 @@ -14,11 +14,10 @@
package org.eclipse.lemminx.extensions.contentmodel;

import static org.eclipse.lemminx.XMLAssert.r;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;

import org.apache.xerces.impl.XMLEntityManager;
Expand Down Expand Up @@ -74,15 +73,17 @@ public void webXML() throws Exception {
String httpDTDUri = server.getUri("/dtd/web-app_2_3.dtd");
Path cachedFilePath = CacheResourcesManager.getResourceCachePath(httpDTDUri);
Files.deleteIfExists(cachedFilePath);
// Download the DTD by waiting 1 sec

// Download the DTD by waiting 1 sec
CacheResourcesManager cacheResourcesManager = new CacheResourcesManager();
try {
cacheResourcesManager.getResource(httpDTDUri);
cacheResourcesManager.getResource(httpDTDUri);
} catch (CacheResourceDownloadingException ignored) {
}
TimeUnit.MILLISECONDS.sleep(200);

TimeUnit.MILLISECONDS.sleep(1000);
assertTrue(Files.exists(cachedFilePath),
"'" + cachedFilePath + "' file should be downloaded in the cache.");

// Process hover with the DTD (http dtd)
String dtdFileCacheURI = cachedFilePath.toUri().toString().replace("file:///", "file:/");
String xml = "<!DOCTYPE web-app PUBLIC\n" + //
Expand All @@ -96,7 +97,7 @@ public void webXML() throws Exception {
"The web-app element is the root of the deployment descriptor for a web application." + //
System.lineSeparator() + //
System.lineSeparator() + "Source: [web-app_2_3.dtd](" + dtdFileCacheURI + ")",
r(4, 1, 4, 8));
r(4, 1, 4, 8));
} finally {
server.stop();
}
Expand Down
Loading

0 comments on commit 8243a3f

Please sign in to comment.