Skip to content

Commit d92270d

Browse files
committed
Support advanced characters for entity name.
See redhat-developer/vscode-xml#262 Signed-off-by: azerr <azerr@redhat.com>
1 parent 132b88f commit d92270d

6 files changed

Lines changed: 116 additions & 54 deletions

File tree

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public enum DTDErrorCode implements IXMLErrorCode {
7070
QuoteRequiredInSystemID,
7171
SpaceRequiredAfterSYSTEM,
7272
dtd_not_found("dtd-not-found");
73-
73+
7474
private final String code;
7575

7676
private DTDErrorCode() {
@@ -132,7 +132,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
132132
String attrName = getString(arguments[0]);
133133
return XMLPositionUtility.selectAttributeValueAt(attrName, offset, document);
134134
}
135-
135+
136136
case MSG_ELEMENT_WITH_ID_REQUIRED: {
137137
DOMElement element = document.getDocumentElement();
138138
if (element != null) {
@@ -160,21 +160,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
160160
return XMLPositionUtility.getLastValidDTDDeclParameter(offset, document, true);
161161
}
162162
case EntityNotDeclared: {
163-
try {
164-
Position position = document.positionAt(offset);
165-
int line = position.getLine();
166-
String text = document.lineText(line);
167-
String name = getString(arguments[0]);
168-
String subString = "&" + name + ";";
169-
int start = text.indexOf(subString);
170-
int end = start + subString.length();
171-
Position startPosition = new Position(line, start);
172-
Position endPosition = new Position(line, end);
173-
return new Range(startPosition, endPosition);
174-
} catch (BadLocationException e) {
175-
176-
}
177-
163+
return XMLPositionUtility.selectEntityReference(offset - 1, document);
178164
}
179165
case QuoteRequiredInPublicID:
180166
case QuoteRequiredInSystemID:
@@ -206,7 +192,6 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
206192
try {
207193
return new Range(new Position(0, 0), document.positionAt(document.getEnd()));
208194
} catch (BadLocationException e) {
209-
210195
}
211196
}
212197
return null;

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesCompletionParticipant.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class EntitiesCompletionParticipant extends CompletionParticipantAdapter
4242

4343
@Override
4444
public void onXMLContent(ICompletionRequest request, ICompletionResponse response) throws Exception {
45-
Range entityRange = XMLPositionUtility.selectEntity(request.getOffset(), request.getXMLDocument());
45+
Range entityRange = XMLPositionUtility.selectEntityReference(request.getOffset(), request.getXMLDocument());
4646
if (entityRange == null) {
4747
return;
4848
}

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesDefinitionParticipant.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@
1212
*/
1313
package org.eclipse.lemminx.extensions.entities.participants;
1414

15-
import static org.eclipse.lemminx.utils.StringUtils.findEndWord;
16-
import static org.eclipse.lemminx.utils.StringUtils.findStartWord;
17-
1815
import java.util.Collection;
1916
import java.util.List;
20-
import java.util.function.Predicate;
2117

2218
import org.eclipse.lemminx.dom.DOMDocument;
2319
import org.eclipse.lemminx.dom.DOMDocumentType;
@@ -41,8 +37,6 @@
4137
*/
4238
public class EntitiesDefinitionParticipant extends AbstractDefinitionParticipant {
4339

44-
private static final Predicate<Character> isValidChar = (c) -> Character.isJavaIdentifierPart(c) || c == '-';
45-
4640
@Override
4741
protected boolean match(DOMDocument document) {
4842
return true;
@@ -60,13 +54,18 @@ protected void doFindDefinition(IDefinitionRequest request, List<LocationLink> l
6054
int offset = request.getOffset();
6155
String text = document.getText();
6256

63-
int paramStart = findStartWord(text, offset, isValidChar);
64-
if (paramStart > 0 && text.charAt(paramStart - 1) == '&') {
65-
int paramEnd = findEndWord(text, offset, isValidChar);
57+
int entityReferenceStart = XMLPositionUtility.getEntityReferenceStartOffset(text, offset);
58+
if (entityReferenceStart != -1) {
59+
// the start offset is the amp character, ignore it
60+
entityReferenceStart++;
61+
int entityReferenceEnd = XMLPositionUtility.getEntityReferenceEndOffset(text, offset);
6662
// Find definition of the entity
6763
// abcd &loc|al; --> in this case search local entity
68-
String entityName = text.substring(paramStart, paramEnd);
69-
Range entityRange = XMLPositionUtility.createRange(paramStart, paramEnd, document);
64+
if (text.charAt(entityReferenceEnd - 1) == ';') {
65+
entityReferenceEnd--;
66+
}
67+
String entityName = text.substring(entityReferenceStart, entityReferenceEnd);
68+
Range entityRange = XMLPositionUtility.createRange(entityReferenceStart, entityReferenceEnd, document);
7069
searchInInternalEntities(entityName, entityRange, document, locations, cancelChecker);
7170
searchInExternalEntities(entityName, entityRange, document, locations, request, cancelChecker);
7271
}

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package org.eclipse.lemminx.utils;
1414

1515
import java.util.List;
16+
import java.util.function.Predicate;
1617
import java.util.logging.Level;
1718
import java.util.logging.Logger;
1819

@@ -46,6 +47,11 @@ public class XMLPositionUtility {
4647

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

50+
private static final Predicate<Character> ENTITY_NAME_PREDICATE = ch -> {
51+
// [_:\w-.\d]*
52+
return ch == '_' || ch == ':' || ch == '.' || ch == '-' || Character.isLetterOrDigit(ch);
53+
};
54+
4955
private XMLPositionUtility() {
5056
}
5157

@@ -475,33 +481,74 @@ public static Range selectPreviousNodesEndTag(int offset, DOMDocument document)
475481
// ------------ Entities selection
476482

477483
/**
478-
* Returns the range of the used entity in a text node (ex : &amp;) and null
479-
* otherwise.
484+
* Returns the range of the entity reference in a text node (ex : &amp;) and
485+
* null otherwise.
480486
*
481487
* @param offset the offset
482488
* @param document the document
483-
* @return the range of the used entity in a text node (ex : &amp;) and null
484-
* otherwise.
489+
* @return the range of the entity reference in a text node (ex : &amp;) and
490+
* null otherwise.
485491
*/
486-
public static Range selectEntity(int offset, DOMDocument document) {
492+
public static Range selectEntityReference(int offset, DOMDocument document) {
487493
String text = document.getText();
488494
// Search '&' character on the left of the offset
489-
int startEntityOffset = offset - 1;
490-
while (startEntityOffset > -1 && Character.isLetterOrDigit(text.charAt(startEntityOffset))) {
491-
startEntityOffset--;
492-
}
493-
if (startEntityOffset == -1 || text.charAt(startEntityOffset) != '&') {
495+
int entityReferenceStart = getEntityReferenceStartOffset(text, offset);
496+
if (entityReferenceStart == -1) {
494497
return null;
495498
}
496499
// Search ';' (or character on the right of the offset
497-
int endEntityOffset = offset;
498-
while (endEntityOffset < text.length() && Character.isLetterOrDigit(text.charAt(endEntityOffset))) {
499-
endEntityOffset++;
500+
int entityReferenceEnd = getEntityReferenceEndOffset(text, offset);
501+
return createRange(entityReferenceStart, entityReferenceEnd, document);
502+
}
503+
504+
/**
505+
* Returns the start offset of the entity reference (ex : &am|p;) from the given
506+
* offset and -1 if no entity reference.
507+
*
508+
* @param text the XML content.
509+
* @param offset the offset.
510+
* @return the start offset of the entity reference (ex : &am|p;) from the given
511+
* offset and -1 if no entity reference.
512+
*/
513+
public static int getEntityReferenceStartOffset(String text, int offset) {
514+
offset--;
515+
if (offset < 0) {
516+
return -1;
517+
}
518+
if (text.charAt(offset) == '&') {
519+
return offset;
520+
}
521+
if (offset == 0) {
522+
return -1;
523+
}
524+
int startEntityOffset = StringUtils.findStartWord(text, offset, ENTITY_NAME_PREDICATE);
525+
if (startEntityOffset == -1) {
526+
return -1;
527+
}
528+
if (startEntityOffset != -1 && text.charAt(startEntityOffset - 1) != '&') {
529+
return -1;
530+
}
531+
return startEntityOffset - 1;
532+
}
533+
534+
/**
535+
* Returns the end offset of the entity reference (ex : &am|p;) from the given
536+
* offset and -1 if no entity reference.
537+
*
538+
* @param text the XML content.
539+
* @param offset the offset.
540+
* @return the end offset of the entity reference (ex : &am|p;) from the given
541+
* offset and -1 if no entity reference.
542+
*/
543+
public static int getEntityReferenceEndOffset(String text, int offset) {
544+
int endEntityOffset = StringUtils.findEndWord(text, offset, ENTITY_NAME_PREDICATE);
545+
if (endEntityOffset == -1) {
546+
return offset;
500547
}
501-
if (endEntityOffset + 1 < text.length() && text.charAt(endEntityOffset) == ';') {
548+
if (endEntityOffset < text.length() && text.charAt(endEntityOffset) == ';') {
502549
endEntityOffset++;
503550
}
504-
return createRange(startEntityOffset, endEntityOffset, document);
551+
return endEntityOffset;
505552
}
506553

507554
// ------------ Text selection

org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/entities/EntitiesCompletionExtensionsTest.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class EntitiesCompletionExtensionsTest {
3232
public void afterAmp() throws BadLocationException {
3333
// &|
3434
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
35-
"<!DOCTYPE article [\r\n" + //
35+
"<!DOCTYPE root [\r\n" + //
3636
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
3737
"]>\r\n" + //
3838
"<root>\r\n" + //
@@ -49,7 +49,7 @@ public void afterAmp() throws BadLocationException {
4949
public void afterCharacter() throws BadLocationException {
5050
// &m|
5151
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
52-
"<!DOCTYPE article [\r\n" + //
52+
"<!DOCTYPE root [\r\n" + //
5353
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
5454
"]>\r\n" + //
5555
"<root>\r\n" + //
@@ -66,7 +66,7 @@ public void afterCharacter() throws BadLocationException {
6666
public void inside() throws BadLocationException {
6767
// &m|dblablabla
6868
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
69-
"<!DOCTYPE article [\r\n" + //
69+
"<!DOCTYPE root [\r\n" + //
7070
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
7171
"]>\r\n" + //
7272
"<root>\r\n" + //
@@ -78,11 +78,29 @@ public void inside() throws BadLocationException {
7878
c("&mdash;", "&mdash;", r(5, 2, 5, 14), "&mdash;"));
7979
}
8080

81+
@Test
82+
public void underscoreEntityName() throws BadLocationException {
83+
// &foo_b|
84+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
85+
"<!DOCTYPE root [\r\n" + //
86+
" <!ENTITY foo_bar \"&#x2014;\">\r\n" + //
87+
" <!ENTITY foo_baz \"&#x2014;\">\r\n" + //
88+
"]>\r\n" + //
89+
"<root>\r\n" + //
90+
" &foo_b|\r\n" + // <- here completion shows mdash entity
91+
"</root>";
92+
testCompletionFor(xml, 2 + //
93+
2 /* CDATA and Comments */ + //
94+
PredefinedEntity.values().length /* predefined entities */,
95+
c("&foo_bar;", "&foo_bar;", r(6, 2, 6, 8), "&foo_bar;"), //
96+
c("&foo_baz;", "&foo_baz;", r(6, 2, 6, 8), "&foo_baz;"));
97+
}
98+
8199
@Test
82100
public void insideWithAmp() throws BadLocationException {
83101
// &m|d;blablabla
84102
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
85-
"<!DOCTYPE article [\r\n" + //
103+
"<!DOCTYPE root [\r\n" + //
86104
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
87105
"]>\r\n" + //
88106
"<root>\r\n" + //
@@ -97,7 +115,7 @@ public void insideWithAmp() throws BadLocationException {
97115
@Test
98116
public void none() throws BadLocationException {
99117
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
100-
"<!DOCTYPE article [\r\n" + //
118+
"<!DOCTYPE root [\r\n" + //
101119
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
102120
"]>\r\n" + //
103121
"<root>\r\n" + //

org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/entities/EntitiesDefinitionExtensionsTest.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class EntitiesDefinitionExtensionsTest {
3030
@Test
3131
public void local() throws BadLocationException {
3232
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
33-
"<!DOCTYPE article [\r\n" + //
33+
"<!DOCTYPE root [\r\n" + //
3434
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
3535
"]>\r\n" + //
3636
"<root>\r\n" + //
@@ -42,7 +42,7 @@ public void local() throws BadLocationException {
4242
@Test
4343
public void beforeAmp() throws BadLocationException {
4444
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
45-
"<!DOCTYPE article [\r\n" + //
45+
"<!DOCTYPE root [\r\n" + //
4646
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
4747
"]>\r\n" + //
4848
"<root>\r\n" + //
@@ -54,7 +54,7 @@ public void beforeAmp() throws BadLocationException {
5454
@Test
5555
public void unknownEntity() throws BadLocationException {
5656
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
57-
"<!DOCTYPE article [\r\n" + //
57+
"<!DOCTYPE root [\r\n" + //
5858
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
5959
"]>\r\n" + //
6060
"<root>\r\n" + //
@@ -66,7 +66,7 @@ public void unknownEntity() throws BadLocationException {
6666
@Test
6767
public void insideText() throws BadLocationException {
6868
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
69-
"<!DOCTYPE article [\r\n" + //
69+
"<!DOCTYPE root [\r\n" + //
7070
" <!ENTITY mdash \"&#x2014;\">\r\n" + //
7171
"]>\r\n" + //
7272
"<root>\r\n" + //
@@ -75,6 +75,19 @@ public void insideText() throws BadLocationException {
7575
testDefinitionFor(xml, "test.xml", ll("test.xml", r(5, 7, 5, 12), r(2, 11, 2, 16)));
7676
}
7777

78+
@Test
79+
public void underscoreEntityName() throws BadLocationException {
80+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
81+
"<!DOCTYPE root [\r\n" + //
82+
" <!ENTITY foo_bar \"&#x2014;\">\r\n" + //
83+
" <!ENTITY foo_baz \"&#x2014;\">\r\n" + //
84+
"]>\r\n" + //
85+
"<root>\r\n" + //
86+
" &foo_b|ar;efgh\r\n" + // <- here definition for foo_bar
87+
"</root>";
88+
testDefinitionFor(xml, "test.xml", ll("test.xml", r(6, 2, 6, 9), r(2, 11, 2, 18)));
89+
}
90+
7891
// Test for external entities
7992

8093
public void external() throws BadLocationException, MalformedURIException {

0 commit comments

Comments
 (0)