<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.configuration2.plist;

import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
import org.apache.commons.configuration2.FileBasedConfiguration;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ImmutableConfiguration;
import org.apache.commons.configuration2.MapConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration2.io.FileLocator;
import org.apache.commons.configuration2.io.FileLocatorAware;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.configuration2.tree.InMemoryNodeModel;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Property list file (plist) in XML FORMAT as used by Mac OS X (http://www.apple.com/DTDs/PropertyList-1.0.dtd). This
 * configuration doesn't support the binary FORMAT used in OS X 10.4.
 *
 * <p>
 * Example:
 * </p>
 *
 * <pre>
 * &lt;?xml version="1.0"?&gt;
 * &lt;!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"&gt;
 * &lt;plist version="1.0"&gt;
 *     &lt;dict&gt;
 *         &lt;key&gt;string&lt;/key&gt;
 *         &lt;string&gt;value1&lt;/string&gt;
 *
 *         &lt;key&gt;integer&lt;/key&gt;
 *         &lt;integer&gt;12345&lt;/integer&gt;
 *
 *         &lt;key&gt;real&lt;/key&gt;
 *         &lt;real&gt;-123.45E-1&lt;/real&gt;
 *
 *         &lt;key&gt;boolean&lt;/key&gt;
 *         &lt;true/&gt;
 *
 *         &lt;key&gt;date&lt;/key&gt;
 *         &lt;date&gt;2005-01-01T12:00:00Z&lt;/date&gt;
 *
 *         &lt;key&gt;data&lt;/key&gt;
 *         &lt;data&gt;RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==&lt;/data&gt;
 *
 *         &lt;key&gt;array&lt;/key&gt;
 *         &lt;array&gt;
 *             &lt;string&gt;value1&lt;/string&gt;
 *             &lt;string&gt;value2&lt;/string&gt;
 *             &lt;string&gt;value3&lt;/string&gt;
 *         &lt;/array&gt;
 *
 *         &lt;key&gt;dictionnary&lt;/key&gt;
 *         &lt;dict&gt;
 *             &lt;key&gt;key1&lt;/key&gt;
 *             &lt;string&gt;value1&lt;/string&gt;
 *             &lt;key&gt;key2&lt;/key&gt;
 *             &lt;string&gt;value2&lt;/string&gt;
 *             &lt;key&gt;key3&lt;/key&gt;
 *             &lt;string&gt;value3&lt;/string&gt;
 *         &lt;/dict&gt;
 *
 *         &lt;key&gt;nested&lt;/key&gt;
 *         &lt;dict&gt;
 *             &lt;key&gt;node1&lt;/key&gt;
 *             &lt;dict&gt;
 *                 &lt;key&gt;node2&lt;/key&gt;
 *                 &lt;dict&gt;
 *                     &lt;key&gt;node3&lt;/key&gt;
 *                     &lt;string&gt;value&lt;/string&gt;
 *                 &lt;/dict&gt;
 *             &lt;/dict&gt;
 *         &lt;/dict&gt;
 *
 *     &lt;/dict&gt;
 * &lt;/plist&gt;
 * </pre>
 *
 * @since 1.2
 *
 */
public class XMLPropertyListConfiguration extends BaseHierarchicalConfiguration implements FileBasedConfiguration, FileLocatorAware {
    /** Size of the indentation for the generated file. */
    private static final int INDENT_SIZE = 4;

    /** Constant for the encoding for binary data. */
    private static final String DATA_ENCODING = "UTF-8";

    /** Temporarily stores the current file location. */
    private FileLocator locator;

    /**
     * Creates an empty XMLPropertyListConfiguration object which can be used to synthesize a new plist file by adding
     * values and then saving().
     */
    public XMLPropertyListConfiguration() {
    }

    /**
     * Creates a new instance of {@code XMLPropertyListConfiguration} and copies the content of the specified configuration
     * into this object.
     *
     * @param configuration the configuration to copy
     * @since 1.4
     */
    public XMLPropertyListConfiguration(final HierarchicalConfiguration<ImmutableNode> configuration) {
        super(configuration);
    }

    /**
     * Creates a new instance of {@code XMLPropertyConfiguration} with the given root node.
     *
     * @param root the root node
     */
    XMLPropertyListConfiguration(final ImmutableNode root) {
        super(new InMemoryNodeModel(root));
    }

    private void setPropertyDirect(final String key, final Object value) {
        setDetailEvents(false);
        try {
            clearProperty(key);
            addPropertyDirect(key, value);
        } finally {
            setDetailEvents(true);
        }
    }

    @Override
    protected void setPropertyInternal(final String key, final Object value) {
        // special case for byte arrays, they must be stored as is in the configuration
        if (value instanceof byte[] || value instanceof List) {
            setPropertyDirect(key, value);
        } else if (value instanceof Object[]) {
            setPropertyDirect(key, Arrays.asList((Object[]) value));
        } else {
            super.setPropertyInternal(key, value);
        }
    }

    @Override
    protected void addPropertyInternal(final String key, final Object value) {
        if (value instanceof byte[] || value instanceof List) {
            addPropertyDirect(key, value);
        } else if (value instanceof Object[]) {
            addPropertyDirect(key, Arrays.asList((Object[]) value));
        } else {
            super.addPropertyInternal(key, value);
        }
    }

    /**
     * Stores the current file locator. This method is called before I/O operations.
     *
     * @param locator the current {@code FileLocator}
     */
    @Override
    public void initFileLocator(final FileLocator locator) {
        this.locator = locator;
    }

    @Override
    public void read(final Reader in) throws ConfigurationException {
        // set up the DTD validation
        final EntityResolver resolver = (publicId, systemId) -> new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));

        // parse the file
        final XMLPropertyListHandler handler = new XMLPropertyListHandler();
        try {
            final SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setValidating(true);

            final SAXParser parser = factory.newSAXParser();
            parser.getXMLReader().setEntityResolver(resolver);
            parser.getXMLReader().setContentHandler(handler);
            parser.getXMLReader().parse(new InputSource(in));

            getNodeModel().mergeRoot(handler.getResultBuilder().createNode(), null, null, null, this);
        } catch (final Exception e) {
            throw new ConfigurationException("Unable to parse the configuration file", e);
        }
    }

    @Override
    public void write(final Writer out) throws ConfigurationException {
        if (locator == null) {
            throw new ConfigurationException(
                "Save operation not properly " + "initialized! Do not call write(Writer) directly," + " but use a FileHandler to save a configuration.");
        }
        final PrintWriter writer = new PrintWriter(out);

        if (locator.getEncoding() != null) {
            writer.println("<?xml version=\"1.0\" encoding=\"" + locator.getEncoding() + "\"?>");
        } else {
            writer.println("<?xml version=\"1.0\"?>");
        }

        writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
        writer.println("<plist version=\"1.0\">");

        printNode(writer, 1, getNodeModel().getNodeHandler().getRootNode());

        writer.println("</plist>");
        writer.flush();
    }

    /**
     * Append a node to the writer, indented according to a specific level.
     */
    private void printNode(final PrintWriter out, final int indentLevel, final ImmutableNode node) {
        final String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);

        if (node.getNodeName() != null) {
            out.println(padding + "<key>" + StringEscapeUtils.escapeXml10(node.getNodeName()) + "</key>");
        }

        final List<ImmutableNode> children = node.getChildren();
        if (!children.isEmpty()) {
            out.println(padding + "<dict>");

            final Iterator<ImmutableNode> it = children.iterator();
            while (it.hasNext()) {
                final ImmutableNode child = it.next();
                printNode(out, indentLevel + 1, child);

                if (it.hasNext()) {
                    out.println();
                }
            }

            out.println(padding + "</dict>");
        } else if (node.getValue() == null) {
            out.println(padding + "<dict/>");
        } else {
            final Object value = node.getValue();
            printValue(out, indentLevel, value);
        }
    }

    /**
     * Append a value to the writer, indented according to a specific level.
     */
    private void printValue(final PrintWriter out, final int indentLevel, final Object value) {
        final String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);

        if (value instanceof Date) {
            synchronized (PListNodeBuilder.FORMAT) {
                out.println(padding + "<date>" + PListNodeBuilder.FORMAT.format((Date) value) + "</date>");
            }
        } else if (value instanceof Calendar) {
            printValue(out, indentLevel, ((Calendar) value).getTime());
        } else if (value instanceof Number) {
            if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) {
                out.println(padding + "<real>" + value.toString() + "</real>");
            } else {
                out.println(padding + "<integer>" + value.toString() + "</integer>");
            }
        } else if (value instanceof Boolean) {
            if (((Boolean) value).booleanValue()) {
                out.println(padding + "<true/>");
            } else {
                out.println(padding + "<false/>");
            }
        } else if (value instanceof List) {
            out.println(padding + "<array>");
            for (final Object o : (List<?>) value) {
                printValue(out, indentLevel + 1, o);
            }
            out.println(padding + "</array>");
        } else if (value instanceof HierarchicalConfiguration) {
            // This is safe because we have created this configuration
            @SuppressWarnings("unchecked")
            final HierarchicalConfiguration<ImmutableNode> config = (HierarchicalConfiguration<ImmutableNode>) value;
            printNode(out, indentLevel, config.getNodeModel().getNodeHandler().getRootNode());
        } else if (value instanceof ImmutableConfiguration) {
            // display a flat Configuration as a dictionary
            out.println(padding + "<dict>");

            final ImmutableConfiguration config = (ImmutableConfiguration) value;
            final Iterator<String> it = config.getKeys();
            while (it.hasNext()) {
                // create a node for each property
                final String key = it.next();
                final ImmutableNode node = new ImmutableNode.Builder().name(key).value(config.getProperty(key)).create();

                // print the node
                printNode(out, indentLevel + 1, node);

                if (it.hasNext()) {
                    out.println();
                }<commit_msg>Use forEach()<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.configuration2.plist;

import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
import org.apache.commons.configuration2.FileBasedConfiguration;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ImmutableConfiguration;
import org.apache.commons.configuration2.MapConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration2.io.FileLocator;
import org.apache.commons.configuration2.io.FileLocatorAware;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.configuration2.tree.InMemoryNodeModel;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Property list file (plist) in XML FORMAT as used by Mac OS X (http://www.apple.com/DTDs/PropertyList-1.0.dtd). This
 * configuration doesn't support the binary FORMAT used in OS X 10.4.
 *
 * <p>
 * Example:
 * </p>
 *
 * <pre>
 * &lt;?xml version="1.0"?&gt;
 * &lt;!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"&gt;
 * &lt;plist version="1.0"&gt;
 *     &lt;dict&gt;
 *         &lt;key&gt;string&lt;/key&gt;
 *         &lt;string&gt;value1&lt;/string&gt;
 *
 *         &lt;key&gt;integer&lt;/key&gt;
 *         &lt;integer&gt;12345&lt;/integer&gt;
 *
 *         &lt;key&gt;real&lt;/key&gt;
 *         &lt;real&gt;-123.45E-1&lt;/real&gt;
 *
 *         &lt;key&gt;boolean&lt;/key&gt;
 *         &lt;true/&gt;
 *
 *         &lt;key&gt;date&lt;/key&gt;
 *         &lt;date&gt;2005-01-01T12:00:00Z&lt;/date&gt;
 *
 *         &lt;key&gt;data&lt;/key&gt;
 *         &lt;data&gt;RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==&lt;/data&gt;
 *
 *         &lt;key&gt;array&lt;/key&gt;
 *         &lt;array&gt;
 *             &lt;string&gt;value1&lt;/string&gt;
 *             &lt;string&gt;value2&lt;/string&gt;
 *             &lt;string&gt;value3&lt;/string&gt;
 *         &lt;/array&gt;
 *
 *         &lt;key&gt;dictionnary&lt;/key&gt;
 *         &lt;dict&gt;
 *             &lt;key&gt;key1&lt;/key&gt;
 *             &lt;string&gt;value1&lt;/string&gt;
 *             &lt;key&gt;key2&lt;/key&gt;
 *             &lt;string&gt;value2&lt;/string&gt;
 *             &lt;key&gt;key3&lt;/key&gt;
 *             &lt;string&gt;value3&lt;/string&gt;
 *         &lt;/dict&gt;
 *
 *         &lt;key&gt;nested&lt;/key&gt;
 *         &lt;dict&gt;
 *             &lt;key&gt;node1&lt;/key&gt;
 *             &lt;dict&gt;
 *                 &lt;key&gt;node2&lt;/key&gt;
 *                 &lt;dict&gt;
 *                     &lt;key&gt;node3&lt;/key&gt;
 *                     &lt;string&gt;value&lt;/string&gt;
 *                 &lt;/dict&gt;
 *             &lt;/dict&gt;
 *         &lt;/dict&gt;
 *
 *     &lt;/dict&gt;
 * &lt;/plist&gt;
 * </pre>
 *
 * @since 1.2
 *
 */
public class XMLPropertyListConfiguration extends BaseHierarchicalConfiguration implements FileBasedConfiguration, FileLocatorAware {
    /** Size of the indentation for the generated file. */
    private static final int INDENT_SIZE = 4;

    /** Constant for the encoding for binary data. */
    private static final String DATA_ENCODING = "UTF-8";

    /** Temporarily stores the current file location. */
    private FileLocator locator;

    /**
     * Creates an empty XMLPropertyListConfiguration object which can be used to synthesize a new plist file by adding
     * values and then saving().
     */
    public XMLPropertyListConfiguration() {
    }

    /**
     * Creates a new instance of {@code XMLPropertyListConfiguration} and copies the content of the specified configuration
     * into this object.
     *
     * @param configuration the configuration to copy
     * @since 1.4
     */
    public XMLPropertyListConfiguration(final HierarchicalConfiguration<ImmutableNode> configuration) {
        super(configuration);
    }

    /**
     * Creates a new instance of {@code XMLPropertyConfiguration} with the given root node.
     *
     * @param root the root node
     */
    XMLPropertyListConfiguration(final ImmutableNode root) {
        super(new InMemoryNodeModel(root));
    }

    private void setPropertyDirect(final String key, final Object value) {
        setDetailEvents(false);
        try {
            clearProperty(key);
            addPropertyDirect(key, value);
        } finally {
            setDetailEvents(true);
        }
    }

    @Override
    protected void setPropertyInternal(final String key, final Object value) {
        // special case for byte arrays, they must be stored as is in the configuration
        if (value instanceof byte[] || value instanceof List) {
            setPropertyDirect(key, value);
        } else if (value instanceof Object[]) {
            setPropertyDirect(key, Arrays.asList((Object[]) value));
        } else {
            super.setPropertyInternal(key, value);
        }
    }

    @Override
    protected void addPropertyInternal(final String key, final Object value) {
        if (value instanceof byte[] || value instanceof List) {
            addPropertyDirect(key, value);
        } else if (value instanceof Object[]) {
            addPropertyDirect(key, Arrays.asList((Object[]) value));
        } else {
            super.addPropertyInternal(key, value);
        }
    }

    /**
     * Stores the current file locator. This method is called before I/O operations.
     *
     * @param locator the current {@code FileLocator}
     */
    @Override
    public void initFileLocator(final FileLocator locator) {
        this.locator = locator;
    }

    @Override
    public void read(final Reader in) throws ConfigurationException {
        // set up the DTD validation
        final EntityResolver resolver = (publicId, systemId) -> new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));

        // parse the file
        final XMLPropertyListHandler handler = new XMLPropertyListHandler();
        try {
            final SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setValidating(true);

            final SAXParser parser = factory.newSAXParser();
            parser.getXMLReader().setEntityResolver(resolver);
            parser.getXMLReader().setContentHandler(handler);
            parser.getXMLReader().parse(new InputSource(in));

            getNodeModel().mergeRoot(handler.getResultBuilder().createNode(), null, null, null, this);
        } catch (final Exception e) {
            throw new ConfigurationException("Unable to parse the configuration file", e);
        }
    }

    @Override
    public void write(final Writer out) throws ConfigurationException {
        if (locator == null) {
            throw new ConfigurationException(
                "Save operation not properly " + "initialized! Do not call write(Writer) directly," + " but use a FileHandler to save a configuration.");
        }
        final PrintWriter writer = new PrintWriter(out);

        if (locator.getEncoding() != null) {
            writer.println("<?xml version=\"1.0\" encoding=\"" + locator.getEncoding() + "\"?>");
        } else {
            writer.println("<?xml version=\"1.0\"?>");
        }

        writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
        writer.println("<plist version=\"1.0\">");

        printNode(writer, 1, getNodeModel().getNodeHandler().getRootNode());

        writer.println("</plist>");
        writer.flush();
    }

    /**
     * Append a node to the writer, indented according to a specific level.
     */
    private void printNode(final PrintWriter out, final int indentLevel, final ImmutableNode node) {
        final String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);

        if (node.getNodeName() != null) {
            out.println(padding + "<key>" + StringEscapeUtils.escapeXml10(node.getNodeName()) + "</key>");
        }

        final List<ImmutableNode> children = node.getChildren();
        if (!children.isEmpty()) {
            out.println(padding + "<dict>");

            final Iterator<ImmutableNode> it = children.iterator();
            while (it.hasNext()) {
                final ImmutableNode child = it.next();
                printNode(out, indentLevel + 1, child);

                if (it.hasNext()) {
                    out.println();
                }
            }

            out.println(padding + "</dict>");
        } else if (node.getValue() == null) {
            out.println(padding + "<dict/>");
        } else {
            final Object value = node.getValue();
            printValue(out, indentLevel, value);
        }
    }

    /**
     * Append a value to the writer, indented according to a specific level.
     */
    private void printValue(final PrintWriter out, final int indentLevel, final Object value) {
        final String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);

        if (value instanceof Date) {
            synchronized (PListNodeBuilder.FORMAT) {
                out.println(padding + "<date>" + PListNodeBuilder.FORMAT.format((Date) value) + "</date>");
            }
        } else if (value instanceof Calendar) {
            printValue(out, indentLevel, ((Calendar) value).getTime());
        } else if (value instanceof Number) {
            if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) {
                out.println(padding + "<real>" + value.toString() + "</real>");
            } else {
                out.println(padding + "<integer>" + value.toString() + "</integer>");
            }
        } else if (value instanceof Boolean) {
            if (((Boolean) value).booleanValue()) {
                out.println(padding + "<true/>");
            } else {
                out.println(padding + "<false/>");
            }
        } else if (value instanceof List) {
            out.println(padding + "<array>");
            ((List<?>) value).forEach(o -> printValue(out, indentLevel + 1, o));
            out.println(padding + "</array>");
        } else if (value instanceof HierarchicalConfiguration) {
            // This is safe because we have created this configuration
            @SuppressWarnings("unchecked")
            final HierarchicalConfiguration<ImmutableNode> config = (HierarchicalConfiguration<ImmutableNode>) value;
            printNode(out, indentLevel, config.getNodeModel().getNodeHandler().getRootNode());
        } else if (value instanceof ImmutableConfiguration) {
            // display a flat Configuration as a dictionary
            out.println(padding + "<dict>");

            final ImmutableConfiguration config = (ImmutableConfiguration) value;
            final Iterator<String> it = config.getKeys();
            while (it.hasNext()) {
                // create a node for each property
                final String key = it.next();
                final ImmutableNode node = new ImmutableNode.Builder().name(key).value(config.getProperty(key)).create();

                // print the node
                printNode(out, indentLevel + 1, node);

                if (it.hasNext()) {
                    out.println();
                }
{'fraction_non_alphanumeric': 0.12262885610270166, 'fraction_numerical': 0.006897873155776969, 'mean_word_length': 2.5905338470005503, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 6, '>': 70, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 20}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '684005', 'n_tokens_mistral': 7397, 'n_tokens_neox': 7212, 'n_words': 1767}