001package org.jsoup.parser;
002
003import org.jsoup.helper.Validate;
004import org.jsoup.nodes.Attributes;
005import org.jsoup.nodes.Document;
006import org.jsoup.nodes.Element;
007import org.jsoup.nodes.Node;
008import org.jspecify.annotations.Nullable;
009
010import java.io.Reader;
011import java.io.StringReader;
012import java.util.List;
013import java.util.concurrent.locks.ReentrantLock;
014
015/**
016 Parses HTML or XML into a {@link org.jsoup.nodes.Document}. Generally, it is simpler to use one of the parse methods in
017 {@link org.jsoup.Jsoup}.
018 <p>Note that a given Parser instance object is threadsafe, but not concurrent. (Concurrent parse calls will
019 synchronize.) To reuse a Parser configuration in a multithreaded environment, use {@link #newInstance()} to make
020 copies.</p>
021 */
022public class Parser implements Cloneable {
023    public static final String NamespaceHtml = "http://www.w3.org/1999/xhtml";
024    public static final String NamespaceXml = "http://www.w3.org/XML/1998/namespace";
025    public static final String NamespaceMathml = "http://www.w3.org/1998/Math/MathML";
026    public static final String NamespaceSvg = "http://www.w3.org/2000/svg";
027
028    private final TreeBuilder treeBuilder;
029    private ParseErrorList errors;
030    private ParseSettings settings;
031    private boolean trackPosition = false;
032    private @Nullable TagSet tagSet;
033    private final ReentrantLock lock = new ReentrantLock();
034    private int maxDepth;
035
036    /**
037     * Create a new Parser, using the specified TreeBuilder
038     * @param treeBuilder TreeBuilder to use to parse input into Documents.
039     */
040    public Parser(TreeBuilder treeBuilder) {
041        this.treeBuilder = treeBuilder;
042        settings = treeBuilder.defaultSettings();
043        errors = ParseErrorList.noTracking();
044        maxDepth = treeBuilder.defaultMaxDepth();
045    }
046
047    /**
048     Creates a new Parser as a deep copy of this; including initializing a new TreeBuilder. Allows independent (multi-threaded) use.
049     @return a copied parser
050     */
051    public Parser newInstance() {
052        return new Parser(this);
053    }
054
055    @SuppressWarnings("MethodDoesntCallSuperMethod") // because we use the copy constructor instead
056    @Override
057    public Parser clone() {
058        return new Parser(this);
059    }
060
061    private Parser(Parser copy) {
062        treeBuilder = copy.treeBuilder.newInstance(); // because extended
063        errors = new ParseErrorList(copy.errors); // only copies size, not contents
064        settings = new ParseSettings(copy.settings);
065        trackPosition = copy.trackPosition;
066        maxDepth = copy.maxDepth;
067        tagSet = new TagSet(copy.tagSet());
068    }
069
070    /**
071     Parse the contents of a String.
072
073     @param html HTML to parse
074     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
075     @return parsed Document
076     */
077    public Document parseInput(String html, String baseUri) {
078        return parseInput(new StringReader(html), baseUri);
079    }
080
081    /**
082     Parse the contents of Reader.
083
084     @param inputHtml HTML to parse
085     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
086     @return parsed Document
087     @throws java.io.UncheckedIOException if an I/O error occurs in the Reader
088     */
089    public Document parseInput(Reader inputHtml, String baseUri) {
090        try {
091            lock.lock(); // using a lock vs synchronized to support loom threads
092            return treeBuilder.parse(inputHtml, baseUri, this);
093        } finally {
094            lock.unlock();
095        }
096    }
097
098    /**
099     Parses attribute names and values without a surrounding tag, using this parser's settings.
100     Parsing stops at a tag closer, which is reported if error tracking is enabled.
101
102     @param input the attribute string to parse
103     @return the parsed attributes
104     @see #setTrackErrors(int)
105     @since 1.24.1
106     */
107    public Attributes parseAttributes(String input) {
108        try {
109            lock.lock();
110            return treeBuilder.parseAttributes(new StringReader(input), this);
111        } finally {
112            lock.unlock();
113        }
114    }
115
116    /**
117     Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.
118
119     @param fragment the fragment of HTML to parse
120     @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML).
121     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
122     @return list of nodes parsed from the input HTML.
123     */
124    public List<Node> parseFragmentInput(String fragment, @Nullable Element context, String baseUri) {
125        return parseFragmentInput(new StringReader(fragment), context, baseUri);
126    }
127
128    /**
129     Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.
130
131     @param fragment the fragment of HTML to parse
132     @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML).
133     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
134     @return list of nodes parsed from the input HTML.
135     @throws java.io.UncheckedIOException if an I/O error occurs in the Reader
136     */
137    public List<Node> parseFragmentInput(Reader fragment, @Nullable Element context, String baseUri) {
138        try {
139            lock.lock();
140            return treeBuilder.parseFragment(fragment, context, baseUri, this);
141        } finally {
142            lock.unlock();
143        }
144    }
145
146    // gets & sets
147    /**
148     * Get the TreeBuilder currently in use.
149     * @return current TreeBuilder.
150     */
151    public TreeBuilder getTreeBuilder() {
152        return treeBuilder;
153    }
154
155    /**
156     * Check if parse error tracking is enabled.
157     * @return current track error state.
158     */
159    public boolean isTrackErrors() {
160        return errors.getMaxSize() > 0;
161    }
162
163    /**
164     * Enable or disable parse error tracking for the next parse.
165     * @param maxErrors the maximum number of errors to track. Set to 0 to disable.
166     * @return this, for chaining
167     */
168    public Parser setTrackErrors(int maxErrors) {
169        errors = maxErrors > 0 ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking();
170        return this;
171    }
172
173    /**
174     * Retrieve the parse errors, if any, from the last parse.
175     * @return list of parse errors, up to the size of the maximum errors tracked.
176     * @see #setTrackErrors(int)
177     */
178    public ParseErrorList getErrors() {
179        return errors;
180    }
181
182    /**
183     Test if position tracking is enabled. If it is, Nodes will have a Position to track where in the original input
184     source they were created from. By default, tracking is not enabled.
185     * @return current track position setting
186     */
187    public boolean isTrackPosition() {
188        return trackPosition;
189    }
190
191    /**
192     Enable or disable source position tracking. If enabled, Nodes will have a Position to track where in the original
193     input source they were created from.
194     @param trackPosition position tracking setting; {@code true} to enable
195     @return this Parser, for chaining
196     */
197    public Parser setTrackPosition(boolean trackPosition) {
198        this.trackPosition = trackPosition;
199        return this;
200    }
201
202    /**
203     Update the ParseSettings of this Parser, to control the case sensitivity of tags and attributes.
204     * @param settings the new settings
205     * @return this Parser
206     */
207    public Parser settings(ParseSettings settings) {
208        this.settings = settings;
209        return this;
210    }
211
212    /**
213     Gets the current ParseSettings for this Parser
214     * @return current ParseSettings
215     */
216    public ParseSettings settings() {
217        return settings;
218    }
219
220    /**
221     Set the parser's maximum stack depth (maximum number of open elements). When reached, new open elements will be
222     removed to prevent excessive nesting. Defaults to 512.
223
224     @param maxDepth maximum parser depth; must be >= 1
225     @return this Parser, for chaining
226     */
227    public Parser setMaxDepth(int maxDepth) {
228        Validate.isTrue(maxDepth >= 1, "maxDepth must be >= 1");
229        this.maxDepth = maxDepth;
230        return this;
231    }
232
233    /**
234     * Get the maximum parser depth (maximum number of open elements).
235     * @return the current max parser depth
236     */
237    public int getMaxDepth() {
238        return maxDepth;
239    }
240
241    /**
242     Set a custom TagSet to use for this Parser. This allows you to define your own tags, and control how they are
243     parsed. For example, you can set a tag to preserve whitespace, or to be treated as a block tag.
244     <p>You can start with the {@link TagSet#Html()} defaults and customize, or a new empty TagSet.</p>
245
246     @param tagSet the TagSet to use. This gets copied, so that changes that the parse makes (tags found in the document will be added) do not clobber the original TagSet.
247     @return this Parser
248     @since 1.20.1
249     */
250    public Parser tagSet(TagSet tagSet) {
251        Validate.notNull(tagSet);
252        this.tagSet = new TagSet(tagSet); // copy it as we are going to mutate it
253        return this;
254    }
255
256    /**
257     Get the current TagSet for this Parser, which will be either this parser's default, or one that you have set.
258     @return the current TagSet. After the parse, this will contain any new tags that were found in the document.
259     @since 1.20.1
260     */
261    public TagSet tagSet() {
262        if (tagSet == null)
263            tagSet = treeBuilder.defaultTagSet();
264        return tagSet;
265    }
266
267    public String defaultNamespace() {
268        return getTreeBuilder().defaultNamespace();
269    }
270
271    // static parse functions below
272    /**
273     * Parse HTML into a Document.
274     *
275     * @param html HTML to parse
276     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
277     *
278     * @return parsed Document
279     */
280    public static Document parse(String html, String baseUri) {
281        TreeBuilder treeBuilder = new HtmlTreeBuilder();
282        return treeBuilder.parse(new StringReader(html), baseUri, new Parser(treeBuilder));
283    }
284
285    /**
286     * Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.
287     *
288     * @param fragmentHtml the fragment of HTML to parse
289     * @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This
290     * provides stack context (for implicit element creation).
291     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
292     *
293     * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified.
294     */
295    public static List<Node> parseFragment(String fragmentHtml, Element context, String baseUri) {
296        HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder();
297        return treeBuilder.parseFragment(new StringReader(fragmentHtml), context, baseUri, new Parser(treeBuilder));
298    }
299
300    /**
301     * Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.
302     *
303     * @param fragmentHtml the fragment of HTML to parse
304     * @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This
305     * provides stack context (for implicit element creation).
306     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
307     * @param errorList list to add errors to
308     *
309     * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified.
310     */
311    public static List<Node> parseFragment(String fragmentHtml, Element context, String baseUri, ParseErrorList errorList) {
312        HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder();
313        Parser parser = new Parser(treeBuilder);
314        parser.errors = errorList;
315        return treeBuilder.parseFragment(new StringReader(fragmentHtml), context, baseUri, parser);
316    }
317
318    /**
319     * Parse a fragment of XML into a list of nodes.
320     *
321     * @param fragmentXml the fragment of XML to parse
322     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
323     * @return list of nodes parsed from the input XML.
324     */
325    public static List<Node> parseXmlFragment(String fragmentXml, String baseUri) {
326        XmlTreeBuilder treeBuilder = new XmlTreeBuilder();
327        return treeBuilder.parseFragment(new StringReader(fragmentXml), null, baseUri, new Parser(treeBuilder));
328    }
329
330    /**
331     * Parse a fragment of HTML into the {@code body} of a Document.
332     *
333     * @param bodyHtml fragment of HTML
334     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
335     *
336     * @return Document, with empty head, and HTML parsed into body
337     */
338    public static Document parseBodyFragment(String bodyHtml, String baseUri) {
339        Document doc = Document.createShell(baseUri);
340        Element body = doc.body();
341        List<Node> nodeList = parseFragment(bodyHtml, body, baseUri);
342        body.appendChildren(nodeList);
343        return doc;
344    }
345
346    /**
347     Utility method to unescape HTML entities from a string.
348     <p>To track errors while unescaping, use
349     {@link #unescape(String, boolean)} with a Parser instance that has error tracking enabled.</p>
350
351     @param string HTML escaped string
352     @param inAttribute if the string is to be escaped in strict mode (as attributes are)
353     @return an unescaped string
354     @see #unescape(String, boolean)
355     */
356    public static String unescapeEntities(String string, boolean inAttribute) {
357        Validate.notNull(string);
358        if (string.indexOf('&') < 0) return string; // nothing to unescape
359        return Parser.htmlParser().unescape(string, inAttribute);
360    }
361
362    /**
363     Utility method to unescape HTML entities from a string, using this {@code Parser}'s configuration (for example, to
364     collect errors while unescaping).
365
366     @param string HTML escaped string
367     @param inAttribute if the string is to be escaped in strict mode (as attributes are)
368     @return an unescaped string
369     @see #setTrackErrors(int)
370     @see #unescapeEntities(String, boolean)
371     */
372    public String unescape(String string, boolean inAttribute) {
373        Validate.notNull(string);
374        if (string.indexOf('&') < 0) return string; // nothing to unescape
375        this.treeBuilder.initialiseParse(new StringReader(string), "", this);
376        Tokeniser tokeniser = new Tokeniser(this.treeBuilder);
377        return tokeniser.unescapeEntities(inAttribute);
378    }
379
380    // builders
381
382    /**
383     * Create a new HTML parser. This parser treats input as HTML5, and enforces the creation of a normalised document,
384     * based on a knowledge of the semantics of the incoming tags.
385     * @return a new HTML parser.
386     */
387    public static Parser htmlParser() {
388        return new Parser(new HtmlTreeBuilder());
389    }
390
391    /**
392     * Create a new XML parser. This parser assumes no knowledge of the incoming tags and does not treat it as HTML,
393     * rather creates a simple tree directly from the input.
394     * @return a new simple XML parser.
395     */
396    public static Parser xmlParser() {
397        return new Parser(new XmlTreeBuilder());
398    }
399}