mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 15:56:48 +08:00
Include LemMinX source
This commit is contained in:
Binary file not shown.
@@ -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')
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+51
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -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]*>.*");
|
||||
|
||||
}
|
||||
+336
@@ -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
+48
@@ -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);
|
||||
}
|
||||
+49
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+141
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+9
-35
@@ -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.
Reference in New Issue
Block a user