ProcessingInstruction.java
package org.jsoup.nodes;
import org.jsoup.helper.Validate;
import org.jsoup.internal.QuietAppendable;
import org.jsoup.internal.StringUtil;
import org.jsoup.parser.Parser;
/**
A processing instruction with a target and data, such as {@code <?marker name="country-options"?>}.
<p>The XML declaration {@code <?xml ...?>} is represented by {@link XmlDeclaration}.</p>
<p>If you access this node's attributes, directly or through an attribute selector, the instruction data is parsed into an {@link Attributes} object using the owning document's parser settings, or HTML settings if the node is detached.
The attributes then determine the instruction data.</p>
@since 1.24.1 */
public class ProcessingInstruction extends LeafNode {
private final String target;
private int dataStartPos = -1; // source offset used to rebase parsed attribute ranges; -1 if untracked
/**
Creates a processing instruction with the supplied target and data.
@param target the target following {@code <?}
@param data the data following the target, without the separating whitespace or closing {@code ?>}
*/
public ProcessingInstruction(String target, String data) {
super(data);
Validate.notNull(target);
this.target = target;
}
/** Returns the node name {@code #processing-instruction}. */
@Override public String nodeName() {
return "#processing-instruction";
}
/**
Returns the instruction target.
@return the target following {@code <?}
*/
public String target() {
return target;
}
/**
Returns the data following the target.
If the attributes have been accessed, returns their serialized form.
@return the instruction data
*/
public String data() {
if (!hasAttributes()) return coreValue();
Document owner = ownerDocument();
Document.OutputSettings out = owner != null ? owner.outputSettings() : new Document.OutputSettings();
return data(out);
}
/** Serializes the parsed instruction attributes. */
private String data(Document.OutputSettings out) {
StringBuilder data = StringUtil.borrowBuilder();
attributes().html(QuietAppendable.wrap(data), out);
if (data.length() > 0)
data.deleteCharAt(0); // drop the separator Attributes writes before each attribute
return StringUtil.releaseBuilder(data);
}
/**
Replaces the instruction data.
If the attributes have been accessed, parses the new data and updates the existing {@link Attributes} object.
@param data the new data, without the separating whitespace or closing {@code ?>}
@return this node, for chaining
*/
public ProcessingInstruction data(String data) {
Validate.notNull(data);
dataStartPos = -1;
if (hasAttributes()) {
Attributes parsed = parseAttributes(data);
super.clearAttributes();
attributes().addAll(parsed);
} else {
coreValue(data);
}
return this;
}
/** Returns the instruction data as this node's value. */
@Override public String nodeValue() {
return data();
}
/** Parses the instruction data into attribute storage when requested. */
@Override Attributes newAttributes(String data) {
return parseAttributes(data);
}
/** Parses the instruction data without changing the owning document's parser state. */
private Attributes parseAttributes(String data) {
Document owner = ownerDocument();
Parser parser = owner != null ? owner.parser().newInstance() : Parser.htmlParser();
Range sourceRange = sourceRange();
boolean trackPosition = dataStartPos >= 0 && sourceRange.isTracked();
parser.setTrackPosition(trackPosition);
Attributes attributes = parser.parseAttributes(data);
if (trackPosition) {
Range.Spans spans = attributes.spans();
if (spans != null)
spans.rebaseAttributeRanges(sourceRange, dataStartPos);
}
return attributes;
}
void sourceDataStart(int pos) {
dataStartPos = pos;
}
@Override public String attr(String key) {
ensureAttributes();
return super.attr(key);
}
@Override public ProcessingInstruction attr(String key, String value) {
ensureAttributes();
super.attr(key, value);
return this;
}
@Override public int attributesSize() {
return attributes().size();
}
@Override public ProcessingInstruction clearAttributes() {
ensureAttributes();
super.clearAttributes();
return this;
}
@Override void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {
String data = hasAttributes() ? data(out) : coreValue();
accum.append("<?").append(target);
if (out.syntax() == Document.OutputSettings.Syntax.html || !data.isEmpty())
accum.append(' ');
accum.append(data).append("?>");
}
@Override public ProcessingInstruction clone() {
return (ProcessingInstruction) super.clone();
}
}