Desmond  Gerber

Desmond Gerber

1668772500

How to Change an Element's Id Attribute using jQuery

A guide on changing an element's id using jQuery


To change the id attribute of an HTML element, you can use the jQuery attr() method which allows you to set an element’s attribute value.

The syntax of attr() method is as follows:

Element.attr("attributeName", "attributeValue");

In the following example, the <h1> element with id value of myId will be replaced with newId:

<head>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
  <h1 id="myId">jQuery change ID value</h1>
  <script>
    $("#myId").attr("id", "newId");
  </script>
</body>

You can change any HTML element id attribute value by using the example above as a reference.

Original article source at: https://sebhastian.com/

#jquery #element #id 

How to Change an Element's Id Attribute using jQuery
Marisol  Kuhic

Marisol Kuhic

1667288880

Kassko: A Php Mapper Very Tunable, Cross-orm and Cross-DBMS

data-mapper

A php mapper very tunable, cross-orm and cross-DBMS

  • Objects do not extends a base entity class
  • Map nested objects
  • Support entities with value objects
  • Builder to facilitate to hydrate an object (or a collection) or to extract it
  • Abstract databases
  • Support relationship between all types of sources (relational databases, non relational, caches ...)
  • Can chain some fallbacks sources while a source is unavailable or instable
  • Can evaluate the good source to use from an expression or cancel a source (usefull with ACL)
  • Supports dynamic configuration with expression language
  • Lazy loading
  • Eager loading
  • Various mapping configuration format
  • Builder to configure at a high level the component

Installation

Add to your composer.json:

"require": {
    "kassko/data-mapper": "~0.12.5"
}

Note that:

  • the second version number is used when compatibility is broken
  • the third for new feature
  • the fourth for hotfix
  • the first for new API or to go from pre-release to release (from 0 to 1)

Run tests

./bin/phpunit

Usage

Installation: precisions

If you use annotations format, register the autoloader:

$loader = require 'vendor/autoload.php';

Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($loader);

And run the environment:

(new Kassko\DataMapper\DataMapperBuilder)->run();

Accessing existing datas

Data-mapper style

$id = 1;
//Construct a person, set an id to it and implicitly load person from the given id fetching all sources configured.
$person = new Kassko\Sample\Person($id);

echo $person->getName();

Doctrine style

//Here some stuff to retrieve the entityManager.

$id = 1;
$person = $entityManager->getRepository('Kassko\Sample\Person')->find($id);

echo $person->getName();

With data-mapper, the access logic is in the configuration in object with annotations (note that the configuration also could be in a separed yaml or php file or in the object with inner php or inner yaml).

Maybe you need to create an object you don't know if it already persisted or new.

Data-mapper style (explicit loading from sources configured)

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();

$id = 1;
$person = new Kassko\Sample\Person()->setId($id);//Only construct a person and set an id to it.
$dataMapper->load($person);//Explicitly load person from the given id fetching all sources configured.

echo $person->getName();

The variant above allows not to load automatically the object if parameters are not send by the constructor but by the setters. If the client code always create already persisted object, the first version is good. If this client code sometimes create new objects (objects that are not already persisted), you should use the second version which will not attempt to load your objects that are not already existing in your storage.

Example

namespace Kassko\Sample;

use Kassko\DataMapper\ObjectExtension\LoadableTrait;

/**
 * @DM\RefImplicitSource(id="personSource")
 *
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource", 
 *          class="Kassko\Sample\PersonDataSource", 
 *          method="getData", 
 *          args="#id",
 *          supplySeveralFields=true
 *      )
 * })
 */
class Person
{
    use LoadableTrait;

    /**
     * @DM\ExcludeImplicitSource
     */
    private $id;//Not linked to the implicit source because of the annotation ExcludeImplicitSource.
    private $firstName;//Linked to the implicit source idem for name, email and phone.
    private $name;
    private $email;
    private $phone;

    public function __construct($id = null)
    {
        if (null !== $id) {
            $this->id = $id;
            $this->load();//Implicit loading is performed by the constructor not by the setter setId().
        }
    }

    public function getId() { return $this->id;}
    public function setId($id) { $this->id = $id; return $this; }
    public function getFirstName() { return $this->firstName; }
    public function setFirstName($firstName) { $this->firstName = $firstName; return $this; }
    public function getName() { return $this->name; }
    public function setName($name) { $this->name = $name; return $this; }
    public function getEmail() { return $this->email; }
    public function setEmail($email) { $this->email = $email; return $this; }
    public function getPhone() { return $this->phone; }
    public function setPhone($phone) { $this->phone = $phone; return $this; }
}
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData($id)
    {
        $data = $this->connection->executeQuery('select first_name as firstName, name, email, phone from some_table where id = ?', [$id]);

        if (! isset($data[0])) {
            return null;            
        }

        return $data[0];
    }
}

Source annotation details

  • id. An arbitrary id for the source. Optional but necessary if you need to mentionned the source in another annotation.
  • class. The class of the source that return datas. If the source has some dependencies (above PersonDataSource has a dependency $connection), its instanciation can be performed by a resolver named class-resolver. See more details here.
  • method. The name of the method that return datas.
  • args. Arguments of the method that return datas. You can send a raw value, a field value with the prefix # (ex: #id), an expression and more ... See more details here
  • supplySeveralFields. Whether the source returns the data directly or put them in a key of an array and return the array. If the source supply only one field, it can return directly the data, these data will be bound to the good property. Else the source should return an array with as keys as property to supply. The key is named like the property or a mapping is done in the annotation Field. ```php namespace Kassko\Sample;

class Person { private $name;

/**
 * @DM\Field(name="first_name")
 */
private $firstName;

}


```php
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData($id)
    {
        $data = $this->connection->executeQuery('select name, first_name from some_table where id = ?', [$id]);

        if (! isset($data[0])) {
            return null;            
        }

        return $data[0];
    }
}

Data-mapper is not an ORM so it cannot generate for you some sql statement. But you can use it with an ORM like Doctrine ORM.

Example with a relation with a Doctrine source

namespace Kassko\Sample;

use Kassko\DataMapper\ObjectExtension\LoadableTrait;

/**
 * @DM\RefImplicitSource(id="personSource")
 *
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource", 
 *          class="Kassko\Sample\PersonDataSource", 
 *          method="getData", 
 *          args="#id", 
 *          supplySeveralFields=true
 *      ),
 *      @DM\DataSource(
 *          id="carSource", 
 *          class="Kassko\Sample\CarRepository", 
 *          method="find", 
 *          args="expr(source('personSource')['car_id'])"
 *      )
 * })
 *
 *
 * @DM\RefImplicitSource(id="personSource")
 */
class Person
{
    use LoadableTrait;

    /**
     * @DM\ExcludeImplicitSource
     */
    private $id;
    private $firstName;
    private $name;
    private $email;
    private $phone;

    /**
     * @DM\RefSource(id="carSource")
     */
    private $car;

    public function __construct($id = null)
    {
        if (null !== $id) {
            $this->id = $id;
            $this->load();//Implicit loading is performed by the constructor not by the setter setId().
        }
    }

    public function getId() { return $this->id;}
    public function setId($id) { $this->id = $id; return $this; }
    public function getFirstName() { return $this->firstName; }
    public function setFirstName($firstName) { $this->firstName = $firstName; return $this; }
    public function getName() { return $this->name; }
    public function setName($name) { $this->name = $name; return $this; }
    public function getEmail() { return $this->email; }
    public function setEmail($email) { $this->email = $email; return $this; }
    public function getPhone() { return $this->phone; }
    public function setPhone($phone) { $this->phone = $phone; return $this; }
    public function getCar();
    public function setCar($car) { $this->car = $car; return $this; }
}
namespace Kassko\Sample;

class PersonDataSource
{
    private $connection;

    public function __construct($connection)
    {
        $this->connection = $connection;
    }

    public function getData($id)
    {
        $data = $this->connection->executeQuery('select firstName, name, email, phone, car_id from some_table where id = ?', [$id]);

        if (! isset($data[0])) {
            return null;            
        }

        return $data[0];
    }
}
namespace Kassko\Sample;

use Doctrine\ORM\EntityRepository;

/**
 * CarRepository is a Doctrine source that feed the property $car.
 */
class CarRepository extends EntityRepository
{
}

CarRepository has some dependencies too (the entity manager), it is instantiated with a resolver class-resolver. Reminder: you can see more details here.

Component building: precisions

Run environment with class-resolver:

(new Kassko\DataMapper\DataMapperBuilder)
    ->settings(['class_resolver' => $classResolver])
    ->run()
;

But before, create your class-resolver (example of basic resolver):

$classResolver = function ($class, & $supported = true) {
    if ('Kassko\Sample\PersonDataSource' === $class) {
        return new Kassko\Sample\PersonDataSource(new Kassko\Sample\Connection);
    }

    return null;
};

The settings key class_resolver accepts either a callable (a closure / an array like [$class, $method] / an invocable class) or an instance of Kassko\ClassResolver\ClassResolverInterface.

You can see more details about class-resolver here.

Features: details

=======================================================================================================

Basic usage

Given an object:

namespace Kassko\Sample;

class Watch
{
        private $brand;
        private $color;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function getColor() { return $this->color; }
        public function setColor($color) { $this->color = $color; }
}

Use the ResultBuilder:

The result builder allows you to hydrate all a collection from a result that contains several records. It also allows you to get only one result even if the result contains several records.

$data = [
        0 => [
                'brand' => 'some brand',
                'color' => 'blue',
        ],
        1 => [
                'brand' => 'some other brand',
                'color' => 'green',
        ],
];

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();

$dataMapper->resultBuilder('Kassko\Sample\Watch', $data)->all();//The result will be an array with two objects.
$dataMapper->resultBuilder('Kassko\Sample\Watch', $data)->first();//The result will be a watch object representing the first record.

The code above will display:

array(2) {
    ["brand"]=>
    string(10) "some brand"
    ["color"]=>
    string(4) "blue"
}

Inversely, you can extract values of an object or of an object collection:

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();

$dataMapper->resultBuilder('Kassko\Sample\Watch')->raw($object);
$dataMapper->resultBuilder('Kassko\Sample\Watch')->raw($collection);

Above, we use the default hydrator. But you often need to customize hydration because column names are different of properties name and for many other reasons.

To customize the hydration you should provide a mapping configuration. Severals formats are available but in this documentation we choose to use the annotation format (more details about all mapping configuration format are available here).

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Watch
{
        private $brand;

        /**
         * @DM\Fields(name="COLOR")
         */
        private $color;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function getColor() { return $this->color; }
        public function setColor($color) { $this->color = $color; }
}
$loader = require 'vendor/autoload.php';

Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($loader, 'loadClass'));

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();
$dataMapper->resultBuilder('Kassko\Sample\Watch', $data)->all();

Use the result builder

You can find below all the ways to get results with the ResultBuilder:

        /*
        Return an array of objects.
        So return an array with only one object, if only one fullfill the request.
        */
        $resultBuilder->all();
        /*
        Return the object found.

        If more than one result are found, throw an exception
        Kassko\DataMapper\Result\Exception\NonUniqueResultException.

        If no result found, throw an exception
        Kassko\DataMapper\Result\Exception\NoResultException.
        */
        $resultBuilder->single();
        /*
        Return the object found or null.
        */
        $resultBuilder->one();

        /*
        Return the object found or a default result (like false).
        */
        $resultBuilder->one(false);

        /*
        If more than one result are found, throw an exception
        Kassko\DataMapper\Result\Exception\NonUniqueResultException.
        */
        /*
        Return the first object found or null.
        */
        $resultBuilder->first();

        /*
        Return the first object found or a default result (like value false).
        */
        $resultBuilder->first(false);

        /*
        If no result found, throw an exception
        Kassko\DataMapper\Result\Exception\NoResultException.
        */
        /*
        Return an array indexed by a property value.

        If the index does not exists (allIndexedByUnknown()), throw an exception Kassko\DataMapper\Result\Exception\NotFoundIndexException.

        If the same index is found twice, throw an exception
        Kassko\DataMapper\Result\Exception\DuplicatedIndexException.

        Examples:

        allIndexedByBrand() will index by brand value:
        [
                'BrandA' => $theWatchInstanceWithBrandA,
                'BrandB' => $theWatchInstanceWithBrandB,
        ]

        allIndexedByColor() will index by color value:
        [
                'Blue' => $theWatchInstanceWithColorBlue,
                'Red' => $theWatchInstanceWithColorRed,
        ]

        allIndexedByUnknown() will throw a Kassko\DataMapper\Result\Exception\NotFoundIndexException.
        */
        $resultBuilder->allIndexedByBrand();//Indexed by brand value
        //or
        $resultBuilder->allIndexedByColor();//Indexed by color value
        /*
        Return an iterator.

        Result will not be hydrated immediately but only when you will iterate the results (with "foreach" for example).
        */
        $result = $resultBuilder->iterable();
        foreach ($result as $object) {//$object is hydrated

                if ($object->getColor() === 'blue') {
                        break;

                        //We found the good object then we stop the loop and others objects in results will not be hydrated.
                }
        }
        /*
        Return an iterator indexed by a property value.

        If the index does not exists, throw an exception Kassko\DataMapper\Result\Exception\NotFoundIndexException.

        If the same index is found twice, throw an exception Kassko\DataMapper\Result\Exception\DuplicatedIndexException.
        */
        $resultBuilder->iterableIndexedByBrand();
        //or
        $resultBuilder->iterableIndexedByColor();

Enforce type of fields

This section will be written later.

Apply converters before hydration or extraction

Converter

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\Hydrator\HydrationContextInterface;
use Kassko\DataMapper\Hydrator\Value;
use \DateTime;

class Watch
{
        private static $brandCodeToLabelMap = [1 => 'Brand A', 2 => 'Brand B'];
        private static $brandLabelToCodeMap = ['Brand A' => 1, 'Brand B' => 2];

        /**
         * @DM\Field(readConverter="readBrand", writeConverter="writeBrand")
         */
        private $brand;

        /**
         * @DM\Field(readConverter="hydrateBool", writeConverter="extractBool")
         */
        private $waterProof;

        /**
         * @DM\Field(readConverter="hydrateBoolFromSymbol", writeConverter="extractBoolToSymbol")
         */
        private $stopWatch;

        /**
         * @DM\Field(type="date", readDateConverter="Y-m-d H:i:s", writeDateConverter="Y-m-d H:i:s")
         */
        private $createdDate;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function isWaterProof() { return $this->waterProof; }
        public function setWaterProof($waterProof) { $this->waterProof = $waterProof; }
        public function hasStopWatch() { return $this->stopWatch; }
        public function setStopWatch($stopWatch) { $this->stopWatch = $stopWatch; }
        public function getCreatedDate() { return $this->createdDate; }
        public function setCreatedDate(DateTime $createdDate) { $this->createdDate = $createdDate; }

        public static function readBrand(Value $value, HydrationContextInterface $context)
        {
                if (isset(self::$brandCodeToLabelMap[$value->value])) {
                        $value->value = self::$brandCodeToLabelMap[$value->value];
                }
        }

        public static function writeBrand(Value $value, HydrationContextInterface $context)
        {
                if (isset(self::$brandLabelToCodeMap[$value->value])) {
                        $value->value = self::$brandLabelToCodeMap[$value->value];
                }
        }

        public static function hydrateBool(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value == '1';
        }

        public static function extractBool(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value ? '1' : '0';
        }

        public static function hydrateBoolFromSymbol(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value == 'X';
        }

        public static function extractBoolToSymbol(Value $value, HydrationContextInterface $context)
        {
                $value->value = $value->value ? 'X' : ' ';
        }
}

readDateConverter contains the format of the string to transform into Date object. Internally, DataMapper uses the Php function DateTime::createFromFormat() to create a Date object from a raw string.

writeDateConverter contains the string format in which you want to serialize your Date object. Internally, DataMapper uses the Php function DateTime::createFromFormat() to serialise the Date object in a string.

Given this code:

$data = [
        'brand' => '1',
        'waterProof' => '1',
        'stopWatch' => 'X',
        'created_date' => '2014-09-14 12:36:52',
];

$dataMapper = (new Kassko\DataMapper\DataMapperBuilder)->instance();
$object = new Kassko\Sample\Watch;
$dataMapper->hydrator('Kassko\Sample\Watch')->hydrate($data, $object);
var_dump($object);

Your output will be:

object(Watch)#283 (8) {
        ["brand":"Watch":private]=> string(10) "Brand A"
        ["waterProof":"Watch":private]=> bool(true)
        ["stopWatch":"Watch":private]=> bool(true)
        ["createdDate":"Watch":private]=>
                object(DateTime)#320 (3) { ["date"]=> string(19) "2014-09-14 12:36:52" ["timezone_type"]=> int(3) ["timezone"]=> string(13) "Europe/Berlin" }
}

If the created_date had a bad format, an exception would have been thrown. For example, the format given above in the read date converter is 'Y-m-d H:i:s', so a create_date like '2014 09 14 12h36m52s' is not correct.

Date converter

This section will be written later.

Add callbacks before or after hydration process

This section will be written later.

Customize getters and setters

DataMapper automatically recognize getter (or isser or haser) and setter of a field.

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Watch
{
        /**
         * @DM\Field
         */
        private $brand;

        /**
         * @DM\Field
         */
        private $waterProof;

        /**
         * @DM\Field
         */
        private $stopWatch;

        public function getBrand() { return $this->brand; }
        public function setBrand($brand) { $this->brand = $brand; }
        public function isWaterProof() { return $this->waterProof; }
        public function setWaterProof($waterProof) { $this->waterProof = $waterProof; }
        public function hasStopWatch() { return $this->stopWatch; }
        public function setStopWatch($stopWatch) { $this->stopWatch = $stopWatch; }
}

To retrieve the corresponding getter, DataMapper look in order at:

  • a getter
  • an isser
  • a haser

You also can specify corresponding getters/setters:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Watch
{
        /**
         * @DM\Field(prefix="is")
         */
        private $waterProof;

        /**
         * @DM\Getter(prefix="has")
         */
        private $alarm;

        /**
         * @DM\Getter(prefix="are")
         */
        private $handsYellow;

        /**
         * @DM\Field(name="hasStopWatch")
         */
        private $stopWatch;

        /**
         * @DM\Getter(name="canColorChange")
         * @DM\Setter(name="colorCanChange")
         */
        private $variableColor;

        public function isWaterProof() { return $this->waterProof; }
        public function setWaterProof($waterProof) { $this->waterProof = $waterProof; }
        public function hasAlarm() { return $this->alarm; }
        public function setAlarm($stopWatch) { $this->alarm = $alarm; }
        public function areHandsYellow() { return $this->handsYellow; }
        public function setHandsyellow($handsYellow) { $this->handsYellow = $handsYellow; }
        public function hasStopWatch() { return $this->stopWatch; }
        public function setStopWatch($stopWatch) { $this->stopWatch = $stopWatch; }
        public function canColorChange() { return $this->variableColor; }
        public function colorCanChange($colorCanChange) { $this->variableColor = $colorCanChange; }
}

Hydrate nested objects

This section will be written later.

Configure a php object hydrator instead of using a mapping configuration

This section will be written later.

Work with object complex to create, like service

This section will be written later.

Work with other mapping configuration format

This section will be written later.

Outer mapping configuration format

This section will be written later.

Inner mapping configuration format

This section will be written later.

Improve persistance ignorance

This section will be written later.

Choose a mapping configuration at runtime

You can use the same model with various mapping configuration but you must work with one of the outer mapping configuration and not with mapping embedded in the object. So 'yaml_file' or 'php_file' are correct mapping format but 'annotations', 'inner_php' or 'inner_yaml' are bad format.

namespace Kassko\Sample;

class Color
{
        private $red;
        private $green;
        private $blue;

        public function getRed() { return $this->red; }
        public function setRed($red) { $this->red = $red; }
        public function getGreen() { return $this->green; }
        public function setGreen($green) { $this->green = $green; }
        public function getBlue() { return $this->blue; }
        public function setBlue($blue) { $this->blue = $blue; }
}

A english data source with the mapping in yaml:

# color_en.yml

fields:
        red: ~
        green: ~
        blue: ~

A french data source with the mapping in yaml:

# color_fr.yml

fields:
        red:
                name: rouge
        green:
                name: vert
        blue:
                name: bleu

And imagine we've got a spanish data source with the mapping in a php format.

//color_es.php

return [
        'fields' => [
                'red' => 'rojo',
                'green' => 'verde',
                'blue' => 'azul',
        ],
];
use DataMapper\Configuration\RuntimeConfiguration;

$data = [
        'red' => '255',
        'green' => '0',
        'blue' => '127',
];

$resultBuilder = $dataMapper->resultBuilder('Kassko\Sample\Color', $data);
$resultBuilder->setRuntimeConfiguration(
        (new RuntimeConfiguration)
        ->addClassMetadataDir('Color', 'some_resource_dir')//Optional, if not specified Configuration::defaultClassMetadataResourceDir is used.
        ->addMappingResourceInfo('Color', 'color_en.yml', 'inner_yaml')
);

$resultBuilder->single();
use DataMapper\Configuration\RuntimeConfiguration;

$data = [
        'rouge' => '255',
        'vert' => '0',
        'bleu' => '127',
];

$resultBuilder = $dataMapper->resultBuilder('Kassko\Sample\Color', $data);
$resultBuilder->setRuntimeConfiguration(
        (new RuntimeConfiguration)
        ->addClassMetadataDir('Color', 'some_resource_dir')
        ->addMappingResourceInfo('Color', 'color_fr.yml', 'inner_yaml')
);

$resultBuilder->single();
use DataMapper\Configuration\RuntimeConfiguration;

$data = [
        'rojo' => '255',
        'verde' => '0',
        'azul' => '127',
];

$resultBuilder = $dataMapper->resultBuilder('Kassko\Sample\Color', $data);
$resultBuilder->setRuntimeConfiguration(
        (new RuntimeConfiguration)
        ->addClassMetadataDir('Color', 'some_resource_dir')
        ->addMappingResourceInfo('Color', 'color_es.php', 'inner_php')
);

$resultBuilder->single();

Bind a mapping configuration to a property especially

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Customer
{
        /**
         * @DM\Field
         * @DM\Id
         */
        private $id;

        /**
         * @DM\Field(class="Kassko\Sample\Address")
         * @DM\Config(mappingResourceType="yaml", mappingResourceName="billing_address.yml")
         */
        private $billingAddress;//$billingAddress is a value object.

        /**
         * @DM\Field(class="Kassko\Sample\Address")
         * @DM\Config(mappingResourceType="yaml", mappingResourceName="shipping_address.yml")
         */
        private $shippingAddress;//$shippingAddress is a value object too.
}
namespace Kassko\Sample;

class Address
{
        private $street;
        private $town;
        private $postalCode;
        private $country;
}
# billing_address.yml

fields:
        street:
                name: billing_street
        town:
                name: billing_town
        postalCode:
                name: billing_postal_code
        country:
                name: billing_country
# shipping_address.yml

fields:
        street:
                name: shipping_street
        town:
                name: shipping_town
        postalCode:
                name: shipping_postal_code
        country:
                name: shipping_country

$data = [
        'id' => 1,
        'billing_street' => '12 smarties street',
        'billing_town' => 'Nuts',
        'billing_postal_code' => '654321'
        'billing_country' => 'England',
        'shipping_street' => '23 smarties street',
        'shipping_town' => 'Mars',
        'shipping_postal_code' => '987654'
        'shipping_country' => 'England',
];

$dataMapper->resultBuilder('Kassko\Sample\Customer', $data)->single();

Note that you can have value objects which contains value objects and so on. And each value object can use it's own mapping configuration format.

Bind a source to a property or a set of properties / hydrate object from multi-sources, multi-orm

Data source

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Information
{
    /**
     * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getBestShop")
     * @DM\Field(class='Kassko\Sample\Shop')
     */
    private $bestShop;

    /**
     * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getNbShops")
     */
    private $nbShops;

    public function getBestShop() { return $this->bestShop; }
    public function setBestShop(Shop $shop) { $this->bestShop = $bestShop; }
}
namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Shop
{
    /**
     * @DM\Field
     */
    private $name;

    /**
     * @DM\Field
     */
    private $address;
}
namespace Kassko\Sample;

class ShopDataSource
{
    public function getBestShop()
    {
        return [
            'name' => 'The best',
            'address' => 'Street of the bests',
        ];
    }

    public function getNbShops()
    {
        return 25;
    }
}

Method arguments

This section will be written later.

Lazy loading

Below, we load properties "bestShop" and "keyboard" only when we use it.

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;
use Kassko\DataMapper\ObjectExtension\LazyLoadableTrait;

class Information
{
    use LazyLoadableTrait;

        /**
         * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getBestShop", lazyLoading=true)
         * @DM\Field(class='Kassko\Sample\Shop')
         */
        private $bestShop;

        /**
         * @DM\DataSource(class="Kassko\Sample\ShopDataSource", method="getNbShops", lazyLoading=true)
         */
        private $nbShops;

        public function getBestShop()
        {
                $this->loadProperty('bestShop');//<= Load the best shop from the property name if not loaded.
                return $this->bestShop;
        }

        public function getNbShop()
        {
                $this->loadProperty('nbShops');//<= Load the best shop from the property name if not loaded.
                return $this->nbShops;
        }
}
namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Shop
{
        /**
         * @DM\Field
         */
        private $name;

        /**
         * @DM\Field
         */
        private $address;
}
namespace Kassko\Sample;

class ShopDataSource
{
    public function getBestShop()
    {
        return [
            'name' => 'The best',
            'address' => 'Street of the bests',
        ];
    }

    public function getNbShops()
    {
        return 25;
    }
}

Source store

Given this code:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Person
{
    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $name;

    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $address;

    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $phone;

    /**
     * @DM\DataSource(class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true, supplySeveralFields=true)
     */
    private $email;
}
namespace Kassko\Sample;

class PersonDataSource
{
    public function getData()
    {
        return [
            'name' => 'Foo',
            'address' => 'Blue road',
            'phone' => '01 02 03 04 05',
            'email' => 'foo@bar.baz',
        ];
    }
}

We can remove the duplication:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="some_source", class="Kassko\Sample\PersonDataSource", method="getData", lazyLoading=true
 *      )
 * })
 */
class Person
{
    /**
     * @DM\RefSource(id="some_source")
     */
    private $name;

    /**
     * @DM\RefSource(id="some_source")
     */
    private $address;

    /**
     * @DM\RefSource(id="some_source")
     */
    private $phone;

    /**
     * @DM\RefSource(id="some_source")
     */
    private $email;
}

Fallback source

Here, sourceB replace sourceA if its not stable:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="sourceA", class="Kassko\Sample\ShopDataSource", method="getData", lazyLoading=true,
 *          fallbackSourceId="sourceB", onFail="checkException", exceptionClass="Kassko\Sample\NotStableSourceException"
 *      )
 * })
 */
class Person
{
}

Or:

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="sourceA", class="Kassko\Sample\ShopDataSource", method="getData", lazyLoading=true,
 *          fallbackSourceId="sourceB", onFail="checkReturnValue", badReturnValue="null"
 *      )
 * })
 */
class Person
{
}

Bad return values could be: "null", "false", "emptyString" or "emptyArray".

Processor

namespace Kassko\Sample;

use Kassko\DataMapper\Annotation as DM;

class Key
{
    use LazyLoadableTrait;

    private $note;
    private $octave = 3;

    /**
     * @DM\DataSource(
     * id="ida", 
     * class="Kassko\Samples\KeyLLManager", 
     * method="getData", supplySeveralFields=true,
     * preprocessors = @DM\Methods({
     *  @DM\Method(method="somePreprocessor"),
     *  @DM\Method(class="Kassko\Sample\KeyProcessor", method="preprocess", args="##this")
     * })
     * processors = @DM\Methods({
     *  @DM\Method(method="someProcessor"),
     *  @DM\Method(class="Kassko\Sample\KeyProcessor", method="process", args="##this")
     * })
     *)
     */
    public $color;

    public function getColor() 
    {
        $this->loadProperty('color');
        return $this->color;
    }

    public function somePrepocessor()
    {
        //Some stuff
    }

    public function someProcessor())
    {
        //Some stuff
    }
}
namespace Kassko\Sample;

class KeyPreprocessor
{
    public function preprocess(Key $key)
    {
        $this->logger->info(sprintf('Before key hydration %s', $key->getId()));
    }

    public function process($keyColor)
    {
        $this->logger->info(sprintf('After key hydration %s', $key->getId()));
    }
}

Or:

class Key
{
    use LazyLoadableTrait;

    private $note;
    private $octave = 3;

    /**
     * @DM\DataSource(
     * id="ida", 
     * class="Kassko\Samples\KeyLLManager", 
     * method="getData", supplySeveralFields=true,
     * preprocessor = @DM\Method(method="somePreprocessor"),  
     * processor = @DM\Method(method="someProcessor")
     *)
     */
    public $color;

    public function getColor() 
    {
        $this->loadProperty('color');
        return $this->color;
    }

    public function somePrepocessor()
    {
        //Some stuff
    }

    public function someProcessor())
    {
        //Some stuff
    }
}

Depends

depends contains sources of which depends an other source. It's usefull when you need to ensures that a property is already available in a zone.

This section will be completed later.

Work with relations

This section will be written later.

How to have DataMapper/ResultBuilder ignorance in the client code

This section will be written later.

Use expression language

This section will be written later.

Basic usage of expression language

This section will be written later.

Add a function provider

This section will be written later.

Object listener

This section will be written later.

Add a custom mapping configuration format

This section will be written later.

Inherit mapping configuration

This section will be written later.

Component configuration reference

[
    'mapping' =>
    [
        //Default is "annotations" or other type (1).
        'default_resource_type' => 'annotations',

        //Optional key.
        'default_resource_dir' => 'some_dir',

        //Optional key. Has only sense if you use inner_php or inner_yaml format.
        //It's the method whereby you provide the mapping.
        'default_provider_method' => 'some_method_name',

        //Optional section.
        'groups' =>
        [
            'some_group' =>
            [
                //Default is "annotations" or other type (1).
                'resource_type' => annotations,

                //The resource dir of the given bundle.
                'resource_dir' => 'some_dir',

                //Default value is null.
                'provider_method' => null,
            ],
        ],

        //Optional section
        'objects':
        [
            [
                //Required (the full qualified object class name).
                'class' => 'some_fqcn',

                //Optional key. Allows to inherit settings from a group if there are not specified.
                'group' => 'some_group',

                //Optional key.
                'resource_type' => 'yaml_file',

                //Optional key.
                //The resource directory with the resource name.
                //If not defined, data-mapper fallback to resource_name and prepend to it a resource_dir (this object resource_dir or a group resource_dir or the default_resource_dir).
                //So if resource_path is not defined, keys resource_name and a resource_dir should be defined.
                'resource_path' => 'some_path',

                //Optional key. Only the resource name (so without the directory).
                'resource_name' => 'some_ressource.yml',

                //Optional key. Override default_provider_method.
                'provider_method' => 'some_method_name',
            ],
        ],
    ],

    //Optional section.
    'cache' =>
    [
        //Optional section. The cache for mapping metadata.
        'metadata_cache' =>
        [
            //A cache instance which implements Kassko\Cache\CacheInterface. Default is Kassko\Cache\ArrayCache.
            //If you use a third-party cache provider, maybe you need to wrap it into an adapter to enforce compatibility with Kassko\Cache\CacheInterface.
            'instance' => $someCacheInstance,

            //Default value is 0.
            //0 means the data will never been deleted from the cache.
            //Obviously, 'life_time' has no sense with an "ArrayCache" implementation.
            'life_time' => 0,

            //Default value is false. Indicates if the cache is shared or not.
            //If you don't specify it, you're not wrong. It optimises the
            'is_shared' => false,
        ],

        //Optional section. The cache for query results.
        //This section has the same keys as 'metadata_cache' section.
        'result_cache': => [],
    ],

    //Optional key. A logger instance which implements Psr\Logger\LoggerInterface.
    'logger' => $logger,

    //Optional key. Needed to retrieve repositories specified in 'repository_class' mapping attributes and which creation is assigned to a creator (a factory, a container, a callable).
    'class_resolver' => $someClassResolver,

    //Optional key. Needed to retrieve object listener specified in 'object_listener' mapping attributes and which creation is assigned to a creator (a factory, a container, a callable).
    'object_listener_resolver' => $someObjectListenerResolver,
]

(1) availables types are annotations, yaml_file, php_file, inner_php, inner_yaml. And maybe others if you add some custom mapping loaders.

Mapping configuration reference

Config config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Config(
     *      class="\ValueObjectClass",
     *      mappingResourceName="valueObjectResourceName",
     *      mappingResourcePath="valueObjectResourcePath",
     *      mappingResourceType="valueObjectResourceType"
     * )
     */
    protected $firstField;

Yaml format:

fields: [firstField]
config:
    firstField:
        class: "\\\ValueObjectClass"
        mappingResourceName: valueObjectResourceName
        mappingResourcePath: valueObjectResourcePath
        mappingResourceType: valueObjectResourceType

Php format:

[
    'fields' => ['firstField'],
    'config' => [
        'firstField'    => [
            'class' => '\ValueObjectClass',
            'mappingResourceName' => 'valueObjectResourceName',
            'mappingResourcePath' => 'valueObjectResourcePath',
            'mappingResourceType' => 'valueObjectResourceType'
        ]
    ]
];

CustomHydrator config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * Class SomeClass
 * 
 * @DM\CustomHydrator(
 *      class="SomeClassHydrator",
 *      hydrateMethod="hydrateMethod",
 *      extractMethod="extractMethod"
 * )
 */
class SomeClass
{
}

Yaml format:

object:
  customHydrator:
    class: SomeClassHydrator
    hydrateMethod: hydrateMethod
    extractMethod: extractMethod

Php format:

[
    'object'    => [
        'customHydrator'    => [
            'class' => 'SomeClassHydrator',
            'hydrateMethod' => 'hydrateMethod',
            'extractMethod' => 'extractMethod'
        ]
    ]
];

The methods prototype:

class SomeClassHydrator
{
    /**
     * Hydrate a SomeClass object from raw data $data.
     *
     * @param array The raw data
     * @return SomeClass
     */
    public function hydrateMethod(array $data)
    {
    }

    /**
     * Extract data from a SomeClass instance $object.
     *
     * @param SomeClass The object
     * @return array
     */
    public function extractMethod(SomeClass $object)
    {
    }
}

DataSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\DataSource(
     *      id="firstFieldId",
     *      lazyLoading=true,
     *      supplySeveralFields=true,
     *      depends={"depend#1","depend#2"},
     *      onFail="checkException",
     *      exceptionClass="\RuntimeException",
     *      badReturnValue="emptyString",
     *      fallbackSourceId="firstFieldFallbackSourceId",
     *      preprocessor=@DM\Method(method="fooPreprocessor"),
     *      processors = @DM\Methods({
     *          @DM\Method(method="barProcessor"),
     *          @DM\Method(method="bazProcessor")
     *      })
     *      class="\stdClass",
     *      method="someMethod",
     *      args={"argument#1", "argument#2"}
     * )
     */
     protected $firstField;
}

Yaml format:

fields:
    firstField:
        name: originalFieldName
        dataSource:
            id: firstFieldId
            lazyLoading: true
            supplySeveralFields: true
            depends: [depend#1, depend#2]
            onFail: checkException
            exceptionClass: "\\\RuntimeException"
            badReturnValue: emptyString
            fallbackSourceId: firstFieldFallbackSourceId
            preprocessor:
                class: "##this"
                method: fooPreprocessor
                args: []
            processor: ~
            preprocessors: []
            processors: 
                - {method: barProcessor}
                - {method: bazProcessor}
            class: "\\\stdClass"
            method: someMethod
            args: [argument#1, argument#2]

Php format:

[
    'fields' => [
        'firstField' => [
            'name'       => 'originalFieldName',
            'dataSource' => [
                'id'                  => 'firstFieldId',
                'lazyLoading'         => true,
                'supplySeveralFields' => true,
                'depends'             => ['depend#1', 'depend#2'],
                'onFail'              => 'checkException',
                'exceptionClass'      => '\RuntimeException',
                'badReturnValue'      => 'emptyString',
                'fallbackSourceId'    => 'firstFieldFallbackSourceId',
                'preprocessor'        => [
                    'class'  => '##this',
                    'method' => 'fooPreprocessor',
                    'args'   => []
                ],
                'processor'           => [],
                'preprocessors'       => [],
                'processors'          => [
                    ['method' => 'barProcessor'],
                    ['method' => 'bazProcessor'],
                ],
                'class'               => '\stdClass',
                'method'              => 'someMethod',
                'args'                => ['argument#1', 'argument#2']
            ]
        ]
    ]
];

To know more about the "Method config" usage, please see its dedicated documentation "Method config".

DataSourcesStore config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="personSource",
 *          class="Kassko\Sample\PersonDataSource",
 *          method="getData",
 *          args="#id",
 *          lazyLoading=true,
 *          supplySeveralFields=true,
 *          onFail="checkException",
 *          exceptionClass="\RuntimeException",
 *          badReturnValue="emptyString",
 *          fallbackSourceId="testFallbackSourceId",
 *          depends="#dependsFirst",
 *          preprocessor = @DM\Method(method="somePreprocessor"),
 *          processor = @DM\Method(method="someProcessor")
 *      )
 * })
 */
class SomeClass
{
}

Yaml format:

object:
    dataSourcesStore:
        - id: personSource
        class: "Kassko\\\Sample\\\PersonDataSource"
        method: getData
        args: [#id]
        lazyLoading: true
        supplySeveralFields: true
        onFail: checkException
        exceptionClass: \RuntimeException
        badReturnValue: emptyString
        fallbackSourceId: testFallbackSourceId
        depends: [#dependsFirst]
        preprocessor:
            class: ""
            method: somePreprocessor
            args: []
        processor:
            class: ""
            method: someProcessor
            args: []

Php format:

[
    'object'    => [
        'dataSourcesStore'    => [
            [
                'id'=> 'personSource',
                'class'=> 'Kassko\Sample\PersonDataSource',
                'method'=> 'getData',
                'args' => ['#id'],
                'lazyLoading' => true,
                'supplySeveralFields' => true,
                'onFail'    => 'checkException',
                'exceptionClass' => '\RuntimeException',
                'badReturnValue' => 'emptyString',
                'fallbackSourceId' => 'testFallbackSourceId',
                'depends' => ['#dependsFirst'],
                'preprocessor' => [
                    'class' => '',
                    'method' => 'somePreprocessor',
                    'args' => []
                ],
                'processor' => [
                    'class' => '',
                    'method' => 'someProcessor',
                    'args' => []
                ]
            ]
        ]
    ]
];

To know more about the "Method config" usage, please see its dedicated documentation "Method config".

ExcludeImplicitSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\RefImplicitSource(id="RefImplicitSourceId")
 */
class SomeClass
{
    protected $fieldToBindAutoToImplicitSource;

    protected $anotherFieldToBindAutoToImplicitSource;

    /**
     *@DM\ExcludeImplicitSource
     */
    protected $fieldNotToBindAutoToImplicitSource;
}

Yaml format:

object:
    RefImplicitSource: RefImplicitSourceId
fields:
    fieldToBindAutoToImplicitSource:
        name: fieldToBindAutoToImplicitSource
    anotherFieldToBindAutoToImplicitSource:
        name: anotherFieldToBindAutoToImplicitSource
    fieldNotToBindAutoToImplicitSource:
        name: fieldNotToBindAutoToImplicitSource
fieldsNotToBindToImplicitSource: [fieldNotToBindAutoToImplicitSource]

Php format:

[
    'object' => [
        'RefImplicitSource' => 'RefImplicitSourceId'
    ],
    'fields' => [
        'fieldToBindAutoToImplicitSource' => [
            'name'      => 'fieldToBindAutoToImplicitSource',
        ],
        'anotherFieldToBindAutoToImplicitSource' => [
            'name'      => 'anotherFieldToBindAutoToImplicitSource',
        ],
        'fieldNotToBindAutoToImplicitSource' => [
            'name'      => 'fieldNotToBindAutoToImplicitSource',
        ]
    ],
    'fieldsNotToBindToImplicitSource' => [
        'fieldNotToBindAutoToImplicitSource'
    ]
];

Field config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Field(
     *      name="FirstField",
     *      type="string",
     *      class="stdClass",
     *      readConverter="readConvertFirstField",
     *      writeConverter="writeConvertFirstField",
     *      fieldMappingExtensionClass="ExtensionClass"
     * )
     */
    protected $fieldOne;

    /**
     * @DM\Field(
     *      name="SecondField",
     *      type="integer",
     *      class="\DateTime",
     *      readDateConverter="readDateConvertSecondField",
     *      writeDateConverter="writeDateConvertSecondField",
     *      fieldMappingExtensionClass="ExtensionClass",
     *      defaultValue="12"
     * )
     */
    protected $fieldTwo;

    /**
     * @DM\Field(
     *      name="DateField",
     *      type="date"
     * )
     */
    protected $dateField;
}

Yaml format:

fields:
    fieldOne:
        name: FirstField
        type: string
        class: stdClass
        readConverter: readConvertFirstField
        writeConverter: writeConvertFirstField
        fieldMappingExtensionClass: ExtensionClass
    fieldTwo:
        name: SecondField
        type: integer
        class: "\\\DateTime"
        readDateConverter: readDateConvertSecondField
        writeDateConverter: writeDateConvertSecondField
        fieldMappingExtensionClass: ExtensionClass
        defaultValue: 12
    dateField:
        name: DateField
        type: date

Php format:

[
    'fields' => [
        'fieldOne'  => [
            'name'                       => 'FirstField',
            'type'                       => 'string',
            'class'                      => 'stdClass',
            'readConverter'              => 'readConvertFirstField',
            'writeConverter'             => 'writeConvertFirstField',
            'fieldMappingExtensionClass' => 'ExtensionClass',
        ],
        'fieldTwo'  => [
            'name'                       => 'SecondField',
            'type'                       => 'integer',
            'class'                      => '\DateTime',
            'readDateConverter'          => 'readDateConvertSecondField',
            'writeDateConverter'         => 'writeDateConvertSecondField',
            'fieldMappingExtensionClass' => 'ExtensionClass',
            'defaultValue'               => 12
        ],
        'dateField' => [
            'name'                       => 'DateField',
            'type'                       => 'date'
        ]
    ]
];

Getter config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Getter(
     *      name="getterName"
     * )
     */
    protected $firstField;

    /**
     * @DM\Getter(
     *      prefix="is"
     * )
     */
    protected $secondField;
}

Yaml format:

fields:
    firstField:
        name: firstField
        getter: 
            name: getterName
    secondField:
        name: secondField
        getter:
            prefix: is

Php format:

[
    'fields'    => [
        'firstField'    => [
            'name'      => 'firstField',
            'getter'    => ['name' => 'getterName'],
        ],
        'secondField'    => [
            'name'      => 'secondField',
            'getter'    => ['prefix' => 'is'],
        ],
    ]
];

Id config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Id
     */
    protected $firstField;
}

Yaml format:

id: firstField
fields: ["firstField"]

Php format:

[
    'id'        => 'firstField',
    'fields'    => ['firstField']
];

IdCompositePart config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\IdCompositePart
     */
    protected $firstField;

    /**
     * @DM\IdCompositePart
     */
    protected $secondField;
}

Yaml format:

idComposite:    [firstField, secondField]
fields:         [firstField, secondField]

Php format:

[
    'idComposite'   => ['firstField', 'secondField'],
    'fields'    => ['firstField', 'secondField']
];

Listeners config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Listeners(
 *  preHydrate = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="preHydrateMethodName")   
 * }),
 *  postHydrate = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="postHydrateMethodName"),
 *      @DM\Method(class="SomeClassB", method="postHydrateMethodName") 
 * }),
 *  preExtract = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="preExtractMethodName", args="foo")   
 * }),
 *  postExtract = @DM\Methods({
 *      @DM\Method(class="SomeClass", method="postExtractMethodName", args={"foo", "#bar"})   
 * })
 * )
 */
class SomeClass
{
}

Yaml format:

listeners:
    preHydrate: 
        - {class: SomeClass, method: preHydrateMethodName}
    postHydrate: 
        - {class: SomeClass, method: postHydrateMethodName}
        - {class: SomeClassB, method: postHydrateMethodName}
    preExtract: 
        - {class: SomeClass, method: preExtractMethodName, args: foo}
    postExtract: 
        - {class: SomeClass, method: postExtractMethodName, args: ['foo', '#bar']}

Php format:

[
    'listeners' => [
        'preHydrate' => ['class' => 'SomeClass', 'method' => 'preHydrateMethodName'],                
        'postHydrate' => 
        [
            ['class' => 'SomeClass', 'method' => 'postHydrateMethodName'],
            ['class' => 'SomeClassB', 'method' => 'postHydrateMethodName'],
        ], 
        'preExtract' => ['class' => 'SomeClass', 'method' => 'preExtractMethodName', 'args' => 'foo'],
        'postExtract' => ['class' => 'SomeClass', 'method' => 'postExtractMethodName', 'args' => ['foo', '#bar']],
    ]
];

Method config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Method(method="someMethod")
 * @DM\Method(class="SomeClass", method="someMethod", args="abc")
 * @DM\Method(class="@some_object_created_from_factory_or_container", method="someMethod", args= {"##this", "abc"}
 */

Yaml format:

some_key:
    'method': someMethod

some_key:
    class: SomeClass
    method: someMethod
    args: abc

some_key:
    class: @some_object_created_from_factory_or_container
    method: someMethod
    args: ['##this', 'abc']

Php format:

[
    'method' => 'someMethod',
]

[
    'class' => 'SomeClass',
    'method' => 'someMethod',
    'args' => 'abc'
]

[
    'class' => '@some_object_created_from_factory_or_container',
    'method' => 'someMethod',
    'args' => ['##this', 'abc']
]

Object config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(
 *      fieldExclusionPolicy="exclude_all",
 *      providerClass="testProviderClass",
 *      readDateConverter="testReadDateConverter",
 *      writeDateConverter="testWriteDateConverter",
 *      propertyAccessStrategy=true,
 *      fieldMappingExtensionClass="testFieldMappingExtensionClass",
 *      classMappingExtensionClass="testClassMappingExtensionClass"
 * )
 */
class SomeClass
{

}

Yaml format:

fieldExclusionPolicy: exclude_all
object:
    providerClass: testProviderClass
    readDateConverter: testReadDateConverter
    writeDateConverter: testWriteDateConverter
    propertyAccessStrategy: true
    fieldMappingExtensionClass: testFieldMappingExtensionClass
    classMappingExtensionClass: testClassMappingExtensionClass

Php format:

[
    'fieldExclusionPolicy'  => 'exclude_all',
    'object'    => [
        'providerClass'         => 'testProviderClass',
        'readDateConverter'     => 'testReadDateConverter',
        'writeDateConverter'    => 'testWriteDateConverter',
        'propertyAccessStrategy'=> true,
        'fieldMappingExtensionClass' => 'testFieldMappingExtensionClass',
        'classMappingExtensionClass' => 'testClassMappingExtensionClass'
    ]
];

ObjectListeners config - DEPRECATED - SEE Listeners Config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/** 
 * @DM\ObjectListeners(
 *      classList={"SomeListenerAClass", "SomeListenerBClass"}
 * )
 */
class SomeClass
{
}

Yaml format:

objectListeners: [SomeListenerAClass, SomeListenerBClass]

Php format:

[
    'objectListeners'   => ['SomeListenerAClass', 'SomeListenerBClass']
];

PostExtract config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PostExtract(method="postExtractMethodName")
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    postExtract: postExtractMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'postExtract'    => 'postExtractMethodName'
    ]
];

PostHydrate config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PostHydrate(method="postHydrateMethodName")
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    postHydrate: postHydrateMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'postHydrate'    => 'postHydrateMethodName'
    ]
];

PreExtract config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PreExtract(
 *      method="preExtractMethodName"
 * )
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    preExtract: preExtractMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'preExtract'    => 'preExtractMethodName'
    ]
];

PreHydrate config - DEPRECATED - SEE Listeners Config

Deprecated. Use Listeners config instead.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(classMappingExtensionClass="mappingExtensionClass")
 *
 * @DM\PreHydrate(method="preHydrateMethodName")
 */
class SomeClass
{
}

Yaml format:

object:
    classMappingExtensionClass: mappingExtensionClass

interceptors:
    preHydrate: preHydrateMethodName

Php format:

[
    'object' => ['classMappingExtensionClass' => 'mappingExtensionClass'],
    'interceptors'  => [
        'preHydrate'    => 'preHydrateMethodName'
    ]
];

Provider config - DEPRECATED - SEE DataSource Config

Deprecated. Use DataSource config instead. Configuration is the same as DataSource.

ProvidersStore config - DEPRECATED - SEE DataSourcesStore Config

Deprecated. Use DataSourcesStore config instead. Configuration is the same as DataSourceStore.

RefImplicitSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\RefImplicitSource(id="RefImplicitSourceId")
 */
class SomeClass
{

}

Yaml format:

object:
    RefImplicitSource: RefImplicitSourceId
fields:
    mockField:
        name: mockFieldName

Php format:

[
    'object' => [
        'RefImplicitSource' => 'RefImplicitSourceId'
    ],
    'fields' => [
        'mockField' => [
            'name'      => 'mockFieldName',
        ]
    ]
];

RefSource config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\DataSourcesStore({
 *      @DM\DataSource(
 *          id="someDataSource",
 *          class="Kassko\Sample\PersonDataSource",
 *          method="getData"
 *      )
 * })
 */
class SomeClass
{
    /**
     * @DM\RefSource(id="someDataSource")
     */
    private $fieldOne;
}

Yaml format:

object:
    dataSourcesStore:
        - id: personSource
        class: "Kassko\\\Sample\\\PersonDataSource"
        method: getData
fields:
    fieldOne:
        name: fieldOne
        refSource: someDataSource

Php format:

[
    'object'    => [
        'dataSourcesStore'    => [
            [
                'id'=> 'personSource',
                'class'=> 'Kassko\Sample\PersonDataSource',
                'method'=> 'getData',
            ]
        ]
    ],
    'fields'    => [
        'fieldOne' => [
            'name'  => 'fieldOne',
            'refSource' => 'someDataSource',
        ]
    ],
];
### Setter config

Annotation format:
```php
use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Setter(
     *      prefix="setterPrefix",
     *      name="setterName"
     * )
     */
    protected $firstField;
}

Yaml format:

fields:
    firstField:
        name: firstField
        setter: 
            name: setterName

Php format:

[
    'fields'    => [
        'firstField'    => [
            'name'      => 'firstField',
            'setter'    => ['name' => 'setterName'],
        ]
    ]
];

ToExclude config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * Optional because it's the default settings for fieldExclusionPolicy.
 * @DM\Object(fieldExclusionPolicy="include_all")
 */
class SomeClass
{
    /**
     * @DM\ToExclude
     */
    protected $excludedField;

    /**
     * @DM\Field
     */
    protected $anotherField;
}

Yaml format:

# Optional because it's the default settings for fieldExclusionPolicy.
# object:
#    fieldExclusionPolicy: include_all
excludedFields: [excludedField]
fields:
  excludedField:
    name: excludedField
  anotherField:
    name: anotherField

Php format:

[
    /*
    //Optional because it's the default settings for fieldExclusionPolicy.
    'object' => [
        'fieldExclusionPolicy' => 'include_all'
    ],
    */
    'excludedFields' => [
        'excludedField'
    ],
    'fields'  => [
        'excludedField' => [
            'name'     => 'excludedField'
        ],
        'anotherField'         => [
            'name'     => 'anotherField'
        ]
    ]
];

ToInclude config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

/**
 * @DM\Object(fieldExclusionPolicy="exclude_all")
 */
class SomeClass
{
    /**
     * @DM\Include
     */
    protected $includedField;

    /**
     * @DM\Field
     */
    protected $field;
}

Yaml format:

fieldExclusionPolicy: exclude_all
fieldsToInclude: [includedField]
fields:
    includedField:
        name: includedField
    anotherField:
        name: anotherField

Php format:

[
    'fieldExclusionPolicy' => 'exclude_all'
    'fieldsToInclude' => [
        'includedField'
    ],
    'fields'  => [
        'includedField' => [
            'name'     => 'includedField'
        ],
        'anotherField' => [
            'name'     => 'anotherField'
        ],
    ]
];

Transient config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Transient
     */
    protected $firstField;

    /**
     * @var string
     */
    protected $secondField;
}

Yaml format:

transient: [firstField]
fields: [firstField]

Php format:

[
    'transient' => ['firstField'],
    'fields'    => [
        'firstField'    => [
            'name'      => 'firstFieldName'
        ]
    ]
];

ValueObject config - DEPRECATED - SEE Config Config

Deprecated. Use Config config instead.

Annotation format: Annotation @Kassko\DataMapper\Annotation\ValueObject becomes @Kassko\DataMapper\Annotation\Config.

Yaml and Php format: Now, there's a field key 'fields.some_field.config'. Uses it instead of the global key 'valueObjects'.

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class ValueObject
{
    /**
     * @DM\Config(
     *      class="\ValueObjectClass",
     *      mappingResourceName="valueObjectResourceName",
     *      mappingResourcePath="valueObjectResourcePath",
     *      mappingResourceType="valueObjectResourceType"
     * )
     */
    protected $firstField;
}

Yaml format:

fields:
  firstField:
    name: firstField
valueObjects:
    firstField:
        class: "\\\ValueObjectClass"
        mappingResourceName: valueObjectResourceName
        mappingResourcePath: valueObjectResourcePath
        mappingResourceType: valueObjectResourceType

Php format:

[
    'fields' => [
        'firstField' => [
            'name'         => 'firstField',
        ]
    ],
    'valueObjects' => [
        'firstField'    => [
            'class' => '\ValueObjectClass',
            'mappingResourceName' => 'valueObjectResourceName',
            'mappingResourcePath' => 'valueObjectResourcePath',
            'mappingResourceType' => 'valueObjectResourceType'
        ]
    ]
];

Version config

Annotation format:

use Kassko\DataMapper\Annotation as DM;

class SomeClass
{
    /**
     * @DM\Version
     */
    protected $firstField;
}

Yaml format:

version: firstField
fields: [firstField]

Php format:

[
    'version'   => 'firstField',
    'fields'    => ['firstField']
];

========================================


Download Details:

Author: kassko
Source Code: https://github.com/kassko/data-mapper

License: MIT license

#php

Kassko: A Php Mapper Very Tunable, Cross-orm and Cross-DBMS
Gordon  Matlala

Gordon Matlala

1667279100

Jekyll-spaceship: Jekyll Plugin for Astronauts

 🚀 Jekyll Spaceship 🚀 

Jekyll plugin for Astronauts.

Spaceship is a minimalistic, powerful and extremely customizable Jekyll plugin. It combines everything you may need for convenient work, without unnecessary complications, like a real spaceship.

Jekyll Spaceship Demo

💡 Tip: I hope you enjoy using this plugin. If you like this project, a little star for it is your way make a clear statement: My work is valued. I would appreciate your support! Thank you!

Requirements

  • Ruby >= 2.3.0

Installation

Add jekyll-spaceship plugin in your site's Gemfile, and run bundle install.

# If you have any plugins, put them here!
group :jekyll_plugins do
  gem 'jekyll-spaceship'
end

Or you better like to write in one line:

gem 'jekyll-spaceship', group: :jekyll_plugins

Add jekyll-spaceship to the plugins: section in your site's _config.yml.

plugins:
  - jekyll-spaceship

💡 Tip: Note that GitHub Pages runs in safe mode and only allows a set of whitelisted plugins. To use the gem in GitHub Pages, you need to build locally or use CI (e.g. travis, github workflow) and deploy to your gh-pages branch.

Additions for Unlimited GitHub Pages

  • Here is a GitHub Action named jekyll-deploy-action for Jekyll site deployment conveniently. 👍
  • Here is a Jekyll site using Travis to build and deploy to GitHub Pages for your references.

Configuration

This plugin runs with the following configuration options by default. Alternative settings for these options can be explicitly specified in the configuration file _config.yml.

# Where things are
jekyll-spaceship:
  # default enabled processors
  processors:
    - table-processor
    - mathjax-processor
    - plantuml-processor
    - mermaid-processor
    - polyfill-processor
    - media-processor
    - emoji-processor
    - element-processor
  mathjax-processor:
    src:
      - https://polyfill.io/v3/polyfill.min.js?features=es6
      - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
    config:
      tex:
        inlineMath:
          - ['$','$']
          - ['\(','\)']
        displayMath:
          - ['$$','$$']
          - ['\[','\]']
      svg:
        fontCache: 'global'
    optimize: # optimization on building stage to check and add mathjax scripts
      enabled: true # value `false` for adding to all pages
      include: []   # include patterns for math expressions checking (regexp)
      exclude: []   # exclude patterns for math expressions checking (regexp)
  plantuml-processor:
    mode: default  # mode value 'pre-fetch' for fetching image at building stage
    css:
      class: plantuml
    syntax:
      code: 'plantuml!'
      custom: ['@startuml', '@enduml']
    src: http://www.plantuml.com/plantuml/svg/
  mermaid-processor:
    mode: default  # mode value 'pre-fetch' for fetching image at building stage
    css:
      class: mermaid
    syntax:
      code: 'mermaid!'
      custom: ['@startmermaid', '@endmermaid']
    config:
      theme: default
    src: https://mermaid.ink/svg/
  media-processor:
    default:
      id: 'media-{id}'
      class: 'media'
      width: '100%'
      height: 350
      frameborder: 0
      style: 'max-width: 600px; outline: none;'
      allow: 'encrypted-media; picture-in-picture'
  emoji-processor:
    css:
      class: emoji
    src: https://github.githubassets.com/images/icons/emoji/

Usage

1. Table Usage

For now, these extended features are provided:

  • Cells spanning multiple columns
  • Cells spanning multiple rows
  • Cells text align separately
  • Table header not required
  • Grouped table header rows or data rows

Noted that GitHub filters out style property, so the example displays with the obsolete align property. But in actual this plugin outputs style property with text-align CSS attribute.

Rowspan and Colspan

^^ in a cell indicates it should be merged with the cell above.
This feature is contributed by pmccloghrylaing.

|              Stage | Direct Products | ATP Yields |
| -----------------: | --------------: | ---------: |
|         Glycolysis |          2 ATP              ||
| ^^                 |          2 NADH |   3--5 ATP |
| Pyruvaye oxidation |          2 NADH |      5 ATP |
|  Citric acid cycle |          2 ATP              ||
| ^^                 |          6 NADH |     15 ATP |
| ^^                 |          2 FADH |      3 ATP |
|                               30--32 ATP        |||

Code above would be parsed as:

StageDirect ProductsATP Yields
Glycolysis2 ATP
2 NADH3–5 ATP
Pyruvaye oxidation2 NADH5 ATP
Citric acid cycle2 ATP
6 NADH15 ATP
2 FADH23 ATP
30–32 ATP

Multiline

A backslash at end to join cell contents with the following lines.
This feature is contributed by Lucas-C.

| :    Easy Multiline   : |||
| :----- | :----- | :------ |
| Apple  | Banana | Orange  \
| Apple  | Banana | Orange  \
| Apple  | Banana | Orange
| Apple  | Banana | Orange  \
| Apple  | Banana | Orange  |
| Apple  | Banana | Orange  |

Code above would be parsed as:

Easy Multiline
Apple
Apple
Apple
Banana
Banana
Banana
Orange
Orange
Orange
Apple
Apple
Banana
Banana
Orange
Orange
AppleBananaOrange

Headerless

Table header can be eliminated.

|--|--|--|--|--|--|--|--|
|♜| |♝|♛|♚|♝|♞|♜|
| |♟|♟|♟| |♟|♟|♟|
|♟| |♞| | | | | |
| |♗| | |♟| | | |
| | | | |♙| | | |
| | | | | |♘| | |
|♙|♙|♙|♙| |♙|♙|♙|
|♖|♘|♗|♕|♔| | |♖|

Code above would be parsed as:

 
  
      
      
       
       
 
  

Cell Alignment

Markdown table syntax use colons ":" for forcing column alignment.
Therefore, here we also use it for forcing cell alignment.

Table cell can be set alignment separately.

| :        Fruits \|\| Food       : |||
| :--------- | :-------- | :--------  |
| Apple      | : Apple : | Apple      \
| Banana     |   Banana  | Banana     \
| Orange     |   Orange  | Orange     |
| :   Rowspan is 4    : || How's it?  |
|^^    A. Peach         ||   1. Fine :|
|^^    B. Orange        ||^^ 2. Bad   |
|^^    C. Banana        ||  It's OK!  |

Code above would be parsed as:

Fruits || Food
Apple
Banana
Orange
Apple
Banana
Orange
Apple
Banana
Orange
Rowspan is 4 
A. Peach 
B. Orange 
C. Banana
 
How's it?
1. Fine
2. Bad
It' OK!

Cell Markdown

Sometimes we may need some abundant content (e.g., mathjax, image, video) in Markdown table
Therefore, here we also make markown syntax possible inside a cell.

| :                   MathJax \|\| Image                 : |||
| :------------ | :-------- | :----------------------------- |
| Apple         | : Apple : | Apple                          \
| Banana        | Banana    | Banana                         \
| Orange        | Orange    | Orange                         |
| :     Rowspan is 4     : || :        How's it?           : |
| ^^     A. Peach          ||    1. ![example][cell-image]   |
| ^^     B. Orange         || ^^ 2. $I = \int \rho R^{2} dV$ |
| ^^     C. Banana         || **It's OK!**                   |

[cell-image]: https://jekyllrb.com/img/octojekyll.png "An exemplary image"

Code above would be parsed as:

MathJax || Image
Apple
Banana
Orange
Apple
Banana
Orange
Apple
Banana
Orange
Rowspan is 4 
A. Peach 
B. Orange 
C. Banana
 
How's it?
It' OK!

 

Cell Inline Attributes

This feature is very useful for custom cell such as using inline style. (e.g., background, color, font)
The idea and syntax comes from the Maruku package.

 

Following are some examples of attributes definitions (ALDs) and afterwards comes the syntax explanation:

{:ref-name: #id .cls1 .cls2}
{:second: ref-name #id-of-other title="hallo you"}
{:other: ref-name second}

An ALD line has the following structure:

  • a left brace, optionally preceded by up to three spaces,
  • followed by a colon, the id and another colon,
  • followed by attribute definitions (allowed characters are backslash-escaped closing braces or any character except a not escaped closing brace),
  • followed by a closing brace and optional spaces until the end of the line.

If there is more than one ALD with the same reference name, the attribute definitions of all the ALDs are processed like they are defined in one ALD.

An inline attribute list (IAL) is used to attach attributes to another element.
Here are some examples for span IALs:

{: #id .cls1 .cls2} <!-- #id <=> id="id", .cls1 .cls2 <=> class="cls1 cls2" -->
{: ref-name title="hallo you"}
{: ref-name class='.cls3' .cls4}

Here is an example for custom table cell with IAL:

{:color-style: style="background: black;"}
{:color-style: style="color: white;"}
{:text-style: style="font-weight: 800; text-decoration: underline;"}

|:             Here's an Inline Attribute Lists example                :||||
| ------- | ------------------ | -------------------- | ------------------ |
|:       :|:  <div style="color: red;"> &lt; Normal HTML Block > </div> :|||
| ^^      |   Red    {: .cls style="background: orange" }                |||
| ^^ IALs |   Green  {: #id style="background: green; color: white" }    |||
| ^^      |   Blue   {: style="background: blue; color: white" }         |||
| ^^      |   Black  {: color-style text-style }                         |||

Code above would be parsed as:

IALs

Additionally, here you can learn more details about IALs.

2. MathJax Usage

MathJax is an open-source JavaScript display engine for LaTeX, MathML, and AsciiMath notation that works in all modern browsers.

Some of the main features of MathJax include:

  • High-quality display of LaTeX, MathML, and AsciiMath notation in HTML pages
  • Supported in most browsers with no plug-ins, extra fonts, or special setup for the reader
  • Easy for authors, flexible for publishers, extensible for developers
  • Supports math accessibility, cut-and-paste interoperability, and other advanced functionality
  • Powerful API for integration with other web applications

2.1 Performance optimization

At building stage, the MathJax engine script will be added by automatically checking whether there is a math expression in the page, this feature can help you improve the page performance on loading speed.

2.2 How to use?

Put your math expression within $...$

$ a * b = c ^ b $
$ 2^{\frac{n-1}{3}} $
$ \int\_a^b f(x)\,dx. $

Code above would be parsed as:

MathJax Expression

3. PlantUML Usage

PlantUML is a component that allows to quickly write:

  • sequence diagram,
  • use case diagram,
  • class diagram,
  • activity diagram,
  • component diagram,
  • state diagram,
  • object diagram

There are two ways to create a diagram in your Jekyll blog page:

```plantuml!
Bob -> Alice : hello world
```

or

@startuml
Bob -> Alice : hello
@enduml

Code above would be parsed as:

PlantUML Diagram

4. Mermaid Usage

Mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.

It allows to quickly write:

  • flow chart,
  • pie chart,
  • sequence diagram,
  • class diagram,
  • state diagram,
  • entity relationship diagram,
  • user journey,
  • gantt

There are two ways to create a diagram in your Jekyll blog page:

```mermaid!
pie title Pets adopted by volunteers
  "Dogs" : 386
  "Cats" : 85
  "Rats" : 35
```

or

@startmermaid
pie title Pets adopted by volunteers
  "Dogs" : 386
  "Cats" : 85
  "Rats" : 35
@endmermaid

Code above would be parsed as:

Mermaid Diagram

5. Media Usage

How often did you find yourself googling "How to embed a video/audio in markdown?"

While its not possible to embed a video/audio in markdown, the best and easiest way is to extract a frame from the video/audio. To add videos/audios to your markdown files easier I developped this tool for you, and it will parse the video/audio link inside the image block automatically.

For now, these media links parsing are provided:

  • Youtube
  • Vimeo
  • DailyMotion
  • Spotify
  • SoundCloud
  • General Video ( mp4 | avi | ogg | ogv | webm | 3gp | flv | mov ... )
  • General Audio ( mp3 | wav | ogg | mid | midi | aac | wma ... )

There are two ways to embed a video/audio in your Jekyll blog page:

Inline-style:

![]({media-link})

Reference-style:

![][{reference}]

[{reference}]: {media-link}

For configuring media attributes (e.g, width, height), just adding query string to the link as below:

![](https://www.youtube.com/watch?v=Ptk_1Dc2iPY?width=800&height=500)

![](https://www.dailymotion.com/video/x7tfyq3?width=100%&height=400&autoplay=1)

Youtube Usage

![](https://www.youtube.com/watch?v=Ptk_1Dc2iPY)

![](//www.youtube.com/watch?v=Ptk_1Dc2iPY?width=800&height=500)

Vimeo Usage

![](https://vimeo.com/263856289)

![](https://vimeo.com/263856289?width=500&height=320)

DailyMotion Usage

![](https://www.dailymotion.com/video/x7tfyq3)

![](https://dai.ly/x7tgcev?width=100%&height=400)

Spotify Usage

![](http://open.spotify.com/track/4Dg5moVCTqxAb7Wr8Dq2T5)

Spotify Podcast Usage

![](https://open.spotify.com/episode/31AxcwYdjsFtStds5JVWbT)

SoundCloud Usage

![](https://soundcloud.com/aviciiofficial/preview-avicii-vs-lenny)

General Video Usage

![](//www.html5rocks.com/en/tutorials/video/basics/devstories.webm)

![](//techslides.com/demos/sample-videos/small.ogv?allow=autoplay)

![](//techslides.com/demos/sample-videos/small.mp4?width=400)

General Audio Usage

![](//www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3)

![](//www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3?autoplay=1&loop=1)

6. Hybrid HTML with Markdown

As markdown is not only a lightweight markup language with plain-text-formatting syntax, but also an easy-to-read and easy-to-write plain text format, so writing a hybrid HTML with markdown is an awesome choice.

It's easy to write markdown inside HTML:

<script type="text/markdown">
# Hybrid HTML with Markdown is a not bad choice ^\_^

## Table Usage

| :        Fruits \|\| Food       : |||
| :--------- | :-------- | :--------  |
| Apple      | : Apple : | Apple      \
| Banana     |   Banana  | Banana     \
| Orange     |   Orange  | Orange     |
| :   Rowspan is 4    : || How's it?  |
|^^    A. Peach         ||   1. Fine :|
|^^    B. Orange        ||^^ 2. Bad   |
|^^    C. Banana        ||  It's OK!  |

## PlantUML Usage

@startuml
Bob -> Alice : hello
@enduml

## Video Usage

![](https://www.youtube.com/watch?v=Ptk_1Dc2iPY)
</script>

7. Markdown Polyfill

It allows us to polyfill features for extending markdown syntax.

For now, these polyfill features are provided:

  • Escape ordered list

7.1 Escape Ordered List

A backslash at begin to escape the ordered list.

Normal:

1. List item Apple.
3. List item Banana.
10. List item Cafe.

Escaped:

\1. List item Apple.
\3. List item Banana.
\10. List item Cafe.

Code above would be parsed as:

Normal:

1. List item Apple.
2. List item Banana.
3. List item Cafe.

Escaped:

1. List item Apple.
3. List item Banana.
10. List item Cafe.

8. Emoji Usage

GitHub-flavored emoji images and names would allow emojifying content such as: it's raining :cat:s and :dog:s!

Noted that emoji images are served from the GitHub.com CDN, with a base URL of https://github.githubassets.com, which results in emoji image URLs like https://github.githubassets.com/images/icons/emoji/unicode/1f604.png.

In any page or post, use emoji as you would normally, e.g.

I give this plugin two :+1:!

Code above would be parsed as:

I give this plugin two :+1:!

8.1 Emoji Customizing

If you'd like to serve emoji images locally, or use a custom emoji source, you can specify so in your _config.yml file:

jekyll-spaceship:
  emoji-processor:
    src: "/assets/images/emoji"

See the Gemoji documentation for generating image files.

9. Modifying Element Usage

It allows us to modify elements via CSS3 selectors. Through it you can easily modify the attributes of an element tag, replace the children nodes and so on, it's very flexible, but here is example usage for modifying a document:

# Here is a comprehensive example
jekyll-spaceship:
  element-processor:
    css:
      - a: '<h1>Test</h1>'                     # Replace all `a` tags (String Style)
      - ['a.link1', 'a.link2']:                # Replace all `a.link1`, `a.link2` tags (Hash Style)
          name: img                            # Replace element tag name
          props:                               # Replace element properties
            title: Good image                  # Add a title attribute
            src: ['(^.*$)', '\0?a=123']        # Add query string to src attribute by regex pattern
            style:                             # Add style attribute (Hash Style)
              color: red
              font-size: '1.2em'
          children:                            # Add children to the element
            -                                  # First empty for adding after the last child node
            - "<span>Google</span>"            # First child node (String Style)
            -                                  # Middle empty for wrapping the children nodes
            - name: span                       # Second child node (Hash Style)
              props:
                prop1: "1"                     # Custom property1
                prop2: "2"                     # Custom property2
                prop3: "3"                     # Custom property3
              children:                        # Add nested chidren nodes
                - "<span>Jekyll</span>"        # First child node (String Style)
                - name: span                   # Second child node (Hash Style)
                  props:                       # Add attributes to child node (Hash Style)
                    prop1: "a"
                    prop2: "b"
                    prop3: "c"
                  children: "<b>Yap!</b>"      # Add children nodes (String Style)
            -                                  # Last empty for adding before the first child node
      - a.link: '<a href="//t.com">Link</a>'   # Replace all `a.link` tags (String Style)
      - 'h1#title':                            # Replace `h1#title` tags (Hash Style)
          children: I'm a title!               # Replace inner html to new text

Example 1

Automatically adds a target="_blank" rel="noopener noreferrer" attribute to all external links in Jekyll's content.

jekyll-spaceship:
  element-processor:
    css:
      - a:                                     # Replace all `a` tags
          props:
            class: ['(^.*$)', '\0 ext-link']   # Add `ext-link` to class by regex pattern
            target: _blank                     # Replace `target` value to `_blank`
            rel: noopener noreferrer           # Replace `rel` value to `noopener noreferrer`

Example 2

Automatically adds loading="lazy" to img and iframe tags to natively load lazily. Browser support is growing. If a browser does not support the loading attribute, it will load the resource just like it would normally.

jekyll-spaceship:
  element-processor:
    css:
      - a:                                     # Replace all `a` tags
          props:                               #
            loading: lazy                      # Replace `loading` value to `lazy`

In case you want to prevent loading some images/iframes lazily, add loading="eager" to their tags. This might be useful to prevent flickering of images during navigation (e.g. the site's logo).

See the following examples to prevent lazy loading.

jekyll-spaceship:
  element-processor:
    css:
      - a:                                     # Replace all `a` tags
          props:                               #
            loading: eager                     # Replace `loading` value to `eager`

There are three options when using this method to lazy load images. Here are the supported values for the loading attribute:

  • auto: Default lazy-loading behavior of the browser, which is the same as not including the attribute.
  • lazy: Defer loading of the resource until it reaches a calculated distance from the viewport.
  • eager: Load the resource immediately, regardless of where it’s located on the page.

Credits

  • Jekyll - A blog-aware static site generator in Ruby.
  • MultiMarkdown - Lightweight markup processor to produce HTML, LaTeX, and more.
  • markdown-it-multimd-table - Multimarkdown table syntax plugin for markdown-it markdown parser.
  • jmoji - GitHub-flavored emoji plugin for Jekyll.
  • jekyll-target-blank - Automatically opens external links in a new browser for Jekyll Pages, Posts and Docs.
  • jekyll-loading-lazy - Automatically adds loading="lazy" to img and iframe tags to natively load lazily.
  • mermaid - Generation of diagram and flowchart from text in a similar manner as markdown.

Contributing

Issues and Pull Requests are greatly appreciated. If you've never contributed to an open source project before I'm more than happy to walk you through how to create a pull request.

You can start by opening an issue describing the problem that you're looking to resolve and we'll go from there.

Download Details:

Author: jeffreytse
Source Code: https://github.com/jeffreytse/jekyll-spaceship 
License: MIT license

#jekyll #music #emoji #html 

 Jekyll-spaceship: Jekyll Plugin for Astronauts

Drift Is A Reactive Persistence Library for Flutter and Dart

Drift

Proudly Sponsored by Stream 💙

Drift is a reactive persistence library for Flutter and Dart, built on top of sqlite. Drift is

  • Flexible: Drift lets you write queries in both SQL and Dart, providing fluent apis for both languages. You can filter and order results or use joins to run queries on multiple tables. You can even use complex sql features like WITH and WINDOW clauses.
  • 🔥 Feature rich: Drift has builtin support for transactions, schema migrations, complex filters and expressions, batched updates and joins. We even have a builtin IDE for SQL!
  • 📦 Modular: Thanks to builtin support for daos and imports in sql files, drift helps you keep your database code simple.
  • 🛡️ Safe: Drift generates typesafe code based on your tables and queries. If you make a mistake in your queries, drift will find it at compile time and provide helpful and descriptive lints.
  • ⚡ Fast: Even though drift lets you write powerful queries, it can keep up with the performance of key-value stores. Drift is the only major persistence library with builtin threading support, allowing you to run database code across isolates with zero additional effort.
  • Reactive: Turn any sql query into an auto-updating stream! This includes complex queries across many tables
  • ⚙️ Cross-Platform support: Drift works on Android, iOS, macOS, Windows, Linux and the web. This template is a Flutter todo app that works on all platforms
  • 🗡️ Battle tested and production ready: Drift is stable and well tested with a wide range of unit and integration tests. It powers production Flutter apps.

With drift, persistence on Flutter is fun!

To start using drift, read our detailed docs.

If you have any questions, feedback or ideas, feel free to create an issue. If you enjoy this project, I'd appreciate your 🌟 on GitHub.

For the web

For information to use this library on the web (including Flutter web), follow the instructions here. Keep in mind that web support is still experimental.

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add drift

With Flutter:

 $ flutter pub add drift

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  drift: ^2.2.0

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:drift/drift.dart'; 

example/main.dart

// For more information on using drift, please see https://drift.simonbinder.eu/docs/getting-started/
// A full cross-platform example is available here: https://github.com/simolus3/drift/tree/develop/examples/app

import 'package:drift/drift.dart';
import 'package:drift/native.dart';

part 'main.g.dart';

@DataClassName('TodoCategory')
class TodoCategories extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
}

class TodoItems extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text()();
  TextColumn get content => text().nullable()();
  IntColumn get categoryId => integer().references(TodoCategories, #id)();

  TextColumn get generatedText => text().nullable().generatedAs(
      title + const Constant(' (') + content + const Constant(')'))();
}

abstract class TodoCategoryItemCount extends View {
  TodoItems get todoItems;
  TodoCategories get todoCategories;

  Expression<int> get itemCount => todoItems.id.count();

  @override
  Query as() => select([
        todoCategories.name,
        itemCount,
      ]).from(todoCategories).join([
        innerJoin(todoItems, todoItems.categoryId.equalsExp(todoCategories.id))
      ]);
}

@DriftView(name: 'customViewName')
abstract class TodoItemWithCategoryNameView extends View {
  TodoItems get todoItems;
  TodoCategories get todoCategories;

  Expression<String> get title =>
      todoItems.title +
      const Constant('(') +
      todoCategories.name +
      const Constant(')');

  @override
  Query as() => select([todoItems.id, title]).from(todoItems).join([
        innerJoin(
            todoCategories, todoCategories.id.equalsExp(todoItems.categoryId))
      ]);
}

@DriftDatabase(tables: [
  TodoItems,
  TodoCategories,
], views: [
  TodoCategoryItemCount,
  TodoItemWithCategoryNameView,
])
class Database extends _$Database {
  Database(QueryExecutor e) : super(e);

  @override
  int get schemaVersion => 2;

  @override
  MigrationStrategy get migration {
    return MigrationStrategy(
      onCreate: (m) async {
        await m.createAll();

        // Add a bunch of default items in a batch
        await batch((b) {
          b.insertAll(todoItems, [
            TodoItemsCompanion.insert(title: 'A first entry', categoryId: 0),
            TodoItemsCompanion.insert(
              title: 'Todo: Checkout drift',
              content: const Value('Drift is a persistence library for Dart '
                  'and Flutter applications.'),
              categoryId: 0,
            ),
          ]);
        });
      },
    );
  }

  // The TodoItem class has been generated by drift, based on the TodoItems
  // table description.
  //
  // In drift, queries can be watched by using .watch() in the end.
  // For more information on queries, see https://drift.simonbinder.eu/docs/getting-started/writing_queries/
  Stream<List<TodoItem>> get allItems => select(todoItems).watch();
}

Future<void> main() async {
  // Create an in-memory instance of the database with todo items.
  final db = Database(NativeDatabase.memory());

  db.allItems.listen((event) {
    print('Todo-item in database: $event');
  });

  // Add category
  final categoryId = await db
      .into(db.todoCategories)
      .insert(TodoCategoriesCompanion.insert(name: 'Category'));

  // Add another entry
  await db.into(db.todoItems).insert(TodoItemsCompanion.insert(
      title: 'Another entry added later', categoryId: categoryId));

  (await db.select(db.customViewName).get()).forEach(print);
  (await db.select(db.todoCategoryItemCount).get()).forEach(print);

  // Delete all todo items
  await db.delete(db.todoItems).go();
} 

Download Details:

Author: simolus3

Source Code: https://github.com/simolus3/drift

#flutter #dart 

Drift Is A Reactive Persistence Library for Flutter and Dart
Dmitriy Zub

Dmitriy Zub

1665127784

13 ways to scrape any public data from any website

Originally published at SerpApi: https://serpapi.com/blog/13-ways-to-scrape-any-data-from-any-website/

Intro

This ongoing blog post is about understanding ways of extracting data from any website, either if the website is static/fully JavaScript driven or if reverse engineering needs to be applied.

After this blog post, you can understand and apply this knowledge to create structured data from messy data from any website.

At SerpApi we're extracting data from all sorts of websites and each one of them requires a specific approach that will able us to parse data fast and efficiently and most importantly without browser automation. We want to share a little of our knowledge gained while building our APIs.

📌 Some of the techniques will not be mentioned intentionally in order not to disclose some of the methods we use.

Shown tips should be plenty enough to get you going or understand where to potentially look to solve the problem.

🐍 This blog post uses Python as language to show code examples.

Browser Dev Tools

Dev tools is possibly one of the very first thing that needs to be checked before writing actual code.

I, wrote quite a lot of code before looking at dev tools with the realization that instead of parsing data, I can make a simple HTTP request, either GET or POST to the website API/server and get a JSON response with all the necessary data that being already structured.

Under dev tools, you can find a Elements, Network, and Source/Application tabs.

The follow-up sections will go from easiest to hardest methods with tools that can be used to make the job done.

image

CSS Selectors or/and XPath

CSS Selectors

CSS selectors are patterns used to select match the element(s) you want to style extract from HTML page.

While for building websites CSS selectors help to style the website, in webscraping those selectors used to match certain HTML nodes (elements) from which we want data to be extracted.

We've covered types of selectors with actual code examples in the web scraping with CSS selectors blog post and which selector works best for a particular task, and how to test them in the browser.

Type of Selectors

There're a few often-used type of selectors:

SELECTOR TYPESYNTAXPURPOSE
Type Selectorselement_nameMatches HTML node based on element
Class Selectors.class_nameMatches HTML node based on class
ID Selectors#id_valueMatches HTML node based on ID
Attribute Selectors[attribute=attribute_value]/[attribute]Extracts attribute data from HTML node
Selector Listelement, element, element, .../selector, selector, selector, ...Matches all nodes based on given selector(s). Great for handling different HTML layouts
Descendant combinatorselector1 selector2Matches data from a second selector if they have an ancestor

XPath

XPath is a query language that is used to navigate DOM and could be is more flexible than CSS selectors.

It's mostly helpful when HTML element doesn't have a selector name, or has a very weird location in a messy HTML. XPath uses expressions to create a query:

//div[@class='content']//text()[not(ancestor::div/@class[contains(., 'code')])][normalize-space()]

SelectorGadget Chrome Extension

SelectorGadget extension allows to quickly grab CSS selector(s) by clicking on the desired element in your browser. It returns a CSS selector(s).

Example of selecting certain HTML elements and returned CSS selector(s):

GIF

We've shown how SelectorGadget works in a separate web scraping with CSS selectors blog post.

Scraping from Meta Elements

Selectors are not the only place where valuable data could be extracted. Some of the data can be directly in the HTML <meta> elements. <meta> element loads no matter if the page is static or dynamic.

For example, on Medium you can extract basic profile info from <meta> elements:

import requestsfrom bs4 import BeautifulSouphtml = requests.get('https://medium.com/@dimitryzub').textsoup = BeautifulSoup(html, 'html.parser')description = soup.select('meta')[13]['content']link = soup.select('meta')[16]['content']profile_picture = soup.select('meta')[21]['content']print(description, link, profile_picture, sep='\n')'''Read writing from Dmitriy Zub ☀️ on Medium. Developer Advocate at SerpApi. I help to make structured data from a pile of mess.https://dimitryzub.medium.comhttps://miro.medium.com/max/2400/1*he-C8WKaIjHIe58F4aMyhw.jpeg'''
CODEEXPLANATION
select()to select all 'meta' elemets
[<number>]is to access a certain index number from a list of extracted elements (nodes)
['content']to extract attribute from the HTML element (node)

Scraping From Tables

Scraping tables is an additional separate thing that can be done either with parsel or bs4 web scraping libraries. However, pandas simplifies this task a lot by providing a read_html() method that can parse data from the <table>.

Installation:

$ pip install pandas

A basic example of extracting table data from Wikipedia:

import pandas as pdtable = pd.read_html('https://en.wikipedia.org/wiki/History_of_Python')[0] # [0] = first tabledf = pd.DataFrame(data=table[['Latest micro version', 'Release date']])    # grabs 2 columns# df.set_index('Latest micro version', inplace=True)                       # drops default pandas DataFrame indexes, but can't be used in a for loopprint(df)for data in df['Latest micro version']:    print(data)

Outputs:

                                 Latest micro version                                       Release date
0                                            0.9.9[2]                                      1991-02-20[2]
1                                            1.0.4[2]                                      1994-01-26[2]
2                                            1.1.1[2]                                      1994-10-11[2]
3                                                 NaN                                      1995-04-13[2]
4                                                 NaN                                      1995-10-13[2]
5                                                 NaN                                      1996-10-25[2]
6                                           1.5.2[42]                                      1998-01-03[2]
7                                           1.6.1[42]                                     2000-09-05[43]
8                                           2.0.1[44]                                     2000-10-16[45]
9                                           2.1.3[44]                                     2001-04-15[46]
10                                          2.2.3[44]                                     2001-12-21[47]
11                                          2.3.7[44]                                     2003-06-29[48]
12                                          2.4.6[44]                                     2004-11-30[49]
13                                          2.5.6[44]                                     2006-09-19[50]
14                                          2.6.9[27]                                     2008-10-01[27]
15                                         2.7.18[32]                                     2010-07-03[32]
16                                          3.0.1[44]                                     2008-12-03[27]
17                                          3.1.5[52]                                     2009-06-27[52]
18                                          3.2.6[54]                                     2011-02-20[54]
19                                          3.3.7[55]                                     2012-09-29[55]
20                                         3.4.10[56]                                     2014-03-16[56]
21                                         3.5.10[58]                                     2015-09-13[58]
22                                         3.6.15[60]                                     2016-12-23[60]
23                                         3.7.13[61]                                     2018-06-27[61]
24                                         3.8.13[62]                                     2019-10-14[62]
25                                         3.9.14[63]                                     2020-10-05[63]
26                                         3.10.7[65]                                     2021-10-04[65]
27                                      3.11.0rc2[66]                                     2022-10-24[66]
28                                                NaN                                        2023-10[64]
29                                            Legend:  Old versionOlder version, still maintainedLate...
30  Italics indicates the latest micro version of ...  Italics indicates the latest micro version of ...

for loop output:

0.9.9[2]
1.0.4[2]
1.1.1[2]
nan
nan
nan
1.5.2[42]
1.6.1[42]
2.0.1[44]
2.1.3[44]
2.2.3[44]
2.3.7[44]
2.4.6[44]
2.5.6[44]
2.6.9[27]
2.7.18[32]
3.0.1[44]
3.1.5[52]
3.2.6[54]
3.3.7[55]
3.4.10[56]
3.5.10[58]
3.6.15[60]
3.7.13[61]
3.8.13[62]
3.9.14[63]
3.10.7[65]
3.11.0rc2[66]
nan
Legend:
Italics indicates the latest micro version of currently supported versions as of 2022-07-11[needs update].

Keep in mind that those are just examples and additional data cleaning needs to be applied to make this data usable 🙂

Have a look at the gotchas that could happen with read_html():

image

Scraping with Regular Expression

Scraping with regular expression in Python is possible by re module.

Why to scrape data with regular expressions in the first place?

  1. if the HTML structure is very, very messy.
  2. if there are not CSS selectors and XPath didn't work also.
  3. if the data you want is directly in the text string.
  4. similar reasons to mention above.

There're a few main methods that could be used:

METHODPURPOSE
re.findall()Returns a list of matches. To find all occurrences.
re.search()Returns a first match. To find the first occurrence.
re.match()To find match and the beginning of the string. search() vs match()
group()Returns one or more subgroups of the match.
import redummy_text = '''Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscureLatin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature,discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance.The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.'''dates = re.findall(r'\d{1}\.\d{2}\.\d{2}', dummy_text) # https://regex101.com/r/VKYiA9/1years_bc = re.findall(r'\d+\s?\bBC\b', dummy_text)     # https://regex101.com/r/ApypoB/1print(dates)print(years_bc)# ['1.10.32', '1.10.33', '1.10.32']# ['45 BC', '45 BC']

Here's a visualization of what is being matched by the regular expressions above:

image

The above regular expressions read like this:

image

Python Web Scraping Tools

In this section, we'll go over most popular Python web scraping tools that can extract data from static and dynamic websites.

Python Parsing Libraries

There're a few Python web scraping packages/libraries to parse data from websites that are not JavaScript driven as such packages are designed to scrape data from static pages.

Parsel

Parsel is a library built to extract data from XML/HTML documents with XPath and CSS selectors support, and could be combined with regular expressions. It uses lxml parser under the hood by default.

The great thing I like about parsel (apart from XPath support) is that it returns None if certain data is not present, so there's no need to create a lot of try/except blocks to the same thing that looks ugly.

Installation:

$ pip install parsel

A few examples of extraction methods:

variable.css(".X5PpBb::text").get()                    # returns a text valuevariable.css(".gs_a").xpath("normalize-space()").get() # https://github.com/scrapy/parsel/issues/192#issuecomment-1042301716variable.css(".gSGphe img::attr(srcset)").get()        # returns a attribute valuevariable.css(".I9Jtec::text").getall()                 # returns a list of strings valuesvariable.xpath('th/text()').get()                      # returns text value using xpath 
CODEEXPLANATION
css()parse data from the passed CSS selector(s). Every CSS query traslates to XPath using csselect package under the hood.
::text or ::attr(<attribute>)extract textual or attribute data from the node.
get()get actual data returned from parsel
getall()get all a list of matches.
.xpath('th/text()')grabs textual data from <th> element

Practical example using parsel:

# https://serpapi.com/blog/scrape-naver-related-search-results-with-python/#full_codeimport requests, jsonfrom parsel import Selector# https://docs.python-requests.org/en/master/user/quickstart/#passing-parameters-in-urlsparams = {    "query": "minecraft",  # search query    "where": "web"         # web results. works with nexearch as well}# https://docs.python-requests.org/en/master/user/quickstart/#custom-headersheaders = {    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"}html = requests.get("https://search.naver.com/search.naver", params=params, headers=headers, timeout=30)selector = Selector(html.text)related_results = []# https://www.programiz.com/python-programming/methods/built-in/enumeratefor index, related_result in enumerate(selector.css(".related_srch .keyword"), start=1):    keyword = related_result.css(".tit::text").get().strip()    link = f'https://search.naver.com/search.naver{related_result.css("a::attr(href)").get()}'    related_results.append({        "position": index,    # 1,2,3..        "title": keyword,        "link": link    })print(json.dumps(related_results, indent=2, ensure_ascii=False))

BeautifulSoup

BeautifulSoup is also a library that build to extract data from HTML/XML documents. It's also could be combined with lxml parser and also can be used in combo with regular expressions.

Unlike parsel, BeautifulSoup don't have support for XPath which would be pretty handy is some situations. Additionally, it lacks getall() method that returns a list of matches which is a shorthand of list comprehension, and it needs a lot of try/except to handle missing data.

However, it can create new HTML nodes, for example, using wrap() method or other methods for similar things.

It's very handy if parts of the data you want to extract are not properly structured e.g. HTML table without <table>...</table> element.

You can create this element and then easily parse table data with pandas read_html() method.

Installation:

$ pip install bs4

A few examples of extraction methods using select() and select_one():

variable.select('.gs_r.gs_or.gs_scl')    # return a list of matchesvariable.select_one('.gs_rt').text       # returns a single text value matchvariable.select_one('.gs_rt a')['href']  # returns a single attribute value match

Practical example using BeautifulSoup:

# https://stackoverflow.com/a/71237540/15164646from bs4 import BeautifulSoupimport requests, lxmlparams = {    "user": "VxOmZDgAAAAJ", # user-id, in this case Masatoshi Nei    "hl": "en",             # language    "gl": "us",             # country to search from    "cstart": 0,            # articles page. 0 is the first page    "pagesize": "100"       # articles per page}headers = {    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"}all_articles = []html = requests.post("https://scholar.google.com/citations", params=params, headers=headers, timeout=30)soup = BeautifulSoup(html.text, "lxml")for index, article in enumerate(soup.select("#gsc_a_b .gsc_a_t"), start=1):    article_title = article.select_one(".gsc_a_at").text    article_link = f'https://scholar.google.com{article.select_one(".gsc_a_at")["href"]}'    article_authors = article.select_one(".gsc_a_at+ .gs_gray").text    article_publication = article.select_one(".gs_gray+ .gs_gray").text    all_articles.append({        "title": article_title,        "link": article_link,        "authors": article_authors,        "publication": article_publication    })

Python Browser Automation

Browser automation is handy when you need to perform some sort of interaction with the webiste, for example, scrolling, clicks and similar things.

Such things could be done without browser automation, this is how we tend to do at SerpApi, however, it could be very complicated but on the flip side the reward is much faster data extraction.

Playwright

playwright is a modern alternative to selenium. It can perform pretty much all interactions as the user would do i.e clicks, scrolls and many more.

Installation:

$ pip install playwright

Practial example of website interaction using playwright and parsel to extract the data.

The following script scrolls through all Google Play app reviews and then extract data:

# https://serpapi.com/blog/scrape-all-google-play-app-reviews-in-python/#full_codeimport time, json, refrom parsel import Selectorfrom playwright.sync_api import sync_playwrightdef run(playwright):    page = playwright.chromium.launch(headless=True).new_page()    page.goto("https://play.google.com/store/apps/details?id=com.collectorz.javamobile.android.books&hl=en_GB&gl=US")    user_comments = []    # if "See all reviews" button present    if page.query_selector('.Jwxk6d .u4ICaf button'):        print("the button is present.")        print("clicking on the button.")        page.query_selector('.Jwxk6d .u4ICaf button').click(force=True)        print("waiting a few sec to load comments.")        time.sleep(4)                last_height = page.evaluate('() => document.querySelector(".fysCi").scrollTop')  # 2200        while True:            print("scrolling..")            page.keyboard.press("End")            time.sleep(3)            new_height = page.evaluate('() => document.querySelector(".fysCi").scrollTop')            if new_height == last_height:                break            else:                last_height = new_height    selector = Selector(text=page.content())    page.close()    print("done scrolling. Exctracting comments...")    for index, comment in enumerate(selector.css(".RHo1pe"), start=1):        user_comments.append({            "position": index,            "user_name": comment.css(".X5PpBb::text").get(),            "app_rating": re.search(r"\d+", comment.css(".iXRFPc::attr(aria-label)").get()).group(),            "comment_date": comment.css(".bp9Aid::text").get(),            "developer_comment": {                "dev_title": comment.css(".I6j64d::text").get(),                "dev_comment": comment.css(".ras4vb div::text").get(),                "dev_comment_date": comment.css(".I9Jtec::text").get()            }        })    print(json.dumps(user_comments, indent=2, ensure_ascii=False))with sync_playwright() as playwright:    run(playwright)

Selenium

selenium is very similar to playwright but a little older with slightly different approaches of doing things.

$ pip install selenium

The following script performs a scroll until hits the very bottom of the Google Play search and then extracts each section with games:

from selenium import webdriverfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom parsel import Selectorimport json, timegoogle_play_games = {    'Top charts': {        'Top free': [],        'Top grossing': [],        'Top paid': []    },}def scroll_page(url):    service = Service(ChromeDriverManager().install())    options = webdriver.ChromeOptions()    options.add_argument("--headless")    options.add_argument("--lang=en")    options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36")    driver = webdriver.Chrome(service=service, options=options)    driver.get(url)    while True:        try:            scroll_button = driver.find_element(By.CSS_SELECTOR, '.snByac')            driver.execute_script("arguments[0].click();", scroll_button)            WebDriverWait(driver, 10000).until(EC.visibility_of_element_located((By.TAG_NAME, 'body')))            break        except:            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")            WebDriverWait(driver, 10000).until(EC.visibility_of_element_located((By.TAG_NAME, 'body')))    selector = Selector(driver.page_source)    driver.quit()    return selector	def scrape_all_sections(selector):	    for section in selector.css('.Ubi8Z section'):        section_title = section.css('.kcen6d span::text').get()        time.sleep(2)        google_play_games[section_title] = []        for game in section.css('.TAQqTe'):            title = game.css('.OnEJge::text').get()            link = 'https://play.google.com' + game.css('::attr(href)').get()            category = game.css('.ubGTjb .sT93pb.w2kbF:not(.K4Wkre)::text').get()            rating = game.css('.CKzsaf .w2kbF::text').get()            rating = float(rating) if rating else None            google_play_games[section_title].append({                'title': title,                'link': link,                'category': category,                'rating': rating,            })        print(json.dumps(google_play_games, indent=2, ensure_ascii=False))	def scrape_google_play_games():    params = {        'device': 'phone',          'hl': 'en_GB',		# language         'gl': 'US',			# country of the search    }    URL = f"https://play.google.com/store/games?device={params['device']}&hl={params['hl']}&gl={params['gl']}"    result = scroll_page(URL)    scrape_all_sections(result)if __name__ == "__main__":    scrape_google_play_games()

Python Web Scraping Frameworks

Scrapy

scrapy is a high-level webscraping framework designed to scrape data at scale and can be used to create a whole ETL pipeline.

However, you have to keep in mind that it's bulky, and could be quite confusing, and while it provides a lot of things for you, most of those things you may not need.

Installation:

$ pip install scrapy

Very simple scrapy script:

import scrapyclass ScholarAuthorTitlesSpider(scrapy.Spider):    name = 'google_scholar_author_titles'    def scrapy_request(self):        params = {            "user": "cp-8uaAAAAAJ",  # user-id            "hl": "en",              # language            "gl": "us",              # country to search from            "cstart": 0,             # articles page. 0 is the first page            "pagesize": "100"        # articles per page        }        headers = {            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"        }        yield scrapy.Request(url="https://scholar.google.com/citations", method="GET", headers=headers, meta=params, callback=self.parse)    def parse(self, response):        for index, article in enumerate(response.css("#gsc_a_b .gsc_a_t"), start=1):            yield {                "position": index,                "title": article.css(".gsc_a_at::text").get(),                "link": f'https://scholar.google.com{article.css(".gsc_a_at::attr(href)").get()}',                "authors": article.css(".gsc_a_at+ .gs_gray::text").get(),                "publication": article.css(".gs_gray+ .gs_gray::text").get()            }

XHR Requests

XHR request allows to talk to the server by making a request and getting data back in response. It's one of the first things that you can check before writing actual code. Those requests can be used to get data dircetly from website's "source" without the need to use parsing libraries/frameworks.

To find a certain XHR request you need:

  1. Open browser dev tools (F12)
  2. Network
  3. Fetch/XHR
  4. Refresh page as data may come on page update.
  5. Click through every request and see if there's any data you want.

If you find the request with the data you want, you can preview the data (example from the Poocoin.app):

image

How extract data from XHR request

When making XHR request, we need to pass URL parameters so that server can understand and "reply" to us.

Here's a simple illustration of that process:

image

To find those headers and URL query parameters, we need to go to a certain URL and look at Headers and Payload tabs and make sure we see what request method is used (GET, POST, etc).

We can do it like so:

  1. Copy URL as CURL (Bash) and use it with online CURL runner or tools such as Insomnia.
  2. Copy request URL under headers tab.

image

From Insomnia (URL copied from the XHR->Headers tab).

📌Keep in mind that some of the passed URL parameters need to be scraped and passed to the URL beforehand (before making request to the server/api). URL can have some sort of a unique token or something different and can't be worked without it.

image

If the response is successful and you want to make an exact request in the script, those parameters could be automatically generated with tools such as Insomnia (or other alternatives) where you can test different types of requests with different parameters and headers.

image

Simple example but the same approach will be on other websites with or without passing URL parameters and headers:

import requests# https://requests.readthedocs.io/en/latest/user/quickstart/#json-response-contenthtml = requests.get('https://api.chucknorris.io/jokes/random').json()print(html['value'])# Once, due to an engine stall of his F-22 Raptor during a Dessert Storm sorte', Chuck Norris had to ejaculate over the Red Sea.

Page Source

This is the next thing that could be checked after Dev Tools -> XHR. It's about looking at page source and trying to find the data there, that is either hidden in the rendered HTML or can't be scraped with selectors because it's being rendered by JavaScript.

One of the ways to find if there's the data you want is in the inline JSON or not:

  1. select and copy any type of data you want to extract (title, name, etc.)
  2. open page source CTRL + U
  3. find the data CTRL + F, if some of the occurrences will be inside <script> elements then congratulation, you found inline JSON or something similar 🙂

Inline JSON

Here's a visual example of how inline JSON could look like (from the TripAdvisor):

trip-advisor-inline-json-example

When we found that there's a data we want locates in the inline JSON, there're a few ways to extract it:

  • using regular expression to extract parts of the inline JSON.
  • (if needed) once again using regular expression(s) to extract portion of the data: links, dates, emails, etc.
  • using regular expression to parse inline JSON and covert it to usable JSON that could be used as a Python dict.

Extract inline JSON data with Regex

The following example is from our Google Arts & Culture - Artists blog post where we select() all <script> elements and then extract other data from matched element(s) using regular expressions:

from bs4 import BeautifulSoupimport requests, json, re, lxmlheaders = {    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36'}params = {    'tab': 'pop'}html = requests.get(f'https://artsandculture.google.com/category/artist', params=params, headers=headers, timeout=30)soup = BeautifulSoup(html.text, 'lxml') # 👇👇👇 data extractionall_script_tags = soup.select('script')# https://regex101.com/r/JQXUBm/1portion_of_script_tags = re.search('("stella\.common\.cobject",".*)\[\[\"A",\["stella\.pr","PrefixedAssets', str(all_script_tags)).group(1)# https://regex101.com/r/c9m9B0/1authors = re.findall(r'"stella\.common\.cobject","(.*?)","\d+', str(portion_of_script_tags))
CODEEXPLANATION
re.findall()Returns a list of matches.
re.search()Returns a first match.
group()Returns one or more subgroups of the match.

Extract inline JSON data with Regex and convert usable JSON

The following example is from one of our Scrape Google Play Store App in Python blog posts.

The flow would be to also parse <script> elements with regex and then turn it into dict using json.loads():

json.loads(    re.findall(        r"<script nonce=\"\w+\" type=\"application/ld\+json\">({.*?)</script>", # regular expression        str(soup.select("script")[11]),                                         # input from where to search data        re.DOTALL,                                                              # match any character: https://docs.python.org/3/library/re.html#re.DOTALL    )[0]                                                                        # access `list` from re.finall())                                                                               # convert to `dict` using json.loads()

After that, we can access it as a dictionary:

app_data["basic_info"]["name"] = basic_app_info.get("name")app_data["basic_info"]["type"] = basic_app_info.get("@type")app_data["basic_info"]["url"] = basic_app_info.get("url")

Full example:

from bs4 import BeautifulSoupimport requests, lxml, re, jsonheaders = {    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"}params = {    "id": "com.nintendo.zara",     # app name    "gl": "US",                    # country of the search    "hl": "en_GB"                  # language of the search}# make a request and pass response to BeautifulSouphtml = requests.get("https://play.google.com/store/apps/details", params=params, headers=headers, timeout=30)soup = BeautifulSoup(html.text, "lxml")# where all app data will be storedapp_data = {    "basic_info": {}}# 👇👇👇 data extraction# [11] index is a basic app information# https://regex101.com/r/zOMOfo/1basic_app_info = json.loads(re.findall(r"<script nonce=\"\w+\" type=\"application/ld\+json\">({.*?)</script>", str(soup.select("script")[11]), re.DOTALL)[0])app_data["basic_info"]["name"] = basic_app_info.get("name")app_data["basic_info"]["type"] = basic_app_info.get("@type")app_data["basic_info"]["url"] = basic_app_info.get("url")

Reverse engineering & Debugging

The great examples of reverse engineering at our blog:

Make sure to check them both as here we're not going to duplicate the same information.

📌Information about Source and Application tabs is kinda introductory information as it's a big topics with a lot of steps to reproduce and it will be out of the scope of this blog post.

Sources tab

One of the approaches, when something complex needs to be extracted, could be done under the Source tab.

It could be done by degubbing website JS source code from certain files with debugger breakpoints (Dev tools -> sources -> debugger) by trying to trace what is going on in the code and how can we intercept/create by ourselves data and use it the parser.

image

We've also done a SerpApi Podcast about scraping dynamic websites which is essentially the topic about using Source tab.

In this video Ilya told about his approach about investigating and reproducing Google Maps data URL parameter which defines data about certain Google Maps palace. This parameter hold data about place id, latitude and longitude.

Application tab

A similar thing could be done in the Dev tools -> Application tab where we see, for example, cookies data and either intercept it on reverse engineer it by understanding how this cookie was built.

image

Ilya, one of the engineers at SerpApi has written in more detail about reverse engineering Location cookies from Walmart and his approach for such task.

Links

Join us on Twitter | YouTube

13 ways to scrape any public data from any website
Florida  Feeney

Florida Feeney

1662479160

SwiftSoup: Pure Swift HTML Parser, with Best Of DOM, CSS, and Jquery

SwiftSoup is a pure Swift library, cross-platform (macOS, iOS, tvOS, watchOS and Linux!), for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jQuery-like methods. SwiftSoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.

  • Scrape and parse HTML from a URL, file, or string
  • Find and extract data, using DOM traversal or CSS selectors
  • Manipulate the HTML elements, attributes, and text
  • Clean user-submitted content against a safe white-list, to prevent XSS attacks
  • Output tidy HTML SwiftSoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; SwiftSoup will create a sensible parse tree.

Swift

  • Swift 5 >=2.0.0

Swift 4.2 1.7.4

Installation

Cocoapods

SwiftSoup is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'SwiftSoup'

Carthage

SwiftSoup is also available through Carthage. To install it, simply add the following line to your Cartfile:

github "scinfu/SwiftSoup"

Swift Package Manager

SwiftSoup is also available through Swift Package Manager. To install it, simply add the dependency to your Package.Swift file:

...
dependencies: [
    .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.4.3"),
],
targets: [
    .target( name: "YourTarget", dependencies: ["SwiftSoup"]),
]
...

Try

Try out the simple online CSS selectors site:

SwiftSoup Test Site

Try out the example project opening Terminal and type:

pod try SwiftSoup

SwiftSoup SwiftSoup

To parse an HTML document:

do {
   let html = "<html><head><title>First parse</title></head>"
       + "<body><p>Parsed HTML into a doc.</p></body></html>"
   let doc: Document = try SwiftSoup.parse(html)
   return try doc.text()
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}
  • Unclosed tags (e.g. <p>Lorem <p>Ipsum parses to <p>Lorem</p> <p>Ipsum</p>)
  • Implicit tags (e.g. a naked <td>Table data</td> is wrapped into a <table><tr><td>...)
  • Reliably creating the document structure (html containing a head and body, and only appropriate elements within the head)

The object model of a document

  • Documents consist of Elements and TextNodes
  • The inheritance chain is: Document extends Element extends Node.TextNode extends Node.
  • An Element contains a list of children Nodes, and has one parent Element. They also have provide a filtered list of child Elements only.

Extract attributes, text, and HTML from elements

Problem

After parsing a document, and finding some elements, you'll want to get at the data inside those elements.

Solution

  • To get the value of an attribute, use the Node.attr(_ String key) method
  • For the text on an element (and its combined children), use Element.text()
  • For HTML, use Element.html(), or Node.outerHtml() as appropriate
do {
    let html: String = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>"
    let doc: Document = try SwiftSoup.parse(html)
    let link: Element = try doc.select("a").first()!
    
    let text: String = try doc.body()!.text() // "An example link."
    let linkHref: String = try link.attr("href") // "http://example.com/"
    let linkText: String = try link.text() // "example"
    
    let linkOuterH: String = try link.outerHtml() // "<a href="http://example.com/"><b>example</b></a>"
    let linkInnerH: String = try link.html() // "<b>example</b>"
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Description

The methods above are the core of the element data access methods. There are additional others:

  • Element.id()
  • Element.tagName()
  • Element.className() and Element.hasClass(_ String className)

All of these accessor methods have corresponding setter methods to change the data.

Parse a document from a String

Problem

You have HTML in a Swift String, and you want to parse that HTML to get at its contents, or to make sure it's well formed, or to modify it. The String may have come from user input, a file, or from the web.

Solution

Use the static SwiftSoup.parse(_ html: String) method, or SwiftSoup.parse(_ html: String, _ baseUri: String).

do {
    let html = "<html><head><title>First parse</title></head>"
        + "<body><p>Parsed HTML into a doc.</p></body></html>"
    let doc: Document = try SwiftSoup.parse(html)
    return try doc.text()
} catch Exception.Error(let type, let message) {
    print("")
} catch {
    print("")
}

Description

The parse(_ html: String, _ baseUri: String) method parses the input HTML into a new Document. The base URI argument is used to resolve relative URLs into absolute URLs, and should be set to the URL where the document was fetched from. If that's not applicable, or if you know the HTML has a base element, you can use the parse(_ html: String) method.

As long as you pass in a non-null string, you're guaranteed to have a successful, sensible parse, with a Document containing (at least) a head and a body element.

Once you have a Document, you can get at the data using the appropriate methods in Document and its supers Element and Node.

Parsing a body fragment

Problem

You have a fragment of body HTML (e.g. div containing a couple of p tags; as opposed to a full HTML document) that you want to parse. Perhaps it was provided by a user submitting a comment, or editing the body of a page in a CMS.

Solution

Use the SwiftSoup.parseBodyFragment(_ html: String) method.

do {
    let html: String = "<div><p>Lorem ipsum.</p>"
    let doc: Document = try SwiftSoup.parseBodyFragment(html)
    let body: Element? = doc.body()
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Description

The parseBodyFragment method creates an empty shell document, and inserts the parsed HTML into the body element. If you used the normal SwiftSoup(_ html: String) method, you would generally get the same result, but explicitly treating the input as a body fragment ensures that any bozo HTML provided by the user is parsed into the body element.

The Document.body() method retrieves the element children of the document's body element; it is equivalent to doc.getElementsByTag("body").

Stay safe

If you are going to accept HTML input from a user, you need to be careful to avoid cross-site scripting attacks. See the documentation for the Whitelist based cleaner, and clean the input with clean(String bodyHtml, Whitelist whitelist).

Sanitize untrusted HTML (to prevent XSS)

Problem

You want to allow untrusted users to supply HTML for output on your website (e.g. as comment submission). You need to clean this HTML to avoid cross-site scripting (XSS) attacks.

Solution

Use the SwiftSoup HTML Cleaner with a configuration specified by a Whitelist.

do {
    let unsafe: String = "<p><a href='http://example.com/' onclick='stealCookies()'>Link</a></p>"
    let safe: String = try SwiftSoup.clean(unsafe, Whitelist.basic())!
    // now: <p><a href="http://example.com/" rel="nofollow">Link</a></p>
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Discussion

A cross-site scripting attack against your site can really ruin your day, not to mention your users'. Many sites avoid XSS attacks by not allowing HTML in user submitted content: they enforce plain text only, or use an alternative markup syntax like wiki-text or Markdown. These are seldom optimal solutions for the user, as they lower expressiveness, and force the user to learn a new syntax.

A better solution may be to use a rich text WYSIWYG editor (like CKEditor or TinyMCE). These output HTML, and allow the user to work visually. However, their validation is done on the client side: you need to apply a server-side validation to clean up the input and ensure the HTML is safe to place on your site. Otherwise, an attacker can avoid the client-side Javascript validation and inject unsafe HMTL directly into your site

The SwiftSoup whitelist sanitizer works by parsing the input HTML (in a safe, sand-boxed environment), and then iterating through the parse tree and only allowing known-safe tags and attributes (and values) through into the cleaned output.

It does not use regular expressions, which are inappropriate for this task.

SwiftSoup provides a range of Whitelist configurations to suit most requirements; they can be modified if necessary, but take care.

The cleaner is useful not only for avoiding XSS, but also in limiting the range of elements the user can provide: you may be OK with textual a, strong elements, but not structural div or table elements.

See also

  • See the XSS cheat sheet and filter evasion guide, as an example of how regular-expression filters don't work, and why a safe whitelist parser-based sanitizer is the correct approach.
  • See the Cleaner reference if you want to get a Document instead of a String return
  • See the Whitelist reference for the different canned options, and to create a custom whitelist
  • The nofollow link attribute

Set attribute values

Problem

You have a parsed document that you would like to update attribute values on, before saving it out to disk, or sending it on as a HTTP response.

Solution

Use the attribute setter methods Element.attr(_ key: String, _ value: String), and Elements.attr(_ key: String, _ value: String).

If you need to modify the class attribute of an element, use the Element.addClass(_ className: String) and Element.removeClass(_ className: String) methods.

The Elements collection has bulk attribute and class methods. For example, to add a rel="nofollow" attribute to every a element inside a div:

do {
    try doc.select("div.comments a").attr("rel", "nofollow")
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Description

Like the other methods in Element, the attr methods return the current Element (or Elements when working on a collection from a select). This allows convenient method chaining:

do {
    try doc.select("div.masthead").attr("title", "swiftsoup").addClass("round-box")
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Set the HTML of an element

Problem

You need to modify the HTML of an element.

Solution

Use the HTML setter methods in Element:

do {
    let doc: Document = try SwiftSoup.parse("<div>One</div><span>One</span>")
    let div: Element = try doc.select("div").first()! // <div>One</div>
    try div.html("<p>lorem ipsum</p>") // <div><p>lorem ipsum</p></div>
    try div.prepend("<p>First</p>")
    try div.append("<p>Last</p>")
    print(div)
    // now div is: <div><p>First</p><p>lorem ipsum</p><p>Last</p></div>
    
    let span: Element = try doc.select("span").first()! // <span>One</span>
    try span.wrap("<li><a href='http://example.com/'></a></li>")
    print(doc)
    // now: <html><head></head><body><div><p>First</p><p>lorem ipsum</p><p>Last</p></div><li><a href="http://example.com/"><span>One</span></a></li></body></html>
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Discussion

  • Element.html(_ html: String) clears any existing inner HTML in an element, and replaces it with parsed HTML.
  • Element.prepend(_ first: String) and Element.append(_ last: String) add HTML to the start or end of an element's inner HTML, respectively
  • Element.wrap(_ around: String) wraps HTML around the outer HTML of an element.

See also

You can also use the Element.prependElement(_ tag: String) and Element.appendElement(_ tag: String) methods to create new elements and insert them into the document flow as a child element.

Setting the text content of elements

Problem

You need to modify the text content of an HTML document.

Solution

Use the text setter methods of Element:

do {
    let doc: Document = try SwiftSoup.parse("<div></div>")
    let div: Element = try doc.select("div").first()! // <div></div>
    try div.text("five > four") // <div>five &gt; four</div>
    try div.prepend("First ")
    try div.append(" Last")
    // now: <div>First five &gt; four Last</div>
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Discussion

The text setter methods mirror the [[HTML setter|Set the HTML of an element]] methods:

  • Element.text(_ text: String) clears any existing inner HTML in an element, and replaces it with the supplied text.
  • Element.prepend(_ first: String) and Element.append(_ last: String) add text nodes to the start or end of an element's inner HTML, respectively The text should be supplied unencoded: characters like <, > etc will be treated as literals, not HTML.

Use DOM methods to navigate a document

Problem

You have a HTML document that you want to extract data from. You know generally the structure of the HTML document.

Solution

Use the DOM-like methods available after parsing HTML into a Document.

do {
    let html: String = "<a id=1 href='?foo=bar&mid&lt=true'>One</a> <a id=2 href='?foo=bar&lt;qux&lg=1'>Two</a>"
    let els: Elements = try SwiftSoup.parse(html).select("a")
    for link: Element in els.array() {
        let linkHref: String = try link.attr("href")
        let linkText: String = try link.text()
    }
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Description

Elements provide a range of DOM-like methods to find elements, and extract and manipulate their data. The DOM getters are contextual: called on a parent Document they find matching elements under the document; called on a child element they find elements under that child. In this way you can window in on the data you want.

Finding elements

  • getElementById(_ id: String)
  • getElementsByTag(_ tag:String)
  • getElementsByClass(_ className: String)
  • getElementsByAttribute(_ key: String) (and related methods)
  • Element siblings: siblingElements(), firstElementSibling(), lastElementSibling(), nextElementSibling(), previousElementSibling()
  • Graph: parent(), children(), child(_ index: Int)

Element data

  • attr(_ key: Strin) to get and attr(_ key: String, _ value: String) to set attributes
  • attributes() to get all attributes
  • id(), className() and classNames()
  • text() to get and text(_ value: String) to set the text content
  • html() to get and html(_ value: String) to set the inner HTML content
  • outerHtml() to get the outer HTML value
  • data() to get data content (e.g. of script and style tags)
  • tag() and tagName()

Manipulating HTML and text

  • append(_ html: String), prepend(html: String)
  • appendText(text: String), prependText(text: String)
  • appendElement(tagName: String), prependElement(tagName: String)
  • html(_ value: String)

Use selector syntax to find elements

Problem

You want to find or manipulate elements using a CSS or jQuery-like selector syntax.

Solution

Use the Element.select(_ selector: String) and Elements.select(_ selector: String) methods:

do {
    let doc: Document = try SwiftSoup.parse("...")
    let links: Elements = try doc.select("a[href]") // a with href
    let pngs: Elements = try doc.select("img[src$=.png]")
    // img with src ending .png
    let masthead: Element? = try doc.select("div.masthead").first()
    // div with class=masthead
    let resultLinks: Elements? = try doc.select("h3.r > a") // direct a after h3
} catch Exception.Error(let type, let message) {
    print(message)
} catch {
    print("error")
}

Description

SwiftSoup elements support a CSS (or jQuery) like selector syntax to find matching elements, that allows very powerful and robust queries.

The select method is available in a Document, Element, or in Elements. It is contextual, so you can filter by selecting from a specific element, or by chaining select calls.

Select returns a list of Elements (as Elements), which provides a range of methods to extract and manipulate the results.

Selector overview

  • tagname: find elements by tag, e.g. a
  • ns|tag: find elements by tag in a namespace, e.g. fb|name finds <fb:name> elements
  • #id: find elements by ID, e.g. #logo
  • .class: find elements by class name, e.g. .masthead
  • [attribute]: elements with attribute, e.g. [href]
  • [^attr]: elements with an attribute name prefix, e.g. [^data-] finds elements with HTML5 dataset attributes
  • [attr=value]: elements with attribute value, e.g. [width=500] (also quotable, like [data-name='launch sequence'])
  • [attr^=value], [attr$=value], [attr*=value]: elements with attributes that start with, end with, or contain the value, e.g. [href*=/path/]
  • [attr~=regex]: elements with attribute values that match the regular expression; e.g. img[src~=(?i)\.(png|jpe?g)]
  • *: all elements, e.g. *

Selector combinations

  • el#id: elements with ID, e.g. div#logo
  • el.class: elements with class, e.g. div.masthead
  • el[attr]: elements with attribute, e.g. a[href]
  • Any combination, e.g. a[href].highlight
  • Ancestor child: child elements that descend from ancestor, e.g. .body p finds p elements anywhere under a block with class "body"
  • parent > child: child elements that descend directly from parent, e.g. div.content > p finds p elements; and body > * finds the direct children of the body tag
  • siblingA + siblingB: finds sibling B element immediately preceded by sibling A, e.g. div.head + div
  • siblingA ~ siblingX: finds sibling X element preceded by sibling A, e.g. h1 ~ p
  • el, el, el: group multiple selectors, find unique elements that match any of the selectors; e.g. div.masthead, div.logo

Pseudo selectors

  • :lt(n): find elements whose sibling index (i.e. its position in the DOM tree relative to its parent) is less than n; e.g. td:lt(3)
  • :gt(n): find elements whose sibling index is greater than n; e.g. div p:gt(2)
  • :eq(n): find elements whose sibling index is equal to n; e.g. form input:eq(1)
  • :has(seletor): find elements that contain elements matching the selector; e.g. div:has(p)
  • :not(selector): find elements that do not match the selector; e.g. div:not(.logo)
  • :contains(text): find elements that contain the given text. The search is case-insensitive; e.g. p:contains(swiftsoup)
  • :containsOwn(text): find elements that directly contain the given text
  • :matches(regex): find elements whose text matches the specified regular expression; e.g. div:matches((?i)login)
  • :matchesOwn(regex): find elements whose own text matches the specified regular expression
  • Note that the above indexed pseudo-selectors are 0-based, that is, the first element is at index 0, the second at 1, etc

Examples

To parse an HTML document from String:

let html = "<html><head><title>First parse</title></head><body><p>Parsed HTML into a doc.</p></body></html>"
guard let doc: Document = try? SwiftSoup.parse(html) else { return }

Get all text nodes:

guard let elements = try? doc.getAllElements() else { return html }
for element in elements {
    for textNode in element.textNodes() {
        [...]
    }
}

Set CSS using SwiftSoup:

try doc.head()?.append("<style>html {font-size: 2em}</style>")

Get HTML value

let html = "<div class=\"container-fluid\">"
    + "<div class=\"panel panel-default \">"
    + "<div class=\"panel-body\">"
    + "<form id=\"coupon_checkout\" action=\"http://uat.all.com.my/checkout/couponcode\" method=\"post\">"
    + "<input type=\"hidden\" name=\"transaction_id\" value=\"4245\">"
    + "<input type=\"hidden\" name=\"lang\" value=\"EN\">"
    + "<input type=\"hidden\" name=\"devicetype\" value=\"\">"
    + "<div class=\"input-group\">"
    + "<input type=\"text\" class=\"form-control\" id=\"coupon_code\" name=\"coupon\" placeholder=\"Coupon Code\">"
    + "<span class=\"input-group-btn\">"
    + "<button class=\"btn btn-primary\" type=\"submit\">Enter Code</button>"
    + "</span>"
    + "</div>"
    + "</form>"
    + "</div>"
    + "</div>"
guard let doc: Document = try? SwiftSoup.parse(html) else { return } // parse html
let elements = try doc.select("[name=transaction_id]") // query
let transaction_id = try elements.get(0) // select first element
let value = try transaction_id.val() // get value
print(value) // 4245

How to remove all the html from a string

guard let doc: Document = try? SwiftSoup.parse(html) else { return } // parse html
guard let txt = try? doc.text() else { return }
print(txt)

How to get and update XML values

let xml = "<?xml version='1' encoding='UTF-8' something='else'?><val>One</val>"
guard let doc = try? SwiftSoup.parse(xml, "", Parser.xmlParser()) else { return }
guard let element = try? doc.getElementsByTag("val").first() else { return } // Find first element
try element.text("NewValue") // Edit Value
let valueString = try element.text() // "NewValue"

How to get all <img src>

do {
    let doc: Document = try SwiftSoup.parse(html)
    let srcs: Elements = try doc.select("img[src]")
    let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
    // do something with srcsStringArray
} catch Exception.Error(_, let message) {
    print(message)
} catch {
    print("error")
}

Get all href of <a>

let html = "<a id=1 href='?foo=bar&mid&lt=true'>One</a> <a id=2 href='?foo=bar&lt;qux&lg=1'>Two</a>"
guard let els: Elements = try? SwiftSoup.parse(html).select("a") else { return }
for element: Element in els.array() {
    print(try? element.attr("href"))
}

Output:

"?foo=bar&mid&lt=true"
"?foo=bar<qux&lg=1"

Escape and Unescape

let text = "Hello &<> Å å π 新 there ¾ © »"

print(Entities.escape(text))
print(Entities.unescape(text))


print(Entities.escape(text, OutputSettings().encoder(String.Encoding.ascii).escapeMode(Entities.EscapeMode.base)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.extended)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.xhtml)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.extended)))
print(Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.xhtml)))

Output:

"Hello &amp;&lt;&gt; Å å π 新 there ¾ © »"
"Hello &<> Å å π 新 there ¾ © »"


"Hello &amp;&lt;&gt; &Aring; &aring; &#x3c0; &#x65b0; there &frac34; &copy; &raquo;"
"Hello &amp;&lt;&gt; &angst; &aring; &pi; &#x65b0; there &frac34; &copy; &raquo;"
"Hello &amp;&lt;&gt; &#xc5; &#xe5; &#x3c0; &#x65b0; there &#xbe; &#xa9; &#xbb;"
"Hello &amp;&lt;&gt; Å å π 新 there ¾ © »"
"Hello &amp;&lt;&gt; Å å π 新 there ¾ © »"

Author

Nabil Chatbi, scinfu@gmail.com

Note

SwiftSoup was ported to Swift from Java Jsoup library.

License

SwiftSoup is available under the MIT license. See the LICENSE file for more info.


Download Details:

Author: scinfu
Source code: https://github.com/scinfu/SwiftSoup

License: MIT license
#swift 

SwiftSoup: Pure Swift HTML Parser, with Best Of DOM, CSS, and Jquery

A Julia Package to Estimate The Geographic Location Of IP Addresses

GeoIP

IP Geolocalization using the Geolite2 Database

Installation

The package is registered in the General registry and so can be installed at the REPL with

julia> using Pkg
julia> Pkg.add("GeoIP")

Usage

Data files

You can use MaxMind geolite2 csv files downloaded from the site. Due to the MaxMind policy, GeoLite.jl does not distribute GeoLite2 files and does not provide download utilities. For automated download it is recommended to use MaxMind GeoIP Update program. For proper functioning of GeoIP.jl you need to download GeoLite2 City datafile, usually it should have a name like GeoLite2-City-CSV_20191224.zip.

Files processing and loading provided with load() call. Directory where data is located should be located either in ENV["GEOIP_DATADIR"] or it can be passed as an argument to load function. Zip file location can be passed as an argument or it can be stored in ENV["GEOIP_ZIPFILE"]. For example

using GeoIP

geodata = load(zipfile = "GeoLite2-City-CSV_20191224.zip", datadir = "/data")

If ENV["GEOIP_DATADIR"] is set to "/data" and ENV["GEOIP_ZIPFILE"] is set to "GeoLite2-City-CSV_20191224.zip" then it is equivalent to

using GeoIP

geodata = load()

Example

You can get the ip data with the geolocate function or by using []

using GeoIP

geodata = load(zipfile = "GeoLite2-City-CSV_20191224.zip")
geolocate(geodata, ip"1.2.3.4")        # returns dictionary with all relevant information

# Equivalent to
geodata[ip"1.2.3.4"]

# Equivalent, but slower version
geodata["1.2.3.4"]

geolocate form is useful for broadcasting

geolocate.(geodata, [ip"1.2.3.4", ip"8.8.8.8"])  # returns vector of geo data.

Localization

It is possible to use localized version of geo files. To load localized data, one can use locales argument of the load function. To switch between different locales is possible with the help of setlocale function.

using GeoIP

geodata = load(zipfile = "GeoLite2-City-CSV_20191224.zip", locales = [:en, :fr])

geodata[ip"201.186.185.1"]
# Dict{String, Any} with 21 entries:
#   "time_zone"                     => "America/Santiago"
#   "subdivision_2_name"            => missing
#   "accuracy_radius"               => 100
#   "geoname_id"                    => 3874960
#   "continent_code"                => "SA"
#   "postal_code"                   => missing
#   "continent_name"                => "South America"
#   "locale_code"                   => "en"
#   "subdivision_2_iso_code"        => missing
#   "location"                      => Location(-72.9436, -41.4709, 0.0, "WGS84")
#   "v4net"                         => IPv4Net("201.186.185.0/24")
#   "subdivision_1_name"            => "Los Lagos Region"
#   "subdivision_1_iso_code"        => "LL"
#   "city_name"                     => "Port Montt"
#   "metro_code"                    => missing
#   "registered_country_geoname_id" => 3895114
#   "is_in_european_union"          => 0
#   "is_satellite_provider"         => 0
#   "is_anonymous_proxy"            => 0
#   "country_name"                  => "Chile"
#   "country_iso_code"              => "CL"

geodata_fr = setlocale(geodata, :fr)
geodata_fr[ip"201.186.185.1"]
# Dict{String, Any} with 21 entries:
#   "time_zone"                     => "America/Santiago"
#   "subdivision_2_name"            => missing
#   "accuracy_radius"               => 100
#   "geoname_id"                    => 3874960
#   "continent_code"                => "SA"
#   "postal_code"                   => missing
#   "continent_name"                => "Amérique du Sud"
#   "locale_code"                   => "fr"
#   "subdivision_2_iso_code"        => missing
#   "location"                      => Location(-72.9436, -41.4709, 0.0, "WGS84")
#   "v4net"                         => IPv4Net("201.186.185.0/24")
#   "subdivision_1_name"            => missing
#   "subdivision_1_iso_code"        => "LL"
#   "city_name"                     => "Puerto Montt"
#   "metro_code"                    => missing
#   "registered_country_geoname_id" => 3895114
#   "is_in_european_union"          => 0
#   "is_satellite_provider"         => 0
#   "is_anonymous_proxy"            => 0
#   "country_name"                  => "Chili"
#   "country_iso_code"              => "CL"

During load procedure, it is possible to use either Symbol notation, i.e. locales = [:en, :fr] or one can pass Vector of Pair, where first argument is the locale name and second argument is a regular expression, which defines the name of the CSV file, which contains necessary localization. For example locales = [:en => r"Locations-en.csv%", :fr => r"Locations-fr.csv"]. By default, following locales are supported :en, :de, :ru, :ja, :es, :fr, :pt_br, :zh_cn.

Default locale, which is used in getlocale response can be set with the help of deflocale argument of the load function. For example, to get :fr locale by default

geodata = load(zipfile = "GeoLite2-City-CSV_20191224.zip", locales = [:en, :fr], deflocale = :fr)

Acknowledgements

This product uses, but not include, GeoLite2 data created by MaxMind, available from http://www.maxmind.com.

Download Details:

Author: JuliaWeb
Source Code: https://github.com/JuliaWeb/GeoIP.jl 
License: View license

#julia #id #location 

A Julia Package to Estimate The Geographic Location Of IP Addresses
Rocio  O'Keefe

Rocio O'Keefe

1660692780

Code_id_storage: A Package local storage for use for company code.id

code_id_flutter

A Combined Flutter library & reusable code for use

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add code_id_storage

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  code_id_storage: ^0.0.8

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:code_id_storage/code_id_storage.dart';

About the Package

This package is use for internal code.id Company

For how to use you can contact me. To know to use it. ]

Original article source at: https://pub.dev/packages/code_id_storage 

#flutter #dart #id #storage 

Code_id_storage: A Package local storage for use for company code.id
Elian  Harber

Elian Harber

1659997920

MikkelHJuul/ld: Lean Database

ld    

Lean database - it's a simple-database, it's just an rpc-based server with the basic Get/Set/Delete operations. The database can ingest any value and store it at a key, it only supports rpc, via gRPC. The encoded binary message is stored, and served without touching the value on the server-side. To that end it is mostly a gRPC-cache, but I intend it to be a more general building block.

The database is operating on "key"-level only. If you need secondary indexes you needed to maintain two versions of the data or actually create the index (id'-id-mapping) table yourself. Some key-value databases offer more solutions than this; this does not, and will not, offering too many solutions most often lead to poorer solutions in general.

There is no Query language to clutter your code! I know, awesome, right?!

This project started out as a learning project for myself, to learn golang, rpc and gRPC.

The project is written in golang. It will be packaged as a scratch-container (linux amd64). I will not support other ways of downloading. As always you can simply go build

Docker images

images are mjuul/ld:<tag> and (alpine)mjuul/ld:<tag>-client. There is also a standalone client container mjuul/ld-client.

The container mjuul/ld:<tag> is just a scratch container with the Linux/amd64 image as entrypoint.

The container mjuul/ld:<tag>-client is based on the image mjuul/ld-client adding the binary ld to it and running that at startup. The client serves as an interactive shell for the database, see client.

Implementation

This project exposes badgerDB. You should be able to use the badgerDB CLI-tool on the database.

API

Hashmap Get-Set-Delete semantics! With bidirectional streaming rpc's. No lists, because aggregation of data should be kept at a minimum. The APIs for get and delete further implement unidirectional server-side streams for querying via KeyRange.

I consider the gRPC api to be feature complete. While the underlying implementation may change to enable better database configuration and/or usage of this code as a library. Maturity may also bring changes to the server implementation.

See test for a client implementations, the testing package builds on the data from DMI - Free data initiative (specifically the lightning data set), but can easily be changed to ingest other data, ingestion and read separated into two different clients.

Working with the API

The API is expandable. Because of how gRPC encoding works you can replace the bytes type value tag on the client side with whatever you want. This way you could use it to store dynamically typed objects using Any. Or you can save and query the database with a fixed or reflected type.

The test folder holds two small programs that implements a fixed type: my_message.proto.

The client uses reflection to serialize/deserialize json to a message given a .proto-file.

CRUD - why not CRUD?

CRUD operations must be implemented client side, use Get -> [decision] -> Set to implement create or update, the way you want to. fx

    Create      Get -> if {empty response} -> Set
    Update      Get/Delete -> if {non-empty} -> [map?] -> Set

To have done this server side would cause so much friction. All embeddable key-value databases, to my knowledge, implement Get-Set-Delete semantics, so whether you go with bolt/bbolt or badger you would always end up having this friction; so naturally you implement it without CRUD-semantics. Implementing a concurrent GetMany/SetMany ping-pong client-service feels a lot more elegant anyways.

Configuration

via flags or environment variables:

flag            ENV             default     description
------------------------------------------------------------------------------------
-port            PORT            5326        "5326" spells out "lean" in T9 keyboards
-db-location     DB_LOCATION     ld_badger   The folder location where badger stores its database-files
-in-mem          IN_MEM          false       save data in memory (or not) setting this to true ignores db-location.
-log-level       LOG_LEVEL       INFO        the logging level of the server

The container mjuul/ld:<tag>-client does not support flags for ld, use environment variables. (Since it is ld-client that is the entrypoint)

Comparison to ProfaneDB

ProfaneDB uses field options to find your object's key, and can ingest a list (repeated), your key can be composite, and you don't have to think about your key. (I envy the design a bit (it's shiny), but then again I don't feel like that is the best design).

ld forces you to design your key, and force single-object(no-aggregation/non-repeated) thinking.

ProfaneDB does not support any type of non-singular key queries; you will have to build query objects with very high knowledge of your keys (specific keys). This may force you to make fewer keys, and do more work in the client. (you may end up searching for a needle in a haystack, or completely loosing a key)

ld supports KeyRanges, you can then make very specific keys, and more of them, and think about the key-design, and query that via, From, To, Prefix and/or Pattern syntax.

ProfaneDB uses an inbuilt extension for its .proto. pro: you can use their .proto file as is. con: google's Any-type is just like map, and requires the implementer to send type-knowledge on each object on the wire.

ld use the underlying protocol buffers encoding design, con: this force the implementer to edit their .proto file, which is an anti-pattern. pro: while the database will not know anything about the value it saves, the type will be packed binary and can be serialised.

ld support bulk operations (via stream methods) natively. ProfaneDB via a repeated nested object, Memory-wise, streaming is preferred.

TODO

  • benchmarks

Author: MikkelHJuul
Source Code: https://github.com/MikkelHJuul/ld 
License: Unlicense license

#go #golang #id #database 

MikkelHJuul/ld: Lean Database
许 志强

许 志强

1659153600

CSS :has() 选择器的高级指南

CSS:has()伪类多年来一直是最受期待的特性之一。这是一个 4 级 CSS 选择器,现在作为Chrome 105及更高版本中完全受支持的功能提供,并且很可能很快也会成为其他浏览器的常规功能。

CSS 中的:has()in CSS 是一个关系伪类,允许您检查给定元素是否包含某些子元素,如果找到任何匹配项,则选择它,然后相应地对其进行样式设置。

本文解释了:has()选择器的需求、它的一般用法、从简单到高级的各种应用程序和用例、浏览器兼容性以及后备。

为什么:has()需要选择器?

大多数开发人员依赖 JavaScript 来实现 CSS 默认不支持的某些功能。然而,今天的网络浏览器比以往任何时候都更强大,这为许多新的和有趣的 CSS 功能打开了大门。

选择:has()器就是这样一种功能。它适用于父元素而不是子元素,并使用逗号分隔的选择器列表作为参数,然后在它所代表的元素的子元素中查找匹配项。它的功能类似于 jQueryhas()方法。

让我们回顾一下前端开发中选择器的一些一般需求,这些需求:has()解释了为什么多年来对这个特性的需求如此之高。

首先是检查一个元素是否包含某些其他元素的可能性,然后相应地对其进行样式设置。JavaScript 是以前唯一的方法,你可以在这里看到:

let content = document.querySelector("#content"),
    headings = [] 

if(content) {
  headings = content.querySelectorAll("h1, h2, h3")
}

if(headings.length) {
  // do something
}

在上面的代码中,我们使用querySelectorAll了 ,这是一种 Web API 方法来检查标题的划分元素。该方法返回所提供元素的 NodeList。

一个空的 NodeList 意味着直接父级中不存在任何元素。上面的实现可以在这里找到。或者,您可以在此处查看 CSS:has()替代方案

其次是从子元素中选择父元素的能力。同样,由于缺乏 CSS 工具,开发人员依赖于 Web API 的 parentNode 属性:

let el = document.querySelector(".someElement"),
    elParent = null

if(el) {
  elParent = el.parentNode
}

在此处查看 上面代码的 CodePen 演示,以及选择器如何在:has()此处实现

最后,可以选择给定元素的前一个兄弟。CSS 中的兄弟选择器允许您选择下一个兄弟,但没有 CSS 唯一的方法来选择以前的兄弟。JavaScript 实现如下所示(在此处查看 CodePen 演示):

let el = document.querySelector(".someElement"),
    elPs = null

if(el) {
  elPs = el.previousElementSibling
}

您可以在:has()此处查看如何使用选择器执行此操作。

我们上面用 JavaScript 所做的一切现在都可以用 CSS 来实现:has()。在了解如何在 Google Chrome 上启用和测试此功能后,我们将在下一节中一一介绍。

启用对:has()选择器的支持

如果您使用的是较旧的 Chrome 版本 (v.101-104),则可以从 Chrome 标志启用此功能。确保您拥有 Chrome 101+ 并从浏览器的地址栏中导航到 chrome://flags。

Experimental Web Platform features设置为Enabled,一切顺利。:has()重新启动浏览器,您就可以在 Chrome 浏览器中使用 CSS 。

实验性网络平台功能选项卡 chrome

CSS:has()选择器有什么作用?

让我们探讨一下:has()伪类、它的用法和属性。因为它是一个伪类,所以它可以用冒号附加到任何选择器,并接受类、ID 和 HTML 标记作为参数。

下面的代码解释了它的一般用法和语法。只有当它包含使用伪类作为参数传递给它的元素时,该类.selector才会被选中:has()

.selector:has(.class) { ... }
.selector:has(#id) { ... }
.selector:has(div) { ... }

可链性

只要您认为合适,您就可以一个接一个地链接多个:has()伪类。下面的代码演示了这种链接是如何工作的:

.selector:has(div):has(.class):has(#id) {
  ...
}

多选的参数列表

您还可以提供多个元素选择器的列表,类似于链接,但效率更高:

.selector:has(div, .class, #id) {
  ...
}

灵活性

:has()假设您不小心为伪类提供了无效的元素选择器。忽略它并只考虑有效的选择器是足够聪明的:

.selector:has(div, accordion, .class, ::lobster, #id) {
  ...
}

accordion并且::lobster是无效的选择器,在上述情况下将被忽略。您不会在开发人员工具中看到任何 CSS 错误或警报。

使用场景

让我们看一下 CSS:has()选择器派上用场的一些示例场景。

选择父级

这可能是:has()选择器最常见的用途,因为它的默认行为是选择包含特定元素集的某些内容。但是如果我们只知道子元素呢?

通用选择器 ( *) 可以在这里与:has()子组合器 ( ) 一起使用,>以快速选择父级,甚至不知道它的任何内容。

这里

检查多个孩子

如上面属性部分所述,:has()允许您传递多个实体的列表,这意味着您可以在给定元素中检查任意数量的选择器。

这里

选择上一个兄弟姐妹

通过将 CSS 相邻兄弟组合器与:has()伪类组合,可以选择前一个兄弟。

您可能已经知道,相邻兄弟组合器选择给定元素的下一个兄弟。我们可以使用这种行为has()来获取上一个兄弟。:has()简单地说,如果一个元素有下一个兄弟,用和组合器很容易选择它+

这里

有条件的装饰

使用选择器可以避免单独设置带有和不带有某些子元素的样式:has()。带有和不带标题的图形元素是这里的完美示例。

这里

如果一个figcaption元素存在,但不包含任何文本怎么办?在这种情况下,我们可以使用:notand:empty选择器来检查内容。

与图形装饰类似,我们可以轻松切换多段引用的文本对齐方式。在这里查看它的实际应用。

样式化空状态

我们已经有一个伪类,用于调用:empty不包含任何内容的样式元素。就针对空状态而言,它无法按预期工作。内部的一个空间足以让它将一个元素识别为非空。

:empty在这种情况下使用并不理想。让我们创建一个卡片网格,其中还包含一些无内容卡片。我们将使用选择器设置空卡片状态的样式:has()

这里

类型和块调整

在为文章设计样式时,保持类型和块元素对齐是一项棘手的工作。考虑一个将代码块、图形和块引用与通用类型元素混合的示例。

块元素应该有更多的垂直间距和装饰,以在不同的印刷实体中脱颖而出。这里有一些 CSS 来处理这个问题。

p {
  margin: 0;
}

p:not(:last-child) {
  margin-bottom: 1.5em;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1.3;
  margin: 1.5em 0 1.5rem;
  color: #111;
}

pre,
figure,
blockquote {
  margin: 3em 0;
}

figcaption {
  margin-top: 1em;
  font-size: 0.75em;
  color: #888;
  text-align: center;
}

上面的代码片段会在标题和块元素之间产生不均匀的垂直间距,如此处所示。但是可以使用之前的兄弟选择技巧来弥补这些违规行为。

这里

标志性 CTA

假设您要在 CSS 中创建具有两种变体的 CTA 或按钮组件:第一个是默认的普通按钮,第二个是带有图标的。

现在,如果您打算为此编写两个单独的类,则可以使用:has()选择器轻松避免。问题是简单地检查.btn元素的.btn-icon子元素,然后相应地设置样式。

这里

布局调整

假设标题组件有两种布局变化:一种是固定宽度,另一种是流动的,当然。

为了将标题的内容保持在固定宽度内,我们必须在其中添加一个内容包装器元素。这两个标头版本的标记之间的唯一区别是包装元素。

然后我们可以将:has()with:not()选择器配对并添加 CSS 类,如下面的演示所示。

这里

调整布局的另一个示例可以是在网格中的列数达到一定数量后立即对其进行修改。

当您不使用minmax()确定最佳宽度以适合其网格中的列的功能时,这可能会很方便。

这里

正如您在上面看到的,一旦超过两个项目的标记,网格就会自动调整为三列。

更好的表单可用性

当交互性与反馈正确连接时,交互性得到了最好的定义。对于交互式 HTML 表单,向用户提供有关其输入的反馈是您能做的最好的事情。

在、 和伪类的帮助下:has(),我们可以使我们的表单更加动态,并且不涉及 JavaScript。:valid:invalid

这里

检查浏览器支持

如果没有:has()找到对选择器的支持,上面讨论的示例会显示一个错误对话框。这可以使用@supportsCSS 规则来完成,如下面的代码片段所示:

@supports (selector(:has(*))) {
  .selector:has(...) {
    ...  
  }
}

以上是根据需要检查元素的支持和样式的渐进式方法。如果代码库直接使用了一些新特性,这里有一种方法可以编写相同的向后兼容版本:

@supports not (selector(:has(*))) {
  .selector {
    ...  
  }
}

如果没有找到支持,同样也可以用来提示用户。

您还可以使用 JavaScript 中的 Support API 来检测浏览器对不同功能的支持。:has()这是一个纯粹在 JavaScript中检查支持的示例:

if(!CSS.supports('selector(html:has(body))'))) { // if not supported
  // Use the conventional JavaScript ways to emulate :has()
}

结论

在上面的文章中,我们了解:has()了 4 级 CSS 选择器。我们涵盖了它的需求,它可以在您的前端项目中替换 JavaScript 的一些情况,以及一般到高级用例。

我们还讨论了不同浏览器提供给它的当前支持,并学习了如何检查浏览器支持。

感谢您一直阅读。我希望你通过这个学到新的东西。随时在评论中分享更多:has()有用的示例。 

来源:https ://blog.logrocket.com/advanced-guide-css-has-selector/

#css 

CSS :has() 选择器的高级指南
Léon  Peltier

Léon Peltier

1659153600

Le Guide Avancé Du Sélecteur CSS :has()

La pseudo-classe CSS :has()est l'une des fonctionnalités les plus attendues depuis des années. Il s'agit d'un sélecteur CSS de niveau 4, désormais disponible en tant que fonctionnalité entièrement prise en charge dans Chrome 105 et ultérieur , et qui deviendra probablement bientôt une fonctionnalité régulière sur d'autres navigateurs également.

Le :has()in CSS est une pseudo-classe relationnelle qui vous permet de vérifier si un élément donné contient certains éléments enfants, de le sélectionner si une correspondance est trouvée, puis de le styler en conséquence.

Cet article explique la nécessité du :has()sélecteur, son utilisation générale, diverses applications et cas d'utilisation du plus simple au plus avancé, la compatibilité du navigateur et les solutions de secours.

Pourquoi le :has()sélecteur est-il nécessaire ?

La plupart des développeurs s'appuient sur JavaScript pour certaines fonctionnalités que CSS ne prend pas en charge par défaut. Cependant, les navigateurs Web d'aujourd'hui sont plus puissants que jamais, ce qui ouvre la porte à de nombreuses fonctionnalités CSS nouvelles et intéressantes.

Le :has()sélecteur est l'une de ces fonctionnalités. Il fonctionne sur le parent plutôt que sur les enfants et utilise une liste de sélecteurs séparés par des virgules comme arguments, puis recherche des correspondances parmi les enfants de l'élément qu'il représente. Sa fonctionnalité est similaire à la has()méthode jQuery.

Passons en revue certains des besoins généraux du :has()sélecteur dans le développement frontal qui expliquent pourquoi cette fonctionnalité était si demandée depuis des années.

Le premier est la possibilité de vérifier si un élément contient certains autres éléments ou non, puis de le styliser en conséquence. JavaScript était le seul moyen de le faire auparavant, comme vous pouvez le voir ici :

let content = document.querySelector("#content"),
    headings = [] 

if(content) {
  headings = content.querySelectorAll("h1, h2, h3")
}

if(headings.length) {
  // do something
}

Dans le code ci-dessus, nous avons utilisé querySelectorAll, qui est une méthode d'API Web pour vérifier un élément de division pour les en-têtes. La méthode renvoie une NodeList des éléments fournis.

Une NodeList vide signifierait qu'il n'y a aucun élément présent dans le parent immédiat. Une implémentation de ce qui précède peut être trouvée ici . Ou, vous pouvez voir l' alternative CSS:has() ici .

La deuxième est la possibilité de sélectionner l'élément parent parmi les enfants. Encore une fois, en l'absence d'une installation CSS pour le faire, les développeurs se sont appuyés sur la propriété parentNode de l'API Web :

let el = document.querySelector(".someElement"),
    elParent = null

if(el) {
  elParent = el.parentNode
}

Voir une  démo CodePen du code ci-dessus ici , et comment le :has()sélecteur l'implémente ici .

Enfin, il est possible de sélectionner le frère précédent d'un élément donné. Le sélecteur de frère dans CSS vous permet de sélectionner le frère suivant, mais il n'y avait pas de moyen uniquement CSS de sélectionner les frères et sœurs précédents. L'implémentation de JavaScript ressemble à ceci ( voir une démo CodePen ici ) :

let el = document.querySelector(".someElement"),
    elPs = null

if(el) {
  elPs = el.previousElementSibling
}

Vous pouvez voir comment effectuer cette action avec le :has()sélecteur ici .

Tout ce que nous avons fait ci-dessus avec JavaScript peut maintenant être réalisé avec CSS :has(). Nous les couvrirons un par un dans la section suivante après avoir appris comment activer et tester cette fonctionnalité sur Google Chrome.

Activation de la prise en charge du :has()sélecteur

Si vous utilisez une ancienne version de Chrome (v.101-104), vous pouvez activer cette fonctionnalité à partir des indicateurs Chrome. Assurez-vous que vous disposez de Chrome 101+ et accédez à chrome://flags dans la barre d'adresse du navigateur.

Définissez les fonctionnalités de la plate-forme Web expérimentale sur Activé et vous êtes prêt à partir. Relancez le navigateur et vous pourrez travailler avec CSS :has()dans votre navigateur Chrome.

Chrome de l'onglet des fonctionnalités de la plate-forme Web expérimentale

A quoi sert le sélecteur CSS :has()?

Explorons la :has()pseudo-classe, son utilisation et ses propriétés. Comme il s'agit d'une pseudo-classe, elle peut être attachée à n'importe quel sélecteur avec deux-points et accepte les classes, les ID et les balises HTML comme paramètres.

Le code ci-dessous explique son utilisation générale et sa syntaxe. La classe .selectorn'est sélectionnée que si elle contient les éléments qui lui sont passés en tant que paramètres à l'aide de la has()pseudo-classe :

.selector:has(.class) { ... }
.selector:has(#id) { ... }
.selector:has(div) { ... }

Chaînabilité

Vous pouvez enchaîner plusieurs :has()pseudo-classes les unes après les autres quand bon vous semble. Le code ci-dessous montre comment fonctionne ce chaînage :

.selector:has(div):has(.class):has(#id) {
  ...
}

Liste d'arguments pour plusieurs sélections

Vous pouvez également fournir une liste de plusieurs sélecteurs d'éléments, similaire au chaînage, mais beaucoup plus efficace :

.selector:has(div, .class, #id) {
  ...
}

Souplesse

Supposons que vous ayez accidentellement fourni un sélecteur d'élément invalide à la :has()pseudo-classe. Il est assez intelligent pour ignorer cela et ne considérer que les sélecteurs valides :

.selector:has(div, accordion, .class, ::lobster, #id) {
  ...
}

accordionet ::lobstersont des sélecteurs invalides et seront ignorés dans le cas ci-dessus. Vous ne verrez aucune erreur ou alerte CSS dans les outils de développement.

Scénarios d'utilisation

Jetons un coup d'œil à quelques exemples de scénarios dans lesquels le :has()sélecteur CSS est utile.

Sélection du père

C'est peut-être l'utilisation la plus courante du :has()sélecteur, car son comportement par défaut consiste à sélectionner quelque chose s'il contient un ensemble spécifique d'éléments. Mais que se passe-t-il si nous ne connaissons que l'élément enfant ?

Le sélecteur universel ( *) peut être utilisé ici avec :has()et le combinateur enfant ( >) pour sélectionner rapidement le parent sans même rien savoir à ce sujet.

Ici

Vérification de plusieurs enfants

Comme indiqué dans le segment des propriétés ci-dessus, :has()vous permet de transmettre une liste de plusieurs entités, ce qui signifie que vous pouvez rechercher autant de sélecteurs que vous le souhaitez dans un élément donné.

Ici

Sélection du frère précédent

La sélection du frère précédent est rendue possible en combinant le combinateur de frère adjacent CSS avec la :has()pseudo-classe.

Comme vous le savez peut-être déjà, le combinateur de frères adjacents sélectionne le frère suivant d'un élément donné. Nous pouvons utiliser ce comportement avec has()pour obtenir le frère précédent. En termes simples, si un élément a un frère suivant, il est facile de le sélectionner avec un :has()combinateur+ !

Ici

Décorations conditionnelles

Le style des choses avec et sans certains éléments enfants séparément peut être évité avec le :has()sélecteur. L'élément de figure avec et sans légende en est l'exemple parfait ici.

Ici

Que faire si un figcaptionélément est là, mais ne contient pas de texte ? Dans ce cas, nous pouvons utiliser les sélecteurs :notet :emptypour contrôler le contenu.

Semblable à la décoration des figures, nous pouvons facilement changer l'alignement du texte des guillemets avec plus d'un paragraphe. Voyez-le en action ici .

Styler les états vides

Nous avons déjà une pseudo-classe appelée :emptypour les éléments de style qui ne contiennent rien. En ce qui concerne le ciblage d'un état vide, cela ne fonctionne pas comme prévu. Un espace à l'intérieur suffit pour qu'il reconnaisse un élément comme non vide.

Ce n'est pas idéal à utiliser :emptydans ce cas. Créons une grille de cartes qui contient également quelques cartes sans contenu. Nous allons styliser les états de la carte vide avec le :has()sélecteur.

Ici

Ajustements de type et de bloc

Lors du style d'un article, maintenir l'alignement des éléments de type et de bloc est une tâche délicate. Prenons un exemple de mélange de blocs de code, de chiffres et de guillemets avec les éléments de type général.

Les éléments de bloc doivent avoir plus d'espacement vertical et de décorations pour se démarquer des différentes entités typographiques. Voici quelques CSS pour gérer cela.

p {
  margin: 0;
}

p:not(:last-child) {
  margin-bottom: 1.5em;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1.3;
  margin: 1.5em 0 1.5rem;
  color: #111;
}

pre,
figure,
blockquote {
  margin: 3em 0;
}

figcaption {
  margin-top: 1em;
  font-size: 0.75em;
  color: #888;
  text-align: center;
}

L'extrait ci-dessus produira un espacement vertical irrégulier entre les titres et les éléments de bloc, comme illustré ici. Mais il est possible de compenser ces irrégularités en utilisant le précédent hack de sélection des frères et sœurs.

Ici

CTA iconisés

Supposons que vous créez un CTA ou un composant de bouton en CSS avec deux variantes : la première est le bouton simple par défaut et la seconde est avec une icône.

Maintenant, si vous envisagez d'écrire deux classes distinctes pour cela, cela peut être facilement évité avec le :has()sélecteur. Le problème consiste simplement à vérifier l' .btnélément pour un .btn-iconenfant, puis à le styler en conséquence.

Ici

Ajustements de mise en page

Disons que vous avez deux variantes de mise en page pour un composant d'en-tête : l'une est à largeur fixe et l'autre est liquide, bien sûr.

Pour conserver le contenu de l'en-tête dans une largeur fixe, nous devons ajouter un élément wrapper de contenu à l'intérieur. La seule différence entre le balisage de ces deux versions d'en-tête est l'élément wrapper.

Nous pouvons ensuite associer le sélecteur :has()with :not()et ajouter les classes CSS, comme indiqué dans la démonstration ci-dessous.

Ici

Un autre exemple d'ajustement de la disposition peut être de modifier le nombre de colonnes dans une grille dès qu'elles atteignent un certain nombre.

Cela peut être pratique lorsque vous n'utilisez pas la minmax()fonction qui détermine la meilleure largeur pour ajuster une colonne dans sa grille.

Ici

Comme vous pouvez le voir ci-dessus, la grille s'ajuste automatiquement à trois colonnes dès qu'elle dépasse la marque de deux éléments.

Meilleure convivialité du formulaire

L'interactivité est mieux définie lorsqu'elle est correctement connectée au feedback. En ce qui concerne les formulaires HTML interactifs, offrir à l'utilisateur des commentaires sur sa saisie est la meilleure chose que vous puissiez faire.

Avec l'aide des pseudo-classes :has(), :validet :invalid, nous pouvons rendre nos formulaires beaucoup plus dynamiques, et sans JavaScript impliqué.

Ici

Vérification de la prise en charge du navigateur

Les exemples discutés ci-dessus affichent une boîte de dialogue d'erreur si aucun support pour le :has()sélecteur n'est trouvé. Cela peut être fait en utilisant la @supportsrègle CSS, comme indiqué dans les extraits de code ci-dessous :

@supports (selector(:has(*))) {
  .selector:has(...) {
    ...  
  }
}

Ci-dessus se trouve la manière progressive de vérifier le support et de styliser l'élément selon les besoins. Si une base de code utilise directement certaines nouvelles fonctionnalités, voici un moyen d'écrire les versions rétrocompatibles de celles-ci :

@supports not (selector(:has(*))) {
  .selector {
    ...  
  }
}

La même chose peut également être utilisée pour avertir l'utilisateur si aucun support n'a été trouvé.

Vous pouvez également utiliser l'API de support en JavaScript pour détecter la prise en charge par le navigateur de différentes fonctionnalités. Voici un exemple de vérification de la :has()prise en charge uniquement en JavaScript :

if(!CSS.supports('selector(html:has(body))'))) { // if not supported
  // Use the conventional JavaScript ways to emulate :has()
}

Conclusion

Dans l'article ci-dessus, nous avons découvert :has(), un sélecteur CSS de niveau 4. Nous avons couvert ses besoins, certains cas où il peut remplacer JavaScript dans vos projets frontend, et des cas d'utilisation généraux à avancés.

Nous avons également discuté de la prise en charge actuelle offerte par différents navigateurs et appris comment vérifier la prise en charge du navigateur.

Merci d'avoir lu jusqu'au bout. J'espère que vous avez appris quelque chose de nouveau grâce à cela. N'hésitez pas à partager plus d'exemples de ce :has()qui peut être utile dans les commentaires. 

Source : https://blog.logrocket.com/advanced-guide-css-has-selector/

#css 

Le Guide Avancé Du Sélecteur CSS :has()

O Guia Avançado Para O Seletor CSS :has()

A pseudoclasse CSS :has()tem sido um dos recursos mais esperados há anos. É um seletor de CSS de nível 4, agora disponível como um recurso totalmente compatível no Chrome 105 em diante e provavelmente se tornará um recurso regular em breve também em outros navegadores.

O :has()in CSS é uma pseudo-classe relacional que permite verificar se um determinado elemento contém certos elementos filho, selecioná-lo se houver alguma correspondência e, em seguida, estilize-o de acordo.

Este artigo explica a necessidade do :has()seletor, seu uso geral, vários aplicativos e casos de uso do simples ao avançado, compatibilidade do navegador e os fallbacks.

Por que o :has()seletor é necessário?

A maioria dos desenvolvedores depende do JavaScript para certos recursos que o CSS não suporta por padrão. No entanto, os navegadores da web hoje estão mais poderosos do que nunca, o que está abrindo portas para muitos recursos CSS novos e interessantes.

O :has()seletor é um desses recursos. Ele funciona no pai em vez de nos filhos e usa uma lista de seletores separados por vírgulas como argumentos e, em seguida, procura correspondências entre os filhos do elemento que está representando. Sua funcionalidade é semelhante ao has()método jQuery.

Vamos examinar algumas das necessidades gerais do :has()seletor no desenvolvimento de front-end que explicam por que esse recurso teve uma demanda tão alta por anos.

A primeira é a possibilidade de verificar se um elemento contém certos outros elementos ou não e, em seguida, estilizá-lo de acordo. JavaScript era a única maneira de fazer isso antes, como você pode ver aqui:

let content = document.querySelector("#content"),
    headings = [] 

if(content) {
  headings = content.querySelectorAll("h1, h2, h3")
}

if(headings.length) {
  // do something
}

No código acima, usamos querySelectorAll, que é um método de API da Web para verificar um elemento de divisão para títulos. O método retorna um NodeList dos elementos fornecidos.

Um NodeList vazio significaria que não há elementos presentes no pai imediato. Uma implementação do acima pode ser encontrada aqui . Ou você pode ver a alternativa CSS:has() aqui .

A segunda é a capacidade de selecionar o elemento pai dos filhos. Novamente, na falta de um recurso CSS para fazer isso, os desenvolvedores confiaram na propriedade parentNode da API da Web:

let el = document.querySelector(".someElement"),
    elParent = null

if(el) {
  elParent = el.parentNode
}

Veja uma  demonstração do CodePen do código acima aqui e como o :has()seletor implementa isso aqui .

Por fim, há a possibilidade de selecionar o irmão anterior de um determinado elemento. O seletor de irmãos em CSS permite que você selecione o próximo irmão, mas não havia uma maneira somente de CSS de selecionar irmãos anteriores. A implementação do JavaScript se parece com isso ( veja uma demonstração do CodePen aqui ):

let el = document.querySelector(".someElement"),
    elPs = null

if(el) {
  elPs = el.previousElementSibling
}

Você pode ver como realizar esta ação com o :has()seletor aqui .

Tudo o que fizemos acima com JavaScript agora pode ser feito com CSS :has(). Vamos abordá-los um por um na próxima seção depois de aprender como ativar e testar esse recurso no Google Chrome.

Habilitando o suporte para o :has()seletor

Se você estiver em uma versão mais antiga do Chrome (v.101-104), poderá ativar esse recurso nos sinalizadores do Chrome. Certifique-se de ter o Chrome 101+ e navegue até chrome://flags na barra de endereços do navegador.

Defina os recursos da Plataforma Web Experimental como Ativado e pronto. Reinicie o navegador e você poderá trabalhar com CSS :has()em seu navegador Chrome.

plataforma da web experimental recursos da guia chrome

O que o seletor CSS faz?:has()

Vamos explorar a :has()pseudoclasse, seu uso e propriedades. Por ser uma pseudo-classe, ela pode ser anexada a qualquer seletor com dois pontos e aceita classes, IDs e tags HTML como parâmetros.

O código abaixo explica seu uso geral e sintaxe. A classe .selectorsó é selecionada se contiver os elementos passados ​​a ela como parâmetros usando a has()pseudoclasse:

.selector:has(.class) { ... }
.selector:has(#id) { ... }
.selector:has(div) { ... }

Encadeamento

Você pode encadear várias :has()pseudo-classes uma após a outra sempre que achar melhor. O código abaixo demonstra como esse encadeamento funciona:

.selector:has(div):has(.class):has(#id) {
  ...
}

Lista de argumentos para várias seleções

Você também pode fornecer uma lista de seletores de vários elementos, semelhante ao encadeamento, mas muito mais eficiente:

.selector:has(div, .class, #id) {
  ...
}

Flexibilidade

Suponha que você acidentalmente forneceu um seletor de elemento inválido para a :has()pseudoclasse. É inteligente o suficiente para ignorar isso e considerar apenas os seletores válidos:

.selector:has(div, accordion, .class, ::lobster, #id) {
  ...
}

accordione ::lobstersão seletores inválidos e serão ignorados no caso acima. Você não verá erros ou alertas de CSS nas ferramentas do desenvolvedor.

Cenários de uso

Vamos dar uma olhada em alguns cenários de exemplo em que o :has()seletor de CSS é útil.

Selecionando o pai

Esse pode ser o uso mais comum do :has()seletor, pois seu comportamento padrão é selecionar algo se ele contiver um conjunto específico de elementos. Mas e se estivermos cientes apenas do elemento filho?

O seletor universal ( *) pode ser usado aqui com :has()e o combinador filho ( >) para selecionar rapidamente o pai sem saber nada sobre isso.

Aqui

Verificando se há vários filhos

Conforme discutido no segmento de propriedades acima, :has()permite passar uma lista de várias entidades, o que significa que você pode verificar quantos seletores desejar em um determinado elemento.

Aqui

Selecionando o irmão anterior

A seleção do irmão anterior é possível combinando o combinador irmão adjacente CSS com a :has()pseudo-classe.

Como você já deve saber, o combinador de irmãos adjacentes seleciona o próximo irmão de um determinado elemento. Podemos usar esse comportamento com has()para obter o irmão anterior. Simplificando, se um elemento tem um próximo irmão, é fácil selecioná-lo com um :has()combinador +!

Aqui

Decorações condicionais

Estilizar coisas com e sem certos elementos filho separadamente pode ser evitado com o :has()seletor. O elemento de figura com e sem legenda é o exemplo perfeito aqui.

Aqui

E se um figcaptionelemento estiver lá, mas não contiver nenhum texto? Nesse caso, podemos usar os seletores :note para verificar o conteúdo.:empty

Semelhante à decoração de figuras, podemos alternar facilmente o alinhamento do texto das citações em bloco com mais de um parágrafo. Veja-o em ação aqui .

Estilizando estados vazios

Já temos uma pseudo-classe chamada :emptypara elementos de estilo que não contêm nada. No que diz respeito ao direcionamento de um estado vazio, ele não funciona conforme o esperado. Um espaço interno é suficiente para reconhecer um elemento como não vazio.

Não é ideal para usar :emptyneste caso. Vamos criar uma grade de cartões que também contém alguns cartões sem conteúdo. Vamos estilizar os estados vazios do cartão com o :has()seletor.

Aqui

Ajustes de tipo e bloco

Ao estilizar um artigo, manter os elementos de tipo e bloco alinhados é uma tarefa complicada. Considere um exemplo de mistura de blocos de código, figuras e cotações de bloco com os elementos de tipo geral.

Os elementos do bloco devem ter mais espaçamento vertical e decorações para se destacar entre as diferentes entidades tipográficas. Aqui está algum CSS para lidar com isso.

p {
  margin: 0;
}

p:not(:last-child) {
  margin-bottom: 1.5em;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1.3;
  margin: 1.5em 0 1.5rem;
  color: #111;
}

pre,
figure,
blockquote {
  margin: 3em 0;
}

figcaption {
  margin-top: 1em;
  font-size: 0.75em;
  color: #888;
  text-align: center;
}

O snippet acima produzirá espaçamento vertical desigual entre títulos e elementos de bloco, conforme mostrado aqui. Mas é possível compensar essas irregularidades usando o hack de seleção de irmãos anterior.

Aqui

CTAs iconizadas

Suponha que você esteja criando um CTA ou componente de botão em CSS com duas variações: a primeira é o botão padrão, simples, e a segunda é com um ícone.

Agora, se você planeja escrever duas classes separadas para isso, isso pode ser facilmente evitado com o :has()seletor. O problema é simplesmente verificar o .btnelemento para um .btn-iconfilho e, em seguida, estilizá-lo de acordo.

Aqui

Ajustes de layout

Digamos que você tenha duas variações de layout para um componente de cabeçalho: uma é de largura fixa e a outra é líquida, é claro.

Para manter o conteúdo do cabeçalho dentro de uma largura fixa, temos que adicionar um elemento wrapper de conteúdo dentro dele. A única diferença entre a marcação dessas duas versões de cabeçalho é o elemento wrapper.

Podemos então emparelhar o seletor :has()com :not()e adicionar as classes CSS, conforme mostrado na demonstração abaixo.

Aqui

Outro exemplo de ajuste de layout pode ser a modificação do número de colunas em uma grade assim que atingirem um determinado número.

Isso pode ser útil quando você não estiver usando a minmax()função que determina a melhor largura para ajustar uma coluna em sua grade.

Aqui

Como você pode ver acima, a grade se ajusta automaticamente para três colunas assim que ultrapassa a marca de dois itens.

Melhor usabilidade do formulário

A interatividade é melhor definida quando está devidamente conectada ao feedback. Quando se trata de formulários HTML interativos, oferecer feedback ao usuário sobre sua entrada é a melhor coisa que você pode fazer.

Com a ajuda das classes :has(), :valid, e :invalidpseudo-classes, podemos tornar nossos formulários muito mais dinâmicos e sem JavaScript envolvido.

Aqui

Verificando o suporte do navegador

Os exemplos discutidos acima mostram uma caixa de diálogo de erro se nenhum suporte para o :has()seletor for encontrado. Isso pode ser feito usando a @supportsregra CSS, conforme mostrado nos trechos de código abaixo:

@supports (selector(:has(*))) {
  .selector:has(...) {
    ...  
  }
}

Acima está a maneira progressiva de verificar o suporte e estilizar o elemento conforme necessário. Se uma base de código usa alguns novos recursos diretamente, aqui está uma maneira de escrever as versões compatíveis com versões anteriores do mesmo:

@supports not (selector(:has(*))) {
  .selector {
    ...  
  }
}

O mesmo também pode ser usado para avisar o usuário se nenhum suporte foi encontrado.

Você também pode usar a API de suporte em JavaScript para detectar o suporte do navegador para diferentes recursos. Aqui está um exemplo dessa verificação de :has()suporte puramente em JavaScript:

if(!CSS.supports('selector(html:has(body))'))) { // if not supported
  // Use the conventional JavaScript ways to emulate :has()
}

Conclusão

No artigo acima, aprendemos sobre :has(), um seletor CSS de nível 4. Cobrimos suas necessidades, alguns casos em que ele pode substituir o JavaScript em seus projetos de front-end e casos de uso gerais a avançados.

Também discutimos o suporte atual oferecido a ele por diferentes navegadores e aprendemos como verificar o suporte do navegador.

Obrigado por ler todo o caminho. Espero que você tenha aprendido algo novo com isso. Sinta-se à vontade para compartilhar mais exemplos de como :has()pode ser útil nos comentários. 

Fonte: https://blog.logrocket.com/advanced-guide-css-has-selector/

#css 

O Guia Avançado Para O Seletor CSS :has()

La Guía Avanzada Del Selector CSS :has()

La pseudoclase CSS :has()ha sido una de las características más esperadas durante años. Es un selector de CSS de nivel 4, ahora disponible como una función totalmente compatible en Chrome 105 en adelante , y probablemente también se convertirá en una función regular pronto en otros navegadores.

El :has()CSS es una pseudoclase relacional que le permite verificar si un elemento dado contiene ciertos elementos secundarios, seleccionarlo si se encuentra alguna coincidencia y luego diseñarlo en consecuencia.

Este artículo explica la necesidad del :has()selector, su uso general, varias aplicaciones y casos de uso, desde simples hasta avanzados, la compatibilidad del navegador y las alternativas.

¿Por qué es :has()necesario el selector?

La mayoría de los desarrolladores confían en JavaScript para ciertas funciones que CSS no admite de forma predeterminada. Sin embargo, los navegadores web de hoy en día son más potentes que nunca, lo que abre las puertas a muchas funciones CSS nuevas e interesantes.

El :has()selector es una de esas características. Funciona en el padre en lugar de en los hijos, y usa una lista de selectores separados por comas como argumentos, luego busca coincidencias entre los hijos del elemento que representa. Su funcionalidad es similar al has()método jQuery.

Repasemos algunas de las necesidades generales del :has()selector en el desarrollo frontend que explican por qué esta función tuvo una demanda tan alta durante años.

Primero está la posibilidad de verificar si un elemento contiene ciertos otros elementos o no, y luego diseñarlo en consecuencia. JavaScript era la única forma de hacer esto antes, como puede ver aquí:

let content = document.querySelector("#content"),
    headings = [] 

if(content) {
  headings = content.querySelectorAll("h1, h2, h3")
}

if(headings.length) {
  // do something
}

En el código anterior, hemos utilizado querySelectorAll, que es un método de la API web para verificar los encabezados de un elemento de división. El método devuelve una lista de nodos de los elementos proporcionados.

Una NodeList vacía significaría que no hay elementos presentes en el padre inmediato. Una implementación de lo anterior se puede encontrar aquí . O bien, puede ver la alternativa de CSS:has() aquí .

En segundo lugar está la capacidad de seleccionar el elemento principal de los elementos secundarios. Una vez más, a falta de una función de CSS para hacerlo, los desarrolladores se han basado en la propiedad parentNode de Web API:

let el = document.querySelector(".someElement"),
    elParent = null

if(el) {
  elParent = el.parentNode
}

Vea una  demostración de CodePen del código anterior aquí , y cómo el :has()selector implementa esto aquí .

Finalmente, existe la posibilidad de seleccionar el hermano anterior de un elemento dado. El selector de hermanos en CSS le permite seleccionar al siguiente hermano, pero no había una forma de CSS solo para seleccionar hermanos anteriores. La implementación de JavaScript se ve así ( ver una demostración de CodePen aquí ):

let el = document.querySelector(".someElement"),
    elPs = null

if(el) {
  elPs = el.previousElementSibling
}

Puedes ver cómo realizar esta acción con el :has()selector aquí .

Todo lo que hicimos anteriormente con JavaScript ahora se puede lograr con CSS :has(). Los cubriremos uno por uno en la siguiente sección después de aprender cómo habilitar y probar esta función en Google Chrome.

Habilitación del soporte para el :has()selector

Si tiene una versión anterior de Chrome (v.101-104), puede habilitar esta función desde las banderas de Chrome. Asegúrate de tener Chrome 101+ y navega hasta chrome://flags desde la barra de direcciones del navegador.

Establezca las características de la plataforma web experimental en Habilitado y listo. Vuelva a iniciar el navegador y podrá trabajar con CSS :has()en su navegador Chrome.

pestaña de características de la plataforma web experimental chrome

¿Qué hace el selector de CSS ?:has()

Exploremos la :has()pseudoclase, su uso y propiedades. Debido a que es una pseudoclase, se puede adjuntar a cualquier selector con dos puntos y acepta clases, ID y etiquetas HTML como parámetros.

El siguiente código explica su uso general y sintaxis. La clase .selectorsolo se selecciona si contiene los elementos que se le pasan como parámetros usando la has()pseudoclase:

.selector:has(.class) { ... }
.selector:has(#id) { ... }
.selector:has(div) { ... }

Encadenabilidad

Puede encadenar varias :has()pseudoclases una tras otra cuando lo considere oportuno. El siguiente código demuestra cómo funciona este encadenamiento:

.selector:has(div):has(.class):has(#id) {
  ...
}

Lista de argumentos para selecciones múltiples

También puede proporcionar una lista de selectores de elementos múltiples, similar al encadenamiento, pero mucho más eficiente:

.selector:has(div, .class, #id) {
  ...
}

Flexibilidad

Suponga que accidentalmente proporcionó un selector de elementos no válido a la :has()pseudoclase. Es lo suficientemente inteligente como para ignorar eso y considerar solo los selectores válidos:

.selector:has(div, accordion, .class, ::lobster, #id) {
  ...
}

accordiony ::lobsterson selectores no válidos y se ignorarán en el caso anterior. No verá errores de CSS ni alertas en las herramientas para desarrolladores.

Escenarios de uso

Echemos un vistazo a algunos escenarios de ejemplo en los que el :has()selector de CSS resulta útil.

Seleccionando el padre

Este puede ser el uso más común del :has()selector, porque su comportamiento predeterminado es seleccionar algo si contiene un conjunto específico de elementos. Pero, ¿y si solo somos conscientes del elemento hijo?

El selector universal ( *) se puede usar aquí con :has()el combinador secundario ( >) para seleccionar rápidamente el principal sin siquiera saber nada al respecto.

Aquí

Comprobación de varios niños

Como se discutió en el segmento de propiedades anterior, :has()le permite pasar una lista de múltiples entidades, lo que significa que puede verificar tantos selectores como desee dentro de un elemento dado.

Aquí

Selección del hermano anterior

La selección del hermano anterior es posible al combinar el combinador de hermanos adyacentes de CSS con la :has()pseudoclase.

Como ya sabrá, el combinador de hermanos adyacentes selecciona al siguiente hermano de un elemento dado. Podemos usar este comportamiento con has()para obtener el hermano anterior. En pocas palabras, si un elemento tiene un hermano, ¡es fácil seleccionarlo con un :has()combinador +!

Aquí

Decoraciones condicionales

El estilo de las cosas con y sin ciertos elementos secundarios por separado se puede evitar con el :has()selector. El elemento de figura con y sin título es el ejemplo perfecto aquí.

Aquí

¿Qué pasa si un figcaptionelemento está ahí, pero no contiene ningún texto? En ese caso, podemos usar los selectores :noty :emptypara controlar el contenido.

Similar a la decoración de figuras, podemos cambiar fácilmente la alineación del texto de las citas en bloque con más de un párrafo. Véalo en acción aquí .

Estilo de estados vacíos

Ya tenemos una pseudoclase llamada :emptypara diseñar elementos que no contienen nada. En lo que respecta a apuntar a un estado vacío, no funciona como se esperaba. Un espacio dentro es suficiente para que reconozca un elemento como no vacío.

No es ideal para usar :emptyen este caso. Vamos a crear una cuadrícula de tarjetas que también contenga algunas tarjetas sin contenido. Diseñaremos los estados de tarjeta vacíos con el :has()selector.

Aquí

Ajustes de tipo y bloque

Al diseñar un artículo, mantener alineados los elementos de tipo y bloque es un trabajo complicado. Considere un ejemplo de combinación de bloques de código, figuras y comillas de bloque con los elementos de tipo general.

Los elementos de bloque deben tener más espaciado vertical y decoraciones para destacarse entre las diferentes entidades tipográficas. Aquí hay algo de CSS para manejar eso.

p {
  margin: 0;
}

p:not(:last-child) {
  margin-bottom: 1.5em;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1.3;
  margin: 1.5em 0 1.5rem;
  color: #111;
}

pre,
figure,
blockquote {
  margin: 3em 0;
}

figcaption {
  margin-top: 1em;
  font-size: 0.75em;
  color: #888;
  text-align: center;
}

El fragmento anterior producirá un espacio vertical desigual entre los encabezados y los elementos del bloque, como se muestra aquí. Pero es posible compensar estas irregularidades utilizando el truco de selección de hermanos anterior.

Aquí

CTA iconizados

Suponga que está creando un CTA o componente de botón en CSS con dos variaciones: la primera es el botón simple predeterminado y la segunda es con un icono.

Ahora, si planea escribir dos clases separadas para eso, puede evitarlo fácilmente con el :has()selector. El truco es simplemente verificar el .btnelemento para un .btn-iconniño y luego diseñarlo en consecuencia.

Aquí

Ajustes de diseño

Digamos que tiene dos variaciones de diseño para un componente de encabezado: uno es de ancho fijo y el otro es líquido, por supuesto.

Para mantener el contenido del encabezado dentro de un ancho fijo, debemos agregar un elemento contenedor de contenido dentro de él. La única diferencia entre el marcado de estas dos versiones de encabezado es el elemento contenedor.

Luego podemos emparejar el selector :has()con :not()y agregar las clases de CSS, como se muestra en la siguiente demostración.

Aquí

Otro ejemplo de ajuste del diseño puede ser modificar el número de columnas en una cuadrícula tan pronto como alcancen un número determinado.

Esto puede ser útil cuando no está utilizando la minmax()función que determina el mejor ancho para que quepa una columna en su cuadrícula.

Aquí

Como puede ver arriba, la cuadrícula se ajusta automáticamente a tres columnas tan pronto como excede la marca de dos elementos.

Mejor usabilidad del formulario

La interactividad se define mejor cuando está correctamente conectada a la retroalimentación. Cuando se trata de formularios HTML interactivos, lo mejor que puede hacer es ofrecer comentarios al usuario sobre su entrada.

Con la ayuda de las pseudoclases :has(), :validy :invalid, podemos hacer que nuestros formularios sean mucho más dinámicos y sin JavaScript involucrado.

Aquí

Comprobación de la compatibilidad del navegador

Los ejemplos discutidos anteriormente muestran un cuadro de diálogo de error si no :has()se encuentra soporte para el selector. Esto se puede hacer usando la @supportsregla CSS, como se muestra en los siguientes fragmentos de código:

@supports (selector(:has(*))) {
  .selector:has(...) {
    ...  
  }
}

Arriba está la forma progresiva de verificar el soporte y diseñar el elemento según sea necesario. Si un código base usa algunas características nuevas directamente, aquí hay una forma de escribir las versiones compatibles con versiones anteriores del mismo:

@supports not (selector(:has(*))) {
  .selector {
    ...  
  }
}

Lo mismo también se puede usar para avisar al usuario si no se encontró soporte.

También puede usar la API de soporte en JavaScript para detectar el soporte del navegador para diferentes funciones. Aquí hay un ejemplo de esa verificación de :has()soporte puramente en JavaScript:

if(!CSS.supports('selector(html:has(body))'))) { // if not supported
  // Use the conventional JavaScript ways to emulate :has()
}

Conclusión

En el artículo anterior, aprendimos sobre :has(), un selector de CSS de nivel 4. Cubrimos sus necesidades, algunos casos en los que puede reemplazar JavaScript en sus proyectos frontend y casos de uso generales a avanzados.

También discutimos el soporte actual que le ofrecen diferentes navegadores y aprendimos cómo verificar el soporte del navegador.

Gracias por leer hasta el final. Espero que hayas aprendido algo nuevo a través de esto. Siéntase libre de compartir más ejemplos de cómo :has()puede ser útil en los comentarios. 

Fuente: https://blog.logrocket.com/advanced-guide-css-has-selector/

#css 

La Guía Avanzada Del Selector CSS :has()
高橋  花子

高橋 花子

1659146400

CSSの高度なガイド:has()セレクター

CSS:has()疑似クラスは、何年もの間最も待望されていた機能の1つです。これはレベル4のCSSセレクターであり、 Chrome 105以降で完全にサポートされる機能として利用できるようになり、他のブラウザーでもまもなく通常の機能になる可能性があります。

in CSSはリレーショナル疑似クラスであり、:has()特定の要素に特定の子要素が含まれているかどうかを確認し、一致する要素が見つかった場合はそれを選択して、それに応じてスタイルを設定できます。

この記事では、:has()セレクターの必要性、その一般的な使用法、単純なものから高度なものまでのさまざまなアプリケーションとユースケース、ブラウザーの互換性、およびフォールバックについて説明します。

なぜ:has()セレクターが必要なのですか?

ほとんどの開発者は、CSSがデフォルトでサポートしていない特定の機能をJavaScriptに依存しています。ただし、今日のWebブラウザーはこれまでになく強力であり、多くの新しく興味深いCSS機能への扉を開いています。

:has()セレクターはそのような機能の1つです。子ではなく親で機能し、セレクターのコンマ区切りリストを引数として使用して、それが表す要素の子の間で一致するものを探します。その機能はjQueryhas()メソッドに似ています。

:has()フロントエンド開発におけるセレクターの一般的なニーズのいくつかを調べて、この機能が何年にもわたって非常に高い需要があった理由を説明しましょう。

1つは、要素に他の特定の要素が含まれているかどうかを確認し、それに応じてスタイルを設定する可能性です。あなたがここで見ることができるように、JavaScriptは以前これをする唯一の方法でした:

let content = document.querySelector("#content"),
    headings = [] 

if(content) {
  headings = content.querySelectorAll("h1, h2, h3")
}

if(headings.length) {
  // do something
}

上記のコードquerySelectorAllでは、見出しの分割要素をチェックするためのWebAPIメソッドであるを使用しました。このメソッドは、指定された要素のNodeListを返します。

空のNodeListは、直接の親に要素が存在しないことを意味します。上記の実装はここにあります。または、 CSSの:has()代替案をここで確認できます。

2つ目は、子から親要素を選択する機能です。繰り返しになりますが、そうするためのCSS機能がないため、開発者はWebAPIのparentNodeプロパティに依存しています。

let el = document.querySelector(".someElement"),
    elParent = null

if(el) {
  elParent = el.parentNode
}

上記のコードのCodePenデモと、セレクターがこれを実装する方法:has()をここで表示し ます。

最後に、特定の要素の前の兄弟を選択する機能があります。CSSの兄弟セレクターを使用すると、次の兄弟を選択できますが、前の兄弟を選択するCSSのみの方法はありませんでした。JavaScriptの実装は次のようになります(CodePenのデモはこちらからご覧ください)。

let el = document.querySelector(".someElement"),
    elPs = null

if(el) {
  elPs = el.previousElementSibling
}

セレクターを使用してこのアクションを実行する方法については:has()こちらをご覧ください。

上記でJavaScriptを使用して行ったすべてのことは、CSSを使用して実現できるようになりました:has()。Google Chromeでこの機能を有効にしてテストする方法を学習した後、次のセクションでそれらを1つずつ説明します。

セレクターのサポートを有効にする:has()

古いバージョンのChrome(v.101-104)を使用している場合は、Chromeフラグからこの機能を有効にできます。Chrome 101以降を使用していることを確認し、ブラウザのアドレスバーからchrome://flagsに移動します。

Experimental Web Platformの機能を[有効]に設定すると、準備が整います。:has()ブラウザを再起動すると、ChromeブラウザでCSSを操作できます。

実験的なウェブプラットフォームはタブクロームを備えています

CSS:has()セレクターは何をしますか?

:has()疑似クラス、その使用法、およびプロパティを調べてみましょう。疑似クラスであるため、コロンを使用して任意のセレクターにアタッチでき、パラメーターとしてクラス、ID、およびHTMLタグを受け入れます。

以下のコードは、その一般的な使用法と構文を説明しています。クラスは、疑似クラス.selectorを使用してパラメーターとして渡された要素が含まれている場合にのみ選択されます。has()

.selector:has(.class) { ... }
.selector:has(#id) { ... }
.selector:has(div) { ... }

連鎖性

:has()適切と思われる場合はいつでも、複数の疑似クラスを次々にチェーンできます。以下のコードは、この連鎖がどのように機能するかを示しています。

.selector:has(div):has(.class):has(#id) {
  ...
}

複数選択の引数リスト

チェーンと同様に、複数の要素セレクターのリストを提供することもできますが、はるかに効率的です。

.selector:has(div, .class, #id) {
  ...
}

柔軟性

誤って無効な要素セレクターを:has()疑似クラスに指定したとします。それを無視して、有効なセレクターのみを検討するのは十分にインテリジェントです。

.selector:has(div, accordion, .class, ::lobster, #id) {
  ...
}

accordionおよび::lobsterは無効なセレクターであり、上記の場合は無視されます。開発者ツールにCSSエラーやアラートは表示されません。

使用シナリオ

:has()CSSのセレクターが役立つシナリオの例をいくつか見てみましょう。

親の選択

:has()セレクターのデフォルトの動作は、特定の要素のセットが含まれている場合に何かを選択することであるため、これはセレクターの最も一般的な使用法である可能性があります。しかし、子要素のみを認識している場合はどうなるでしょうか。

ここでは、ユニバーサルセレクター(*:has()と子コンビネーター(>)を使用して、親について何も知らなくてもすばやく親を選択できます。

ここ

複数の子をチェックする

上記のプロパティセグメントで説明したように、:has()複数のエンティティのリストを渡すことができます。つまり、特定の要素内で必要な数のセレクターを確認できます。

ここ

前の兄弟を選択する

:has()前の兄弟の選択は、CSS隣接兄弟コンビネータを疑似クラスと組み合わせることによって可能になります。

ご存知かもしれませんが、隣接する兄弟コンビネータは、特定の要素の次の兄弟を選択します。この動作をで使用しhas()て、前の兄弟を取得できます。簡単に言えば、要素に次の兄弟がある場合、コンビネータを使用して簡単に選択でき:has()ます+

ここ

条件付き装飾

:has()セレクターを使用すると、特定の子要素がある場合とない場合のスタイリングを回避できます。キャプションのある場合とない場合のFigure要素は、ここでの完璧な例です。

ここ

figcaption要素はあるがテキストが含まれていない場合はどうなりますか?その場合、:not:emptyセレクターを使用してコンテンツをチェックできます。

図の装飾と同様に、複数の段落を持つブロック引用符のテキスト配置を簡単に切り替えることができます。ここで実際の動作を確認してください。

空の状態のスタイリング

:empty何も含まないスタイリング要素を要求する疑似クラスがすでにあります。空の状態をターゲットにする限り、期待どおりに機能しません。要素を空でないものとして認識するには、内部に1つのスペースで十分です。

この場合に使用するのは理想的ではありません:empty。コンテンツのないカードもいくつか含まれるカードグリッドを作成しましょう。:has()空のカードの状態をセレクターでスタイリングします。

ここ

タイプとブロックの調整

記事のスタイルを設定する際、タイプ要素とブロック要素を揃えておくのは難しい作業です。コードブロック、図、およびブロック引用符を一般的な型要素と混合する例を考えてみましょう。

ブロック要素は、さまざまな活版印刷エンティティの中で目立つように、より垂直方向の間隔と装飾を持たせる必要があります。これを処理するためのCSSがいくつかあります。

p {
  margin: 0;
}

p:not(:last-child) {
  margin-bottom: 1.5em;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1.3;
  margin: 1.5em 0 1.5rem;
  color: #111;
}

pre,
figure,
blockquote {
  margin: 3em 0;
}

figcaption {
  margin-top: 1em;
  font-size: 0.75em;
  color: #888;
  text-align: center;
}

上記のスニペットは、ここに示すように、見出しとブロック要素の間に不均一な垂直方向の間隔を生成します。しかし、以前の兄弟選択ハックを使用して、これらの不規則性を補うことは可能です。

ここ

アイコン化されたCTA

CSSでCTAまたはボタンコンポーネントを2つのバリエーションで作成するとします。最初のコンポーネントはデフォルトのプレーンボタンで、2番目のコンポーネントはアイコン付きです。

:has()これで、そのために2つの別々のクラスを作成することを計画している場合は、セレクターを使用して簡単に回避できます。キャッチは、単に子の.btn要素をチェックし、.btn-iconそれに応じてスタイルを設定することです。

ここ

レイアウト調整

もちろん、ヘッダーコンポーネントに2つのレイアウトバリエーションがあるとします。1つは固定幅で、もう1つは液体です。

ヘッダーのコンテンツを固定幅内に維持するには、ヘッダー内にコンテンツラッパー要素を追加する必要があります。これら2つのヘッダーバージョンのマークアップの唯一の違いは、ラッパー要素です。

次に、以下のデモンストレーションに示すように、:has()とセレクターをペアにして、CSSクラスを追加できます。:not()

ここ

レイアウトを調整する別の例は、グリッド内の列の数が特定の数に達したらすぐに変更することです。

minmax()これは、列をグリッドに合わせるのに最適な幅を決定する関数を使用していない場合に便利です。

ここ

上記のように、グリッドは2つのアイテムのマークを超えるとすぐに、自動的に3列に調整されます。

フォームの使いやすさの向上

対話性は、フィードバックに適切に接続されている場合に最もよく定義されます。インタラクティブなHTMLフォームに関しては、入力に関するフィードバックをユーザーに提供することが最善の方法です。

:has()、、:validおよび疑似クラスの助けを借りて、:invalidJavaScriptを使用せずに、フォームをより動的にすることができます。

ここ

ブラウザのサポートを確認しています

上記の例は、:has()セレクターのサポートが見つからない場合のエラーダイアログを示しています。これは@supports、以下のコードスニペットに示すように、CSSルールを使用して実行できます。

@supports (selector(:has(*))) {
  .selector:has(...) {
    ...  
  }
}

上記は、サポートを確認し、必要に応じて要素のスタイルを設定するための段階的な方法です。コードベースがいくつかの新機能を直接使用する場合、同じものの下位互換性のあるバージョンを作成する方法は次のとおりです。

@supports not (selector(:has(*))) {
  .selector {
    ...  
  }
}

同じことを使用して、サポートが見つからなかった場合にユーザーにプロンプ​​トを表示することもできます。

JavaScriptのSupportAPIを使用して、さまざまな機能のブラウザーサポートを検出することもできます。:has()純粋にJavaScriptでサポートをチェックする例を次に示します。

if(!CSS.supports('selector(html:has(body))'))) { // if not supported
  // Use the conventional JavaScript ways to emulate :has()
}

結論

:has()上記の記事では、レベル4のCSSセレクターについて学びました。そのニーズ、フロントエンドプロジェクトでJavaScriptを置き換えることができるいくつかのケース、および一般的なユースケースから高度なユースケースまでカバーしました。

また、さまざまなブラウザーによって提供されている現在のサポートについて説明し、ブラウザーのサポートを確認する方法を学びました。

ずっと読んでくれてありがとう。これを通して何か新しいことを学んだことを願っています。:has()コメントでどのように役立つ かについて、より多くの例を自由に共有してください。

ソース:https ://blog.logrocket.com/advanced-guide-css-has-selector/

#css 

CSSの高度なガイド:has()セレクター
Hoang  Ha

Hoang Ha

1659146400

Hướng Dẫn Nâng Cao Về Bộ Chọn CSS: Has ()

Lớp giả CSS :has()là một trong những tính năng được chờ đợi nhất trong nhiều năm. Đó là bộ chọn CSS cấp 4, hiện có sẵn dưới dạng tính năng được hỗ trợ đầy đủ trong Chrome 105 trở đi và có khả năng sẽ sớm trở thành một tính năng thông thường trên các trình duyệt khác.

Trong :has()CSS là một lớp giả quan hệ cho phép bạn kiểm tra xem một phần tử đã cho có chứa các phần tử con nhất định hay không, chọn nó nếu tìm thấy bất kỳ kết quả phù hợp nào và sau đó tạo kiểu cho nó.

Bài viết này giải thích sự cần thiết của :has()bộ chọn, cách sử dụng chung của nó, các ứng dụng và trường hợp sử dụng khác nhau từ đơn giản đến nâng cao, khả năng tương thích của trình duyệt và các dự phòng.

Tại sao :has()bộ chọn lại cần thiết?

Hầu hết các nhà phát triển dựa vào JavaScript cho một số tính năng nhất định mà CSS không hỗ trợ theo mặc định. Tuy nhiên, các trình duyệt web ngày nay mạnh mẽ hơn bao giờ hết, điều này đang mở ra cánh cửa cho nhiều tính năng CSS mới và thú vị.

Bộ :has()chọn là một trong những tính năng như vậy. Nó hoạt động trên phần tử gốc chứ không phải phần tử con và sử dụng danh sách các bộ chọn được phân tách bằng dấu phẩy làm đối số, sau đó tìm kiếm các kết quả phù hợp giữa các phần tử con của phần tử mà nó đại diện. Chức năng của nó tương tự như has()phương thức jQuery.

Chúng ta hãy xem xét một số nhu cầu chung đối với :has()bộ chọn trong phát triển giao diện người dùng để giải thích lý do tại sao tính năng này lại có nhu cầu cao như vậy trong nhiều năm.

Đầu tiên là khả năng kiểm tra xem một phần tử có chứa một số phần tử khác hay không, và sau đó tạo kiểu cho nó. JavaScript là cách duy nhất để làm điều này trước đây, như bạn có thể thấy ở đây:

let content = document.querySelector("#content"),
    headings = [] 

if(content) {
  headings = content.querySelectorAll("h1, h2, h3")
}

if(headings.length) {
  // do something
}

Trong đoạn mã trên, chúng tôi đã sử dụng querySelectorAll, đó là một phương pháp API Web để kiểm tra một phần tử phân chia cho các tiêu đề. Phương thức trả về một NodeList của các phần tử được cung cấp.

Một NodeList trống có nghĩa là không có phần tử nào hiện diện trong phần tử gốc. Có thể tìm thấy cách triển khai ở trên ở đây . Hoặc, bạn có thể xem CSS :has()thay thế tại đây .

Thứ hai là khả năng chọn phần tử cha từ phần tử con. Một lần nữa, do thiếu cơ sở CSS để làm như vậy, các nhà phát triển đã dựa vào thuộc tính parentNode của Web API:

let el = document.querySelector(".someElement"),
    elParent = null

if(el) {
  elParent = el.parentNode
}

Xem  bản demo CodePen của đoạn mã ở trên tại đâycách :has()bộ chọn thực hiện điều này tại đây .

Cuối cùng, có khả năng chọn anh chị em trước của một phần tử nhất định. Bộ chọn anh chị em trong CSS cho phép bạn chọn anh chị em kế tiếp, nhưng không có cách nào chỉ có CSS ​​để chọn anh chị em trước đó. Việc triển khai JavaScript trông như thế này ( xem bản demo CodePen tại đây ):

let el = document.querySelector(".someElement"),
    elPs = null

if(el) {
  elPs = el.previousElementSibling
}

Bạn có thể xem cách thực hiện hành động này với bộ :has()chọn tại đây .

Mọi thứ chúng ta đã làm ở trên với JavaScript bây giờ có thể đạt được với CSS :has(). Chúng tôi sẽ trình bày từng cái một trong phần tiếp theo sau khi tìm hiểu cách bật và thử nghiệm tính năng này trên Google Chrome.

Bật hỗ trợ cho :has()bộ chọn

Nếu bạn đang sử dụng phiên bản Chrome cũ hơn (v.101-104), bạn có thể bật tính năng này từ cờ Chrome. Đảm bảo bạn có Chrome 101+ và điều hướng đến chrome: // flags từ thanh địa chỉ của trình duyệt.

Đặt các tính năng Nền tảng web thử nghiệm thành Đã bật và bạn đã sẵn sàng. Khởi chạy lại trình duyệt và bạn có thể làm việc với CSS :has()trong trình duyệt Chrome của mình.

tính năng nền tảng web thử nghiệm tab chrome

Bộ chọn CSS :has()làm gì?

Hãy cùng khám phá lớp :has()giả, cách sử dụng và thuộc tính của nó. Vì là lớp giả nên nó có thể được gắn vào bất kỳ bộ chọn nào bằng dấu hai chấm và chấp nhận các lớp, ID và thẻ HTML làm tham số.

Đoạn mã dưới đây giải thích cách sử dụng và cú pháp chung của nó. Lớp .selectorchỉ được chọn nếu nó chứa các phần tử được truyền cho nó dưới dạng tham số bằng cách sử dụng lớp has()giả:

.selector:has(.class) { ... }
.selector:has(#id) { ... }
.selector:has(div) { ... }

Cơ hội

Bạn có thể xâu chuỗi nhiều :has()lớp giả lần lượt bất cứ khi nào bạn thấy phù hợp. Đoạn mã dưới đây trình bày cách hoạt động của chuỗi này:

.selector:has(div):has(.class):has(#id) {
  ...
}

Danh sách đối số cho nhiều lựa chọn

Bạn cũng có thể cung cấp danh sách nhiều bộ chọn phần tử, tương tự như chuỗi, nhưng hiệu quả hơn nhiều:

.selector:has(div, .class, #id) {
  ...
}

Uyển chuyển

Giả sử bạn đã vô tình cung cấp một bộ chọn phần tử không hợp lệ cho lớp :has()giả. Đủ thông minh để bỏ qua điều đó và chỉ xem xét các bộ chọn hợp lệ:

.selector:has(div, accordion, .class, ::lobster, #id) {
  ...
}

accordion::lobsterlà các bộ chọn không hợp lệ và sẽ bị bỏ qua trong trường hợp trên. Bạn sẽ không thấy lỗi hoặc cảnh báo CSS nào trong các công cụ dành cho nhà phát triển.

Các tình huống sử dụng

Chúng ta hãy xem xét một số trường hợp ví dụ trong đó bộ chọn CSS :has()có ích.

Chọn cha mẹ

Đây có thể là cách sử dụng phổ biến nhất của :has()bộ chọn, bởi vì hành vi mặc định của nó là chọn một cái gì đó nếu nó chứa một tập hợp các phần tử cụ thể. Nhưng điều gì sẽ xảy ra nếu chúng ta chỉ biết đến phần tử con?

*Ở đây có thể sử dụng bộ chọn phổ quát ( ) với :has()và bộ tổ hợp con ( >) để nhanh chóng chọn bố mẹ mà không cần biết bất cứ điều gì về nó.

Nơi đây

Kiểm tra nhiều trẻ em

Như đã thảo luận trong phân đoạn thuộc tính ở trên, :has()cho phép bạn chuyển một danh sách gồm nhiều thực thể, có nghĩa là bạn có thể kiểm tra bao nhiêu bộ chọn tùy thích trong một phần tử nhất định.

Nơi đây

Chọn anh chị em trước

Việc lựa chọn anh chị em trước đó có thể thực hiện được bằng cách kết hợp bộ tổ hợp anh chị em liền kề CSS với lớp :has()giả.

Như bạn có thể đã biết, bộ tổ hợp anh chị em kế cận sẽ chọn phần tử anh chị em tiếp theo của một phần tử nhất định. Chúng ta có thể sử dụng hành vi này với has()để có được anh chị em trước đó. Nói một cách đơn giản, nếu một phần tử có anh chị em kế tiếp, thật dễ dàng để chọn nó với :has()+tổ hợp!

Nơi đây

Đồ trang trí có điều kiện

Có thể tránh tạo kiểu riêng cho những thứ có và không có một số phần tử con nhất định bằng :has()bộ chọn. Phần tử hình có và không có chú thích là ví dụ hoàn hảo ở đây.

Nơi đây

Điều gì sẽ xảy ra nếu một figcaptionphần tử ở đó, nhưng không chứa bất kỳ văn bản nào? Trong trường hợp đó, chúng tôi có thể sử dụng bộ chọn :not:emptyđể kiểm tra nội dung.

Tương tự như trang trí hình, chúng ta có thể dễ dàng chuyển đổi căn lề văn bản của các dấu ngoặc kép với nhiều hơn một đoạn văn. Xem nó hoạt động ở đây .

Tạo kiểu trạng thái trống

Chúng ta đã có một lớp giả được gọi :emptycho các phần tử tạo kiểu không chứa gì. Liên quan đến việc nhắm mục tiêu trạng thái trống, nó không hoạt động như mong đợi. Một khoảng trống bên trong là đủ để nó nhận ra một phần tử là không trống.

Nó không lý tưởng để sử dụng :emptytrong trường hợp này. Hãy tạo một lưới thẻ cũng chứa một vài thẻ không có nội dung. Chúng tôi sẽ tạo kiểu các trạng thái thẻ trống bằng :has()bộ chọn.

Nơi đây

Nhập và điều chỉnh khối

Trong khi tạo kiểu cho một bài báo, việc giữ cho các phần tử kiểu và khối được căn chỉnh là một công việc khó khăn. Hãy xem xét một ví dụ về trộn các khối mã, số liệu và dấu ngoặc kép với các phần tử kiểu chung.

Các phần tử khối nên có nhiều khoảng cách và trang trí theo chiều dọc hơn để nổi bật giữa các thực thể kiểu chữ khác nhau. Đây là một số CSS để xử lý điều đó.

p {
  margin: 0;
}

p:not(:last-child) {
  margin-bottom: 1.5em;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1.3;
  margin: 1.5em 0 1.5rem;
  color: #111;
}

pre,
figure,
blockquote {
  margin: 3em 0;
}

figcaption {
  margin-top: 1em;
  font-size: 0.75em;
  color: #888;
  text-align: center;
}

Đoạn mã trên sẽ tạo ra khoảng cách theo chiều dọc không đồng đều giữa các tiêu đề và các phần tử khối, như được hiển thị ở đây. Nhưng có thể bù đắp cho những bất thường này bằng cách sử dụng hack lựa chọn anh chị em trước đó.

Nơi đây

CTA được tôi hóa

Giả sử bạn đang tạo CTA hoặc thành phần nút trong CSS với hai biến thể: biến thể đầu tiên là nút mặc định, đơn giản và biến thể thứ hai có biểu tượng.

Bây giờ, nếu bạn đang có kế hoạch viết hai lớp riêng biệt cho điều đó, bạn có thể dễ dàng tránh được điều đó với :has()bộ chọn. Cách nắm bắt là chỉ cần kiểm tra .btnphần tử cho một phần tử .btn-iconcon và sau đó định kiểu cho phù hợp.

Nơi đây

Điều chỉnh bố cục

Giả sử bạn có hai biến thể bố cục cho thành phần tiêu đề: một là chiều rộng cố định và một biến thể khác là chất lỏng, tất nhiên.

Để giữ cho nội dung của tiêu đề trong một chiều rộng cố định, chúng ta phải thêm một phần tử bao bọc nội dung bên trong nó. Sự khác biệt duy nhất giữa đánh dấu của hai phiên bản tiêu đề này là phần tử trình bao bọc.

Sau đó, chúng tôi có thể ghép nối :has()với :not()bộ chọn và thêm các lớp CSS, như được hiển thị trong minh họa bên dưới.

Nơi đây

Một ví dụ khác về việc điều chỉnh bố cục có thể là sửa đổi số lượng cột trong lưới ngay khi chúng đạt đến một số lượng nhất định.

Điều này có thể hữu ích khi bạn không sử dụng minmax()hàm xác định chiều rộng tốt nhất để vừa với một cột trong lưới của nó.

Nơi đây

Như bạn có thể thấy ở trên, lưới sẽ tự động điều chỉnh thành ba cột ngay khi nó vượt quá mốc của hai mục.

Khả năng sử dụng hình thức tốt hơn

Tương tác được xác định tốt nhất khi nó được kết nối đúng cách với phản hồi. Khi nói đến các biểu mẫu HTML tương tác, cung cấp phản hồi của người dùng về đầu vào của nó là điều tốt nhất bạn có thể làm.

Với sự trợ giúp của :has(), :valid:invalidcác lớp giả, chúng ta có thể làm cho các biểu mẫu của mình năng động hơn rất nhiều và không cần đến JavaScript.

Nơi đây

Kiểm tra hỗ trợ trình duyệt

Các ví dụ được thảo luận ở trên hiển thị hộp thoại lỗi nếu không :has()tìm thấy hỗ trợ cho bộ chọn. Điều này có thể được thực hiện bằng cách sử dụng @supportsquy tắc CSS, như được hiển thị trong các đoạn mã dưới đây:

@supports (selector(:has(*))) {
  .selector:has(...) {
    ...  
  }
}

Trên đây là cách kiểm tra lũy tiến để hỗ trợ và tạo kiểu phần tử theo yêu cầu. Nếu một cơ sở mã sử dụng trực tiếp một số tính năng mới, thì đây là cách để viết các phiên bản tương thích ngược của giống nhau:

@supports not (selector(:has(*))) {
  .selector {
    ...  
  }
}

Điều tương tự cũng có thể được sử dụng để nhắc người dùng nếu không tìm thấy hỗ trợ.

Bạn cũng có thể sử dụng API hỗ trợ trong JavaScript để phát hiện trình duyệt hỗ trợ các tính năng khác nhau. Đây là một ví dụ về việc kiểm tra :has()hỗ trợ hoàn toàn bằng JavaScript:

if(!CSS.supports('selector(html:has(body))'))) { // if not supported
  // Use the conventional JavaScript ways to emulate :has()
}

Sự kết luận

Trong bài viết trên, chúng ta đã tìm hiểu về :has()bộ chọn CSS cấp 4. Chúng tôi đã đề cập đến các nhu cầu của nó, một số trường hợp mà nó có thể thay thế JavaScript trong các dự án giao diện người dùng của bạn và các trường hợp sử dụng chung đến nâng cao.

Chúng tôi cũng thảo luận về hỗ trợ hiện tại được cung cấp bởi các trình duyệt khác nhau và tìm hiểu cách kiểm tra hỗ trợ của trình duyệt.

Cảm ơn vì đã đọc tất cả các cách thông qua. Tôi hy vọng bạn đã học được điều gì đó mới thông qua điều này. Vui lòng chia sẻ thêm các ví dụ về cách :has()có thể hữu ích trong các nhận xét. 

Nguồn: https://blog.logrocket.com/advanced-guide-css-has-selector/

#css 

Hướng Dẫn Nâng Cao Về Bộ Chọn CSS: Has ()