Skip to content

Commit

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

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr authored and fbricon committed May 19, 2020
1 parent a382694 commit fbf1b5a
Show file tree
Hide file tree
Showing 7 changed files with 231 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,94 @@ public String getComment(String attrName) {
}
}

private static class ScannedDTDEntityDecl extends DTDEntityDecl {

private static final char[] ENTITY = "<!ENTITY".toCharArray();

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;
}
};
}

/**
* Returns the colunm number where entity name starts (<!ENTITY |name )
*
* @param name the entity name
* @param scannedEntity the scanned entity
* @return the colunm number where entity name starts (<!ENTITY |name )
*/
private static int getEntityNameStartColumnNumber(String name, ScannedEntity scannedEntity) {
int endEntityIndex = scannedEntity.position;
int startLineIndex = endEntityIndex - scannedEntity.columnNumber + 1;
char[] ch = scannedEntity.ch;
int wordIndex = 0;
// loop for line text where entity is declared
// --> <!ENTITY name >
for (int i = startLineIndex; i < endEntityIndex; i++) {
char c = ch[i];
// Search the index after the <!ENTITY
if (wordIndex < ENTITY.length) {
if (c == ENTITY[wordIndex]) {
wordIndex++;
} else {
wordIndex = 0;
}
} else {
// <!ENTITY index id found, search the index where entity name starts.
if (c == name.charAt(0)) {
return i - startLineIndex;
}
}
}
return scannedEntity.columnNumber;
}
}

private final String uri;

private Map<String, DTDElementInfo> hierarchiesMap;
Expand All @@ -102,14 +193,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 +267,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 +403,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
Loading

0 comments on commit fbf1b5a

Please sign in to comment.