Skip to content

Commit

Permalink
Support advanced characters for entity name.
Browse files Browse the repository at this point in the history
  • Loading branch information
angelozerr committed May 26, 2020
1 parent 9a5bf7d commit d3dfc02
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public enum DTDErrorCode implements IXMLErrorCode {
QuoteRequiredInSystemID,
SpaceRequiredAfterSYSTEM,
dtd_not_found("dtd-not-found");

private final String code;

private DTDErrorCode() {
Expand Down Expand Up @@ -132,7 +132,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
String attrName = getString(arguments[0]);
return XMLPositionUtility.selectAttributeValueAt(attrName, offset, document);
}

case MSG_ELEMENT_WITH_ID_REQUIRED: {
DOMElement element = document.getDocumentElement();
if (element != null) {
Expand Down Expand Up @@ -160,21 +160,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
return XMLPositionUtility.getLastValidDTDDeclParameter(offset, document, true);
}
case EntityNotDeclared: {
try {
Position position = document.positionAt(offset);
int line = position.getLine();
String text = document.lineText(line);
String name = getString(arguments[0]);
String subString = "&" + name + ";";
int start = text.indexOf(subString);
int end = start + subString.length();
Position startPosition = new Position(line, start);
Position endPosition = new Position(line, end);
return new Range(startPosition, endPosition);
} catch (BadLocationException e) {

}

return XMLPositionUtility.selectEntityReference(offset - 1, document);
}
case QuoteRequiredInPublicID:
case QuoteRequiredInSystemID:
Expand Down Expand Up @@ -206,7 +192,6 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
try {
return new Range(new Position(0, 0), document.positionAt(document.getEnd()));
} catch (BadLocationException e) {

}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class EntitiesCompletionParticipant extends CompletionParticipantAdapter

@Override
public void onXMLContent(ICompletionRequest request, ICompletionResponse response) throws Exception {
Range entityRange = XMLPositionUtility.selectEntity(request.getOffset(), request.getXMLDocument());
Range entityRange = XMLPositionUtility.selectEntityReference(request.getOffset(), request.getXMLDocument());
if (entityRange == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@
*/
package org.eclipse.lemminx.extensions.entities.participants;

import static org.eclipse.lemminx.utils.StringUtils.findEndWord;
import static org.eclipse.lemminx.utils.StringUtils.findStartWord;

import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
Expand All @@ -41,8 +37,6 @@
*/
public class EntitiesDefinitionParticipant extends AbstractDefinitionParticipant {

private static final Predicate<Character> isValidChar = (c) -> Character.isJavaIdentifierPart(c) || c == '-';

@Override
protected boolean match(DOMDocument document) {
return true;
Expand All @@ -60,13 +54,18 @@ protected void doFindDefinition(IDefinitionRequest request, List<LocationLink> l
int offset = request.getOffset();
String text = document.getText();

int paramStart = findStartWord(text, offset, isValidChar);
if (paramStart > 0 && text.charAt(paramStart - 1) == '&') {
int paramEnd = findEndWord(text, offset, isValidChar);
int entityReferenceStart = XMLPositionUtility.getEntityReferenceStartOffset(text, offset);
if (entityReferenceStart != -1) {
// the start offset is the amp character, ignore it
entityReferenceStart++;
int entityReferenceEnd = XMLPositionUtility.getEntityReferenceEndOffset(text, offset);
// Find definition of the entity
// abcd &loc|al; --> in this case search local entity
String entityName = text.substring(paramStart, paramEnd);
Range entityRange = XMLPositionUtility.createRange(paramStart, paramEnd, document);
if (text.charAt(entityReferenceEnd - 1) == ';') {
entityReferenceEnd--;
}
String entityName = text.substring(entityReferenceStart, entityReferenceEnd);
Range entityRange = XMLPositionUtility.createRange(entityReferenceStart, entityReferenceEnd, document);
searchInInternalEntities(entityName, entityRange, document, locations, cancelChecker);
searchInExternalEntities(entityName, entityRange, document, locations, request, cancelChecker);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,15 +405,15 @@ public static String getString(Object obj) {
* and -1 if no word.
*/
public static int findStartWord(String text, int offset, Predicate<Character> isValidChar) {
if (!isValidChar.test(text.charAt(offset))) {
if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) {
return -1;
}
for (int i = offset - 1; i >= 0; i--) {
if (!isValidChar.test(text.charAt(i))) {
return i + 1;
}
}
return -1;
return 0;
}

/**
Expand All @@ -428,7 +428,7 @@ public static int findStartWord(String text, int offset, Predicate<Character> is
* and -1 if no word.
*/
public static int findEndWord(String text, int offset, Predicate<Character> isValidChar) {
if (!isValidChar.test(text.charAt(offset))) {
if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) {
return -1;
}
for (int i = offset + 1; i < text.length(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.eclipse.lemminx.utils;

import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -47,6 +48,11 @@ public class XMLPositionUtility {

private static final Logger LOGGER = Logger.getLogger(XMLPositionUtility.class.getName());

private static final Predicate<Character> ENTITY_NAME_PREDICATE = ch -> {
// [_:\w-.\d]*
return ch == '_' || ch == ':' || ch == '.' || ch == '-' || Character.isLetterOrDigit(ch);
};

private XMLPositionUtility() {
}

Expand Down Expand Up @@ -476,33 +482,79 @@ public static Range selectPreviousNodesEndTag(int offset, DOMDocument document)
// ------------ Entities selection

/**
* Returns the range of the used entity in a text node (ex : &amp;) and null
* otherwise.
* Returns the range of the entity reference in a text node (ex : &amp;) and
* null otherwise.
*
* @param offset the offset
* @param document the document
* @return the range of the used entity in a text node (ex : &amp;) and null
* otherwise.
* @return the range of the entity reference in a text node (ex : &amp;) and
* null otherwise.
*/
public static Range selectEntity(int offset, DOMDocument document) {
public static Range selectEntityReference(int offset, DOMDocument document) {
String text = document.getText();
// Search '&' character on the left of the offset
int startEntityOffset = offset - 1;
while (startEntityOffset > -1 && Character.isLetterOrDigit(text.charAt(startEntityOffset))) {
startEntityOffset--;
}
if (startEntityOffset == -1 || text.charAt(startEntityOffset) != '&') {
int entityReferenceStart = getEntityReferenceStartOffset(text, offset);
if (entityReferenceStart == -1) {
return null;
}
// Search ';' (or character on the right of the offset
int endEntityOffset = offset;
while (endEntityOffset < text.length() && Character.isLetterOrDigit(text.charAt(endEntityOffset))) {
endEntityOffset++;
int entityReferenceEnd = getEntityReferenceEndOffset(text, offset);
return createRange(entityReferenceStart, entityReferenceEnd, document);
}

/**
* Returns the start offset of the entity reference (ex : &am|p;) from the left
* of the given offset and -1 if no entity reference.
*
* @param text the XML content.
* @param offset the offset.
* @return the start offset of the entity reference (ex : &am|p;) from the left
* of the given offset and -1 if no entity reference.
*/
public static int getEntityReferenceStartOffset(String text, int offset) {
// adjust offset to get the left character of the offset
offset--;
if (offset < 0) {
// case where offset is on the first character
return -1;
}
if (text.charAt(offset) == '&') {
// case with &|abcd
return offset;
}
if (offset == 0) {
// case with a|bcd -> there are no '&'
return -1;
}
int startEntityOffset = StringUtils.findStartWord(text, offset, ENTITY_NAME_PREDICATE);
if (startEntityOffset <= 0) {
return -1;
}
// check if the left character is '&'
if (text.charAt(startEntityOffset - 1) != '&') {
return -1;
}
return startEntityOffset - 1;
}

/**
* Returns the end offset of the entity reference (ex : &am|p;) from the right
* of the given offset.
*
* @param text the XML content.
* @param offset the offset.
* @return the end offset of the entity reference (ex : &am|p;) from the right
* of the given offset.
*/
public static int getEntityReferenceEndOffset(String text, int offset) {
int endEntityOffset = StringUtils.findEndWord(text, offset, ENTITY_NAME_PREDICATE);
if (endEntityOffset == -1) {
return offset;
}
if (endEntityOffset + 1 < text.length() && text.charAt(endEntityOffset) == ';') {
if (endEntityOffset < text.length() && text.charAt(endEntityOffset) == ';') {
endEntityOffset++;
}
return createRange(startEntityOffset, endEntityOffset, document);
return endEntityOffset;
}

// ------------ Text selection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class EntitiesCompletionExtensionsTest {
public void afterAmp() throws BadLocationException {
// &|
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -49,7 +49,7 @@ public void afterAmp() throws BadLocationException {
public void afterCharacter() throws BadLocationException {
// &m|
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -66,7 +66,7 @@ public void afterCharacter() throws BadLocationException {
public void inside() throws BadLocationException {
// &m|dblablabla
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -78,11 +78,29 @@ public void inside() throws BadLocationException {
c("&mdash;", "&mdash;", r(5, 2, 5, 14), "&mdash;"));
}

@Test
public void underscoreEntityName() throws BadLocationException {
// &foo_b|
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY foo_bar \"&#x2014;\">\r\n" + //
" <!ENTITY foo_baz \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
" &foo_b|\r\n" + // <- here completion shows mdash entity
"</root>";
testCompletionFor(xml, 2 + //
2 /* CDATA and Comments */ + //
PredefinedEntity.values().length /* predefined entities */,
c("&foo_bar;", "&foo_bar;", r(6, 2, 6, 8), "&foo_bar;"), //
c("&foo_baz;", "&foo_baz;", r(6, 2, 6, 8), "&foo_baz;"));
}

@Test
public void insideWithAmp() throws BadLocationException {
// &m|d;blablabla
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -97,7 +115,7 @@ public void insideWithAmp() throws BadLocationException {
@Test
public void none() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class EntitiesDefinitionExtensionsTest {
@Test
public void local() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -42,7 +42,7 @@ public void local() throws BadLocationException {
@Test
public void beforeAmp() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -54,7 +54,7 @@ public void beforeAmp() throws BadLocationException {
@Test
public void unknownEntity() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -66,7 +66,7 @@ public void unknownEntity() throws BadLocationException {
@Test
public void insideText() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE article [\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
Expand All @@ -75,6 +75,19 @@ public void insideText() throws BadLocationException {
testDefinitionFor(xml, "test.xml", ll("test.xml", r(5, 7, 5, 12), r(2, 11, 2, 16)));
}

@Test
public void underscoreEntityName() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<!DOCTYPE root [\r\n" + //
" <!ENTITY foo_bar \"&#x2014;\">\r\n" + //
" <!ENTITY foo_baz \"&#x2014;\">\r\n" + //
"]>\r\n" + //
"<root>\r\n" + //
" &foo_b|ar;efgh\r\n" + // <- here definition for foo_bar
"</root>";
testDefinitionFor(xml, "test.xml", ll("test.xml", r(6, 2, 6, 9), r(2, 11, 2, 18)));
}

// Test for external entities

public void external() throws BadLocationException, MalformedURIException {
Expand Down
Loading

0 comments on commit d3dfc02

Please sign in to comment.