<commit_before><?php

namespace SimpleXmlMapper;

use SimpleXMLElement;
use InvalidArgumentException;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;

class XmlMapper
{
    /**
     * The property type extractor instance.
     *
     * @var PropertyTypeExtractorInterface
     */
    protected $extractor;

    /**
     * The default type instance.
     *
     * @var Type
     */
    protected $defaultType;

    /**
     * The custom types.
     *
     * @var array
     */
    protected $types = [];

    /**
     * Create a new mapper instance.
     *
     * @param PropertyTypeExtractorInterface $extractor
     * @param Type|null $defaultType
     */
    public function __construct(PropertyTypeExtractorInterface $extractor, Type $defaultType = null)
    {
        $this->extractor = $extractor;
        $this->defaultType = $defaultType ?: new Type('string');
    }

    /**
     * Add a custom type.
     *
     * @param string $type
     * @param callable $callback
     */
    public function addType($type, callable $callback)
    {
        $this->types[$type] = $callback;
    }

    /**
     * Map XML to the specified entity.
     *
     * @param SimpleXMLElement $xml
     * @param string $class
     * @return mixed
     */
    public function map(SimpleXMLElement $xml, $class)
    {
        if (!class_exists($class)) {
            throw new InvalidArgumentException("Class [$class] does not exist");
        }

        $properties = $this->getProperties($class);
        $entity = new $class;

        foreach ($xml as $node) {
            $name = $node->getName();

            if (in_array($name, $properties)) {
                $type = $this->getType($class, $name) ?: $this->defaultType;
                $entity->{$name} = $this->getValue($node, $type);
            }
        }

        return $entity;
    }

    /**
     * Get all public properties for the specified class.
     *
     * @param string $class
     * @return array
     */
    protected function getProperties($class)
    {
        return array_keys(get_class_vars($class));
    }

    /**
     * Determine the type for the specified property using reflection.
     *
     * @param string $class
     * @param string $name
     * @return Type
     */
    protected function getType($class, $name)
    {
        return $this->extractor->getTypes($class, $name)[0];
    }

    /**
     * Get the value for the specified node.
     *
     * @param SimpleXMLElement $xml
     * @param Type $type
     * @return array|bool|mixed|SimpleXMLElement
     */
    protected function getValue(SimpleXMLElement $xml, Type $type)
    {
        if ($type->isCollection()) {
            $collectionType = $type->getCollectionValueType() ?: $this->defaultType;
            return $this->asCollection($xml, $collectionType);
        }

        $builtinType = $type->getBuiltinType();

        if ($builtinType == 'object') {
            return $this->asObject($xml, $type);
        }

        if ($builtinType == 'bool') {
            return $this->asBool($xml);
        }

        settype($xml, $builtinType);

        return $xml;
    }

    /**
     * Cast node to collection.
     *
     * @param SimpleXMLElement $xml
     * @param Type $type
     * @return array
     */
    protected function asCollection(SimpleXMLElement $xml, Type $type)
    {
        $collection = [];

        foreach ($xml as $node) {
            $collection[] = $this->getValue($node, $type);
        }

        return $collection;
    }

    /**
     * Cast node to object.
     *
     * @param SimpleXMLElement $xml
     * @param Type $type
     * @return mixed
     */
    protected function asObject(SimpleXMLElement $xml, Type $type)
    {
        $class = $type->getClassName();

        if ($this->isCustomType($class)) {
            return $this->callCustomInstantiator($xml, $class);
        }

        return $this->map($xml, $class);
    }

    /**
     * Cast node to boolean.
     *
     * @param SimpleXMLElement $xml
     * @return bool
     */
    protected function asBool(SimpleXMLElement $xml)
    {
        $value = (string)$xml;

        if (is_numeric($value)) {
            return (bool)$value;
        }

        return $value == 'true';
    }

    /**
     * Determine if the specified type has a custom instantiator.
     *
     * @param string $type
     * @return bool
     */
    protected function isCustomType($type)
    {
        return array_key_exists($type, $this->types);
    }

    /**
     * Call the custom instantiator for the specified type.
     *
     * @param SimpleXMLElement $node
     * @param string $type
     * @return mixed
     */
    protected function callCustomInstantiator(SimpleXMLElement $node, $type)
    {
        $callback = $this->types[$type];

        return $callback($node);
    }
}<commit_msg>Be more strict when casting to boolean<commit_after><?php

namespace SimpleXmlMapper;

use SimpleXMLElement;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;

class XmlMapper
{
    /**
     * The property type extractor instance.
     *
     * @var PropertyTypeExtractorInterface
     */
    protected $extractor;

    /**
     * The default type instance.
     *
     * @var Type
     */
    protected $defaultType;

    /**
     * The custom types.
     *
     * @var array
     */
    protected $types = [];

    /**
     * Create a new mapper instance.
     *
     * @param PropertyTypeExtractorInterface $extractor
     * @param Type|null $defaultType
     */
    public function __construct(PropertyTypeExtractorInterface $extractor, Type $defaultType = null)
    {
        $this->extractor = $extractor;
        $this->defaultType = $defaultType ?: new Type('string');
    }

    /**
     * Add a custom type.
     *
     * @param string $type
     * @param callable $callback
     */
    public function addType($type, callable $callback)
    {
        $this->types[$type] = $callback;
    }

    /**
     * Map XML to the specified entity.
     *
     * @param SimpleXMLElement $xml
     * @param string $class
     * @return mixed
     */
    public function map(SimpleXMLElement $xml, $class)
    {
        if (!class_exists($class)) {
            throw new InvalidArgumentException("Class [$class] does not exist");
        }

        $properties = $this->getProperties($class);
        $entity = new $class;

        foreach ($xml as $node) {
            $name = $node->getName();

            if (in_array($name, $properties)) {
                $type = $this->getType($class, $name) ?: $this->defaultType;
                $entity->{$name} = $this->getValue($node, $type);
            }
        }

        return $entity;
    }

    /**
     * Get all public properties for the specified class.
     *
     * @param string $class
     * @return array
     */
    protected function getProperties($class)
    {
        return array_keys(get_class_vars($class));
    }

    /**
     * Determine the type for the specified property using reflection.
     *
     * @param string $class
     * @param string $name
     * @return Type
     */
    protected function getType($class, $name)
    {
        return $this->extractor->getTypes($class, $name)[0];
    }

    /**
     * Get the value for the specified node.
     *
     * @param SimpleXMLElement $xml
     * @param Type $type
     * @return array|bool|mixed|SimpleXMLElement
     */
    protected function getValue(SimpleXMLElement $xml, Type $type)
    {
        if ($type->isCollection()) {
            $collectionType = $type->getCollectionValueType() ?: $this->defaultType;
            return $this->asCollection($xml, $collectionType);
        }

        $builtinType = $type->getBuiltinType();

        if ($builtinType == 'object') {
            return $this->asObject($xml, $type);
        }

        if ($builtinType == 'bool') {
            return $this->asBool($xml);
        }

        settype($xml, $builtinType);

        return $xml;
    }

    /**
     * Cast node to collection.
     *
     * @param SimpleXMLElement $xml
     * @param Type $type
     * @return array
     */
    protected function asCollection(SimpleXMLElement $xml, Type $type)
    {
        $collection = [];

        foreach ($xml as $node) {
            $collection[] = $this->getValue($node, $type);
        }

        return $collection;
    }

    /**
     * Cast node to object.
     *
     * @param SimpleXMLElement $xml
     * @param Type $type
     * @return mixed
     */
    protected function asObject(SimpleXMLElement $xml, Type $type)
    {
        $class = $type->getClassName();

        if ($this->isCustomType($class)) {
            return $this->callCustomInstantiator($xml, $class);
        }

        return $this->map($xml, $class);
    }

    /**
     * Cast node to boolean.
     *
     * @param SimpleXMLElement $xml
     * @return bool
     */
    protected function asBool(SimpleXMLElement $xml)
    {
        $value = (string)$xml;

        switch ($value) {
            case '0':
            case 'false':
                return false;
            case '1':
            case 'true':
                return true;
            default:
                throw new UnexpectedValueException("Could not convert [$value] to boolean");
        }
    }

    /**
     * Determine if the specified type has a custom instantiator.
     *
     * @param string $type
     * @return bool
     */
    protected function isCustomType($type)
    {
        return array_key_exists($type, $this->types);
    }

    /**
     * Call the custom instantiator for the specified type.
     *
     * @param SimpleXMLElement $node
     * @param string $type
     * @return mixed
     */
    protected function callCustomInstantiator(SimpleXMLElement $node, $type)
    {
        $callback = $this->types[$type];

        return $callback($node);
    }
}
{'fraction_non_alphanumeric': 0.12444977991196479, 'fraction_numerical': 0.00040016006402561027, 'mean_word_length': 1.9922178988326849, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 40}, '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': '2310565', 'n_tokens_mistral': 2896, 'n_tokens_neox': 2819, 'n_words': 704}