Include LemMinX source

This commit is contained in:
Akash Yadav
2022-09-25 15:29:15 +05:30
parent a7d92b4b01
commit 3b95d68a41
48 changed files with 10045 additions and 41 deletions
Binary file not shown.
+1 -6
View File
@@ -25,12 +25,7 @@ java {
}
dependencies {
api files ('libs/org.eclipse.lemminx-0.21.0.jar')
api (xml.lsp4j) {
exclude group: 'org.eclipse.lsp4j', module: 'org.eclipse.lsp4j.jsonrpc'
}
implementation project (path: ':shared')
implementation project (path: ':build-tools:jsonrpc')
implementation project(path: ':build-tools:jaxp')
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
/**
* Indicates the attempt to access a non-existing position. The attempt has been
* performed on a text store such as a document or string.
* <p>
* This class is not intended to be serialized.
* </p>
*
* This class is a copy/paste of org.eclipse.jface.text.BadLocationException.
*/
public class BadLocationException extends Exception {
/**
* Serial version UID for this class.
* <p>
* Note: This class is not intended to be serialized.
* </p>
*
* @since 3.1
*/
private static final long serialVersionUID = 3257281452776370224L;
/**
* Creates a new bad location exception.
*/
public BadLocationException() {
super();
}
/**
* Creates a new bad location exception.
*
* @param message the exception message
*/
public BadLocationException(String message) {
super(message);
}
}
@@ -0,0 +1,144 @@
/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
import com.itsaky.androidide.models.Position;
/**
* A line tracker maps character positions to line numbers and vice versa. Initially the line
* tracker is informed about its underlying text in order to initialize the mapping information.
* After that, the line tracker is informed about all changes of the underlying text allowing for
* incremental updates of the mapping information. It is the client's responsibility to actively
* inform the line tacker about text changes. For example, when using a line tracker in combination
* with a document the document controls the line tracker.
*
* <p>In order to provide backward compatibility for clients of <code>ILineTracker</code>, extension
* interfaces are used to provide a means of evolution. The following extension interfaces exist:
*
* <ul>
* <li>{@link org.eclipse.jface.text.ILineTrackerExtension} since version 3.1 introducing the
* concept of rewrite sessions.
* </ul>
*
* <p>Clients may implement this interface or use the standard implementation {@link
* org.eclipse.jface.text.DefaultLineTracker}or {@link
* org.eclipse.jface.text.ConfigurableLineTracker}.
*/
public interface ILineTracker {
/**
* Returns the line delimiter of the specified line. Returns <code>null</code> if the line is not
* closed with a line delimiter.
*
* @param line the line whose line delimiter is queried
* @return the line's delimiter or <code>null</code> if line does not have a delimiter
* @exception BadLocationException if the line number is invalid in this tracker's line structure
*/
String getLineDelimiter(int line) throws BadLocationException;
/**
* Computes the number of lines in the given text.
*
* @param text the text whose number of lines should be computed
* @return the number of lines in the given text
*/
int computeNumberOfLines(String text);
/**
* Returns the number of lines.
*
* <p>Note that a document always has at least one line.
*
* @return the number of lines in this tracker's line structure
*/
int getNumberOfLines();
/**
* Returns the number of lines which are occupied by a given text range.
*
* @param offset the offset of the specified text range
* @param length the length of the specified text range
* @return the number of lines occupied by the specified range
* @exception BadLocationException if specified range is unknown to this tracker
*/
int getNumberOfLines(int offset, int length) throws BadLocationException;
/**
* Returns the position of the first character of the specified line.
*
* @param line the line of interest
* @return offset of the first character of the line
* @exception BadLocationException if the line is unknown to this tracker
*/
int getLineOffset(int line) throws BadLocationException;
/**
* Returns length of the specified line including the line's delimiter.
*
* @param line the line of interest
* @return the length of the line
* @exception BadLocationException if line is unknown to this tracker
*/
int getLineLength(int line) throws BadLocationException;
/**
* Returns the line number the character at the given offset belongs to.
*
* @param offset the offset whose line number to be determined
* @return the number of the line the offset is on
* @exception BadLocationException if the offset is invalid in this tracker
*/
int getLineNumberOfOffset(int offset) throws BadLocationException;
/**
* Returns a line description of the line at the given offset. The description contains the start
* offset and the length of the line excluding the line's delimiter.
*
* @param offset the offset whose line should be described
* @return a region describing the line
* @exception BadLocationException if offset is invalid in this tracker
*/
Line getLineInformationOfOffset(int offset) throws BadLocationException;
/**
* Returns a line description of the given line. The description contains the start offset and the
* length of the line excluding the line's delimiter.
*
* @param line the line that should be described
* @return a region describing the line
* @exception BadLocationException if line is unknown to this tracker
*/
Line getLineInformation(int line) throws BadLocationException;
/**
* Informs the line tracker about the specified change in the tracked text.
*
* @param offset the offset of the replaced text
* @param length the length of the replaced text
* @param text the substitution text
* @exception BadLocationException if specified range is unknown to this tracker
*/
void replace(int offset, int length, String text) throws BadLocationException;
/**
* Sets the tracked text to the specified text.
*
* @param text the new tracked text
*/
void set(String text);
Position getPositionAt(int position) throws BadLocationException;
int getOffsetAt(Position position) throws BadLocationException;
}
@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* This class is a copy/paste of org.eclipse.jface.text.Line
*******************************************************************************/
package org.eclipse.lemminx.commons;
/**
* Describes a line as a particular number of characters beginning at
* a particular offset, consisting of a particular number of characters,
* and being closed with a particular line delimiter.
*/
final class Line /*implements IRegion*/ {
/** The offset of the line */
public int offset;
/** The length of the line */
public int length;
/** The delimiter of this line */
public final String delimiter;
/**
* Creates a new Line.
*
* @param offset the offset of the line
* @param end the last including character offset of the line
* @param delimiter the line's delimiter
*/
public Line(int offset, int end, String delimiter) {
this.offset= offset;
this.length= (end - offset) +1;
this.delimiter= delimiter;
}
/**
* Creates a new Line.
*
* @param offset the offset of the line
* @param length the length of the line
*/
public Line(int offset, int length) {
this.offset= offset;
this.length= length;
this.delimiter= null;
}
//@Override
public int getOffset() {
return offset;
}
//@Override
public int getLength() {
return length;
}
@Override
public String toString() {
return "Line [offset: " + offset + ", length: " + length + ", delimiter: '" + delimiter + "']"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
@@ -0,0 +1,455 @@
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* This class is a copy/paste of org.eclipse.jface.text.ListLineTracker
*******************************************************************************/
package org.eclipse.lemminx.commons;
import com.itsaky.androidide.models.Position;
import java.util.ArrayList;
import java.util.List;
/**
* Abstract, read-only implementation of <code>ILineTracker</code>. It lets the definition of line
* delimiters to subclasses. Assuming that '\n' is the only line delimiter, this abstract
* implementation defines the following line scheme:
*
* <ul>
* <li>"" -> [0,0]
* <li>"a" -> [0,1]
* <li>"\n" -> [0,1], [1,0]
* <li>"a\n" -> [0,2], [2,0]
* <li>"a\nb" -> [0,2], [2,1]
* <li>"a\nbc\n" -> [0,2], [2,3], [5,0]
* </ul>
*
* This class must be subclassed.
*
* @since 3.2
*/
class ListLineTracker implements ILineTracker {
/** The predefined delimiters of this tracker */
public static final String[] DELIMITERS = {
"\r", "\n", "\r\n"
}; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$
/** A predefined delimiter information which is always reused as return value */
private DelimiterInfo fDelimiterInfo = new DelimiterInfo();
/** The line information */
private final List<Line> fLines = new ArrayList<>();
/** The length of the tracked text */
private int fTextLength;
/**
* Combines the information of the occurrence of a line delimiter. <code>delimiterIndex</code> is
* the index where a line delimiter starts, whereas <code>delimiterLength</code>, indicates the
* length of the delimiter.
*/
protected static class DelimiterInfo {
public int delimiterIndex;
public int delimiterLength;
public String delimiter;
}
/** Creates a new line tracker. */
protected ListLineTracker() {}
/**
* Binary search for the line at a given offset.
*
* @param offset the offset whose line should be found
* @return the line of the offset
*/
private int findLine(int offset) {
if (fLines.size() == 0) {
return -1;
}
int left = 0;
int right = fLines.size();
while (left < right - 1) {
int mid = (right + left) >> 1;
Line line = fLines.get(mid);
if (line.offset <= offset) {
left = mid;
} else {
right = mid;
}
}
return (fLines.get(left).offset > offset) ? left - 1 : left;
}
@Override
public final Position getPositionAt(int offset) throws BadLocationException {
int lineNumber = getLineNumberOfOffset(offset);
int lines = fLines.size();
int character = 0;
if (lines > 0) {
if (lineNumber == lines) {
Line l = fLines.get(lineNumber - 1);
character = offset - l.offset - l.length;
} else {
Line l = fLines.get(lineNumber);
character = offset - l.offset;
}
}
return new Position(lineNumber, character);
}
/**
* Returns the number of lines covered by the specified text range.
*
* @param startLine the line where the text range starts
* @param offset the start offset of the text range
* @param length the length of the text range
* @return the number of lines covered by this text range
* @exception BadLocationException if range is undefined in this tracker
*/
private int getNumberOfLines(int startLine, int offset, int length) throws BadLocationException {
if (length == 0) {
return 1;
}
int target = offset + length;
Line l = fLines.get(startLine);
if (l.delimiter == null) {
return 1;
}
if (l.offset + l.length > target) {
return 1;
}
if (l.offset + l.length == target) {
return 2;
}
return getLineNumberOfOffset(target) - startLine + 1;
}
@Override
public final int getLineLength(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException();
}
if (lines == 0 || lines == line) {
return 0;
}
Line l = fLines.get(line);
return l.length;
}
@Override
public final int getLineNumberOfOffset(int position) throws BadLocationException {
if (position < 0) {
throw new BadLocationException("Negative offset : " + position); // $NON-NLS-1$
} else if (position > fTextLength) {
throw new BadLocationException(
"Offset > length: " + position + " > " + fTextLength); // $NON-NLS-1$//$NON-NLS-2$
}
if (position == fTextLength) {
int lastLine = fLines.size() - 1;
if (lastLine < 0) {
return 0;
}
Line l = fLines.get(lastLine);
return (l.delimiter != null ? lastLine + 1 : lastLine);
}
return findLine(position);
}
@Override
public int getOffsetAt(Position position) throws BadLocationException {
int line = position.getLine();
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
int lineOffset = -1;
int lineLength = -1;
if (lines == 0) {
lineOffset = 0;
lineLength = 0;
} else {
if (line == lines) {
Line l = fLines.get(line - 1);
lineOffset = l.offset + l.length;
lineLength = 0;
} else {
Line l = fLines.get(line);
lineOffset = l.offset;
lineLength = l.delimiter != null ? l.length - l.delimiter.length() : l.length;
}
}
int character = position.getColumn();
int offset = lineOffset + character;
int endLineOffset = lineOffset + lineLength;
if (offset > endLineOffset) {
throw new BadLocationException(
"The character value, {" + character + "} of the line" + line + "}, is out of bounds.");
}
return offset;
}
@Override
public final Line getLineInformationOfOffset(int position) throws BadLocationException {
if (position > fTextLength) {
throw new BadLocationException(
"Offset > length: " + position + " > " + fTextLength); // $NON-NLS-1$//$NON-NLS-2$
}
if (position == fTextLength) {
int size = fLines.size();
if (size == 0) {
return new Line(0, 0);
}
Line l = fLines.get(size - 1);
return (l.delimiter != null
? new Line(fTextLength, 0)
: new Line(fTextLength - l.length, l.length));
}
return getLineInformation(findLine(position));
}
@Override
public final Line getLineInformation(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
if (lines == 0) {
return new Line(0, 0);
}
if (line == lines) {
Line l = fLines.get(line - 1);
return new Line(l.offset + l.length, 0);
}
Line l = fLines.get(line);
return (l.delimiter != null ? new Line(l.offset, l.length - l.delimiter.length()) : l);
}
@Override
public final int getLineOffset(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
if (lines == 0) {
return 0;
}
if (line == lines) {
Line l = fLines.get(line - 1);
if (l.delimiter != null) {
return l.offset + l.length;
}
throw new BadLocationException();
}
Line l = fLines.get(line);
return l.offset;
}
@Override
public final int getNumberOfLines() {
int lines = fLines.size();
if (lines == 0) {
return 1;
}
Line l = fLines.get(lines - 1);
return (l.delimiter != null ? lines + 1 : lines);
}
@Override
public final int getNumberOfLines(int position, int length) throws BadLocationException {
if (position < 0 || position + length > fTextLength) {
throw new BadLocationException();
}
if (length == 0) {
return 1;
}
return getNumberOfLines(getLineNumberOfOffset(position), position, length);
}
@Override
public final int computeNumberOfLines(String text) {
int count = 0;
int start = 0;
DelimiterInfo delimiterInfo = nextDelimiterInfo(text, start);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
++count;
start = delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength;
delimiterInfo = nextDelimiterInfo(text, start);
}
return count;
}
public final String getLineDelimiter(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
if (lines == 0) {
return null;
}
if (line == lines) {
return null;
}
Line l = fLines.get(line);
return l.delimiter;
}
/**
* Returns the information about the first delimiter found in the given text starting at the given
* offset.
*
* @param text the text to be searched
* @param offset the offset in the given text
* @return the information of the first found delimiter or <code>null</code>
*/
protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
char ch;
int length = text.length();
for (int i = offset; i < length; i++) {
ch = text.charAt(i);
if (ch == '\r') {
if (i + 1 < length) {
if (text.charAt(i + 1) == '\n') {
fDelimiterInfo.delimiter = DELIMITERS[2];
fDelimiterInfo.delimiterIndex = i;
fDelimiterInfo.delimiterLength = 2;
return fDelimiterInfo;
}
}
fDelimiterInfo.delimiter = DELIMITERS[0];
fDelimiterInfo.delimiterIndex = i;
fDelimiterInfo.delimiterLength = 1;
return fDelimiterInfo;
} else if (ch == '\n') {
fDelimiterInfo.delimiter = DELIMITERS[1];
fDelimiterInfo.delimiterIndex = i;
fDelimiterInfo.delimiterLength = 1;
return fDelimiterInfo;
}
}
return null;
}
/**
* Creates the line structure for the given text. Newly created lines are inserted into the line
* structure starting at the given position. Returns the number of newly created lines.
*
* @param text the text for which to create a line structure
* @param insertPosition the position at which the newly created lines are inserted into the
* tracker's line structure
* @param offset the offset of all newly created lines
* @return the number of newly created lines
*/
private int createLines(String text, int insertPosition, int offset) {
int count = 0;
int start = 0;
DelimiterInfo delimiterInfo = nextDelimiterInfo(text, 0);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
int index = delimiterInfo.delimiterIndex + (delimiterInfo.delimiterLength - 1);
if (insertPosition + count >= fLines.size()) {
fLines.add(new Line(offset + start, offset + index, delimiterInfo.delimiter));
} else {
fLines.add(
insertPosition + count,
new Line(offset + start, offset + index, delimiterInfo.delimiter));
}
++count;
start = index + 1;
delimiterInfo = nextDelimiterInfo(text, start);
}
if (start < text.length()) {
if (insertPosition + count < fLines.size()) {
// there is a line below the current
Line l = fLines.get(insertPosition + count);
int delta = text.length() - start;
l.offset -= delta;
l.length += delta;
} else {
fLines.add(new Line(offset + start, offset + text.length() - 1, null));
++count;
}
}
return count;
}
@Override
public final void replace(int position, int length, String text) throws BadLocationException {
throw new UnsupportedOperationException();
}
@Override
public final void set(String text) {
fLines.clear();
if (text != null) {
fTextLength = text.length();
createLines(text, 0, 0);
}
}
/**
* Returns the internal data structure, a {@link List} of {@link Line}s.
*
* @return the internal list of lines.
*/
final List<Line> getLines() {
return fLines;
}
}
@@ -0,0 +1,127 @@
/**
* Copyright (c) 2018 Angelo ZERR. 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
*
* <p>SPDX-License-Identifier: EPL-2.0
*
* <p>Contributors: Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.commons;
import com.itsaky.androidide.models.Position;
import com.itsaky.androidide.models.Range;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextDocument {
private static String DEFAULT_DELIMTER = System.lineSeparator();
private ILineTracker lineTracker;
private String text;
private String uri;
public TextDocument(String text, String uri) {
setUri(uri);
setText(text);
}
public void setUri(final String uri) {
this.uri = uri;
}
public String getUri() {
return uri;
}
public void setText(final String text) {
this.text = text;
}
public String getText() {
return text;
}
public Position positionAt(int position) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
return lineTracker.getPositionAt(position);
}
public int offsetAt(Position position) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
return lineTracker.getOffsetAt(position);
}
public String lineText(int lineNumber) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
Line line = lineTracker.getLineInformation(lineNumber);
String text = getText();
return text.substring(line.offset, line.offset + line.length);
}
public int lineOffsetAt(int position) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
Line line = lineTracker.getLineInformationOfOffset(position);
return line.offset;
}
public String lineDelimiter(int lineNumber) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
String lineDelimiter = lineTracker.getLineDelimiter(lineNumber);
if (lineDelimiter == null) {
if (lineTracker.getNumberOfLines() > 0) {
lineDelimiter = lineTracker.getLineInformation(0).delimiter;
}
}
if (lineDelimiter == null) {
lineDelimiter = DEFAULT_DELIMTER;
}
return lineDelimiter;
}
public Range getWordRangeAt(int textOffset, Pattern wordDefinition) {
try {
Position pos = positionAt(textOffset);
ILineTracker lineTracker = getLineTracker();
Line line = lineTracker.getLineInformation(pos.getLine());
String text = getText();
String lineText = text.substring(line.offset, textOffset);
int position = lineText.length();
Matcher m = wordDefinition.matcher(lineText);
int currentPosition = 0;
while (currentPosition != position) {
if (m.find()) {
currentPosition = m.end();
if (currentPosition == position) {
return new Range(new Position(pos.getLine(), m.start()), pos);
}
} else {
currentPosition++;
}
m.region(currentPosition, position);
}
return new Range(pos, pos);
} catch (BadLocationException e) {
return null;
}
}
private ILineTracker getLineTracker() {
if (lineTracker == null) {
lineTracker = createLineTracker();
}
return lineTracker;
}
private synchronized ILineTracker createLineTracker() {
if (lineTracker != null) {
return lineTracker;
}
ILineTracker lineTracker = new ListLineTracker();
lineTracker.set(getText());
return lineTracker;
}
}
@@ -0,0 +1,462 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.List;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.TypeInfo;
/**
* An attribute node.
*
*/
public class DOMAttr extends DOMNode implements org.w3c.dom.Attr {
public static final String XMLNS_ATTR = "xmlns";
public static final String XMLNS_NO_DEFAULT_ATTR = "xmlns:";
private String name;
private final AttrName nodeAttrName;
private int delimiter;
private AttrValue nodeAttrValue;
private String quotelessValue;// Value without quotes
private String originalValue;// Exact value from document
private final DOMNode ownerElement;
abstract class AttrNameOrValue implements DOMRange {
private final int start;
private final int end;
public AttrNameOrValue(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
public DOMAttr getOwnerAttr() {
return DOMAttr.this;
}
@Override
public DOMDocument getOwnerDocument() {
return getOwnerAttr().getOwnerDocument();
}
public String getContent() {
return getOwnerDocument().getText().substring(getStart(), getEnd());
}
}
class AttrName extends AttrNameOrValue {
public AttrName(int start, int end) {
super(start, end);
}
}
class AttrValue extends AttrNameOrValue {
public AttrValue(int start, int end) {
super(start, end);
}
@Override
public String getContent() {
if (getOwnerAttr().delimiter < getStart()) {
return super.getContent();
}
return null;
}
}
public DOMAttr(String name, DOMNode ownerElement) {
this(name, NULL_VALUE, NULL_VALUE, ownerElement);
}
public DOMAttr(String name, int start, int end, DOMNode ownerElement) {
super(NULL_VALUE, NULL_VALUE);
this.name = name;
this.delimiter = NULL_VALUE;
this.nodeAttrName = start != -1 ? new AttrName(start, end) : null;
this.ownerElement = ownerElement;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.ATTRIBUTE_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getName();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getName()
*/
@Override
public String getName() {
if (name == null && nodeAttrName != null) {
name = nodeAttrName.getContent();
}
return name;
}
@Override
public String getNodeValue() throws DOMException {
return getValue();
}
@Override
public String getLocalName() {
String name = getName();
int colonIndex = name.indexOf(":");
if (colonIndex > 0) {
return name.substring(colonIndex + 1);
}
return name;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getOwnerElement()
*/
public DOMElement getOwnerElement() {
return ownerElement.isElement() ? (DOMElement) ownerElement : null;
}
@Override
public DOMDocument getOwnerDocument() {
return ownerElement != null ? ownerElement.getOwnerDocument() : null;
}
/*
*
* Returns the attribute's value without quotes.
*/
@Override
public String getValue() {
getOriginalValue();
return quotelessValue;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getSchemaTypeInfo()
*/
@Override
public TypeInfo getSchemaTypeInfo() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getSpecified()
*/
@Override
public boolean getSpecified() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#isId()
*/
@Override
public boolean isId() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#setValue(java.lang.String)
*/
@Override
public void setValue(String value) throws DOMException {
setValue(value, NULL_VALUE, NULL_VALUE);
}
public DOMRange getNodeAttrName() {
return nodeAttrName;
}
public void setDelimiter(int delimiter) {
this.delimiter = delimiter;
}
public boolean hasDelimiter() {
return delimiter != NULL_VALUE;
}
/**
* Get original attribute value from the document.
*
* This will include quotations (", ').
*
* @return attribute value with quotations if it had them.
*/
public String getOriginalValue() {
if (originalValue == null && nodeAttrValue != null) {
originalValue = nodeAttrValue.getContent();
this.quotelessValue = StringUtils.convertToQuotelessValue(originalValue);
}
return originalValue;
}
public void setValue(String value, int start, int end) {
this.originalValue = value;
this.quotelessValue = StringUtils.convertToQuotelessValue(value);
this.nodeAttrValue = start != -1 ? new AttrValue(start, end) : null;
}
public DOMRange getNodeAttrValue() {
return nodeAttrValue;
}
public boolean valueContainsOffset(int offset) {
return nodeAttrValue != null && offset >= nodeAttrValue.getStart() && offset < nodeAttrValue.getEnd();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getPrefix()
*/
@Override
public String getPrefix() {
String name = getName();
if (name == null) {
return null;
}
String prefix = null;
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
prefix = name.substring(0, index);
}
return prefix;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNamespaceURI()
*/
@Override
public String getNamespaceURI() {
if (ownerElement == null || ownerElement.getNodeType() != Node.ELEMENT_NODE) {
return null;
}
String prefix = getPrefix();
// Try to get xmlns attribute from the element
return ((DOMElement) ownerElement).getNamespaceURI(prefix);
}
/**
* Returns true if attribute name is a xmlns attribute and false otherwise.
*
* @param attributeName
* @return true if attribute name is a xmlns attribute and false otherwise.
*/
public boolean isXmlns() {
return isXmlns(getName());
}
public static boolean isXmlns(String attributeName) {
return attributeName.startsWith(XMLNS_ATTR);
}
/**
* Returns true if attribute name is the default xmlns attribute and false
* otherwise.
*
* @param attributeName
* @return true if attribute name is the default xmlns attribute and false
* otherwise.
*/
public boolean isDefaultXmlns() {
return isDefaultXmlns(getName());
}
public static boolean isDefaultXmlns(String attributeName) {
return attributeName.equals(XMLNS_ATTR);
}
public String extractPrefixFromXmlns() {
String name = getName();
if (isDefaultXmlns()) {
return name.substring(XMLNS_ATTR.length(), name.length());
}
return name.substring(XMLNS_NO_DEFAULT_ATTR.length(), name.length());
}
/**
* Returns the prefix if the given URI matches this attributes value.
*
* If the URI doesnt match, null is returned.
*
* @param uri
* @return
*/
public String getPrefixIfMatchesURI(String uri) {
if (isXmlns()) {
String value = getValue();
if (value != null && value.equals(uri)) {
if (isDefaultXmlns()) {
// xmlns="http://"
return null;
}
// xmlns:xxx="http://"
return extractPrefixFromXmlns();
}
}
return null;
}
/**
* Returns true if attribute name is the no default xmlns attribute and false
* otherwise.
*
* @param attributeName
* @return true if attribute name is the no default xmlns attribute and false
* otherwise.
*/
public boolean isNoDefaultXmlns() {
return isNoDefaultXmlns(getName());
}
public static boolean isNoDefaultXmlns(String attributeName) {
return attributeName.startsWith(XMLNS_NO_DEFAULT_ATTR);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNextSibling()
*/
@Override
public DOMNode getNextSibling() {
DOMNode parentNode = getOwnerElement();
if (parentNode == null) {
return null;
}
List<DOMAttr> children = parentNode.getAttributeNodes();
int nextIndex = children.indexOf(this) + 1;
return nextIndex < children.size() ? children.get(nextIndex) : null;
}
public boolean isIncluded(int offset) {
return DOMNode.isIncluded(getStart(), getEnd(), offset);
}
@Override
public int getStart() {
return nodeAttrName.getStart();
}
@Override
public int getEnd() {
if (nodeAttrValue != null) {
// <foo attr="value"| >
return nodeAttrValue.getEnd();
}
if (hasDelimiter()) {
// <foo attr=| >
return delimiter + 1;
}
// <foo attr| >
return nodeAttrName.getEnd();
}
@Override
public int hashCode() {
String name = getName();
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((quotelessValue == null) ? 0 : quotelessValue.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DOMAttr other = (DOMAttr) obj;
String name = getName();
if (name == null) {
if (other.getName() != null) {
return false;
}
} else if (!name.equals(other.getName())) {
return false;
}
String value = getValue();
if (value == null) {
if (other.getValue() != null) {
return false;
}
} else if (!value.equals(other.getValue())) {
return false;
}
return true;
}
public int getDelimiterOffset() {
return delimiter;
}
}
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.CDATASection;
/**
* A CData section node.
*
*/
public class DOMCDATASection extends DOMText implements CDATASection {
int startContent;
int endContent;
public DOMCDATASection(int start, int end) {
super(start, end);
}
@Override
public int getStartContent() {
return startContent;
}
@Override
public int getEndContent() {
return endContent;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.w3c.dom.Text#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.CDATA_SECTION_NODE;
}
}
@@ -0,0 +1,243 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import static java.lang.System.lineSeparator;
import java.util.List;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
/**
* A CharacterData node.
*
*/
public abstract class DOMCharacterData extends DOMNode implements org.w3c.dom.CharacterData {
private String data;
private String normalizedData;
private boolean isWhitespace;
private String delimiter;
public DOMCharacterData(int start, int end) {
super(start, end);
}
public boolean hasMultiLine() {
return getData().contains(getDelimiter());
}
public String getDelimiter() {
if (delimiter != null) {
return delimiter;
}
try {
delimiter = getOwnerDocument().getTextDocument().lineDelimiter(0);
return delimiter;
} catch (BadLocationException e) {
delimiter = lineSeparator();
return delimiter;
}
}
/**
* If data ends with a new line character.
*
* Returns false if a character is found before a new line. Non-newline
* whitespace will be ignored while searching.
*
* If no data exists, returns false.
*
* @return true if newline character ocurrs before non-whitespace character
*/
public boolean endsWithNewLine() {
if (hasData()) {
for (int i = data.length() - 1; i >= 0; i--) {
char c = data.charAt(i);
if (!Character.isWhitespace(c)) {
return false;
}
if (c == '\n') {
return true;
}
}
}
return false;
}
/**
* If data ends with a new line character.
*
* Returns false if a character is found before a new line. Non-newline
* whitespace will be ignored while searching.
*
* @return true if newline character ocurrs before non-whitespace character
*/
public boolean startsWithNewLine() {
if (hasData()) {
for (int i = 0; i < data.length(); i++) {
char c = data.charAt(i);
if (!Character.isWhitespace(c)) {
return false;
}
if (c == '\n' || c == '\r') {
return true;
}
}
}
return false;
}
public String getNormalizedData() {
if (normalizedData == null) {
normalizedData = StringUtils.normalizeSpace(getData());
}
return normalizedData;
}
public boolean hasData() {
return !getData().isEmpty();
}
/**
* Returns true if this node has sibling nodes.
*/
public boolean hasSiblings() {
List<DOMNode> childrenOfParent = this.parent.getChildren();
return childrenOfParent.size() > 1;
}
public int getStartContent() {
return start;
}
public int getEndContent() {
return end;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#getData()
*/
@Override
public String getData() {
if (data == null) {
data = getOwnerDocument().getText().substring(getStartContent(), getEndContent());
}
return data;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeValue()
*/
@Override
public String getNodeValue() throws DOMException {
return getData();
}
/**
* @return the isWhitespace
*/
public boolean isWhitespace() {
return isWhitespace;
}
/**
* Set true if this node's data is all whitespace
*
* @param isWhitespace
*/
public void setWhitespace(boolean isWhitespace) {
this.isWhitespace = isWhitespace;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#appendData(java.lang.String)
*/
@Override
public void appendData(String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#deleteData(int, int)
*/
@Override
public void deleteData(int offset, int count) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#getLength()
*/
@Override
public int getLength() {
return getData().length();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#insertData(int, java.lang.String)
*/
@Override
public void insertData(int offset, String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#replaceData(int, int, java.lang.String)
*/
@Override
public void replaceData(int offset, int count, String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#setData(java.lang.String)
*/
@Override
public void setData(String value) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#substringData(int, int)
*/
@Override
public String substringData(int offset, int count) throws DOMException {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
/**
* A Comment node.
*
*/
public class DOMComment extends DOMCharacterData implements org.w3c.dom.Comment {
boolean commentSameLineEndTag;
int startContent;
int endContent;
public DOMComment(int start, int end) {
super(start, end);
}
public boolean isCommentSameLineEndTag() {
return commentSameLineEndTag;
}
@Override
public int getStartContent() {
return startContent;
}
@Override
public int getEndContent() {
return endContent;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.COMMENT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return "#comment";
}
}
@@ -0,0 +1,920 @@
/**
* Copyright (c) 2018-2020 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom;
import com.itsaky.androidide.models.Position;
import com.itsaky.androidide.models.Range;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.parser.Constants;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.w3c.dom.CDATASection;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NodeList;
/**
* XML document.
*
*/
public class DOMDocument extends DOMNode implements Document {
private SchemaLocation schemaLocation;
private NoNamespaceSchemaLocation noNamespaceSchemaLocation;
private boolean referencedExternalGrammarInitialized;
private boolean referencedSchemaInitialized;
private final URIResolverExtensionManager resolverExtensionManager;
private final TextDocument textDocument;
private boolean hasNamespaces;
private Map<String, String> externalGrammarLocation;
private String schemaInstancePrefix;
private String schemaPrefix;
private CancelChecker cancelChecker;
private String externalGrammarFromNamespaceURI;
public DOMDocument(TextDocument textDocument, URIResolverExtensionManager resolverExtensionManager) {
super(0, textDocument.getText().length());
this.textDocument = textDocument;
this.resolverExtensionManager = resolverExtensionManager;
resetGrammar();
}
public void setCancelChecker(CancelChecker cancelChecker) {
this.cancelChecker = cancelChecker;
}
public CancelChecker getCancelChecker() {
return cancelChecker;
}
public List<DOMNode> getRoots() {
return super.getChildren();
}
public Position positionAt(int offset) throws BadLocationException {
checkCanceled();
return textDocument.positionAt(offset);
}
public int offsetAt(Position position) throws BadLocationException {
checkCanceled();
return textDocument.offsetAt(position);
}
public String lineText(int lineNumber) throws BadLocationException {
checkCanceled();
return textDocument.lineText(lineNumber);
}
public String lineDelimiter(int lineNumber) throws BadLocationException {
checkCanceled();
return textDocument.lineDelimiter(lineNumber);
}
public LineIndentInfo getLineIndentInfo(int lineNumber) throws BadLocationException {
String lineText = lineText(lineNumber);
String lineDelimiter = lineDelimiter(lineNumber);
String whitespacesIndent = StringUtils.getStartWhitespaces(lineText);
return new LineIndentInfo(lineDelimiter, whitespacesIndent);
}
/**
* Returns the element name on the left of the given position and null
* otherwise.
*
* @param textOffset
* @return the element name on the left of the given position and null
* otherwise.
*/
public Range getElementNameRangeAt(int textOffset) {
checkCanceled();
return textDocument.getWordRangeAt(textOffset, Constants.ELEMENT_NAME_REGEX);
}
@Override
public String getNamespaceURI() {
DOMElement documentElement = getDocumentElement();
return documentElement != null ? documentElement.getNamespaceURI() : null;
}
/**
* Returns the text content of the XML document.
*
* @return the text content of the XML document.
*/
public String getText() {
return textDocument.getText();
}
public TextDocument getTextDocument() {
return textDocument;
}
/**
* Returns true if the document is bound to a grammar and false otherwise.
*
* @return true if the document is bound to a grammar and false otherwise.
*/
public boolean hasGrammar() {
return hasDTD() || hasSchemaLocation() || hasNoNamespaceSchemaLocation() || hasExternalGrammar();
}
// -------------------------- Grammar with XML Schema
/**
* Returns the declared "xsi:schemaLocation" and null otherwise.
*
* @return the declared "xsi:schemaLocation" and null otherwise.
*/
public SchemaLocation getSchemaLocation() {
initializeReferencedSchemaIfNeeded();
return schemaLocation;
}
/**
* Returns true if XML root element declares a "xsi:schemaLocation" and false
* otherwise.
*
* @return true if XML root element declares a "xsi:schemaLocation" and false
* otherwise.
*/
public boolean hasSchemaLocation() {
return getSchemaLocation() != null;
}
/**
* Returns the declared "xsi:noNamespaceSchemaLocation" and null otherwise.
*
* @return the declared "xsi:noNamespaceSchemaLocation" and null otherwise.
*/
public NoNamespaceSchemaLocation getNoNamespaceSchemaLocation() {
initializeReferencedSchemaIfNeeded();
return noNamespaceSchemaLocation;
}
/**
* Returns true if XML root element declares a "xsi:noNamespaceSchemaLocation"
* and false otherwise.
*
* @return true if XML root element declares a "xsi:noNamespaceSchemaLocation"
* and false otherwise.
*/
public boolean hasNoNamespaceSchemaLocation() {
return getNoNamespaceSchemaLocation() != null;
}
/**
* Returns true if document defines namespaces (with xmlns) and false otherwise.
*
* @return true if document defines namespaces (with xmlns) and false otherwise.
*/
public boolean hasNamespaces() {
initializeReferencedSchemaIfNeeded();
return hasNamespaces;
}
/**
* Returns the (xsi) schema instance prefix and null otherwise.
*
* @return the (xsi) schema instance prefix and null otherwise.
*/
public String getSchemaInstancePrefix() {
initializeReferencedSchemaIfNeeded();
return schemaInstancePrefix;
}
/**
* Returns true if (xsi) schema instance prefix exists.
*
* @return true if (xsi) schema instance prefix exists.
*/
public boolean hasSchemaInstancePrefix() {
initializeReferencedSchemaIfNeeded();
return schemaInstancePrefix != null;
}
/**
* Initialize schemaLocation, noNamespaceSchemaLocation and hasNamespaces
* information if needed.
*/
private void initializeReferencedSchemaIfNeeded() {
if (referencedSchemaInitialized) {
return;
}
initializeReferencedSchema();
referencedSchemaInitialized = true;
}
/**
* Initialize namespaces and schema location declaration .
*/
private synchronized void initializeReferencedSchema() {
if (referencedSchemaInitialized) {
return;
}
// Get root element
DOMElement documentElement = getDocumentElement();
if (documentElement == null) {
return;
}
schemaInstancePrefix = null;
schemaPrefix = null;
// Search if document element root declares namespace with "xmlns".
if (documentElement.hasAttributes()) {
for (DOMAttr attr : documentElement.getAttributeNodes()) {
String attributeName = attr.getName();
if (attributeName != null) {
if (attributeName.equals(DOMAttr.XMLNS_ATTR)
|| attributeName.startsWith(DOMAttr.XMLNS_NO_DEFAULT_ATTR)) {
hasNamespaces = true;
String attributeValue = documentElement.getAttribute(attributeName);
if (attributeValue != null && attributeValue.startsWith("http://www.w3.org/")) {
if (attributeValue.endsWith("/XMLSchema-instance")) {
schemaInstancePrefix = attributeName.equals("xmlns") ? ""
: getUnprefixedName(attributeName);
} else if (attributeValue.endsWith("/XMLSchema")) {
schemaPrefix = attributeName.equals("xmlns") ? "" : getUnprefixedName(attributeName);
}
}
}
}
}
if (schemaInstancePrefix != null) {
// DOM document can declared xsi:noNamespaceSchemaLocation and
// xsi:schemaLocation both even it's not valid
noNamespaceSchemaLocation = createNoNamespaceSchemaLocation(documentElement, schemaInstancePrefix);
schemaLocation = createSchemaLocation(documentElement, schemaInstancePrefix);
}
}
}
/**
* If document has {@code <?xml ... ?>}
*
* @return
*/
public boolean hasProlog() {
List<DOMNode> children = getChildren();
return (children != null && !children.isEmpty() && children.get(0).isProlog());
}
/**
* If document has {@code <?xml ... ?>}, return this prolog, null otherwise
*
* @return prolog DOMNode, if the document has one, null otherwise
*/
public DOMNode getProlog() {
if (hasProlog()) {
return getChild(0);
} else {
return null;
}
}
/**
* Returns true, if the given offset is before XML declaration
* ({@code <?xml ...?>}), false otherwise.
*
* @param offset the offset position in question
* @return true, if before xml declaration; false otherwise
*/
public boolean isBeforeProlog(int offset) {
return hasProlog() && offset <= getProlog().getStart();
}
private static SchemaLocation createSchemaLocation(DOMNode root, String schemaInstancePrefix) {
DOMAttr attr = root.getAttributeNode(getPrefixedName(schemaInstancePrefix, "schemaLocation"));
// Check that the attribute and the attribute value of xsi:schemaLocation is not null
if (attr == null || attr.getNodeAttrValue() == null) {
return null;
}
return new SchemaLocation(attr);
}
private static NoNamespaceSchemaLocation createNoNamespaceSchemaLocation(DOMNode root,
String schemaInstancePrefix) {
DOMAttr attr = root.getAttributeNode(getPrefixedName(schemaInstancePrefix, "noNamespaceSchemaLocation"));
if (attr == null || attr.getValue() == null) {
return null;
}
return new NoNamespaceSchemaLocation(attr);
}
// -------------------------- Grammar with DTD
/**
* Returns true if XML document has a DTD declaration and false otherwise.
*
* @return true if XML document has a DTD declaration and false otherwise.
*/
public boolean hasDTD() {
return getDoctype() != null;
}
// -------------------------- External Grammar (XML file associations, catalog)
/**
* Returns true if the document is bound to an external grammar (XML file
* associations, XLM catalog) and false otherwise.
*
* @return true if the document is bound to an external grammar (XML file
* associations, XLM catalog) and false otherwise.
*/
public boolean hasExternalGrammar() {
return getExternalGrammarLocation() != null || getExternalGrammarFromNamespaceURI() != null;
}
/**
* Returns the external grammar location (XSD, DTD from xml file associations)
* and null otherwise.
*
* @return the external grammar location (XSD, DTD from xml file associations)
* and null otherwise.
*/
public Map<String, String> getExternalGrammarLocation() {
initializeReferencedExternalGrammarIfNeeded();
return externalGrammarLocation;
}
/**
* Returns the grammar location found by the namespace URI from the document
* root element (ex : found with XML catalog) and null otherwise.
*
* @return the grammar location found by the namespace URI from the document
* root element (ex : found with XML catalog) and null otherwise.
*/
public String getExternalGrammarFromNamespaceURI() {
initializeReferencedExternalGrammarIfNeeded();
return externalGrammarFromNamespaceURI;
}
private void initializeReferencedExternalGrammarIfNeeded() {
if (referencedExternalGrammarInitialized) {
return;
}
intializeExternalGrammar();
}
private synchronized void intializeExternalGrammar() {
if (referencedExternalGrammarInitialized) {
return;
}
try {
if (resolverExtensionManager != null) {
// No grammar found with standard mean, check if it some components like XML
// file associations bind this XML document to a grammar with external schema
// location.
try {
externalGrammarLocation = resolverExtensionManager
.getExternalGrammarLocation(new URI(getDocumentURI()));
if (externalGrammarLocation != null) {
return;
}
} catch (URISyntaxException e) {
// Do nothing
}
// No grammar found with standard mean and external schema location, check if
// it some components like XML
// Catalog, XSL and XSD resolvers, etc bind this XML document to a grammar.
// Get root element
DOMElement documentElement = getDocumentElement();
if (documentElement == null) {
return;
}
String namespaceURI = documentElement.getNamespaceURI();
externalGrammarFromNamespaceURI = resolverExtensionManager.resolve(getDocumentURI(), namespaceURI,
null);
}
} finally {
referencedExternalGrammarInitialized = true;
}
}
private static String getUnprefixedName(String name) {
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
name = name.substring(index + 1);
}
return name;
}
private static String getPrefixedName(String prefix, String localName) {
return prefix != null && prefix.length() > 0 ? prefix + ":" + localName : localName; //$NON-NLS-1$
}
public DOMElement createElement(int start, int end) {
return new DOMElement(start, end);
}
public DOMCDATASection createCDataSection(int start, int end) {
return new DOMCDATASection(start, end);
}
public DOMProcessingInstruction createProcessingInstruction(int start, int end) {
return new DOMProcessingInstruction(start, end);
}
public DOMComment createComment(int start, int end) {
return new DOMComment(start, end);
}
public DOMText createText(int start, int end) {
return new DOMText(start, end);
}
public DOMDocumentType createDocumentType(int start, int end) {
return new DOMDocumentType(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.DOCUMENT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return "#document";
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDocumentElement()
*/
@Override
public DOMElement getDocumentElement() {
List<DOMNode> roots = getRoots();
if (roots != null) {
for (DOMNode node : roots) {
if (node.isElement()) {
return (DOMElement) node;
}
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDoctype()
*/
@Override
public DOMDocumentType getDoctype() {
List<DOMNode> roots = getRoots();
if (roots != null) {
for (DOMNode node : roots) {
if (node.isDoctype()) {
return (DOMDocumentType) node;
}
}
}
return null;
}
@Override
public DOMDocument getOwnerDocument() {
return this;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDocumentURI()
*/
@Override
public String getDocumentURI() {
return textDocument.getUri();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setDocumentURI(java.lang.String)
*/
@Override
public void setDocumentURI(String documentURI) {
textDocument.setUri(documentURI);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#adoptNode(org.w3c.dom.Node)
*/
@Override
public DOMNode adoptNode(org.w3c.dom.Node source) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createAttribute(java.lang.String)
*/
@Override
public DOMAttr createAttribute(String name) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createAttributeNS(java.lang.String,
* java.lang.String)
*/
@Override
public DOMAttr createAttributeNS(String namespaceURI, String qualifiedName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createCDATASection(java.lang.String)
*/
@Override
public CDATASection createCDATASection(String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createComment(java.lang.String)
*/
@Override
public DOMComment createComment(String data) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createDocumentFragment()
*/
@Override
public DocumentFragment createDocumentFragment() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createElement(java.lang.String)
*/
@Override
public DOMElement createElement(String tagName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createElementNS(java.lang.String, java.lang.String)
*/
@Override
public DOMElement createElementNS(String namespaceURI, String qualifiedName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createEntityReference(java.lang.String)
*/
@Override
public EntityReference createEntityReference(String name) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createProcessingInstruction(java.lang.String,
* java.lang.String)
*/
@Override
public DOMProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createTextNode(java.lang.String)
*/
@Override
public DOMText createTextNode(String data) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDomConfig()
*/
@Override
public DOMConfiguration getDomConfig() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getElementById(java.lang.String)
*/
@Override
public DOMElement getElementById(String elementId) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getElementsByTagName(java.lang.String)
*/
@Override
public NodeList getElementsByTagName(String tagname) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getElementsByTagNameNS(java.lang.String,
* java.lang.String)
*/
@Override
public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getImplementation()
*/
@Override
public DOMImplementation getImplementation() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getInputEncoding()
*/
@Override
public String getInputEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getStrictErrorChecking()
*/
@Override
public boolean getStrictErrorChecking() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getXmlEncoding()
*/
@Override
public String getXmlEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getXmlStandalone()
*/
@Override
public boolean getXmlStandalone() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getXmlVersion()
*/
@Override
public String getXmlVersion() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#importNode(org.w3c.dom.Node, boolean)
*/
@Override
public DOMNode importNode(org.w3c.dom.Node importedNode, boolean deep) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#normalizeDocument()
*/
@Override
public void normalizeDocument() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#renameNode(org.w3c.dom.Node, java.lang.String,
* java.lang.String)
*/
@Override
public DOMNode renameNode(org.w3c.dom.Node n, String namespaceURI, String qualifiedName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setStrictErrorChecking(boolean)
*/
@Override
public void setStrictErrorChecking(boolean strictErrorChecking) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setXmlStandalone(boolean)
*/
@Override
public void setXmlStandalone(boolean xmlStandalone) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setXmlVersion(java.lang.String)
*/
@Override
public void setXmlVersion(String xmlVersion) throws DOMException {
throw new UnsupportedOperationException();
}
/**
* Reset the cached grammar flag.
*/
public void resetGrammar() {
this.referencedExternalGrammarInitialized = false;
this.referencedSchemaInitialized = false;
}
public URIResolverExtensionManager getResolverExtensionManager() {
return resolverExtensionManager;
}
/**
* Returns true if the XML document is a DTD and false otherwise.
*
* @return true if the XML document is a DTD and false otherwise.
*/
public boolean isDTD() {
String uri = this.getDocumentURI();
if (DOMUtils.isDTD(uri)) {
return true;
}
return false;
}
/**
* Returns true if 'offset' is within an internal DOCTYPE dtd. Else false.
*
* @param offset
* @return
*/
public boolean isWithinInternalDTD(int offset) {
DOMDocumentType doctype = this.getDoctype();
if (doctype != null && doctype.internalSubset != null) {
return offset > doctype.internalSubset.start && offset < doctype.internalSubset.end;
}
return false;
}
public Range getTrimmedRange(Range range) {
if (range != null) {
return getTrimmedRange(range.getStart().getColumn(), range.getEnd().getColumn());
}
return null;
}
public Range getTrimmedRange(int start, int end) {
String text = getText();
char c = text.charAt(start);
while (Character.isWhitespace(c)) {
start++;
c = text.charAt(start);
}
if (start == end) {
return null;
}
end--;
c = text.charAt(end);
while (Character.isWhitespace(c)) {
end--;
c = text.charAt(end);
}
end++;
return XMLPositionUtility.createRange(start, end, this);
}
/**
* Returns the DTD Attribute list for the given element name and empty
* otherwise.
*
* @param elementName
* @return the DTD Attribute list for the given element name and empty
* otherwise.
*/
public Collection<DOMNode> findDTDAttrList(String elementName) {
DOMDocumentType docType = getDoctype();
if (docType == null || elementName == null) {
return Collections.emptyList();
}
return docType.getChildren().stream().filter(DOMNode::isDTDAttListDecl)
.filter(n -> elementName.equals(((DTDAttlistDecl) n).getElementName())).collect(Collectors.toList());
}
/**
* Returns the XML Schema prefix (ex : 'xs' for
* xmlns:xs="http://www.w3.org/2001/XMLSchema")
*
* @return the XML Schema prefix (ex : 'xs' for
* xmlns:xs="http://www.w3.org/2001/XMLSchema")
*/
public String getSchemaPrefix() {
initializeReferencedSchemaIfNeeded();
return schemaPrefix;
}
private void checkCanceled() {
if (cancelChecker != null) {
cancelChecker.checkCanceled();
}
}
}
@@ -0,0 +1,232 @@
/**
* Copyright (c) 2018-2020 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.List;
import org.w3c.dom.NamedNodeMap;
/**
* A doctype node.
*
*/
public class DOMDocumentType extends DTDDeclNode implements org.w3c.dom.DocumentType {
public static enum DocumentTypeKind {
PUBLIC, SYSTEM, INVALID
}
private XMLNamedNodeMap<DTDEntityDecl> entitiesNodes;
DTDDeclParameter kind; // SYSTEM || PUBLIC
DTDDeclParameter publicId;
DTDDeclParameter systemId;
DTDDeclParameter internalSubset;
private String content; // |<!DOCTYPE ... >|
public DOMDocumentType(int start, int end) {
super(start, end);
}
@Override
public DOMDocumentType getOwnerDocType() {
return this;
}
@Override
public String getTextContent() {
if (content == null) {
content = getOwnerDocument().getText().substring(getStart(), getEnd());
}
return content;
}
/**
* Returns the document type kind (PUBLIC or SYSTEM)
*
* @return the document type kind (PUBLIC or SYSTEM)
*/
public String getKind() {
return kind != null ? kind.getParameter() : null;
}
/**
* Returns the node where document type kind (PUBLIC or SYSTEM) is declared
*
* @return the node where document type kind (PUBLIC or SYSTEM) is declared
*/
public DTDDeclParameter getKindNode() {
return kind;
}
/**
* @param kind the DocumentTypeKind to set
*/
void setKind(int start, int end) {
kind = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getName();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.DOCUMENT_TYPE_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getEntities()
*/
@Override
public NamedNodeMap getEntities() {
if (entitiesNodes == null) {
entitiesNodes = new XMLNamedNodeMap<>();
List<DOMNode> children = super.getChildren();
for (DOMNode child : children) {
if (child.getNodeType() == DOMNode.ENTITY_NODE) {
entitiesNodes.add((DTDEntityDecl) child);
}
}
}
return entitiesNodes;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getInternalSubset()
*/
@Override
public String getInternalSubset() {
if (internalSubset != null) {
// Returns subset without '[' and ']'
return internalSubset.getParameterWithoutFirstAndLastChar();
}
return null;
}
public void setStartInternalSubset(int start) {
internalSubset = addNewParameter(start, start + 1);
}
public void setEndInternalSubset(int end) {
updateLastParameterEnd(end);
}
public boolean isInternalSubset(DTDDeclParameter parameter) {
if (this.internalSubset != null) {
return this.internalSubset.equals(parameter);
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getNotations()
*/
@Override
public NamedNodeMap getNotations() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getPublicId()
*/
@Override
public String getPublicId() {
return publicId != null ? publicId.getParameter() : null;
}
public String getPublicIdWithoutQuotes() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}
public DTDDeclParameter getPublicIdNode() {
return publicId;
}
/**
* @param publicId the publicId to set
*/
void setPublicId(int start, int end) {
publicId = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getSystemId()
*/
@Override
public String getSystemId() {
return systemId != null ? systemId.getParameter() : null;
}
public DTDDeclParameter getSystemIdNode() {
return systemId;
}
public String getSystemIdWithoutQuotes() {
return systemId != null ? systemId.getParameterWithoutFirstAndLastChar() : null;
}
/**
* @param systemId the systemId to set
*/
void setSystemId(int start, int end) {
systemId = addNewParameter(start, end);
}
/**
* Returns a substring of the whole document.
*
*
* Since offset values are relative to 'this.start' we need to subtract
* getStart() to make them relative to 'content'
*/
public String getSubstring(int start, int end) {
String textContent = getTextContent();
if (textContent == null) {
return null;
}
return textContent.substring(start - getStart(), end - getStart());
}
/**
* Returns the declaration parameter that represents the internal subset, or null if there is no internal subset
*
* @return the declaration parameter that represents the internal subset, or null if there is no internal subset
*/
public DTDDeclParameter getInternalSubsetNode() {
return this.internalSubset;
}
}
@@ -0,0 +1,576 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import static org.eclipse.lemminx.dom.DOMAttr.XMLNS_ATTR;
import static org.eclipse.lemminx.dom.DOMAttr.XMLNS_NO_DEFAULT_ATTR;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.NodeList;
import org.w3c.dom.TypeInfo;
/**
* An Element node.
*
*/
public class DOMElement extends DOMNode implements org.w3c.dom.Element {
String tag;
boolean selfClosed;
// DomElement.start == startTagOpenOffset
int startTagOpenOffset = NULL_VALUE; // |<root>
int startTagCloseOffset = NULL_VALUE; // <root |>
int endTagOpenOffset = NULL_VALUE; // <root> |</root >
int endTagCloseOffset = NULL_VALUE;// <root> </root |>
// DomElement.end = <root> </root>| , is always scanner.getTokenEnd()
public DOMElement(int start, int end) {
super(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.ELEMENT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getTagName();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Element#getTagName()
*/
@Override
public String getTagName() {
return tag;
}
/**
* Returns true if the DOM element have a tag name and false otherwise (ex : '<'
* or '</').
*
* @return true if the DOM element have a tag name and false otherwise (ex : '<'
* or '</').
*/
public boolean hasTagName() {
return tag != null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getLocalName()
*/
@Override
public String getLocalName() {
String name = getTagName();
if (name == null) {
return null;
}
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
name = name.substring(index + 1);
}
return name;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getPrefix()
*/
@Override
public String getPrefix() {
String name = getTagName();
if (name == null) {
return null;
}
String prefix = null;
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
prefix = name.substring(0, index);
}
return prefix;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNamespaceURI()
*/
@Override
public String getNamespaceURI() {
String prefix = getPrefix();
// Try to get xmlns attribute from the element
return getNamespaceURI(prefix);
}
/**
* Returns the namespace URI for the given prefix and null otherwise.
*
* @param prefix the prefix.
*
* @return the namespace URI for the given prefix and null otherwise.
*/
public String getNamespaceURI(String prefix) {
String namespaceURI = getNamespaceURI(prefix, this);
if (namespaceURI != null) {
return namespaceURI;
}
// try to get the namespace from the parent element
DOMNode parent = getParentNode();
while (parent != null) {
if (parent.getNodeType() == DOMNode.ELEMENT_NODE) {
DOMElement parentElement = ((DOMElement) parent);
namespaceURI = getNamespaceURI(prefix, parentElement);
if (namespaceURI != null) {
return namespaceURI;
}
}
parent = parent.getParentNode();
}
return null;
}
/**
* Returns the namespace URI from the given prefix declared in the given element
* and null otherwise.
*
* @param prefix the prefix
* @param element the DOM element
* @return the namespace URI from the given prefix declared in the given element
* and null otherwise.
*/
private static String getNamespaceURI(String prefix, DOMElement element) {
boolean hasPrefix = !StringUtils.isEmpty(prefix);
return hasPrefix ? element.getAttribute(XMLNS_NO_DEFAULT_ATTR + prefix) : element.getAttribute(XMLNS_ATTR);
}
public Collection<String> getAllPrefixes() {
if (hasAttributes()) {
Collection<String> prefixes = new ArrayList<>();
for (DOMAttr attr : getAttributeNodes()) {
if (attr.isNoDefaultXmlns()) {
prefixes.add(attr.extractPrefixFromXmlns());
}
}
return prefixes;
}
return Collections.emptyList();
}
/**
* Returns the xmlns prefix from the given namespace URI and null otherwise.
*
* @param namespaceURI the namespace
* @return the xmlns prefix from the given namespace URI and null otherwise.
*/
public String getPrefix(String namespaceURI) {
if (namespaceURI == null) {
return null;
}
if (hasAttributes()) {
for (DOMAttr attr : getAttributeNodes()) {
String prefix = attr.getPrefixIfMatchesURI(namespaceURI);
if (prefix != null) {
return prefix;
}
}
}
// try to get the prefix in the parent element
DOMNode parent = getParentNode();
while (parent != null) {
if (parent.getNodeType() == DOMNode.ELEMENT_NODE) {
DOMElement parentElement = ((DOMElement) parent);
String prefix = parentElement.getPrefix(namespaceURI);
if (prefix != null) {
return prefix;
}
}
parent = parent.getParentNode();
}
return null;
}
public boolean isDocumentElement() {
return this.equals(getOwnerDocument().getDocumentElement());
}
public boolean isSelfClosed() {
return selfClosed;
}
/**
* Will traverse backwards from the start offset returning an offset of the
* given character if it's found before another character. Whitespace is
* ignored.
*
* Returns null if the character is not found.
*
* The initial value for the start offset is not included. So have the offset 1
* position after the character you want to start at.
*/
public Integer endsWith(char c, int startOffset) {
String text = this.getOwnerDocument().getText();
if (startOffset > text.length() || startOffset < 0) {
return null;
}
startOffset--;
while (startOffset >= 0) {
char current = text.charAt(startOffset);
if (Character.isWhitespace(current)) {
startOffset--;
continue;
}
if (current != c) {
return null;
}
return startOffset;
}
return null;
}
/**
* Returns true if the given tag is the same tag of this element and false
* otherwise.
*
* @param tag tag element
* @return true if the given tag is the same tag of this element and false
* otherwise.
*/
public boolean isSameTag(String tag) {
return Objects.equals(this.tag, tag);
}
public boolean isInStartTag(int offset) {
if (startTagOpenOffset == NULL_VALUE || startTagCloseOffset == NULL_VALUE) {
// case <|
return true;
}
if (offset > startTagOpenOffset && offset <= startTagCloseOffset) {
// case <bean | >
return true;
}
return false;
}
public boolean isInEndTag(int offset) {
return isInEndTag(offset, false);
}
public boolean isInEndTag(int offset, boolean afterBackSlash) {
if (endTagOpenOffset == NULL_VALUE) {
// case >|
return false;
}
if (offset > endTagOpenOffset + (afterBackSlash ? 1 : 0) && offset < getEnd()) {
// case </bean | >
return true;
}
return false;
}
public boolean isInInsideStartEndTag(int offset) {
return offset > startTagCloseOffset && offset <= endTagOpenOffset;
}
/**
* Returns the start tag open offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*
* @return the start tag open offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*/
public int getStartTagOpenOffset() {
return startTagOpenOffset;
}
/**
* Returns the start tag close offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*
* @return the start tag close offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*/
public int getStartTagCloseOffset() {
return startTagCloseOffset;
}
/**
* Returns the end tag open offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*
* @return the end tag open offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*/
public int getEndTagOpenOffset() {
return endTagOpenOffset;
}
/**
* Returns the end tag close offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*
* @return the end tag close offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*/
public int getEndTagCloseOffset() {
return endTagCloseOffset;
}
/**
* Returns true if has a start tag.
*
* In our source-oriented DOM, a lone end tag will cause a node to be created in
* the tree, unlike well-formed-only DOMs.
*
* @return true if has a start tag.
*/
public boolean hasStartTag() {
return getStartTagOpenOffset() != NULL_VALUE;
}
/**
* Returns true if has an end tag.
*
* In our source-oriented DOM, sometimes Elements are "ended", even without an
* explicit end tag in the source.
*
* @return true if has an end tag.
*/
public boolean hasEndTag() {
return getEndTagOpenOffset() != NULL_VALUE;
}
/**
* If '>' exists in <root>
*/
public boolean isStartTagClosed() {
return getStartTagCloseOffset() != NULL_VALUE;
}
/**
* If '>' exists in </root>
*/
public boolean isEndTagClosed() {
return getEndTagCloseOffset() != NULL_VALUE;
}
/**
* Returns true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) and false otherwise.
*
* @return true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) and false otherwise.
*/
public boolean isOrphanEndTag() {
return hasEndTag() && !hasStartTag();
}
/**
* Returns true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) of the given tag name and false otherwise.
*
* @param tagName the end tag name.
* @return true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) of the given tag name and false otherwise.
*/
public boolean isOrphanEndTagOf(String tagName) {
return isSameTag(tagName) && isOrphanEndTag();
}
/**
* Returns the offset at which the given unclosed start tag should be closed
* with an angle bracket
*
* @returns the offset at which the given unclosed start tag should be closed
* with an angle bracket
*/
public int getUnclosedStartTagCloseOffset() {
String documentText = getOwnerDocument().getText();
int i = getStart() + 1;
for (; i < documentText.length() && documentText.charAt(i) != '/' && documentText.charAt(i) != '<'; i++) {
}
if (i < documentText.length() && documentText.charAt(i) == '/') {
return i + 1;
}
i--;
for (; i > 0 && Character.isWhitespace(documentText.charAt(i)); i--) {
}
return i + 1;
}
@Override
public DOMElement getOrphanEndElement(int offset, String tagName) {
if (getEnd() <= offset) {
// <employee />|
// <employee /> |
// <employee></employee> |
// check if next sibling node is an element like <\tagName>
return super.getOrphanEndElement(offset, tagName);
}
if (isSameTag(tagName) && isInStartTag(offset)) {
// <employe|e></employee>
return hasEndTag() ? this : null;
}
// search if it exists an end tag
List<DOMNode> children = getChildren();
for (DOMNode child : children) {
if (child.isElement()) {
DOMElement childElement = (DOMElement) child;
if (childElement.isOrphanEndTagOf(tagName)) {
return childElement;
}
}
}
return null;
}
/**
* Returns true if element has a closing end tag (eg: <a> </a>) and false
* otherwise (eg: <a> </b>).
*
* @return true if element has a closing end tag (eg: <a> </a>) and false
* otherwise (eg: <a> </b>).
*/
@Override
public boolean isClosed() {
return super.isClosed();
}
@Override
public String getAttributeNS(String arg0, String arg1) throws DOMException {
return null;
}
@Override
public DOMAttr getAttributeNode(String name) {
return super.getAttributeNode(name);
}
@Override
public DOMAttr getAttributeNodeNS(String arg0, String arg1) throws DOMException {
return null;
}
@Override
public NodeList getElementsByTagName(String arg0) {
return null;
}
@Override
public NodeList getElementsByTagNameNS(String arg0, String arg1) throws DOMException {
return null;
}
@Override
public TypeInfo getSchemaTypeInfo() {
return null;
}
@Override
public boolean hasAttributeNS(String arg0, String arg1) throws DOMException {
return false;
}
@Override
public void removeAttribute(String arg0) throws DOMException {
}
@Override
public void removeAttributeNS(String arg0, String arg1) throws DOMException {
}
@Override
public DOMAttr removeAttributeNode(org.w3c.dom.Attr arg0) throws DOMException {
return null;
}
@Override
public void setAttributeNS(String arg0, String arg1, String arg2) throws DOMException {
}
@Override
public DOMAttr setAttributeNode(org.w3c.dom.Attr arg0) throws DOMException {
return null;
}
@Override
public DOMAttr setAttributeNodeNS(org.w3c.dom.Attr arg0) throws DOMException {
return null;
}
@Override
public void setIdAttribute(String arg0, boolean arg1) throws DOMException {
}
@Override
public void setIdAttributeNS(String arg0, String arg1, boolean arg2) throws DOMException {
}
@Override
public void setIdAttributeNode(org.w3c.dom.Attr arg0, boolean arg1) throws DOMException {
}
/**
* Returns true if the element is empty and false otherwise.
*
* @return true if the element is empty and false otherwise.
*/
public boolean isEmpty() {
if (!hasChildNodes()) {
return true;
}
for (DOMNode child : getChildren()) {
if (child.isText()) {
DOMText text = (DOMText) child;
if (!text.isElementContentWhitespace()) {
return false;
}
} else {
return false;
}
}
return true;
}
public int getOffsetAfterStartTag() {
if (hasTagName()) {
return getStartTagOpenOffset() + 1;
}
return getStartTagOpenOffset() + getTagName().length() + 1;
}
}
@@ -0,0 +1,846 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import org.w3c.dom.DOMException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
/**
* DOM node.
*
*/
public abstract class DOMNode implements Node, DOMRange {
/**
* Null value used for offset.
*/
public static final int NULL_VALUE = -1;
/**
* The node is a <code>DTD Element Declaration</code>.
*/
public static final short DTD_ELEMENT_DECL_NODE = 101;
/**
* The node is a <code>DTD Attribute List</code>.
*/
public static final short DTD_ATT_LIST_NODE = 102;
/**
* The node is a <code>DTD Entity Declaraction</code>.
*/
public static final short DTD_ENTITY_DECL_NODE = 103;
/**
* The node is a <code>DTD Notation Declaraction</code>.
*/
public static final short DTD_NOTATION_DECL = 104;
/**
* The node is a generic <code>DTD Decl Node</code>.
*/
public static final short DTD_DECL_NODE = 105;
boolean closed = false;
private XMLNamedNodeMap<DOMAttr> attributeNodes;
private XMLNodeList<DOMNode> children;
final int start; // |<root> </root>
int end; // <root> </root>|
DOMNode parent;
private static final NodeList EMPTY_CHILDREN = new NodeList() {
@Override
public Node item(int index) {
return null;
}
@Override
public int getLength() {
return 0;
}
};
static class XMLNodeList<T extends DOMNode> extends ArrayList<T> implements NodeList {
private static final long serialVersionUID = 1L;
@Override
public int getLength() {
return super.size();
}
@Override
public DOMNode item(int index) {
return super.get(index);
}
}
static class XMLNamedNodeMap<T extends DOMNode> extends ArrayList<T> implements NamedNodeMap {
private static final long serialVersionUID = 1L;
@Override
public int getLength() {
return super.size();
}
@Override
public T getNamedItem(String name) {
for (T node : this) {
if (name.equals(node.getNodeName())) {
return node;
}
}
return null;
}
@Override
public T getNamedItemNS(String name, String arg1) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T item(int index) {
return super.get(index);
}
@Override
public T removeNamedItem(String arg0) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T removeNamedItemNS(String arg0, String arg1) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T setNamedItem(org.w3c.dom.Node arg0) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T setNamedItemNS(org.w3c.dom.Node arg0) throws DOMException {
throw new UnsupportedOperationException();
}
}
public DOMNode(int start, int end) {
this.start = start;
this.end = end;
this.closed = false;
}
/**
* Returns the owner document and null otherwise.
*
* @return the owner document and null otherwise.
*/
@Override
public DOMDocument getOwnerDocument() {
Node node = parent;
while (node != null) {
if (node.getNodeType() == Node.DOCUMENT_NODE) {
return (DOMDocument) node;
}
node = node.getParentNode();
}
return null;
}
@Override
public String toString() {
return toString(0);
}
private String toString(int indent) {
StringBuilder result = new StringBuilder("");
for (int i = 0; i < indent; i++) {
result.append("\t");
}
result.append("{start: ");
result.append(start);
result.append(", end: ");
result.append(end);
result.append(", name: ");
result.append(getNodeName());
result.append(", closed: ");
result.append(closed);
if (children != null && children.size() > 0) {
result.append(", \n");
for (int i = 0; i < indent + 1; i++) {
result.append("\t");
}
result.append("children:[");
for (int i = 0; i < children.size(); i++) {
DOMNode node = children.get(i);
result.append("\n");
result.append(node.toString(indent + 2));
if (i < children.size() - 1) {
result.append(",");
}
}
result.append("\n");
for (int i = 0; i < indent + 1; i++) {
result.append("\t");
}
result.append("]");
result.append("\n");
for (int i = 0; i < indent; i++) {
result.append("\t");
}
result.append("}");
} else {
result.append("}");
}
return result.toString();
}
/**
* Returns the node before
*/
public DOMNode findNodeBefore(int offset) {
List<DOMNode> children = getChildren();
int idx = findFirst(children, c -> offset <= c.start) - 1;
if (idx >= 0) {
DOMNode child = children.get(idx);
if (offset > child.start) {
if (offset < child.end) {
return child.findNodeBefore(offset);
}
DOMNode lastChild = child.getLastChild();
if (lastChild != null && lastChild.end == child.end) {
return child.findNodeBefore(offset);
}
return child;
}
}
return this;
}
public DOMNode findNodeAt(int offset) {
List<DOMNode> children = getChildren();
int idx = findFirst(children, c -> offset <= c.start) - 1;
if (idx >= 0) {
DOMNode child = children.get(idx);
if (isIncluded(child, offset)) {
return child.findNodeAt(offset);
}
}
return this;
}
/**
* Returns true if the node included the given offset and false otherwise.
*
* @param node
* @param offset
* @return true if the node included the given offset and false otherwise.
*/
public static boolean isIncluded(DOMRange node, int offset) {
if (node == null) {
return false;
}
return isIncluded(node.getStart(), node.getEnd(), offset);
}
public static boolean isIncluded(int start, int end, int offset) {
return offset >= start && offset <= end;
}
public DOMAttr findAttrAt(int offset) {
DOMNode node = findNodeAt(offset);
return findAttrAt(node, offset);
}
public static DOMAttr findAttrAt(DOMNode node, int offset) {
if (node != null && node.hasAttributes()) {
for (DOMAttr attr : node.getAttributeNodes()) {
if (attr.isIncluded(offset)) {
return attr;
}
}
}
return null;
}
public static DOMNode findNodeOrAttrAt(DOMDocument document, int offset) {
DOMNode node = document.findNodeAt(offset);
if (node != null) {
DOMAttr attr = findAttrAt(node, offset);
if (attr != null) {
return attr;
}
}
return node;
}
/**
* Takes a sorted array and a function p. The array is sorted in such a way that
* all elements where p(x) is false are located before all elements where p(x)
* is true.
*
* @returns the least x for which p(x) is true or array.length if no element
* full fills the given function.
*/
private static <T> int findFirst(List<T> array, Function<T, Boolean> p) {
int low = 0, high = array.size();
if (high == 0) {
return 0; // no children
}
while (low < high) {
int mid = (int) Math.floor((low + high) / 2);
if (p.apply(array.get(mid))) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
public DOMAttr getAttributeNode(String name) {
return getAttributeNode(null, name);
}
/**
* Returns the attribute that matches the given name.
*
* If there is no namespace, set prefix to null.
*/
public DOMAttr getAttributeNode(String prefix, String suffix) {
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
sb.append(":");
}
sb.append(suffix);
String name = sb.toString();
if (!hasAttributes()) {
return null;
}
for (DOMAttr attr : attributeNodes) {
if (name.equals(attr.getName())) {
return attr;
}
}
return null;
}
public String getAttribute(String name) {
DOMAttr attr = getAttributeNode(name);
String value = attr != null ? attr.getValue() : null;
if (value == null) {
return null;
}
if (value.isEmpty()) {
return value;
}
// remove quote
char c = value.charAt(0);
if (c == '"' || c == '\'') {
if (value.charAt(value.length() - 1) == c) {
return value.substring(1, value.length() - 1);
}
return value.substring(1, value.length());
}
return value;
}
/**
* Returns the attribute at the given index, the order is how the attributes
* appear in the document.
*
* @param index Starting at 0, index of attribute you want
* @return
*/
public DOMAttr getAttributeAtIndex(int index) {
if (!hasAttributes()) {
return null;
}
if (index > attributeNodes.getLength() - 1) {
return null;
}
return attributeNodes.get(index);
}
public boolean hasAttribute(String name) {
return hasAttributes() && getAttributeNode(name) != null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#hasAttributes()
*/
@Override
public boolean hasAttributes() {
return attributeNodes != null && attributeNodes.size() != 0;
}
public void setAttribute(String name, String value) {
DOMAttr attr = getAttributeNode(name);
if (attr == null) {
attr = new DOMAttr(name, this);
setAttributeNode(attr);
}
attr.setValue(value);
}
public void setAttributeNode(DOMAttr attr) {
if (attributeNodes == null) {
attributeNodes = new XMLNamedNodeMap<>();
}
attributeNodes.add(attr);
}
public List<DOMAttr> getAttributeNodes() {
return attributeNodes;
}
/**
* Returns a list of children, each having an attribute called name, with a
* value of value
*
* @param name name of attribute
* @param value value of attribute
* @return list of children, each having a specified attribute name and value
*/
public List<DOMNode> getChildrenWithAttributeValue(String name, String value) {
List<DOMNode> result = new ArrayList<>();
for (DOMNode child : getChildren()) {
if (child.hasAttribute(name)) {
String attrValue = child.getAttribute(name);
if (Objects.equals(attrValue, value)) {
result.add(child);
}
}
}
return result;
}
/**
* Returns the node children.
*
* @return the node children.
*/
public List<DOMNode> getChildren() {
if (children == null) {
return Collections.emptyList();
}
return children;
}
/**
* Add node child and set child.parent to {@code this}
*
* @param child the node child to add.
*/
public void addChild(DOMNode child) {
child.parent = this;
if (children == null) {
children = new XMLNodeList<>();
}
getChildren().add(child);
}
/**
* Returns node child at the given index.
*
* @param index
* @return node child at the given index.
*/
public DOMNode getChild(int index) {
return getChildren().get(index);
}
public boolean isClosed() {
return closed;
}
public DOMElement getParentElement() {
DOMNode parent = getParentNode();
DOMDocument ownerDocument = getOwnerDocument();
while (parent != null && parent != ownerDocument) {
if (parent.isElement()) {
return (DOMElement) parent;
}
parent = parent.getParentNode();
}
return null;
}
public boolean isComment() {
return getNodeType() == DOMNode.COMMENT_NODE;
}
public boolean isProcessingInstruction() {
return (getNodeType() == DOMNode.PROCESSING_INSTRUCTION_NODE)
&& ((DOMProcessingInstruction) this).isProcessingInstruction();
}
public boolean isProlog() {
return (getNodeType() == DOMNode.PROCESSING_INSTRUCTION_NODE) && ((DOMProcessingInstruction) this).isProlog();
}
public boolean isCDATA() {
return getNodeType() == DOMNode.CDATA_SECTION_NODE;
}
public boolean isDoctype() {
return getNodeType() == DOMNode.DOCUMENT_TYPE_NODE;
}
public boolean isGenericDTDDecl() {
return getNodeType() == DOMNode.DTD_DECL_NODE;
}
public boolean isElement() {
return getNodeType() == DOMNode.ELEMENT_NODE;
}
public boolean isAttribute() {
return getNodeType() == DOMNode.ATTRIBUTE_NODE;
}
public boolean isText() {
return getNodeType() == DOMNode.TEXT_NODE;
}
public boolean isCharacterData() {
return isCDATA() || isText() || isProcessingInstruction() || isComment();
}
public boolean isDTDElementDecl() {
return getNodeType() == DOMNode.DTD_ELEMENT_DECL_NODE;
}
public boolean isDTDAttListDecl() {
return getNodeType() == DOMNode.DTD_ATT_LIST_NODE;
}
public boolean isDTDEntityDecl() {
return getNodeType() == Node.ENTITY_NODE;
}
public boolean isDTDNotationDecl() {
return getNodeType() == DOMNode.DTD_NOTATION_DECL;
}
public boolean isOwnerDocument() {
return getNodeType() == Node.DOCUMENT_NODE;
}
public boolean isChildOfOwnerDocument() {
if (parent == null) {
return false;
}
return parent.getNodeType() == Node.DOCUMENT_NODE;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getLocalName()
*/
@Override
public String getLocalName() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getParentNode()
*/
@Override
public DOMNode getParentNode() {
return parent;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getFirstChild()
*/
@Override
public DOMNode getFirstChild() {
return this.children != null && children.size() > 0 ? this.children.get(0) : null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getLastChild()
*/
@Override
public DOMNode getLastChild() {
return this.children != null && this.children.size() > 0 ? this.children.get(this.children.size() - 1) : null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getAttributes()
*/
@Override
public NamedNodeMap getAttributes() {
return attributeNodes;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getChildNodes()
*/
@Override
public NodeList getChildNodes() {
return children != null ? children : EMPTY_CHILDREN;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#appendChild(org.w3c.dom.Node)
*/
@Override
public org.w3c.dom.Node appendChild(org.w3c.dom.Node newChild) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#cloneNode(boolean)
*/
@Override
public org.w3c.dom.Node cloneNode(boolean deep) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
*/
@Override
public short compareDocumentPosition(org.w3c.dom.Node other) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getBaseURI()
*/
@Override
public String getBaseURI() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getFeature(java.lang.String, java.lang.String)
*/
@Override
public Object getFeature(String arg0, String arg1) {
throw new UnsupportedOperationException();
}
@Override
public String getNamespaceURI() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNextSibling()
*/
@Override
public DOMNode getNextSibling() {
DOMNode parentNode = getParentNode();
if (parentNode == null) {
return null;
}
List<DOMNode> children = parentNode.getChildren();
int nextIndex = children.indexOf(this) + 1;
return nextIndex < children.size() ? children.get(nextIndex) : null;
}
@Override
public String getNodeValue() throws DOMException {
return null;
}
@Override
public String getPrefix() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getPreviousSibling()
*/
@Override
public DOMNode getPreviousSibling() {
DOMNode parentNode = getParentNode();
if (parentNode == null) {
return null;
}
List<DOMNode> children = parentNode.getChildren();
int previousIndex = children.indexOf(this) - 1;
return previousIndex >= 0 ? children.get(previousIndex) : null;
}
public DOMNode getPreviousNonTextSibling() {
DOMNode prev = getPreviousSibling();
while (prev != null && prev.isText()) {
prev = prev.getPreviousSibling();
}
return prev;
}
public DOMElement getOrphanEndElement(int offset, String tagName) {
DOMNode next = getNextSibling();
if (next == null || !next.isElement()) {
return null;
}
// emp| </employe>
DOMElement nextElement = (DOMElement) next;
if (nextElement.isOrphanEndTagOf(tagName)) {
return nextElement;
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getTextContent()
*/
@Override
public String getTextContent() throws DOMException {
return getNodeValue();
}
@Override
public Object getUserData(String arg0) {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#hasChildNodes()
*/
@Override
public boolean hasChildNodes() {
return children != null && !children.isEmpty();
}
@Override
public org.w3c.dom.Node insertBefore(org.w3c.dom.Node arg0, org.w3c.dom.Node arg1) throws DOMException {
return null;
}
@Override
public boolean isDefaultNamespace(String arg0) {
return false;
}
@Override
public boolean isEqualNode(org.w3c.dom.Node arg0) {
return false;
}
@Override
public boolean isSameNode(org.w3c.dom.Node arg0) {
return false;
}
@Override
public boolean isSupported(String arg0, String arg1) {
return false;
}
@Override
public String lookupNamespaceURI(String arg0) {
return null;
}
@Override
public String lookupPrefix(String arg0) {
return null;
}
@Override
public void normalize() {
}
@Override
public org.w3c.dom.Node removeChild(org.w3c.dom.Node arg0) throws DOMException {
return null;
}
@Override
public org.w3c.dom.Node replaceChild(org.w3c.dom.Node arg0, org.w3c.dom.Node arg1) throws DOMException {
return null;
}
@Override
public void setNodeValue(String arg0) throws DOMException {
}
@Override
public void setPrefix(String arg0) throws DOMException {
}
@Override
public void setTextContent(String arg0) throws DOMException {
}
@Override
public Object setUserData(String arg0, Object arg1, UserDataHandler arg2) {
return null;
}
}
@@ -0,0 +1,677 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.parser.Scanner;
import org.eclipse.lemminx.dom.parser.TokenType;
import org.eclipse.lemminx.dom.parser.XMLScanner;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
/**
* Tolerant XML parser.
*
*/
public class DOMParser {
private static final Logger LOGGER = Logger.getLogger(DOMParser.class.getName());
private static final DOMParser INSTANCE = new DOMParser();
public static DOMParser getInstance() {
return INSTANCE;
}
private DOMParser() {
}
public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager) {
return parse(new TextDocument(text, uri), resolverExtensionManager);
}
public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager,
boolean ignoreWhitespaceContent) {
return parse(new TextDocument(text, uri), resolverExtensionManager, ignoreWhitespaceContent);
}
public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager) {
return parse(document, resolverExtensionManager, true);
}
public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager,
boolean ignoreWhitespaceContent) {
return parse(document, resolverExtensionManager, ignoreWhitespaceContent, null);
}
public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager,
boolean ignoreWhitespaceContent, CancelChecker monitor) {
boolean isDTD = DOMUtils.isDTD(document.getUri());
boolean inDTDInternalSubset = false;
String text = document.getText();
Scanner scanner = XMLScanner.createScanner(text, 0, isDTD);
DOMDocument xmlDocument = new DOMDocument(document, resolverExtensionManager);
xmlDocument.setCancelChecker(monitor);
DOMNode curr = isDTD ? new DOMDocumentType(0, text.length()) : xmlDocument;
if (isDTD) {
xmlDocument.addChild(curr);
// This DOMDocumentType object is hidden, and just represents the DTD file
// nothing should affect it's closed status
curr.closed = true;
}
DOMNode lastClosed = curr;
DOMAttr attr = null;
int endTagOpenOffset = -1;
DOMNode tempWhitespaceContent = null;
boolean isInitialDeclaration = true; // A declaration can have multiple internal declarations
boolean previousTokenWasEndTagOpen = false;
TokenType token = scanner.scan();
while (token != TokenType.EOS) {
if (monitor != null) {
monitor.checkCanceled();
}
if (tempWhitespaceContent != null && token != TokenType.EndTagOpen) {
tempWhitespaceContent = null;
}
if (previousTokenWasEndTagOpen) {
previousTokenWasEndTagOpen = false;
if (token != TokenType.EndTag) {
// The excepted token is not an EndTag, create a fake end tag element
DOMElement element = xmlDocument.createElement(endTagOpenOffset, endTagOpenOffset + 2);
element.endTagOpenOffset = endTagOpenOffset;
curr.addChild(element);
}
}
switch (token) {
case StartTagOpen: {
if (!curr.isClosed() && curr.parent != null) {
// The next node's parent (curr) is not closed at this point
// so the node's parent (curr) will have its end position updated
// to a newer end position.
curr.end = scanner.getTokenOffset();
}
if ((curr.isClosed()) || curr.isDoctype()) {
// The next node being considered is a child of 'curr'
// and if 'curr' is already closed then 'curr' was not updated properly.
// Or if we get a Doctype node then we know it was not closed and 'curr'
// wasn't updated properly.
curr = curr.parent;
inDTDInternalSubset = false; // In case it was previously in the internal subset
}
DOMElement child = xmlDocument.createElement(scanner.getTokenOffset(), scanner.getTokenEnd());
child.startTagOpenOffset = scanner.getTokenOffset();
curr.addChild(child);
curr = child;
break;
}
case StartTag: {
DOMElement element = (DOMElement) curr;
element.tag = scanner.getTokenText();
curr.end = scanner.getTokenEnd();
break;
}
case StartTagClose:
if (curr.isElement()) {
DOMElement element = (DOMElement) curr;
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
element.startTagCloseOffset = scanner.getTokenOffset();
// never enters isEmptyElement() is always false
if (element.hasTagName() && isEmptyElement(element.getTagName()) && curr.parent != null) {
curr.closed = true;
curr = curr.parent;
}
} else if (curr.isProcessingInstruction() || curr.isProlog()) {
DOMProcessingInstruction element = (DOMProcessingInstruction) curr;
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
element.startTagClose = true;
if (element.getTarget() != null && isEmptyElement(element.getTarget()) && curr.parent != null) {
curr.closed = true;
curr = curr.parent;
}
}
curr.end = scanner.getTokenEnd();
break;
case EndTagOpen:
if (tempWhitespaceContent != null) {
curr.addChild(tempWhitespaceContent);
tempWhitespaceContent = null;
}
endTagOpenOffset = scanner.getTokenOffset();
curr.end = scanner.getTokenOffset();
previousTokenWasEndTagOpen = true;
break;
case EndTag:
// end tag (ex: </root>)
String closeTag = scanner.getTokenText();
DOMNode current = curr;
/**
* eg: <a><b><c></d> will set a,b,c end position to the start of |</d>
*/
while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag)) && curr.parent != null) {
curr.end = endTagOpenOffset;
curr = curr.parent;
}
if (curr != xmlDocument) {
curr.closed = true;
if (curr.isElement()) {
((DOMElement) curr).endTagOpenOffset = endTagOpenOffset;
} else if (curr.isProcessingInstruction() || curr.isProlog()) {
((DOMProcessingInstruction) curr).endTagOpenOffset = endTagOpenOffset;
}
curr.end = scanner.getTokenEnd();
} else {
// element open tag not found (ex: <root>) add a fake element which only has an
// end tag (no start tag).
DOMElement element = xmlDocument.createElement(scanner.getTokenOffset() - 2, scanner.getTokenEnd());
element.endTagOpenOffset = endTagOpenOffset;
element.tag = closeTag;
current.addChild(element);
curr = element;
}
break;
case StartTagSelfClose:
if (curr.parent != null) {
curr.closed = true;
((DOMElement) curr).selfClosed = true;
curr.end = scanner.getTokenEnd();
lastClosed = curr;
curr = curr.parent;
}
break;
case EndTagClose:
if (curr.parent != null) {
curr.end = scanner.getTokenEnd();
lastClosed = curr;
if (lastClosed.isElement()) {
((DOMElement) curr).endTagCloseOffset = scanner.getTokenOffset();
}
if (curr.isDoctype()) {
curr.closed = true;
}
curr = curr.parent;
}
break;
case AttributeName: {
attr = new DOMAttr(null, scanner.getTokenOffset(),
scanner.getTokenEnd(), curr);
curr.setAttributeNode(attr);
curr.end = scanner.getTokenEnd();
break;
}
case DelimiterAssign: {
if (attr != null) {
// Sets the value to the '=' position in case there is no AttributeValue
attr.setDelimiter(scanner.getTokenOffset());
}
break;
}
case AttributeValue: {
if (curr.hasAttributes() && attr != null) {
attr.setValue(null, scanner.getTokenOffset(), scanner.getTokenEnd());
}
attr = null;
curr.end = scanner.getTokenEnd();
break;
}
case CDATATagOpen: {
DOMCDATASection cdataNode = xmlDocument.createCDataSection(scanner.getTokenOffset(), text.length());
curr.addChild(cdataNode);
curr = cdataNode;
break;
}
case CDATAContent: {
DOMCDATASection cdataNode = (DOMCDATASection) curr;
cdataNode.startContent = scanner.getTokenOffset();
cdataNode.endContent = scanner.getTokenEnd();
curr.end = scanner.getTokenEnd();
break;
}
case CDATATagClose: {
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case StartPrologOrPI: {
DOMProcessingInstruction prologOrPINode = xmlDocument
.createProcessingInstruction(scanner.getTokenOffset(), text.length());
curr.addChild(prologOrPINode);
curr = prologOrPINode;
break;
}
case PIName: {
DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr);
processingInstruction.target = scanner.getTokenText();
processingInstruction.processingInstruction = true;
break;
}
case PrologName: {
DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr);
processingInstruction.target = scanner.getTokenText();
processingInstruction.prolog = true;
break;
}
case PIContent: {
DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) curr;
processingInstruction.startContent = scanner.getTokenOffset();
processingInstruction.endContent = scanner.getTokenEnd();
break;
}
case PIEnd:
case PrologEnd: {
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case StartCommentTag: {
// Incase the tag before the comment tag (curr) was not properly closed
// curr should be set to the root node.
if (xmlDocument.isDTD() || inDTDInternalSubset) {
while (!curr.isDoctype()) {
curr = curr.parent;
}
} else if ((curr.isClosed())) {
curr = curr.parent;
}
DOMComment comment = xmlDocument.createComment(scanner.getTokenOffset(), text.length());
curr.addChild(comment);
curr = comment;
try {
int endLine = document.positionAt(lastClosed.end).getLine();
int startLine = document.positionAt(curr.start).getLine();
if (endLine == startLine && lastClosed.end <= curr.start) {
comment.commentSameLineEndTag = true;
}
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "XMLParser StartCommentTag bad offset in document", e);
}
break;
}
case Comment: {
DOMComment comment = (DOMComment) curr;
comment.startContent = scanner.getTokenOffset();
comment.endContent = scanner.getTokenEnd();
break;
}
case EndCommentTag: {
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case Content: {
boolean currIsDeclNode = curr instanceof DTDDeclNode;
if (currIsDeclNode) {
curr.end = scanner.getTokenOffset() - 1;
while (!curr.isDoctype()) {
curr = curr.getParentNode();
}
}
int start = scanner.getTokenOffset();
int end = scanner.getTokenEnd();
DOMText textNode = xmlDocument.createText(start, end);
textNode.closed = true;
if (scanner.isTokenTextBlank()) {
if (ignoreWhitespaceContent) {
if (curr.hasChildNodes()) {
break;
}
tempWhitespaceContent = textNode;
break;
} else if (!currIsDeclNode) {
textNode.setWhitespace(true);
} else {
break;
}
}
curr.addChild(textNode);
break;
}
// DTD
case DTDStartDoctypeTag: {
DOMDocumentType doctype = xmlDocument.createDocumentType(scanner.getTokenOffset(), text.length());
curr.addChild(doctype);
doctype.parent = curr;
curr = doctype;
break;
}
case DTDDoctypeName: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDocTypeKindPUBLIC: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDocTypeKindSYSTEM: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDoctypePublicId: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDoctypeSystemId: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDStartInternalSubset: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setStartInternalSubset(scanner.getTokenOffset());
inDTDInternalSubset = true;
break;
}
case DTDEndInternalSubset: {
while (!curr.isDoctype()) {
curr.end = scanner.getTokenOffset() - 1;
curr = curr.getParentNode();
}
inDTDInternalSubset = false;
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setEndInternalSubset(scanner.getTokenEnd());
break;
}
case DTDStartElement: {
// If previous 'curr' was an unclosed DTD Declaration
while (!curr.isDoctype()) {
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDElementDecl child = new DTDElementDecl(scanner.getTokenOffset(), text.length());
curr.addChild(child);
curr = child;
break;
}
case DTDElementDeclName: {
DTDElementDecl element = (DTDElementDecl) curr;
element.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDElementCategory: {
DTDElementDecl element = (DTDElementDecl) curr;
element.setCategory(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDStartElementContent: {
DTDElementDecl element = (DTDElementDecl) curr;
element.setContent(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDElementContent: {
DTDElementDecl element = (DTDElementDecl) curr;
element.updateLastParameterEnd(scanner.getTokenEnd());
break;
}
case DTDEndElementContent: {
DTDElementDecl element = (DTDElementDecl) curr;
element.updateLastParameterEnd(scanner.getTokenEnd());
break;
}
case DTDStartAttlist: {
while (!curr.isDoctype()) { // If previous DTD Decl was unclosed
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDAttlistDecl child = new DTDAttlistDecl(scanner.getTokenOffset(), text.length());
isInitialDeclaration = true;
curr.addChild(child);
curr = child;
break;
}
case DTDAttlistElementName: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
attribute.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDAttlistAttributeName: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
if (isInitialDeclaration == false) {
// All additional declarations are created as new DTDAttlistDecl's
DTDAttlistDecl child = new DTDAttlistDecl(attribute.getStart(), attribute.getEnd());
attribute.addAdditionalAttDecl(child);
child.parent = attribute;
attribute = child;
curr = child;
}
attribute.setAttributeName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDAttlistAttributeType: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
attribute.setAttributeType(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDAttlistAttributeValue: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
attribute.setAttributeValue(scanner.getTokenOffset(), scanner.getTokenEnd());
if (attribute.parent.isDTDAttListDecl()) { // Is not the root/main ATTLIST node
curr = attribute.parent;
} else {
isInitialDeclaration = false;
}
break;
}
case DTDStartEntity: {
while (!curr.isDoctype()) { // If previous DTD Decl was unclosed
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDEntityDecl child = new DTDEntityDecl(scanner.getTokenOffset(), text.length());
curr.addChild(child);
curr = child;
break;
}
case DTDEntityPercent: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setPercent(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityName: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityValue: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setValue(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityKindPUBLIC:
case DTDEntityKindSYSTEM: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityPublicId: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntitySystemId: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDStartNotation: {
while (!curr.isDoctype()) { // If previous DTD Decl was unclosed
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDNotationDecl child = new DTDNotationDecl(scanner.getTokenOffset(), text.length());
curr.addChild(child);
curr = child;
isInitialDeclaration = true;
break;
}
case DTDNotationName: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationKindPUBLIC: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationKindSYSTEM: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationPublicId: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationSystemId: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEndTag: {
if ((curr.isDTDElementDecl() || curr.isDTDAttListDecl() || curr.isDTDEntityDecl()
|| curr.isDTDNotationDecl())) {
while (curr.parent != null && !curr.parent.isDoctype()) {
curr = curr.parent;
}
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
}
break;
}
case DTDEndDoctypeTag: {
((DOMDocumentType) curr).end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case DTDUnrecognizedParameters: {
DTDDeclNode node = (DTDDeclNode) curr;
node.setUnrecognized(scanner.getTokenOffset(), ((XMLScanner) scanner).getLastNonWhitespaceOffset());
break;
}
default:
}
token = scanner.scan();
}
if (previousTokenWasEndTagOpen) {
previousTokenWasEndTagOpen = false;
if (token != TokenType.EndTag) {
// The excepted token is not an EndTag, create a fake end tag element
DOMElement element = xmlDocument.createElement(endTagOpenOffset, endTagOpenOffset + 2);
element.endTagOpenOffset = endTagOpenOffset;
curr.addChild(element);
}
}
while (curr.parent != null) {
curr.end = text.length();
curr = curr.parent;
}
return xmlDocument;
}
private static boolean isEmptyElement(String tag) {
return false;
}
}
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.DOMException;
/**
* A processing instruction node.
*
*/
public class DOMProcessingInstruction extends DOMCharacterData implements org.w3c.dom.ProcessingInstruction {
boolean startTagClose;
String target;
boolean prolog = false;
boolean processingInstruction = false;
int startContent;
int endContent;
int endTagOpenOffset = NULL_VALUE;
public DOMProcessingInstruction(int start, int end) {
super(start, end);
}
public boolean isProlog() {
return prolog;
}
public boolean isProcessingInstruction() {
return processingInstruction;
}
public int getStartContent() {
return startContent;
}
public int getEndContent() {
return endContent;
}
/**
* Returns the end tag start offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*
* @return the end tag start offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*/
public int getEndTagStart() {
return endTagOpenOffset;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.PROCESSING_INSTRUCTION_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getTarget();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.ProcessingInstruction#getTarget()
*/
@Override
public String getTarget() {
return target;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.ProcessingInstruction#getData()
*/
@Override
public String getData() {
return super.getData().trim();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.ProcessingInstruction#setData(java.lang.String)
*/
@Override
public void setData(String data) throws DOMException {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,42 @@
/*******************************************************************************
* 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.dom;
/**
* DOM range
*
* @author Angelo ZERR
*
*/
public interface DOMRange {
/**
* Returns the start offset of the node.
*
* @return the start offset of the node.
*/
int getStart();
/**
* Returns the end offset of the node.
*
* @return the end offset of the node.
*/
int getEnd();
/**
* Returns the owner document.
*
* @return the owner document.
*/
DOMDocument getOwnerDocument();
}
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
/**
* A Text node.
*
*/
public class DOMText extends DOMCharacterData implements org.w3c.dom.Text {
public DOMText(int start, int end) {
super(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.TEXT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return "#text";
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#getWholeText()
*/
@Override
public String getWholeText() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#isElementContentWhitespace()
*/
@Override
public boolean isElementContentWhitespace() {
String text = getOwnerDocument().getOwnerDocument().getText();
return StringUtils.isWhitespace(text, getStart(), getEnd());
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#replaceWholeText(java.lang.String)
*/
@Override
public DOMText replaceWholeText(String content) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#splitText(int)
*/
@Override
public DOMText splitText(int offset) throws DOMException {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,130 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.ArrayList;
import java.util.List;
/**
* DTD Attribute List declaration <!ATTLIST
*
* @see https://www.w3.org/TR/REC-xml/#attdecls
*
*/
public class DTDAttlistDecl extends DTDDeclNode {
/**
* Format:
*
* <!ATTLIST element-name attribute-name attribute-type "attribute-value>""
*
* or
*
* <pre>
* <!ATTLIST element-name
* attribute-name1 attribute-type1 "attribute-value1"
* attribute-name2 attribute-type2 "attribute-value2"
* ..
* </pre>
*
* >
*/
public DTDDeclParameter attributeName;
public DTDDeclParameter attributeType;
public DTDDeclParameter attributeValue;
private List<DTDAttlistDecl> internalChildren; // Holds all additional internal attlist declaractions
public DTDAttlistDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 9);
}
@Override
public String getNodeName() {
return getAttributeName();
}
/**
* Returns the element name
*
* @return the element name
*/
public String getElementName() {
return getName();
}
/**
* Returns the attribute name
*
* @return the attribute name
*/
public String getAttributeName() {
return attributeName != null ? attributeName.getParameter() : null;
}
public void setAttributeName(int start, int end) {
attributeName = addNewParameter(start, end);
}
public String getAttributeType() {
return attributeType != null ? attributeType.getParameter() : null;
}
public void setAttributeType(int start, int end) {
attributeType = addNewParameter(start, end);
}
public String getAttributeValue() {
return attributeValue != null ? attributeValue.getParameter() : null;
}
public void setAttributeValue(int start, int end) {
attributeValue = addNewParameter(start, end);
}
@Override
public short getNodeType() {
return DOMNode.DTD_ATT_LIST_NODE;
}
/**
* Add another internal attlist declaration to the list of children.
*
* An ATTLIST decl can internally declare multiple declarations, see top of
* file. This will add another one to its list of additional declarations.
*/
void addAdditionalAttDecl(DTDAttlistDecl child) {
if (internalChildren == null) {
internalChildren = new ArrayList<>();
}
internalChildren.add(child);
}
public List<DTDAttlistDecl> getInternalChildren() {
return internalChildren;
}
/**
* Returns true if this node's parent is the Doctype node.
*
*
* This is used because an Attlist declaration can have multiple attribute
* declarations within a tag that are each represented by this class.
*/
public boolean isRootAttlist() {
return this.parent.isDoctype();
}
}
@@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (c) 2018 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 java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Node;
/**
* DTDNode
*/
public class DTDDeclNode extends DOMNode {
/**
* This class is the base for all declaration nodes for DTD's.
*
* It can also be used to represent an undefined tag, meaning it is not any of:
* ELEMENT, ATTLIST, ENTITY, or NOTATION
*/
public DTDDeclParameter unrecognized; // holds all content after parsing goes wrong in a DTD declaration (ENTITY,
// ATTLIST, ...).
public DTDDeclParameter declType; // represents the actual name of the decl eg: ENTITY, ATTLIST, ...
private List<DTDDeclParameter> parameters;
private DTDDeclParameter name;
public DTDDeclNode(int start, int end) {
super(start, end);
}
public String getName() {
DTDDeclParameter name = getNameParameter();
return name != null ? name.getParameter() : null;
}
public DTDDeclParameter getNameParameter() {
return name;
}
protected DTDDeclParameter getParameterAtIndex(int index) {
return parameters != null && parameters.size() > index ? parameters.get(index) : null;
}
public void setName(int start, int end) {
name = addNewParameter(start, end);
}
public boolean isInNameParameter(int offset) {
DTDDeclParameter name = getNameParameter();
return DOMNode.isIncluded(name, offset);
}
public DOMDocumentType getOwnerDocType() {
Node node = parent;
while (node != null) {
if (node.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
return (DOMDocumentType) node;
}
node = node.getParentNode();
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getName();
}
@Override
public short getNodeType() {
return DOMNode.DTD_DECL_NODE;
}
public String getUnrecognized() {
return unrecognized.getParameter();
}
public void setUnrecognized(int start, int end) {
unrecognized = addNewParameter(start, end);
}
public DTDDeclParameter addNewParameter(int start, int end) {
if (parameters == null) {
parameters = new ArrayList<>();
}
DTDDeclParameter parameter = new DTDDeclParameter(this, start, end);
parameters.add(parameter);
this.end = end; // updates end position of the node.
return parameter;
}
public void updateLastParameterEnd(int end) {
if (parameters != null && parameters.size() > 0) {
DTDDeclParameter last = parameters.get(parameters.size() - 1);
last.end = end;
this.end = end;
}
}
public List<DTDDeclParameter> getParameters() {
if (parameters == null) {
parameters = new ArrayList<>();
}
return parameters;
}
public void setDeclType(int start, int end) {
declType = new DTDDeclParameter(this, start, end);
}
public String getDeclType() {
if (declType != null) {
return declType.getParameter();
}
return null;
}
/**
* Returns the parameter name which references a DTD element declaration
* (<!ELEMENT) at the given offset and null otherwise.
*
* <p>
* <!ATTLIST element-name ... will return position of 'element-name' which
* references the <!ELEMENT element-name
* </p>
*
* @param offset the offset
* @return the parameter name which references a DTD element declaration
* (<!ELEMENT) at the given offset and null otherwise.
*/
public DTDDeclParameter getReferencedElementNameAt(int offset) {
if (isInNameParameter(offset)) {
return getNameParameter();
}
return null;
}
}
@@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright (c) 2018 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 com.itsaky.androidide.models.Range;
import org.eclipse.lemminx.utils.XMLPositionUtility;
/** DTDDeclParameter */
public class DTDDeclParameter implements DOMRange, TargetRange {
private final DTDDeclNode ownerNode;
String parameter;
int start, end;
public DTDDeclParameter(DTDDeclNode ownerNode, int start, int end) {
this.ownerNode = ownerNode;
this.start = start;
this.end = end;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
@Override
public DOMDocument getOwnerDocument() {
return getOwnerNode().getOwnerDocument();
}
public DOMDocumentType getOwnerDocType() {
return getOwnerNode().getOwnerDocType();
}
public DTDDeclNode getOwnerNode() {
return ownerNode;
}
public String getParameter() {
if (parameter == null) {
parameter = getOwnerDocType().getSubstring(start, end);
}
return parameter;
}
/**
* Will get the parameter with the first and last character removed
*
* <p>Can be used to remove the quotations from a URL value...
*/
public String getParameterWithoutFirstAndLastChar() {
if (parameter == null) {
parameter = getOwnerDocType().getSubstring(start + 1, end - 1);
}
return parameter;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DTDDeclParameter)) {
return false;
}
DTDDeclParameter temp = (DTDDeclParameter) obj;
return start == temp.start && end == temp.end;
}
@Override
public Range getTargetRange() {
return XMLPositionUtility.createRange(this);
}
@Override
public String getTargetURI() {
return getOwnerDocument().getDocumentURI();
}
}
@@ -0,0 +1,196 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import static org.eclipse.lemminx.utils.StringUtils.findEndWord;
import static org.eclipse.lemminx.utils.StringUtils.findStartWord;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
/**
* DTD Element Declaration <!ELEMENT
*
* @see https://www.w3.org/TR/REC-xml/#dt-eldecl
*
*/
public class DTDElementDecl extends DTDDeclNode {
private static final Predicate<Character> isValidChar = (c) -> Character.isJavaIdentifierPart(c) || c == '-';
/**
* Formats:
*
* <!ELEMENT element-name category> or <!ELEMENT element-name (element-content)>
*
*/
public DTDDeclParameter category;
public DTDDeclParameter content;
public DTDElementDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 9);
}
@Override
public String getNodeName() {
return getName();
}
public String getCategory() {
return category != null ? category.getParameter() : null;
}
public void setCategory(int start, int end) {
category = addNewParameter(start, end);
}
public String getContent() {
return content != null ? content.getParameter() : null;
}
public void setContent(int start, int end) {
content = addNewParameter(start, end);
}
@Override
public short getNodeType() {
return DOMNode.DTD_ELEMENT_DECL_NODE;
}
/**
* Returns the offset of the end of tag <!ELEMENT
*
* @return the offset of the end of tag <!ELEMENT
*/
public int getEndElementTag() {
return getStart() + "<!ELEMENT".length();
}
/**
* Returns the parameter (start/end offset) at the given offset and null
* otherwise.
*
* <p>
* <!ELEMENT note (to,from,head|ing,body)> will return (start/end offset) of
* heading.
* </p>
*
* <p>
* <!ELEMENT n|ote (to,from,heading,body)> will return null.
* </p>
*
* @param offset the offset
* @return the parameter (start/end offset) at the given offset and null
* otherwise.
*/
public DTDDeclParameter getParameterAt(int offset) {
// Check if offset is in the <!ELEMENT nam|e
if (isInNameParameter(offset)) {
return null;
}
// We are after the <!ELEMENT name, search the parameter
String text = getOwnerDocument().getText();
// Find the start word offset from the left of the offset (ex : (head|ing) will
// return offset of 'h'
int paramStart = findStartWord(text, offset, isValidChar);
// Find the end word to the right of the offset (ex : (head|ing) will return
// offset of 'g'
int paramEnd = findEndWord(text, offset, isValidChar);
if (paramStart == -1 || paramEnd == -1) {
// no word
return null;
}
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();
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.test(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 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;
}
}
@@ -0,0 +1,206 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.Entity;
import org.w3c.dom.Node;
/**
* DOM Entity declaration <!ENTITY
*
* @see https://www.w3.org/TR/REC-xml/#dt-entdecl
*/
public class DTDEntityDecl extends DTDDeclNode implements Entity {
/**
* Formats:
*
* <!ENTITY entity-name "entity-value">
*
* or
*
* <!ENTITY % entity-name "entity-value">
*
* or
*
* <!ENTITY % entity-name SYSTEM "systemId">
*
* or
*
* <!ENTITY % entity-name PUBLIC "publicId" "systemId">
*
* or
*
* <!ENTITY % entity-name SYSTEM "systemId" NDATA name>
*
* or
*
* <!ENTITY % entity-name PUBLIC "publicId" "systemId" NDATA name>
*/
DTDDeclParameter percent;
DTDDeclParameter value;
DTDDeclParameter kind;
DTDDeclParameter publicId;
DTDDeclParameter systemId;
public DTDEntityDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 8);
}
/**
* Returns the '%' and null otherwise.
*
* <p>
* <!ENTITY |% ...
* </p>
*
* @return the '%' and null otherwise.
*/
public String getPercent() {
return percent != null ? percent.getParameter() : null;
}
public void setPercent(int start, int end) {
percent = addNewParameter(start, end);
}
/**
* Returns the entity value node and null otherwise.
*
* <p>
* <!ENTITY % entity-name |"entity-value" ...
* </p>
*
* @return the entity value node and null otherwise.
*/
public DTDDeclParameter getValueNode() {
return value;
}
/**
* Returns the entity value content and null otherwise.
*
* <p>
* <!ENTITY % entity-name |"entity-value" ...
* </p>
*
* @return the entity value content and null otherwise.
*/
public String getValue() {
return value != null ? value.getParameterWithoutFirstAndLastChar() : null;
}
public void setValue(int start, int end) {
value = addNewParameter(start, end);
}
/**
* Returns the entity kind (PUBLIC, SYSTEM) and null otherwise.
*
* <p>
* <!ENTITY % entity-name |PUBLIC
* </p>
*
* @return the entity kind (PUBLIC, SYSTEM) and null otherwise.
*/
public String getKind() {
return kind != null ? kind.getParameter() : null;
}
public void setKind(int start, int end) {
kind = addNewParameter(start, end);
}
@Override
public short getNodeType() {
return DOMNode.ENTITY_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getInputEncoding()
*/
@Override
public String getInputEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getNotationName()
*/
@Override
public String getNotationName() {
return getValue();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getPublicId()
*/
@Override
public String getPublicId() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}
public void setPublicId(int start, int end) {
publicId = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getSystemId()
*/
@Override
public String getSystemId() {
return systemId != null ? systemId.getParameterWithoutFirstAndLastChar() : null;
}
/**
* Returns the system id node and null otherwise.
*
* @return the system id node and null otherwise.
*/
public DTDDeclParameter getSystemIdNode() {
return systemId;
}
public void setSystemId(int start, int end) {
systemId = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getXmlEncoding()
*/
@Override
public String getXmlEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getXmlVersion()
*/
@Override
public String getXmlVersion() {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,72 @@
/*******************************************************************************
* Copyright (c) 2018 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;
/**
* DTDNotationDecl
*/
public class DTDNotationDecl extends DTDDeclNode {
/**
* Format:
*
* <!NOTATION Name PUBLIC PublicID>
*
* or
*
* <!NOTATION Name PUBLIC PublicID SystemID>
*
* or
*
* <!NOTATION Name SYSTEM SystemID>
*/
DTDDeclParameter kind;
DTDDeclParameter publicId;
DTDDeclParameter systemId;
public DTDNotationDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 10);
}
void setKind(int start, int end) {
kind = addNewParameter(start, end);
}
public String getKind() {
return kind != null ? kind.getParameter() : null;
}
void setPublicId(int start, int end) {
publicId = addNewParameter(start, end);
}
public String getPublicId() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}
void setSystemId(int start, int end) {
systemId = addNewParameter(start, end);
}
public String getSystemId() {
return systemId != null ? systemId.getParameterWithoutFirstAndLastChar() : null;
}
@Override
public short getNodeType() {
return DOMNode.DTD_NOTATION_DECL;
}
}
@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2018 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:
* Angelo Zerr. - initial API and implementation
*
*******************************************************************************/
package org.eclipse.lemminx.dom;
public class LineIndentInfo {
private final String lineDelimiter;
private final String whitespacesIndent;
public LineIndentInfo(String lineDelimiter, String whitespacesIndent) {
this.lineDelimiter = lineDelimiter;
this.whitespacesIndent = whitespacesIndent;
}
public String getLineDelimiter() {
return lineDelimiter;
}
public String getWhitespacesIndent() {
return whitespacesIndent;
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
/**
*
* The declared "xsi:noNamespaceSchemaLocation"
*/
public class NoNamespaceSchemaLocation {
private final DOMAttr attr;
public NoNamespaceSchemaLocation(DOMAttr attr) {
this.attr = attr;
}
public DOMAttr getAttr() {
return attr;
}
/**
* Returns the location declared in the attribute value of
* "xsi:noNamespaceSchemaLocation"
*
* @return the location declared in the attribute value of
* "xsi:noNamespaceSchemaLocation"
*/
public String getLocation() {
return attr.getValue();
}
}
@@ -0,0 +1,82 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* The declared "xsi:schemaLocation"
*/
public class SchemaLocation {
private final Map<String, SchemaLocationHint> schemaLocationValuePairs;
private final DOMAttr attr;
// The text to match is of the form:
// http://example.org/schema/root root.xsd http://example.org/schema/bison bison.xsd http://example.org/schema/potato potato.xsd
private static final Pattern SCHEMA_LOCATION_PAIR_PATTERN = Pattern.compile("\\s*([^\\s]+)\\s+([^\\s]+)\\s*");
public SchemaLocation(DOMAttr attr) {
this.attr = attr;
this.schemaLocationValuePairs = new HashMap<>();
String value = attr.getValue();
Matcher locPairMatcher = SCHEMA_LOCATION_PAIR_PATTERN.matcher(value);
while (locPairMatcher.find()) {
String namespaceURI = locPairMatcher.group(1);
String locationHint = locPairMatcher.group(2);
if (namespaceURI == null || locationHint == null) {
break;
}
DOMRange valNode = attr.getNodeAttrValue();
// http://example.org/schema/root |root.xsd http://example.org/schema/bison bison.xsd
int start = valNode.getStart() + locPairMatcher.start(2) + 1;
// http://example.org/schema/root root.xsd| http://example.org/schema/bison bison.xsd
int end = valNode.getStart() + locPairMatcher.end(2) + 1;
schemaLocationValuePairs.put(namespaceURI,
new SchemaLocationHint(start, end, locationHint, this));
}
}
/**
* Returns the location hint, as a string, that is associated with the given namespace URI.
*
* If the given namespace URI was not referred to in this xsi:schemaLocation, then null is returned.
*
* @param namespaceURI The namespace URI to find the location hint for
* @return The associated location hint, as a string, or null
* if the namespace was not referred to in xsi:schemaLocation
*/
public SchemaLocationHint getLocationHint(String namespaceURI) {
return schemaLocationValuePairs.get(namespaceURI);
}
public DOMAttr getAttr() {
return attr;
}
/**
* Returns all the location hints given in this xsi:schemaLocation attribute
*
* @return A Collection of all the location hints as <code>SchemaLocationHint</code>
*/
public Collection<SchemaLocationHint> getSchemaLocationHints() {
return schemaLocationValuePairs.values();
}
}
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2020 Red Hat Inc.
* 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;
/**
* Represents one of the location hints provided in an xsi:schemaLocation
* attribute.
*/
public class SchemaLocationHint implements DOMRange {
private final int start, end;
private final String hint;
private final SchemaLocation parent;
/**
* Create a new SchemaLocationHint object that represents one of the location
* hints given in the parent <code>SchemaLocation</code>.
*
* @param start The offset from the beginning of the document where this
* location hint starts
* @param end The offset from the beginning of the document where this
* location hint ends
* @param hint The hint to the location of a schema (A URI that points to a
* schema)
* @param parent The <code>SchemaLocation</code> in which this hint was given
*/
public SchemaLocationHint(int start, int end, String hint, SchemaLocation parent) {
this.start = start;
this.end = end;
this.hint = hint;
this.parent = parent;
}
/**
* Returns the location hint that this SchemaLocationHint represents
*
* @return The location hint, a URI to a schema, as a String
*/
public String getHint() {
return this.hint;
}
/**
* Returns the offset from the beginning of the document where this location
* hint starts
*
* @return The offset from the beginning of the document where this location
* hint starts
*/
@Override
public int getStart() {
return this.start;
}
/**
* Returns the offset from the beginning of the document where this location
* hint ends
*
* @return The offset from the beginning of the document where this location
* hint ends
*/
@Override
public int getEnd() {
return this.end;
}
@Override
public DOMDocument getOwnerDocument() {
return parent.getAttr().getOwnerDocument();
}
}
@@ -0,0 +1,31 @@
/**
* 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
*
* <p>SPDX-License-Identifier: EPL-2.0
*
* <p>Contributors: Red Hat Inc. - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import com.itsaky.androidide.models.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();
}
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
import java.util.regex.Pattern;
public class Constants {
public final static int _EXL = "!".codePointAt(0);
public final static int _MIN = "-".codePointAt(0);
public final static int _UDS = "_".codePointAt(0);
public final static int _DDT = ":".codePointAt(0);
public final static int _DOT = ".".codePointAt(0);
public final static int _LAN = "<".codePointAt(0);
public final static int _RAN = ">".codePointAt(0);
public final static int _FSL = "/".codePointAt(0);
public final static int _EQS = "=".codePointAt(0);
public final static int _CMA = ",".codePointAt(0);
public final static int _DQO = "\"".codePointAt(0);
public final static int _SQO = "\"".codePointAt(0);
public final static int _SIQ = "\'".codePointAt(0);
public final static int _NWL = "\n".codePointAt(0);
public final static int _CAR = "\r".codePointAt(0);
public final static int _LFD = "\f".codePointAt(0);
public final static int _WSP = " ".codePointAt(0);
public final static int _TAB = "\t".codePointAt(0);
public final static int _OSB = "[".codePointAt(0);
public final static int _CSB = "]".codePointAt(0);
public final static int _ORB = "(".codePointAt(0);
public final static int _CRB = ")".codePointAt(0);
public final static int _OCB = "{".codePointAt(0);
public final static int _CCB = "}".codePointAt(0);
public final static int _CVL = "C".codePointAt(0);
public final static int _DVL = "D".codePointAt(0);
public final static int _AVL = "A".codePointAt(0);
public final static int _TVL = "T".codePointAt(0);
public final static int _OVL = "O".codePointAt(0);
public final static int _YVL = "Y".codePointAt(0);
public final static int _PVL = "P".codePointAt(0);
public final static int _EVL = "E".codePointAt(0);
public final static int _LVL = "L".codePointAt(0);
public final static int _MVL = "M".codePointAt(0);
public final static int _NVL = "N".codePointAt(0);
public final static int _IVL = "I".codePointAt(0);
public final static int _SVL = "S".codePointAt(0);
public final static int _QMA = "?".codePointAt(0);
public final static int _XVL = "x".codePointAt(0);
public final static int _mVL = "m".codePointAt(0);
public final static int _lVL = "l".codePointAt(0);
public final static int _PCT = "%".codePointAt(0);
public final static int _AST = "*".codePointAt(0);
public final static int _PLS = "+".codePointAt(0);
public static final Pattern ENTITY_NAME_REGEX = Pattern.compile("");
public static final Pattern ELEMENT_NAME_REGEX = Pattern.compile("^[_:\\w][_:\\w-.\\d]*");
public static final Pattern ATTRIBUTE_NAME_REGEX = Pattern.compile("^[^\\s\\?\"'<>/=\\x00-\\x0F\\x7F\\x80-\\x9F]*");
public static final Pattern ATTRIBUTE_VALUE_REGEX = Pattern.compile("^(\"[^\"]*\"?)|(\'[^\']*\'?)");
public static final Pattern URL_VALUE_REGEX = Pattern.compile("^(\"[^<>\"]*\")|(\'[^<>\']*\')");
public static final Pattern PROLOG_NAME_OPTIONS = Pattern.compile("^(xml)[\\s<>?]?");
public static final Pattern PI_TAG_NAME = Pattern.compile("^[a-zA-Z0-9]+");
// Add coming processing instructions that are defined to have attributes as
// content
public static final Pattern PI_WITH_VARIABLES = Pattern.compile("^(xml-stylesheet)[\\s<>?]?");
public static final Pattern DOCTYPE_KIND_OPTIONS = Pattern.compile("^(PUBLIC|SYSTEM)([\\s<>\"'])");
public static final Pattern DTD_ELEMENT_CATEGORY = Pattern.compile("^(EMPTY|ANY)([\\s<>\"'])");
public static final Pattern DTD_ELEMENT_CONTENT = Pattern.compile("^(\\((([^\\s,]+,)*[^\\s,]+)\\))|\\(\\)");
public static final Pattern DTD_PCDATA = Pattern.compile("^#PCDATA");
public static final Pattern DTD_ATTLIST_ATTRIBUTE_TYPE = Pattern
.compile("^(CDATA|IDREFS|IDREF|ID|NMTOKENS|NMTOKEN|ENTITIES|ENTITY|NOTATION|xml:|\\(.*\\))([\\s<>\"'])");
public static final Pattern DTD_ATTLIST_ATTRIBUTE_VALUE = Pattern
.compile("^(#REQUIRED|#IMPLIED|\".*\"|#FIXED \".*\")([\\s<>\"'])");
public static final Pattern DTD_ENTITY_VALUE = Pattern.compile("^\".*\"");
public static final Pattern DOCTYPE_NAME = Pattern.compile("^[_:\\w][_:\\w-.\\d]*");
public static final Pattern DOCUMENTATION_CONTENT = Pattern.compile(".*<[\\S]+:?documentation[^\\>]*>(.*)<\\/[\\S]+:?documentation[\\s]*>.*");
}
@@ -0,0 +1,336 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
import static org.eclipse.lemminx.dom.parser.Constants._CAR;
import static org.eclipse.lemminx.dom.parser.Constants._CSB;
import static org.eclipse.lemminx.dom.parser.Constants._LAN;
import static org.eclipse.lemminx.dom.parser.Constants._LFD;
import static org.eclipse.lemminx.dom.parser.Constants._NWL;
import static org.eclipse.lemminx.dom.parser.Constants._OSB;
import static org.eclipse.lemminx.dom.parser.Constants._RAN;
import static org.eclipse.lemminx.dom.parser.Constants._TAB;
import static org.eclipse.lemminx.dom.parser.Constants._WSP;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Multi line stream.
*
*/
public class MultiLineStream {
private static final Predicate<Integer> WHITESPACE_PREDICATE = ch -> {
return ch == _WSP || ch == _TAB || ch == _NWL || ch == _LFD || ch == _CAR;
};
private final String source;
private final int len;
private int position;
private final Map<Pattern, Matcher> regexpCache;
public MultiLineStream(String source, int position) {
this.source = source;
this.len = source.length();
this.position = position;
this.regexpCache = new HashMap<>();
}
public boolean eos() {
return this.len <= this.position;
}
public String getSource() {
return this.source;
}
public int pos() {
return this.position;
}
public void goBackTo(int pos) {
this.position = pos;
}
public void goBack(int n) {
this.position -= n;
}
public void advance(int n) {
this.position += n;
}
public void goToEnd() {
this.position = len;
}
/*
* public int nextChar() { // return this.source.codePointAt(this.position++) ||
* 0; int next = this.source.codePointAt(this.position++); return next >= 0 ?
* next : 0; }
*/
public int peekChar() {
return peekChar(0);
}
/**
* Peeks at next char at position + n. peekChar() == peekChar(0)
* @param n
* @return
*/
public int peekChar(int n) {
int pos = this.position + n;
if (pos >= len) {
return -1;
}
return this.source.codePointAt(pos);
}
/**
* Peeks at the char at position 'offset' of the whole document
* @param offset
* @return
*/
public int peekCharAtOffset(int offset) {
if (offset >= len || offset < 0) {
return -1;
}
return this.source.codePointAt(offset);
}
public boolean advanceIfChar(int ch) {
if (ch == peekChar()) {
this.position++;
return true;
}
return false;
}
public boolean advanceIfChars(int[] ch) {
int i;
if (this.position + ch.length > this.len) {
return false;
}
for (i = 0; i < ch.length; i++) {
if (peekChar(i) != ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
public int advanceIfAnyOfChars(int[] ch) {
int i;
if (this.position + 1 > this.len) {
return -1;
}
for (i = 0; i < ch.length; i++) {
if (advanceIfChar(ch[i])) {
return ch[i];
}
}
return -1;
}
public String advanceIfRegExp(Pattern regex) {
Matcher match = getCachedMatcher(regex);
// Initialize start region where search must be started.
match.region(this.position, this.len);
if (match.find()) {
this.position = match.end();
return match.group(0);
}
return "";
}
/**
* Advances stream on regex, but will grab the first group
* @param regex
* @return
*/
public String advanceIfRegExpGroup1(Pattern regex) {
Matcher match = getCachedMatcher(regex);
// Initialize start region where search must be started.
match.region(this.position, this.len);
if (match.find()) {
this.position = match.end(1);
return match.group(1);
}
return "";
}
/**
* Advances stream.position no matter what until it hits ch or eof(this.len)
*
* @return boolean: was the char found
*/
public boolean advanceUntilChar(int ch) {
while (this.position < this.len) {
if (peekChar() == ch) {
return true;
}
this.advance(1);
}
return false;
}
/**
* Will advance until any of the provided chars are encountered
*/
public boolean advanceUntilAnyOfChars(int[] ch) {
while (this.position < this.len) {
for (int i = 0; i < ch.length; i++) {
if (peekChar() == ch[i]) {
return true;
}
}
this.advance(1);
}
return false;
}
/**
* Will advance the stream position until ch or '<'
*/
public boolean advanceUntilCharOrNewTag(int ch) {
while (this.position < this.len) {
if (peekChar() == ch || peekChar() == _LAN) {
return true;
}
this.advance(1);
}
return false;
}
/**
* Will advance the stream position until 'closingBracket' or using a stack
* to consider possible open/closed bracket pairs in between.
*
* 'closingBracket' should be the closing bracket eg: > | ]
*/
public boolean advanceUntilCharUsingStack(int closingBracket) {
int openingBracket;
if(closingBracket == _RAN) { // >
openingBracket = _LAN;// <
}
else if(closingBracket == _CSB) { // ]
openingBracket = _OSB; // [
}
else {
return false; // The provided closingBracket is not implemented
}
int stack = 0;
while (this.position < this.len) {
if(peekChar() == openingBracket) {
stack ++;
}
else if(peekChar() == closingBracket) {
if(stack == 0) {
return true;
}
stack--;
}
this.advance(1);
}
return false;
}
public boolean advanceUntilChars(int[] ch) {
while (this.position + ch.length <= this.len) {
int i = 0;
for (; i < ch.length && peekChar(i) == ch[i]; i++) {
}
if (i == ch.length) {
return true;
}
this.advance(1);
}
this.goToEnd();
return false;
}
/**
* Advances until it matches int[] ch OR it hits '<' If this returns true, peek
* if next char is '<' to check which case was hit
*/
public boolean advanceUntilCharsOrNewTag(int[] ch) {
while (this.position + ch.length <= this.len) {
int i = 0;
if (peekChar(0) == _LAN) { // <
return true;
}
for (; i < ch.length && peekChar(i) == ch[i]; i++) {
}
if (i == ch.length) {
return true;
}
this.advance(1);
}
this.goToEnd();
return false;
}
/**
* Advances until it reaches a whitespace character
*/
public boolean skipWhitespace() {
int n = this.advanceWhileChar(WHITESPACE_PREDICATE);
return n > 0;
}
public int advanceWhileChar(Predicate<Integer> condition) {
int posNow = this.position;
while (this.position < this.len && condition.test(peekChar())) {
this.position++;
}
return this.position - posNow;
}
/**
* Returns the cached matcher from the given regex.
*
* @param regex the regex pattern.
* @return the cached matcher from the given regex.
*/
private Matcher getCachedMatcher(Pattern regex) {
Matcher matcher = regexpCache.get(regex);
if (matcher == null) {
matcher = regex.matcher(source);
regexpCache.put(regex, matcher);
} else {
matcher.reset(); // Cached regex caused issues, needed to reset it.
}
return matcher;
}
public int getLastNonWhitespaceOffset() {
int posNow = this.position;
while (posNow > 0 && WHITESPACE_PREDICATE.test(peekCharAtOffset(posNow - 1))) {
posNow--;
}
return posNow;
}
}
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
/**
* Scanner API.
*
*/
public interface Scanner {
TokenType scan();
TokenType getTokenType();
/**
* Starting offset position of the current token
*
* @return int of token's start offset
*/
int getTokenOffset();
int getTokenLength();
/**
* Ending offset position of the current token
*
* @return int of token's end offset
*/
int getTokenEnd();
String getTokenText();
String getTokenError();
ScannerState getScannerState();
/**
* @return True if the token's Text is empty or all whitespace
*/
boolean isTokenTextBlank();
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
* - Nikolas Komonen <nkomonen@redhat.com> - additions for CDATA and Processing Instruction
*/
package org.eclipse.lemminx.dom.parser;
/**
* XML Scanner state.
*
*/
public enum ScannerState {
WithinContent, AfterOpeningStartTag, AfterOpeningEndTag, WithinProlog, WithinTag, WithinEndTag,
WithinComment, AfterAttributeName, BeforeAttributeValue, WithinCDATA, AfterClosingCDATATag, StartCDATATag,
AfterPrologOpen, PrologOrPI, WithinPI,
// DTD
DTDWithinDoctype, DTDAfterDoctypeName, DTDAfterDoctypePUBLIC, DTDAfterDoctypeSYSTEM,
DTDAfterDoctypePublicId,DTDWithinContent, DTDWithinElement, DTDWithinAttlist, DTDWithinEntity,
DTDElementAfterName, DTDElementWithinContent, DTDAfterAttlistName, DTDAfterAttlistElementName,
DTDAfterAttlistAttributeName, DTDAfterAttlistAttributeType, DTDAfterEntityName, DTDUnrecognizedParameters,
DTDWithinNotation, DTDAfterNotationName, DTDAfterNotationPUBLIC, DTDAfterNotationSYSTEM,
DTDAfterNotationPublicId, DTDAfterEntityPUBLIC, DTDAfterEntitySYSTEM, DoctypeUnrecognizedParameters;
}
@@ -0,0 +1,102 @@
/**
* Copyright (c) 2018 Angelo ZERR.
* 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
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
/**
* XML Token type.
*
*/
public enum TokenType {
StartCommentTag,
Comment,
EndCommentTag,
CDATATagOpen,
CDATAContent,
CDATATagClose,
StartTagOpen,
StartTagClose,
StartTagSelfClose,
StartTag,
EndTagOpen,
EndTagClose,
EndTag,
DelimiterAssign,
AttributeName,
AttributeValue,
StartPrologOrPI,
PrologName,
PIName,
PIContent,
PIEnd,
PrologEnd,
Content,
Whitespace,
Unknown,
EOS,
// DTD
DTDStartDoctypeTag,
DTDDoctypeName,
DTDDocTypeKindPUBLIC,
DTDDocTypeKindSYSTEM,
DTDDoctypePublicId,
DTDDoctypeSystemId,
DTDEndDoctypeTag,
DTDStartInternalSubset,
DTDEndInternalSubset,
// DTD Element declaration
DTDStartElement,
DTDElementDeclName,
DTDElementCategory,
DTDStartElementContent,
DTDElementContent,
DTDEndElementContent,
// DTD AttrList declaration
DTDStartAttlist,
DTDAttlistElementName,
DTDAttlistAttributeValue,
DTDAttlistAttributeType,
DTDAttlistAttributeName,
// DTD Entity
DTDStartEntity,
DTDEntityPercent,
DTDEntityKindPUBLIC,
DTDEntityKindSYSTEM,
DTDEntityPublicId,
DTDEntitySystemId,
DTDEntityName,
DTDEntityValue,
// DTD Notation
DTDStartNotation,
DTDNotationName,
DTDNotationKindPUBLIC,
DTDNotationKindSYSTEM,
DTDNotationPublicId,
DTDNotationSystemId,
//For any DTD Decl Tag that has an unrecognized parameter
DTDUnrecognizedParameters,
//End of any DTD Decl Tag
DTDEndTag;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2011 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
//package org.eclipse.wst.xml.core.internal.contentmodel.modelquery;
package org.eclipse.lemminx.uriresolver;
import java.net.URI;
import java.util.Map;
public interface IExternalGrammarLocationProvider {
String SCHEMA_LOCATION = "http://apache.org/xml/properties/schema/external-schemaLocation"; //$NON-NLS-1$
String NO_NAMESPACE_SCHEMA_LOCATION = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation"; //$NON-NLS-1$
String DOCTYPE = "http://apache.org/xml/properties/dtd/external-doctype"; //$NON-NLS-1$
/**
* Provided the file path URI, this will return the external schema location.
* The schema location is associated to a specific property, which serves as the
* key in the map. The supported properties are:
*
* <p>
* <b>http://apache.org/xml/properties/schema/external-schemaLocation</b> - The
* value for this property follows the same rules as the schemaLocation
* attribute for an XML document (i.e., multiple schemas are acceptable and must
* be white-space separated).
* </p>
*
* <p>
* <b>http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation</b>
* - The value for this property follows the same rules as the
* noNamespaceSchemaLocation attribute for an XML document (i.e., only one
* schema is allowed).
*
* @param filePath the path for the XML file
* @return a {@link Map} associating the external-schemaLocation and/or
* external-noNamespaceSchemaLocation to the schema location
*/
Map<String, String> getExternalGrammarLocation(URI fileURI);
}
@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.uriresolver;
/**
* Resolver URI information.
*
*/
public class ResolvedURIInfo {
private final String resolvedURI;
private final URIResolverExtension resolver;
public ResolvedURIInfo(String resolvedURI, URIResolverExtension resolver) {
this.resolvedURI = resolvedURI;
this.resolver = resolver;
}
/**
* Returns the resolved URI.
*
* @return the resolved URI.
*/
public String getResolvedURI() {
return resolvedURI;
}
/**
* Returns the resolver name {@link URIResolverExtension#getName()} (ex :
* 'default', 'file association) used to resolve the URI.
*
* @return the resolver name {@link URIResolverExtension#getName()} (ex :
* 'default', 'file association) used to resolve the URI.
*/
public String getResolverName() {
return resolver.getName();
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2018 Angelo ZERR 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
*
* <p>SPDX-License-Identifier: EPL-2.0
*
* <p>Contributors: Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.uriresolver;
import java.io.IOException;
import jaxp.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import jaxp.sun.org.apache.xerces.internal.xni.XNIException;
import jaxp.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
import jaxp.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
/** URI resolver API */
public interface URIResolverExtension extends XMLEntityResolver {
public static final String DEFAULT = "default";
default String getName() {
return getClass().getSimpleName();
}
/**
* @param baseLocation - the location of the resource that contains the uri
* @param publicId - an optional public identifier (i.e. namespace name), or null if none
* @param systemId - an absolute or relative URI, or null if none
* @return an absolute URI representation of the 'logical' location of the resource
*/
public String resolve(String baseLocation, String publicId, String systemId);
@Override
default XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier)
throws XNIException, IOException {
return null;
}
}
@@ -0,0 +1,141 @@
/**
* Copyright (c) 2018 Angelo ZERR 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
*
* <p>SPDX-License-Identifier: EPL-2.0
*
* <p>Contributors: Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.uriresolver;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jaxp.sun.org.apache.xerces.internal.impl.XMLEntityManager;
import jaxp.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import jaxp.sun.org.apache.xerces.internal.xni.XNIException;
import jaxp.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
/** URI resolver manager. */
public class URIResolverExtensionManager
implements URIResolverExtension, IExternalGrammarLocationProvider {
private final List<URIResolverExtension> resolvers;
private final URIResolverExtension defaultURIResolverExtension;
public URIResolverExtensionManager() {
resolvers = new ArrayList<>();
this.defaultURIResolverExtension = new DefaultURIResolverExtension();
}
static class DefaultURIResolverExtension implements URIResolverExtension {
@Override
public String getName() {
return URIResolverExtension.DEFAULT;
}
@Override
public String resolve(String baseLocation, String publicId, String systemId) {
try {
return XMLEntityManager.expandSystemId(systemId, baseLocation, false);
} catch (jaxp.sun.org.apache.xerces.internal.util.URI.MalformedURIException e) {
return systemId;
}
}
@Override
public XMLInputSource resolveEntity(XMLResourceIdentifier rid)
throws XNIException, IOException {
XMLInputSource is = null;
String id = rid.getPublicId();
if (id == null) {
id = rid.getNamespace();
}
String location = null;
if (id != null || rid.getLiteralSystemId() != null) {
location = this.resolve(rid.getBaseSystemId(), id, rid.getLiteralSystemId());
}
if (location != null) {
is = new XMLInputSource(rid.getPublicId(), location, location);
}
return is;
}
}
/**
* Register an URI resolver.
*
* @param resolver the URI resolver to register.
*/
public void registerResolver(URIResolverExtension resolver) {
resolvers.add(resolver);
}
/**
* Unregister an URI resolver.
*
* @param resolver the URI resolver to unregister.
*/
public void unregisterResolver(URIResolverExtension resolver) {
resolvers.add(resolver);
}
@Override
public String resolve(String baseLocation, String publicId, String systemId) {
for (URIResolverExtension resolver : resolvers) {
String resolved = resolver.resolve(baseLocation, publicId, systemId);
if (resolved != null && !resolved.isEmpty()) {
return resolved;
}
}
return defaultURIResolverExtension.resolve(baseLocation, publicId, systemId);
}
public ResolvedURIInfo resolveInfo(String baseLocation, String publicId, String systemId) {
for (URIResolverExtension resolver : resolvers) {
String resolvedURI = resolver.resolve(baseLocation, publicId, systemId);
if (resolvedURI != null && !resolvedURI.isEmpty()) {
return new ResolvedURIInfo(resolvedURI, resolver);
}
}
String resolvedURI = defaultURIResolverExtension.resolve(baseLocation, publicId, systemId);
if (resolvedURI != null && !resolvedURI.isEmpty()) {
return new ResolvedURIInfo(resolvedURI, defaultURIResolverExtension);
}
return null;
}
@Override
public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier)
throws XNIException, IOException {
XMLInputSource is = null;
for (URIResolverExtension resolver : resolvers) {
is = resolver.resolveEntity(resourceIdentifier);
if (is != null) {
return is;
}
}
return defaultURIResolverExtension.resolveEntity(resourceIdentifier);
}
@Override
public Map<String, String> getExternalGrammarLocation(URI fileURI) {
for (URIResolverExtension resolver : resolvers) {
if (resolver instanceof IExternalGrammarLocationProvider) {
Map<String, String> result =
((IExternalGrammarLocationProvider) resolver).getExternalGrammarLocation(fileURI);
if (result != null) {
return result;
}
}
}
return null;
}
}
@@ -0,0 +1,160 @@
/*******************************************************************************
* Copyright (c) 2018 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.utils;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMParser;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
/**
* DOM Utilities.
*
*/
public class DOMUtils {
private static final Logger LOGGER = Logger.getLogger(DOMUtils.class.getName());
private static final String XSD_EXTENSION = ".xsd";
// DTD file extensions
private static final String DTD_EXTENSION = ".dtd";
private static final String ENT_EXTENSION = ".ent";
private static final String MOD_EXTENSION = ".mod";
private static final String HTTP_WWW_W3_ORG_2001_XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";
private static final String URN_OASIS_NAMES_TC_ENTITY_XMLNS_XML_CATALOG_NS = "urn:oasis:names:tc:entity:xmlns:xml:catalog";
private DOMUtils() {
}
/**
* Returns true if the XML document is a XML Schema and false otherwise.
*
* @return true if the XML document is a XML Schema and false otherwise.
*/
public static boolean isXSD(DOMDocument document) {
if (document == null) {
return false;
}
String uri = document.getDocumentURI();
if (isXSD(uri)) {
return true;
}
// check root element is bound with XML Schema namespace
// (http://www.w3.org/2001/XMLSchema)
return checkRootNamespace(document, HTTP_WWW_W3_ORG_2001_XML_SCHEMA_NS);
}
/**
* Returns true if the given URI is a XML Schema and false otherwise.
*
* @param uri the URI to check
* @return true if the given URI is a XML Schema and false otherwise.
*/
public static boolean isXSD(String uri) {
return uri != null && uri.endsWith(XSD_EXTENSION);
}
/**
* Returns true if the XML document is a XML Catalog and false otherwise.
*
* @return true if the XML document is a XML Catalog and false otherwise.
*/
public static boolean isCatalog(DOMDocument document) {
// check root element is bound with XML Catalog namespace
// (urn:oasis:names:tc:entity:xmlns:xml:catalog)
return checkRootNamespace(document, URN_OASIS_NAMES_TC_ENTITY_XMLNS_XML_CATALOG_NS);
}
/**
* Returns true if the document element root is bound to the given namespace and
* false otherwise.
*
* @param document
* @param namespace
* @return true if the document element root is bound to the given namespace and
* false otherwise.
*/
private static boolean checkRootNamespace(DOMDocument document, String namespace) {
DOMElement documentElement = document.getDocumentElement();
return documentElement != null && namespace.equals(documentElement.getNamespaceURI());
}
/**
* Returns true if the given URI is a DTD and false otherwise.
*
* @param uri the URI to check
* @return true if the given URI is a DTD and false otherwise.
*/
public static boolean isDTD(String uri) {
return uri != null
&& (uri.endsWith(DTD_EXTENSION) || uri.endsWith(ENT_EXTENSION) || uri.endsWith(MOD_EXTENSION));
}
/**
* Returns true if element contains only DOMText and false otherwise.
*
* @return true if element contains only DOMText and false otherwise.
*/
public static boolean containsTextOnly(DOMElement element) {
return element.getChildNodes().getLength() == 1 && element.getFirstChild().isText();
}
/**
* Returns the DOM document from the given XML Schema uri.
*
* @param documentURI the schema URI
* @param resolverExtensionManager
* @return the DOM document from the given XML Schema uri.
*/
public static DOMDocument loadDocument(String documentURI, URIResolverExtensionManager resolverExtensionManager) {
try {
return DOMParser.getInstance().parse(IOUtils.convertStreamToString(new URL(documentURI).openStream()),
documentURI, resolverExtensionManager);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while loading XML Schema '" + documentURI + "'.", e);
return null;
}
}
/**
* Returns an instance of SAX parser factory by disabling external entities
* declarations.
*
* @return an instance of SAX parser factory by disabling external entities
* declarations.
* @throws SAXNotRecognizedException
* @throws SAXNotSupportedException
* @throws ParserConfigurationException
*/
public static SAXParserFactory newSAXParserFactory()
throws SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException {
SAXParserFactory factory = SAXParserFactory.newInstance();
// to be more secure, completely disable DOCTYPE declaration:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
return factory;
}
}
@@ -0,0 +1,38 @@
/*******************************************************************************
* 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.utils;
import java.io.InputStream;
import java.util.Scanner;
/**
* IO utilities class
*
* @author Angelo ZERR
*
*/
public class IOUtils {
/**
* Convert the given {@link InputStream} into a String. The source InputStream
* will then be closed.
*
* @param is the input stream
* @return the given input stream in a String.
*/
public static String convertStreamToString(InputStream is) {
try (Scanner s = new java.util.Scanner(is)) {
s.useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
}
}
@@ -0,0 +1,476 @@
/**
* Copyright (c) 2018 Red Hat, Inc. 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
*
* <p>SPDX-License-Identifier: EPL-2.0
*
* <p>Contributors: Nikolas Komonen <nikolaskomonen@gmail.com>, Red Hat Inc. - initial API and
* implementation
*/
package org.eclipse.lemminx.utils;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Predicate;
/** String utilities. */
public class StringUtils {
public static final String[] EMPTY_STRING = new String[0];
public static final String TRUE = "true";
public static final String FALSE = "false";
public static final Collection<String> TRUE_FALSE_ARRAY = Arrays.asList(TRUE, FALSE);
private StringUtils() {}
public static boolean isEmpty(String value) {
return value == null || value.isEmpty();
}
// Utilities class.
public static boolean isQuote(char c) {
return c == '\'' || c == '"';
}
public static boolean isWhitespace(String value, int index) {
return isWhitespace(value, index, value.length());
}
public static boolean isWhitespace(String value, int index, int end) {
if (value == null) {
return false;
}
char c;
while (index < end) {
c = value.charAt(index);
if (Character.isWhitespace(c) == false) {
return false;
}
index++;
}
return true;
}
public static boolean isWhitespace(String value) {
return isWhitespace(value, 0);
}
/**
* Checks if a string is null or consists of only whitespace characters.
*
* @param value The string to check
* @return <code>true</code> if any of the below hold, and false otherwise:
* <ul>
* <li>The String is <code>null</code>
* <li>The String is of length 0
* <li>The String contains only whitespace characters
* </ul>
*/
public static boolean isBlank(String value) {
return isEmpty(value) || isWhitespace(value);
}
/**
* Normalizes the whitespace characters of a given string and applies it to the given string
* builder.
*
* @param str
* @return the result of normalize space of the given string.
*/
public static void normalizeSpace(String str, StringBuilder b) {
String space = "";
for (int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
if (Character.isWhitespace(c)) {
if (i == 0 || Character.isWhitespace(str.charAt(i - 1))) {
continue;
}
space = " ";
continue;
}
b.append(space);
space = "";
b.append(c);
}
}
/**
* Returns the result of normalize space of the given string.
*
* @param str
* @return the result of normalize space of the given string.
*/
public static String normalizeSpace(String str) {
StringBuilder b = new StringBuilder(str.length());
normalizeSpace(str, b);
return b.toString();
}
/**
* Returns the start whitespaces of the given line text.
*
* @param lineText
* @return the start whitespaces of the given line text.
*/
public static String getStartWhitespaces(String lineText) {
return getWhitespaces(lineText, 0, lineText.length());
}
/**
* Returns the whitespaces from the given range start/end of the given text.
*
* @param start the range start
* @param end the range end
* @param text the text
* @return the whitespaces from the given range start/end of the given text.
*/
public static String getWhitespaces(String text, int start, int end) {
StringBuilder whitespaces = new StringBuilder();
for (int i = start; i < end; i++) {
char c = text.charAt(i);
if (Character.isWhitespace(c)) {
whitespaces.append(c);
} else {
break;
}
}
return whitespaces.toString();
}
public static void trimNewLines(String value, StringBuilder s) {
int len = value.length();
int st = 0;
char[] val = value.toCharArray();
// left trim
boolean hasNewLine = false;
int start = 0;
while ((st < len) && (Character.isWhitespace(val[st]))) {
if (val[st] == '\r' || val[st] == '\n') {
hasNewLine = true;
} else if (hasNewLine) {
break;
}
st++;
}
if (hasNewLine) {
start = st;
// adjust offset with \r\n
if (st > 0 && st < len && val[st - 1] == '\r' && val[st] == '\n') {
start++;
}
}
// right trim
hasNewLine = false;
int end = len;
while ((st < len) && (Character.isWhitespace(val[len - 1]))) {
if (val[len - 1] == '\r' || val[len - 1] == '\n') {
hasNewLine = true;
} else if (hasNewLine) {
break;
}
len--;
}
if (hasNewLine) {
end = len;
// adjust offset with \r\n
if (val[len - 1] == '\r' && val[len] == '\n') {
end--;
}
}
s.append(value, start, end);
}
public static String trimNewLines(String value) {
StringBuilder s = new StringBuilder();
trimNewLines(value, s);
return s.toString();
}
public static String lTrim(String value) {
int len = value.length();
int i = 0;
char[] val = value.toCharArray();
char c = val[i];
// left trim
while (i < value.length() && Character.isWhitespace(c)) {
i++;
c = val[i];
}
return value.substring(i, len);
}
/**
* Given a string that is only whitespace, this will return the amount of newline characters.
*
* <p>If the newLineCounter becomes > newLineLimit, then the value of newLineLimit is always
* returned.
*
* @param text
* @param isWhitespace
* @param delimiter
* @return
*/
public static int getNumberOfNewLines(
String text, boolean isWhitespace, String delimiter, int newLineLimit) {
if (!isWhitespace) {
return 0;
}
int newLineCounter = 0;
boolean delimiterHasTwoCharacters = delimiter.length() == 2;
for (int i = 0; newLineCounter <= newLineLimit && i < text.length(); i++) {
String c;
if (delimiterHasTwoCharacters) {
if (i + 1 < text.length()) {
c = text.substring(i, i + 2);
if (delimiter.equals(c)) {
newLineCounter++;
i++; // skip the second char of the delimiter
}
}
} else {
c = String.valueOf(text.charAt(i));
if (delimiter.equals(c)) {
newLineCounter++;
}
}
}
return newLineCounter;
}
/**
* Given a string will give back a non null string that is either the given string, or an empty
* string.
*
* @param text
* @return
*/
public static String getDefaultString(String text) {
if (text != null) {
return text;
}
return "";
}
/**
* Traverses backwards from the endOffset until it finds a whitespace character.
*
* <p>The offset of the character after the whitespace is returned.
*
* <p>(text = "abcd efg|h", endOffset = 8) -> 5
*
* @param text
* @param endOffset non-inclusive
* @return Start offset directly after the first whitespace.
*/
public static int getOffsetAfterWhitespace(String text, int endOffset) {
if (text == null || endOffset <= 0 || endOffset > text.length()) {
return -1;
}
char c = text.charAt(endOffset - 1);
int i = endOffset;
if (!Character.isWhitespace(c)) {
while (!Character.isWhitespace(c)) {
i--;
if (i <= 0) {
break;
}
c = text.charAt(i - 1);
}
return i;
}
return -1;
}
/**
* Returns the number of consecutive whitespace characters in front of text
*
* @param text String of interest
* @return the number of consecutive whitespace characters in front of text
*/
public static int getFrontWhitespaceLength(String text) {
if (StringUtils.isWhitespace(text)) {
return text.length();
}
int i = 0;
while (Character.isWhitespace(text.charAt(i))) {
i++;
}
return i;
}
/**
* Returns the number of consecutive whitespace characters from the end of text
*
* @param text String of interest
* @return the number of consecutive whitespace characters from the end of text
*/
public static int getTrailingWhitespaceLength(String text) {
if (StringUtils.isWhitespace(text)) {
return text.length();
}
int i = text.length() - 1;
while (Character.isWhitespace(text.charAt(i))) {
i--;
}
return text.length() - i - 1;
}
public static String cleanPathForWindows(String pathString) {
if (pathString.startsWith("/")) {
if (pathString.length() > 3) {
char letter = pathString.charAt(1);
char colon = pathString.charAt(2);
if (Character.isLetter(letter) && ':' == colon) {
pathString = pathString.substring(1);
}
}
}
pathString = pathString.replace("/", "\\");
return pathString;
}
public static String escapeBackticks(String text) {
int i = text.length() - 1;
StringBuilder b = new StringBuilder(text);
while (i >= 0) {
char c = text.charAt(i);
if (c == '`') {
b.insert(i, "\\");
}
i--;
}
return b.toString();
}
public static boolean isTagOutsideOfBackticks(String text) {
int i = 0;
boolean inBacktick = false;
while (i < text.length()) {
char c = text.charAt(i);
if (c == '`') {
if (inBacktick) {
inBacktick = false;
} else {
inBacktick = true;
}
} else if (c == '<') {
i++;
while (i < text.length()) {
c = text.charAt(i);
if (c == '`') {
i--;
break;
}
if (c == '>') {
if (!inBacktick) {
return true;
}
break;
}
i++;
}
}
i++;
}
return false;
}
public static String getString(Object obj) {
if (obj != null) {
return obj.toString();
}
return null;
}
/**
* Returns the start word offset from the left of the given <code>offset</code> and -1 if no word.
*
* @param text the text
* @param offset the offset
* @param isValidChar predicate to check if current character belong to the word.
* @return the start word offset from the left of the given <code>offset</code> and -1 if no word.
*/
public static int findStartWord(String text, int offset, Predicate<Character> isValidChar) {
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 0;
}
/**
* Returns the end word offset from the right of the given <code>offset</code> and -1 if no word.
*
* @param text the text
* @param offset the offset
* @param isValidChar predicate to check if current character belong to the word.
* @return the start word offset from the right of the given <code>offset</code> and -1 if no
* word.
*/
public static int findEndWord(String text, int offset, Predicate<Character> isValidChar) {
if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) {
return -1;
}
for (int i = offset + 1; i < text.length(); i++) {
if (!isValidChar.test(text.charAt(i))) {
return i;
}
}
return -1;
}
/**
* Returns <code>value</code> without surrounding quotes.
*
* <p>If <code>value</code> does not have matching surrounding quotes, returns <code>value</code>.
*
* @param value
* @return <code>value</code> without surrounding quotes.
*/
public static String convertToQuotelessValue(String value) {
if (value == null || !isQuoted(value)) {
return value;
}
return value.substring(1, value.length() - 1);
}
/**
* Returns true if <code>value</code> has matching surrounding quotes and false otherwise.
*
* @param value
* @return true if <code>value</code> has matching surrounding quotes.
*/
public static boolean isQuoted(String value) {
if (value == null || value.length() < 2) {
return false;
}
char quoteValueStart = value.charAt(0);
if (quoteValueStart != '\"' && quoteValueStart != '\'') {
return false;
}
char quoteValueEnd = value.charAt(value.length() - 1);
return quoteValueEnd == quoteValueStart;
}
}
+9 -35
View File
@@ -1,42 +1,16 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
import com.itsaky.androidide.Versions
android {
namespace "com.itsaky.androidide.shared"
compileSdkVersion rootProject.ext.compileSdk
buildToolsVersion rootProject.ext.buildTools
defaultConfig {
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
}
buildFeatures.viewBinding true
compileOptions {
sourceCompatibility rootProject.ext.javaSourceVersion
targetCompatibility rootProject.ext.javaTargetVersion
}
kotlinOptions {
jvmTarget = "11"
}
buildTypes {
release {
minifyEnabled true
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
}
java {
sourceCompatibility = rootProject.ext.javaSourceVersion
targetCompatibility = rootProject.ext.javaTargetVersion
}
dependencies {
implementation google.guava
implementation project(path: ':logger')
}
Binary file not shown.