001package org.jsoup.nodes; 002 003import org.jsoup.helper.Validate; 004import org.jsoup.internal.QuietAppendable; 005import org.jsoup.internal.StringUtil; 006import org.jsoup.parser.Parser; 007 008/** 009 A processing instruction with a target and data, such as {@code <?marker name="country-options"?>}. 010 011 <p>The XML declaration {@code <?xml ...?>} is represented by {@link XmlDeclaration}.</p> 012 <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. 013 The attributes then determine the instruction data.</p> 014 015 @since 1.24.1 */ 016public class ProcessingInstruction extends LeafNode { 017 private final String target; 018 private int dataStartPos = -1; // source offset used to rebase parsed attribute ranges; -1 if untracked 019 020 /** 021 Creates a processing instruction with the supplied target and data. 022 @param target the target following {@code <?} 023 @param data the data following the target, without the separating whitespace or closing {@code ?>} 024 */ 025 public ProcessingInstruction(String target, String data) { 026 super(data); 027 Validate.notNull(target); 028 this.target = target; 029 } 030 031 /** Returns the node name {@code #processing-instruction}. */ 032 @Override public String nodeName() { 033 return "#processing-instruction"; 034 } 035 036 /** 037 Returns the instruction target. 038 @return the target following {@code <?} 039 */ 040 public String target() { 041 return target; 042 } 043 044 /** 045 Returns the data following the target. 046 If the attributes have been accessed, returns their serialized form. 047 @return the instruction data 048 */ 049 public String data() { 050 if (!hasAttributes()) return coreValue(); 051 052 Document owner = ownerDocument(); 053 Document.OutputSettings out = owner != null ? owner.outputSettings() : new Document.OutputSettings(); 054 return data(out); 055 } 056 057 /** Serializes the parsed instruction attributes. */ 058 private String data(Document.OutputSettings out) { 059 StringBuilder data = StringUtil.borrowBuilder(); 060 attributes().html(QuietAppendable.wrap(data), out); 061 if (data.length() > 0) 062 data.deleteCharAt(0); // drop the separator Attributes writes before each attribute 063 return StringUtil.releaseBuilder(data); 064 } 065 066 /** 067 Replaces the instruction data. 068 If the attributes have been accessed, parses the new data and updates the existing {@link Attributes} object. 069 @param data the new data, without the separating whitespace or closing {@code ?>} 070 @return this node, for chaining 071 */ 072 public ProcessingInstruction data(String data) { 073 Validate.notNull(data); 074 dataStartPos = -1; 075 if (hasAttributes()) { 076 Attributes parsed = parseAttributes(data); 077 super.clearAttributes(); 078 attributes().addAll(parsed); 079 } else { 080 coreValue(data); 081 } 082 return this; 083 } 084 085 /** Returns the instruction data as this node's value. */ 086 @Override public String nodeValue() { 087 return data(); 088 } 089 090 /** Parses the instruction data into attribute storage when requested. */ 091 @Override Attributes newAttributes(String data) { 092 return parseAttributes(data); 093 } 094 095 /** Parses the instruction data without changing the owning document's parser state. */ 096 private Attributes parseAttributes(String data) { 097 Document owner = ownerDocument(); 098 Parser parser = owner != null ? owner.parser().newInstance() : Parser.htmlParser(); 099 Range sourceRange = sourceRange(); 100 boolean trackPosition = dataStartPos >= 0 && sourceRange.isTracked(); 101 parser.setTrackPosition(trackPosition); 102 Attributes attributes = parser.parseAttributes(data); 103 if (trackPosition) { 104 Range.Spans spans = attributes.spans(); 105 if (spans != null) 106 spans.rebaseAttributeRanges(sourceRange, dataStartPos); 107 } 108 return attributes; 109 } 110 111 void sourceDataStart(int pos) { 112 dataStartPos = pos; 113 } 114 115 @Override public String attr(String key) { 116 ensureAttributes(); 117 return super.attr(key); 118 } 119 120 @Override public ProcessingInstruction attr(String key, String value) { 121 ensureAttributes(); 122 super.attr(key, value); 123 return this; 124 } 125 126 @Override public int attributesSize() { 127 return attributes().size(); 128 } 129 130 @Override public ProcessingInstruction clearAttributes() { 131 ensureAttributes(); 132 super.clearAttributes(); 133 return this; 134 } 135 136 @Override void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) { 137 String data = hasAttributes() ? data(out) : coreValue(); 138 accum.append("<?").append(target); 139 if (out.syntax() == Document.OutputSettings.Syntax.html || !data.isEmpty()) 140 accum.append(' '); 141 accum.append(data).append("?>"); 142 } 143 144 @Override public ProcessingInstruction clone() { 145 return (ProcessingInstruction) super.clone(); 146 } 147}