📄 sourceformatter.java
字号:
* @param indentAllElements specifies whether all elements are to be indented.
* @return this <code>SourceFormatter</code> instance, allowing multiple property setting methods to be chained in a single statement.
* @see #getIndentAllElements()
*/
public SourceFormatter setIndentAllElements(final boolean indentAllElements) {
this.indentAllElements=indentAllElements;
return this;
}
/**
* Indicates whether all elements are to be indented, including {@linkplain HTMLElements#getInlineLevelElementNames() inline-level elements} and those with preformatted contents.
* <p>
* See the {@link #setIndentAllElements(boolean)} method for a full description of this property.
*
* @return <code>true</code> if all elements are to be indented, otherwise <code>false</code>.
*/
public boolean getIndentAllElements() {
return indentAllElements;
}
/**
* Sets the string to be used to represent a <a target="_blank" href="http://en.wikipedia.org/wiki/Newline">newline</a> in the output.
* <p>
* The default is to use the same new line string as is used in the source document, which is determined via the {@link Source#getNewLine()} method.
* If the source document does not contain any new lines, a "best guess" is made by either taking the new line string of a previously parsed document,
* or using the value from the static {@link Config#NewLine} property.
* <p>
* Specifying a <code>null</code> argument resets the property to its default value, which is to use the same new line string as is used in the source document.
*
* @param newLine the string to be used to represent a <a target="_blank" href="http://en.wikipedia.org/wiki/Newline">newline</a> in the output, may be <code>null</code>.
* @return this <code>SourceFormatter</code> instance, allowing multiple property setting methods to be chained in a single statement.
* @see #getNewLine()
*/
public SourceFormatter setNewLine(final String newLine) {
this.newLine=newLine;
return this;
}
/**
* Returns the string to be used to represent a <a target="_blank" href="http://en.wikipedia.org/wiki/Newline">newline</a> in the output.
* <p>
* See the {@link #setNewLine(String)} method for a full description of this property.
*
* @return the string to be used to represent a <a target="_blank" href="http://en.wikipedia.org/wiki/Newline">newline</a> in the output.
*/
public String getNewLine() {
if (newLine==null) newLine=segment.source.getBestGuessNewLine();
return newLine;
}
/** This class does the actual work, but is first passed final copies of all the parameters for efficiency. */
private static final class Processor {
private final Segment segment;
private final CharSequence sourceText;
private final String indentString;
private final boolean tidyTags;
private final boolean collapseWhiteSpace;
private final boolean removeLineBreaks; // Indicates whether all non-essential line breaks are removed. Must be used with collapseWhiteSpace=true.
private final boolean indentAllElements;
private final boolean indentScriptElements; // at present this parameter is tied to indentAllElements. SCRIPT elements need to be inline to keep functional equivalency of output
private final String newLine;
private Appendable appendable;
private Tag nextTag;
private int index;
public Processor(final Segment segment, final String indentString, final boolean tidyTags, final boolean collapseWhiteSpace, final boolean removeLineBreaks, final boolean indentAllElements, final boolean indentScriptElements, final String newLine) {
this.segment=segment;
sourceText=segment.source.toString();
this.indentString=indentString;
this.tidyTags=tidyTags;
this.collapseWhiteSpace=collapseWhiteSpace || removeLineBreaks;
this.removeLineBreaks=removeLineBreaks;
this.indentAllElements=indentAllElements;
this.indentScriptElements=indentScriptElements;
this.newLine=newLine;
}
public void appendTo(final Appendable appendable) throws IOException {
this.appendable=appendable;
if (segment instanceof Source) ((Source)segment).fullSequentialParse();
nextTag=segment.source.getNextTag(segment.begin);
index=segment.begin;
appendContent(segment.end,segment.getChildElements(),0);
}
private void appendContent(final int end, final List<Element> childElements, final int depth) throws IOException {
assert index<=end;
for (Element element : childElements) {
final int elementBegin=element.begin;
if (elementBegin>=end) break;
if (indentAllElements) {
appendText(elementBegin,depth);
appendElement(element,depth,end,false,false);
} else {
if (inlinable(element)) continue; // skip over elements that can be inlined.
appendText(elementBegin,depth);
final String elementName=element.getName();
if (elementName==HTMLElementName.PRE || elementName==HTMLElementName.TEXTAREA) {
appendElement(element,depth,end,true,true);
} else if (elementName==HTMLElementName.SCRIPT) {
appendElement(element,depth,end,true,false);
} else {
appendElement(element,depth,end,false,!removeLineBreaks && containsOnlyInlineLevelChildElements(element));
}
}
}
appendText(end,depth);
assert index==end;
}
private boolean inlinable(final Element element) {
// returns true if the specified element should be inlined
final StartTagType startTagType=element.getStartTag().getStartTagType();
if (startTagType==StartTagType.DOCTYPE_DECLARATION) return false;
if (startTagType!=StartTagType.NORMAL) return true;
// element is a normal type
final String elementName=element.getName();
if (elementName==HTMLElementName.SCRIPT) return !indentScriptElements;
if (removeLineBreaks && !HTMLElements.getElementNames().contains(elementName)) return true; // inline non-HTML elements if removing line breaks
if (!HTMLElements.getInlineLevelElementNames().contains(elementName)) return false;
// element is inline type
if (removeLineBreaks) return true;
return containsOnlyInlineLevelChildElements(element); // only inline if it doesn't illegally contain non-inline elements
}
private void appendText(final int end, int depth) throws IOException {
assert index<=end;
if (index==end) return;
while (Segment.isWhiteSpace(sourceText.charAt(index))) if (++index==end) return; // trim whitespace.
appendIndent(depth);
if (collapseWhiteSpace) {
appendTextCollapseWhiteSpace(end,depth);
} else {
appendTextInline(end,depth,false);
}
appendFormattingNewLine();
assert index==end;
}
private void appendElement(final Element element, final int depth, final int end, final boolean preformatted, boolean renderContentInline) throws IOException {
assert index==element.begin;
assert index<end;
final StartTag startTag=element.getStartTag();
final EndTag endTag=element.getEndTag();
appendIndent(depth);
appendTag(startTag,depth,end);
if (index==end) {
appendFormattingNewLine();
assert index==Math.min(element.end,end) : index;
return;
}
if (!renderContentInline) appendFormattingNewLine();
int contentEnd=element.getContentEnd();
if (end<contentEnd) contentEnd=end;
if (index<contentEnd) {
if (preformatted) {
if (renderContentInline) {
// Preformatted element such as PRE, TEXTAREA
appendContentPreformatted(contentEnd,depth);
} else {
// SCRIPT element
appendIndentedScriptContent(contentEnd,depth+1);
}
} else {
if (renderContentInline) {
// Element contains only inline-level elements, so don't bother putting start and end tags on separate lines
if (collapseWhiteSpace) {
appendTextCollapseWhiteSpace(contentEnd,depth);
} else {
if (!appendTextInline(contentEnd,depth,true)) {
appendFormattingNewLine();
renderContentInline=false;
}
}
} else {
appendContent(contentEnd,element.getChildElements(),depth+1);
}
}
}
if (endTag!=null && end>endTag.begin) {
if (!renderContentInline) appendIndent(depth);
assert index==endTag.begin;
appendTag(endTag,depth,end);
appendFormattingNewLine();
} else if (renderContentInline) {
appendFormattingNewLine();
}
assert index==Math.min(element.end,end) : index;
}
private void updateNextTag() {
// ensures that nextTag is up to date
while (nextTag!=null) {
if (nextTag.begin>=index) return;
nextTag=nextTag.getNextTag();
}
}
private void appendIndentedScriptContent(final int end, final int depth) throws IOException {
assert index<end;
if (removeLineBreaks) {
appendTextRemoveIndentation(end);
assert index==end;
return;
}
int startOfLinePos=getStartOfLinePos(end,false);
if (index==end) return;
if (startOfLinePos==-1) {
// Script started on same line as start tag. Use the start of the next line to determine the original indent.
appendIndent(depth);
appendLineKeepWhiteSpace(end,depth);
appendEssentialNewLine();
if (index==end) return;
startOfLinePos=getStartOfLinePos(end,true);
if (index==end) return;
}
appendTextPreserveIndentation(end,depth,index-startOfLinePos);
appendEssentialNewLine();
assert index==end;
}
private boolean appendTextPreserveIndentation(final int end, final int depth) throws IOException {
// returns true if all text was on one line, otherwise false
assert index<end;
if (removeLineBreaks) return appendTextRemoveIndentation(end);
// Use the start of the next line to determine the original indent.
appendLineKeepWhiteSpace(end,depth);
if (index==end) return true;
int startOfLinePos=getStartOfLinePos(end,true);
if (index==end) return true;
appendEssentialNewLine();
appendTextPreserveIndentation(end,depth+1,index-startOfLinePos);
assert index==end;
return false;
}
private void appendTextPreserveIndentation(final int end, final int depth, final int originalIndentLength) throws IOException {
assert index<end;
appendIndent(depth);
appendLineKeepWhiteSpace(end,depth);
while (index!=end) {
// Skip over the original indent:
for (int x=0; x<originalIndentLength; x++) {
final char ch=sourceText.charAt(index);
if (!(ch==' ' || ch=='\t')) break;
if (++index==end) return;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -