# Profils de gabarit — schéma 1.0

Un profil décrit le contrat que **vous** attendez de votre gabarit. Le [profil complet de démonstration](profiles/journal-demo.json) est directement importable. Le [JSON Schema](profiles/profile.schema.json) est généré depuis `scripts/profile-schema.mjs`. Le validateur statique au build et les vérifications sémantiques du moteur sont appliqués dans les deux exécutions, navigateur et CLI.

## Configurer avec le formulaire

Importer un témoin. Le profil d’exemple cible `article[data-template="journal-v1"]`. Remplacer ce marqueur et les périmètres par ceux de votre CMS. Déclarer la sélection de l’article (relation à l’URL, @id précis ou seul candidat), puis ouvrir « Configurer les champs et sous-sélecteurs ».

| Lecteur | Sélection HTML d’exemple   | Sous-champs                                 |
| ------- | -------------------------- | ------------------------------------------- |
| Date    | `.published` / `.modified` | Texte + datetime présent, format FR ou ISO. |
| Auteurs | `.author`                  | `.author-name`, lien `a`.                   |
| Fil     | `.breadcrumb li`           | `.crumb-label`, lien `a`.                   |
| FAQ     | `.faq details`             | `summary`, `.answer`, clé `data-key`.       |

Prévisualiser tous les résultats du témoin. Le formulaire conserve les règles avancées du profil importé. Pour changer une cardinalité, rendre une règle optionnelle, désactiver une règle en la retirant, changer les propriétés prises en charge ou la date de référence, utiliser le JSON avancé. **Appliquer ce JSON** est requis avant un nouveau contrôle ; une erreur ne réactive pas silencieusement l’ancien profil.

## Sélecteurs V1

Balise (`time`), universel (`*`), classe (`.date`), ID (`#date`), attribut présent (`[datetime]`), attribut égal avec guillemets (`[itemprop="dateModified"]`), combinaison simple, descendant et enfant `>`. Pas de listes séparées par virgule, pseudo-classes, pseudo-éléments, frères `+`/`~`, opérateurs d’attribut supplémentaires, expressions ou scripts. Les sélecteurs sont sensibles à la casse des valeurs d’attribut et classes. Les noms de balises/attributs HTML sont normalisés en minuscules.

`scope` doit trouver **exactement un** conteneur. `selector` examine ses descendants, sans inclure le conteneur lui-même. Les sous-sélecteurs travaillent de même depuis chaque élément trouvé. Pour sélectionner un lien auteur, utiliser un conteneur `.author` avec un descendant `a` ; ne pas placer directement `a` comme conteneur si son sous-sélecteur est aussi `a`.

Zéro élément → non déterminé, sauf absence optionnelle justifiée. `one` → exactement un. `set` → plusieurs valeurs comparées en ensemble dans les lecteurs compatibles ; `sequence` → ordre significatif (fil obligatoire). Le lecteur `authors` déclare aussi `policy.order`. L’ordre des FAQ est ignoré parce que l’appariement utilise la clé déclarée.

## Exemple complet minimal : deux dates

```json
{
  "schemaVersion": "1.0",
  "id": "mon-magazine",
  "version": "1.0.0",
  "language": "fr",
  "timezone": "Europe/Paris",
  "referenceDate": "2026-10-15",
  "marker": "article.article",
  "pageUrl": { "selector": "link[rel=\"canonical\"]", "attribute": "href" },
  "rules": [
    {
      "id": "published",
      "label": "Publication",
      "reader": "date",
      "expected": true,
      "scope": "article.article",
      "selector": ".publication time",
      "source": "text",
      "cardinality": "one",
      "entity": {
        "types": ["Article", "BlogPosting", "NewsArticle"],
        "by": "page_url"
      },
      "property": "datePublished",
      "absence": "undetermined",
      "ambiguity": "undetermined",
      "policy": {
        "text": "space-presentation",
        "urls": "conservative",
        "dateFormat": "fr"
      }
    },
    {
      "id": "modified",
      "label": "Révision",
      "reader": "date",
      "expected": true,
      "scope": "article.article",
      "selector": ".revision time",
      "source": "text",
      "cardinality": "one",
      "entity": {
        "types": ["Article", "BlogPosting", "NewsArticle"],
        "by": "page_url"
      },
      "property": "dateModified",
      "absence": "undetermined",
      "ambiguity": "undetermined",
      "policy": {
        "text": "space-presentation",
        "urls": "conservative",
        "dateFormat": "fr"
      }
    }
  ]
}
```

L’ordre modification/publication produit un constat distinct de la concordance du texte. Si `datePublished` n’est pas disponible, cet ordre reste non déterminé : ne pas conclure que la date de modification elle-même est fausse.

Pour une valeur portée par un attribut, indiquer `"source": "attribute", "attribute": "content"`. Le texte présent reste comparé pour les dates, ainsi que `datetime` lorsqu’il existe. Les lecteurs composés utilisent `source: text` et leurs sous-champs nommés.

## Entités et absences

- Article unique par ID : `{"types":["Article"],"by":"id","id":"#article"}` ; nécessite une base pour un ID relatif.
- Fil : `{"types":["BreadcrumbList"],"by":"id","id":"#breadcrumb"}` ; propriété `itemListElement`.
- FAQ : `{"types":["FAQPage"],"by":"id","id":"#faq"}` ; propriété `mainEntity`, question/réponse et clé configurées.
- Auteurs : propriété `author`, sous-champs `name` / `url`, politique `order: set` ou `sequence`.

Une règle optionnelle peut déclarer `"expected":false`, `"absence":"not_applicable"`, `"absenceReason":"Ce gabarit autorise les articles sans FAQ."`. L’ambiguïté reste toujours `undetermined`. Il faut conserver au moins une règle attendue ; un profil vide ou totalement optionnel est invalide.

Le profil ne contient aucune expression exécutable. Export volontaire par téléchargement ; pas de sauvegarde dans le navigateur. Après modification, donner une nouvelle version à votre profil et conserver le profil exact avec les rapports pour pouvoir les reproduire.
