Skip to content

Commit

Permalink
Add support for textDocument/documentHighlight for DTD
Browse files Browse the repository at this point in the history
Fixes #545

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jul 24, 2019
1 parent 5961c2d commit 9f0b5fd
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/
package org.eclipse.lsp4xml.dom;

import java.util.function.BiConsumer;

/**
* DTD Element Declaration <!ELEMENT
*
Expand Down Expand Up @@ -107,6 +109,59 @@ public DTDDeclParameter getParameterAt(int offset) {
return new DTDDeclParameter(this, paramStart, paramEnd);
}

@Override
public DTDDeclParameter getReferencedElementNameAt(int offset) {
return getParameterAt(offset);
}

/**
* Collect parameters which matches the given target.
*
* @param target the target
* @param collector the collector to collect parameters.
*/
public void collectParameters(DTDDeclParameter target, BiConsumer<DTDDeclParameter, DTDDeclParameter> collector) {
DTDDeclParameter name = getNameParameter();
if (name == null) {
return;
}
int start = name.getEnd();
int end = getEnd();

String text = getOwnerDocument().getText();
text.length();
int wordStart = -1;
int wordEnd = -1;
// Loop for content after <!ELEMENT element-name (
// to check if target checks words

// Ex : if 'svg' is the word to find (the target node)
// and we have <!ELEMENT element-name (svg, title, font-face)
// we must collect svg as child
for (int i = start; i < end; i++) {
char c = text.charAt(i);
// check if current character is valid for a word.
if (isValidChar(c)) {
if (wordStart == -1) {
// start of the word
wordStart = i;
}
} else if (wordStart != -1) {
// current character is not valid, it's the end of the word.
wordEnd = i;
}
if (wordStart != -1 && wordEnd != -1) {
// a word was found
boolean check = isMatchName(target.getParameter(), text, wordStart, wordEnd);
if (check) {
collector.accept(new DTDDeclParameter(this, wordStart, wordEnd), target);
}
wordStart = -1;
wordEnd = -1;
}
}
}

/**
* Returns the start word offset from the <code>from</code> offset to the
* <code>to</code> offse and -1 if no word.
Expand Down Expand Up @@ -164,9 +219,30 @@ private static boolean isValidChar(char c) {
return Character.isJavaIdentifierPart(c) || c == '-';
}

@Override
public DTDDeclParameter getReferencedElementNameAt(int offset) {
return getParameterAt(offset);
/**
* Returns true if the word in the given <code>text</code> which starts at
* <code>wordStart</code> offset and ends at <code>wordEnd</code> matches the
* given <code>searchWord</code>
*
* @param searchWord the word to search
* @param text the text
* @param wordStart the word start offset
* @param wordEnd the word end offset
* @return true if the word in the given <code>text</code> which starts at
* <code>wordStart</code> offset and ends at <code>wordEnd</code>
* matches the given <code>searchName</code>
*/
private static boolean isMatchName(String searchWord, String text, int wordStart, int wordEnd) {
int length = wordEnd - wordStart;
if (searchWord.length() != length) {
return false;
}
for (int j = 0; j < length; j++) {
if ((searchWord.charAt(j) != text.charAt(wordStart + j))) {
return false;
}
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelProvider;
import org.eclipse.lsp4xml.extensions.dtd.contentmodel.CMDTDContentModelProvider;
import org.eclipse.lsp4xml.extensions.dtd.participants.DTDDefinitionParticipant;
import org.eclipse.lsp4xml.extensions.dtd.participants.DTDHighlightingParticipant;
import org.eclipse.lsp4xml.extensions.dtd.participants.diagnostics.DTDDiagnosticsParticipant;
import org.eclipse.lsp4xml.services.extensions.IDefinitionParticipant;
import org.eclipse.lsp4xml.services.extensions.IHighlightingParticipant;
import org.eclipse.lsp4xml.services.extensions.IXMLExtension;
import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lsp4xml.services.extensions.diagnostics.IDiagnosticsParticipant;
Expand All @@ -29,10 +31,12 @@ public class DTDPlugin implements IXMLExtension {

private final IDiagnosticsParticipant diagnosticsParticipant;
private final IDefinitionParticipant definitionParticipant;
private IHighlightingParticipant highlightingParticipant;

public DTDPlugin() {
diagnosticsParticipant = new DTDDiagnosticsParticipant();
definitionParticipant = new DTDDefinitionParticipant();
highlightingParticipant = new DTDHighlightingParticipant();
}

@Override
Expand All @@ -50,6 +54,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) {
registry.registerDiagnosticsParticipant(diagnosticsParticipant);
// register definition participant
registry.registerDefinitionParticipant(definitionParticipant);
// register highlighting participant
registry.registerHighlightingParticipant(highlightingParticipant);
}

@Override
Expand All @@ -58,5 +64,7 @@ public void stop(XMLExtensionsRegistry registry) {
registry.unregisterDiagnosticsParticipant(diagnosticsParticipant);
// unregister definition participant
registry.unregisterDefinitionParticipant(definitionParticipant);
// unregister highlighting participant
registry.unregisterHighlightingParticipant(highlightingParticipant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lsp4xml.extensions.dtd.participants;

import java.util.List;

import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightKind;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.DTDAttlistDecl;
import org.eclipse.lsp4xml.dom.DTDDeclParameter;
import org.eclipse.lsp4xml.dom.DTDElementDecl;
import org.eclipse.lsp4xml.extensions.dtd.utils.DTDUtils;
import org.eclipse.lsp4xml.services.extensions.IHighlightingParticipant;
import org.eclipse.lsp4xml.utils.XMLPositionUtility;

/**
* DTD highlight participant
*
* @author Angelo ZERR
*
*/

public class DTDHighlightingParticipant implements IHighlightingParticipant {

@Override
public void findDocumentHighlights(DOMNode node, Position position, int offset, List<DocumentHighlight> highlights,
CancelChecker cancelChecker) {
boolean findReferences = false;
DTDDeclParameter parameter = null;
DTDElementDecl elementDecl = null;

if (node.isDTDElementDecl()) {
elementDecl = (DTDElementDecl) node;
if (elementDecl.isInNameParameter(offset)) {
// <!ELEMENT na|me --> here cursor is in the name of <!ELEMENT
// we must find all references from the <!ELEMENT which defines the name
findReferences = true;
parameter = elementDecl.getNameParameter();
} else {
// <!ELEMENT name (chi|ld --> here cursor is in the child element
// we must find only the <!ELEMENT child
parameter = elementDecl.getParameterAt(offset);
}
} else if (node.isDTDAttListDecl()) {
DTDAttlistDecl attlistDecl = (DTDAttlistDecl) node;
if (attlistDecl.isInNameParameter(offset)) {
// <!ATTLIST na|me --> here cusror is in the name of <!ATTLIST
// we must find only the <!ELEMENT name
parameter = attlistDecl.getNameParameter();
}
}

if (parameter == null) {
return;
}

if (findReferences) {
// case with <!ELEMENT na|me

// highlight <!ELEMENT na|me
DTDDeclParameter originNode = parameter;
highlights.add(
new DocumentHighlight(XMLPositionUtility.createRange(originNode), DocumentHighlightKind.Write));

// highlight all references of na|me in ATTLIST and child of <!ELEMENT
DTDUtils.searchDTDOriginElementDecls(elementDecl, (origin, target) -> {
highlights
.add(new DocumentHighlight(XMLPositionUtility.createRange(origin), DocumentHighlightKind.Read));
}, cancelChecker);
} else {
// case with
// - <!ELEMENT name (chi|ld
// - <!ATTLIST na|me

// highlight <!ELEMENT name (chi|ld or <!ATTLIST na|me
DTDDeclParameter targetNode = parameter;
highlights
.add(new DocumentHighlight(XMLPositionUtility.createRange(targetNode), DocumentHighlightKind.Read));

// highlight the target <!ELEMENT nam|e
DTDUtils.searchDTDTargetElementDecl(parameter, true, targetName -> {
highlights.add(
new DocumentHighlight(XMLPositionUtility.createRange(targetName), DocumentHighlightKind.Write));
});
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
*******************************************************************************/
package org.eclipse.lsp4xml.extensions.dtd.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4xml.dom.DOMDocumentType;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.DTDAttlistDecl;
import org.eclipse.lsp4xml.dom.DTDDeclNode;
import org.eclipse.lsp4xml.dom.DTDDeclParameter;
import org.eclipse.lsp4xml.dom.DTDElementDecl;
import org.w3c.dom.Node;
Expand All @@ -31,8 +37,8 @@ public class DTDUtils {
* node.
*
* @param originNameNode the origin name node (<ex :<!ATTLIST name).
* @param matchName true if the attribute value must match the value of
* target attribute value and false otherwise.
* @param matchName true if the origin name must match the value of target
* name and false otherwise.
* @param collector collector to collect DTD <!ELEMENT target name.
*/
public static void searchDTDTargetElementDecl(DTDDeclParameter originNameNode, boolean matchName,
Expand All @@ -59,6 +65,108 @@ public static void searchDTDTargetElementDecl(DTDDeclParameter originNameNode, b
}
}

/**
* Search origin DTD node (<!ATTRLIST element-name) or (child <!ELEMENT
* element-name (child)) from the given target DTD node (<!ELEMENT element-name.
*
* @param targetNode the referenced node
* @param collector the collector to collect reference between an origin and
* target attribute.
*/
public static void searchDTDOriginElementDecls(DTDDeclNode targetNode,
BiConsumer<DTDDeclParameter, DTDDeclParameter> collector, CancelChecker cancelChecker) {
// Collect all potential target DTD nodes (all <!ELEMENT)
List<DTDDeclNode> targetNodes = getTargetNodes(targetNode);
if (targetNodes.isEmpty()) {
// None referenced nodes, stop the search of references
return;
}

// Loop for each <!ELEMENT and check for each target DTD nodes if it reference
// it.
DOMDocumentType docType = targetNode.getOwnerDocType();
if (docType.hasChildNodes()) {
// Loop for <!ELEMENT.
NodeList children = docType.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (cancelChecker != null) {
cancelChecker.checkCanceled();
}
Node origin = children.item(i);
for (DTDDeclNode target : targetNodes) {
if (target.isDTDElementDecl()) {
// target node is <!ELEMENT, check if it is references by the current origin
// node
DTDElementDecl targetElement = (DTDElementDecl) target;
switch (origin.getNodeType()) {
case DOMNode.DTD_ELEMENT_DECL_NODE:
// check if the current <!ELEMENT origin defines a child which references the
// target node <!ELEMENT
// <!ELEMENT from > --> here target node is 'from'
// <!ELEMENT note(from)> --> here origin node is 'note'
// --> here 'note' has a 'from' as child and it should be collected
DTDElementDecl originElement = (DTDElementDecl) origin;
originElement.collectParameters(targetElement.getNameParameter(), collector);
break;
case DOMNode.DTD_ATT_LIST_NODE:
String name = targetElement.getName();
// check if the current <!ATTLIST element-name reference the current <!ELEMENT
// element-name
// <!ELEMENT note --> here target node is 'note'
// <!ATTLIST note ... -> here origin node is 'note'
// --> here <!ATTLIST defines 'note' as element name, and it should be collected
DTDAttlistDecl originAttribute = (DTDAttlistDecl) origin;
if (name.equals(originAttribute.getElementName())) {
// <!ATTLIST origin has the same name than <!ELEMENT target
collector.accept(originAttribute.getNameParameter(), targetElement.getNameParameter());
}
break;
}
}
}
}
}
}

/**
* Returns the referenced attributes list from the given referenced node.
*
* @param referencedNode the referenced node.
* @return the referenced attributes list from the given referenced node.
*/
private static List<DTDDeclNode> getTargetNodes(DTDDeclNode referencedNode) {
List<DTDDeclNode> referencedNodes = new ArrayList<>();
switch (referencedNode.getNodeType()) {
case DOMNode.DTD_ELEMENT_DECL_NODE:
addTargetNode(referencedNode, referencedNodes);
break;
case Node.DOCUMENT_TYPE_NODE:
DOMDocumentType docType = (DOMDocumentType) referencedNode;
if (docType.hasChildNodes()) {
// Loop for element <!ELEMENT.
NodeList children = docType.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == DOMNode.DTD_ELEMENT_DECL_NODE) {
addTargetNode((DTDElementDecl) node, referencedNodes);
}
}
}
break;
}
return referencedNodes;
}

private static void addTargetNode(DTDDeclNode referencedNode, List<DTDDeclNode> referencedNodes) {
if (referencedNode.isDTDElementDecl()) {
// Add only <!ELEMENT which defines a name.
DTDElementDecl elementDecl = (DTDElementDecl) referencedNode;
if (isValid(elementDecl)) {
referencedNodes.add(elementDecl);
}
}
}

/**
* Returns true if the given <!ELEMENT defines a name and false otherwise.
*
Expand Down

0 comments on commit 9f0b5fd

Please sign in to comment.