diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDDeclParameter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDDeclParameter.java index a83c819ea1..931b7a3c75 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDDeclParameter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDDeclParameter.java @@ -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; @@ -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(); + } + } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/TargetRange.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/TargetRange.java new file mode 100644 index 0000000000..df05320125 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/TargetRange.java @@ -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(); +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDDocument.java index 87d1174ef0..290edca39e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDDocument.java @@ -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; @@ -32,6 +32,7 @@ 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; @@ -39,6 +40,8 @@ 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; /** @@ -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 hierarchiesMap; @@ -102,7 +171,7 @@ public String getComment(String attrName) { private Map attributes; private DTDNodeInfo nodeInfo; - private List entities; + private final List entities; public CMDTDDocument() { this(null); @@ -110,6 +179,7 @@ public CMDTDDocument() { public CMDTDDocument(String uri) { this.uri = uri; + this.entities = new ArrayList<>(); } @Override @@ -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) { @@ -304,54 +381,6 @@ public boolean isDirty() { @Override public List getEntities() { - if (entities == null) { - entities = computeEntities(); - } - return entities; - } - - private synchronized List computeEntities() { - if (entities != null) { - return entities; - } - List 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 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++; - } - } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesDefinitionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesDefinitionParticipant.java index 538ee3d4dc..df49a2018a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesDefinitionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesDefinitionParticipant.java @@ -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; @@ -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); } } @@ -116,10 +113,17 @@ private static void searchInExternalEntities(String entityName, Range entityRang if (cmDocument != null) { List 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 locations) { + if (entityName.equals(entity.getName())) { + TargetRange name = entity.getNameParameter(); + locations.add(XMLPositionUtility.createLocationLink(entityRange, name)); + } + } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java index a9e28020a5..662a1ab9d6 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java @@ -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; @@ -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 origin and + * target nodes. + * + * @param origin the origin node. + * @param target the target node. + * @return the location link for the given origin and + * target 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 origin and + * target nodes. + * + * @param origin the origin node. + * @param target the target node. + * @return the location link for the given origin and + * target nodes. + */ + public static LocationLink createLocationLink(Range origin, TargetRange target) { + Range targetRange = target.getTargetRange(); + Range targetSelectionRange = targetRange; + return new LocationLink(target.getTargetURI(), targetRange, targetSelectionRange, origin); } /** diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDHoverExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDHoverExtensionsTest.java index 5d48187970..a3c8957028 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDHoverExtensionsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDHoverExtensionsTest.java @@ -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; @@ -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 = "\r\n" + // + "\r\n" + // + "]>\r\n" + // + "\r\n" + // + "\r\n &f|oo" + // + ""; + 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("///", "/"); + } }