Automated Release v1.1.0

Please find changes in CHANGELOG.md file
This commit is contained in:
LAB-MI 2020-04-09 05:55:35 +00:00
parent 40251245d2
commit 8269a46d3a
31 changed files with 2109 additions and 713 deletions

27
CHANGELOG.md Normal file
View File

@ -0,0 +1,27 @@
# Changelog
## [1.0.13] - 2020-04-06
### Added
- first public release
## [1.1.0] - 2020-04-08
### Added
- (#21, #23, #25, #26, #27, #28, #29) :wheelchair: improve A11Y support
- adds github links in footer
- (#19) clear localstorage
- prevent sending personal data
- 💚 Publish html as workflow asset and code in public repository
- Add sitemap and tell robots.txt to scan the sitemap
- Add Changelog
### Changes
- (#18) 📝 Update header for reasons section
- (#3) ✏ Replace codiv by covid in package.json, README
- changes start target
- (#1) 🐛 Autocomplete birthday field only on keyup, not when deleting
- (#1) 🎨 Improve regex control pattern for birhtday
- :recycle: Move check update code to dedicated file
- :recycle: Move dom utils in dedicated file
- :sparkles: Notify user about newer version
- :sparkles: Add cache with service workers
- :recycle: Cleanup directory structure
- :heavy_plus_sign: Avoid using env specific syntax in npm scripts
- changes PWA display

View File

@ -5,8 +5,8 @@
### Installer le projet
```console
$ git clone https://github.com/lab-mi/deplacement-codiv-19.git
$ cd deplacement-codiv-19
$ git clone https://github.com/lab-mi/deplacement-covid-19.git
$ cd deplacement-covid-19
$ npm i
$ npm start
```
@ -23,7 +23,8 @@ Le code à déployer sera dans le dossier `dist`
Ce projet a été réalisé à partir d'un fork du dépôt [covid-19-certificate](https://github.com/nesk/covid-19-certificate) de [Johann Pardanaud](https://github.com/nesk).
Les projets open source suivants ont été utilisés pour le développement de ce service :
Les projets open source suivants ont été utilisés pour le développement de ce
service :
- [PDF-LIB](https://pdf-lib.js.org/)
- [qrcode](https://github.com/soldair/node-qrcode)

View File

@ -1,19 +0,0 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

2456
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,18 @@
"main": "certificate.js",
"scripts": {
"lint": "eslint ./*.js",
"start": "VERSION=${VERSION:-localversion} parcel index.html robots.txt",
"prebuild": "npm run lint && rimraf dist",
"build": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} ; parcel build --public-url ${PUBLIC_URL} index.html robots.txt",
"postbuild": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} react-snap"
"start": "cross-env VERSION=${VERSION:-localversion} parcel --public-url ${PUBLIC_URL:-/deplacement-covid-19} ./src/index.html",
"clean:dist": "rimraf dist",
"prebuild": "run-s lint clean:dist",
"build": "parcel build --public-url ${PUBLIC_URL:-/deplacement-covid-19} ./src/index.html ./src/robots.txt ./src/sitemap.xml",
"postbuild": "PUBLIC_URL=${PUBLIC_URL:-/deplacement-covid-19} react-snap",
"preserve": "npm run build",
"serve": "serve dist",
"serve:dist": "serve dist"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lab-mi/deplacement-codiv-19"
"url": "git+https://github.com/lab-mi/deplacement-covid-19"
},
"keywords": [],
"author": "",
@ -24,21 +28,28 @@
"@babel/core": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0",
"babel-eslint": "^10.1.0",
"copy-and-watch": "^0.1.4",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.4",
"parcel-plugin-sw-cache": "^0.3.1",
"postcss-current-selector": "0.0.3",
"postcss-nested": "^4.2.1",
"postcss-nested-ancestors": "^2.0.0",
"postcss-preset-env": "^6.7.0",
"react-snap": "^1.23.0",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"serve": "^11.3.0"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"bootstrap": "^4.4.1",
"pdf-lib": "^1.4.1",
"qrcode": "^1.4.4"
@ -52,5 +63,16 @@
"collapseWhitespace": false,
"removeComments": false
}
}
},
"cache": {
"globPatterns": [
"**/*.{html,js,css,jpg,png,pdf,svg,eot,ttf,woff,woff2}"
],
"disablePlugin": false,
"inDev": true,
"strategy": "default",
"clearDist": false,
"templatedURLs": {
"/": ["index.html"]
} }
}

View File

@ -1 +0,0 @@
User-agent: *

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,13 +1,20 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import './main.css'
import { PDFDocument, StandardFonts } from 'pdf-lib'
import QRCode from 'qrcode'
import { library, dom } from '@fortawesome/fontawesome-svg-core'
import { faEye, faFilePdf } from '@fortawesome/free-solid-svg-icons'
import './check-updates'
import { $, $$ } from './dom-utils'
import pdfBase from './certificate.pdf'
import './main.css'
const $ = (...args) => document.querySelector(...args)
const $$ = (...args) => [...document.querySelectorAll(...args)]
library.add(faEye, faFilePdf)
dom.watch()
var year, month, day
const generateQR = async text => {
@ -209,24 +216,34 @@ function isFacebookBrowser () {
}
if (isFacebookBrowser()) {
$('#alert-facebook').value = 'ATTENTION !! Vous utilisez actuellement le navigateur Facebook, ce générateur ne fonctionne pas correctement au sein de ce navigateur ! Merci d\'ouvrir Chrome sur Android ou bien Safari sur iOS.'
$('#alert-facebook').classList.remove('d-none')
}
function addSlash () {
this.value = this.value.replace(/^(\d{2})$/g, '$1/')
this.value = this.value.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/')
$('#field-birthday').value = $('#field-birthday').value.replace(/^(\d{2})$/g, '$1/')
$('#field-birthday').value = $('#field-birthday').value.replace(/^(\d{2})\/(\d{2})$/g, '$1/$2/')
}
$('#field-birthday').addEventListener('keyup', addSlash)
$('#field-birthday').onkeyup = function () {
const key = event.keyCode || event.charCode
if (key === 8 || key === 46) {
return false
} else {
addSlash()
return false
}
}
const snackbar = $('#snackbar')
$('#form-profile').addEventListener('submit', async event => {
$('#generate-btn').addEventListener('click', async event => {
event.preventDefault()
saveProfile()
const reasons = getAndSaveReasons()
const pdfBlob = await generatePdf(getProfile(), reasons)
localStorage.clear()
downloadBlob(pdfBlob, 'attestation.pdf')
snackbar.classList.remove('d-none')

34
src/check-updates.js Normal file
View File

@ -0,0 +1,34 @@
import { $ } from './dom-utils'
// Ce fichier est généré au build par le plugin parcel-plugin-sw-cache
const swName = './sw.js'
window.isUpdateAvailable = new Promise(function (resolve, reject) {
// lazy way of disabling service workers while developing
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(swName)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// new update available
resolve(true)
} else {
// no update available
resolve(false)
}
break
}
}
}
})
.catch(err => console.error('[SW ERROR]', err))
}
})
window.isUpdateAvailable.then(isAvailable => {
$('#reload-btn').addEventListener('click', () => window.location.reload())
$('#update-alert').classList.remove('d-none')
})

View File

@ -40,7 +40,10 @@
déplacement ne font lobjet daucune collecte par le ministère
de lIntérieur. Ces données personnelles<sup>1</sup> sont
exclusivement stockées dans le terminal (ordinateur, tablette,
smartphone) utilisé pour générer lattestation.
smartphone) utilisé pour générer lattestation. Le code source
de ce gérérateur d'attestation est ouvert. Il peut être consulté
sur <a href="https://github.com/LAB-MI/deplacement-covid-19">
https://github.com/LAB-MI/deplacement-covid-19</a>.
</p>
<em>1 - Prénom, Nom, Date de naissance, Lieu de naissance, Adresse,
Code postal, Ville.</em>

2
src/dom-utils.js Normal file
View File

@ -0,0 +1,2 @@
export const $ = (...args) => document.querySelector(...args)
export const $$ = (...args) => [...document.querySelectorAll(...args)]

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 613 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,23 @@
{
"name": "Générateur d'attestation de déplacement dérogatoire",
"short_name": "Déplacement covid-19",
"desctiption": "L'application officielle du gouvernement pour la génération d'attestation de déplacement dérogatoire dématérialisée.",
"categories": ["government", "health"],
"lang": "fr-FR",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"orientation": "portrait-primary",
"theme_color": "#ced4da",
"background_color": "#000191",
"display": "minimal-ui"
}

View File

@ -30,7 +30,7 @@
</head>
<body>
<header class="wrapper">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="">
<img class="logo" src="/MIN_Interieur_RVB.svg" alt="Ministère de l'intérieur. Liberté, égalité, fraternité.">
<div>
<h1 class="flex flex-wrap">
<span class="covid-title">
@ -48,10 +48,13 @@
</div>
</header>
<p class="alert alert-danger d-none" role="alert" id="alert-facebook"></p>
<div class="wrapper">
<form id="form-profile">
<form id="form-profile" accept-charset="UTF-8">
<h2 class="titre-2">Remplissez en ligne votre attestation numérique :</h2>
<p class="text-alert">Tous les champs sont obligatoires.</p>
<div class="form-group">
<label for="field-firstname" id="field-firstname-label">Prénom</label>
@ -63,11 +66,10 @@
name="firstname"
autocomplete="given-name"
placeholder="Jean"
aria-labelledby="field-firstname-label"
required
autofocus
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
@ -82,11 +84,10 @@
name="lastname"
autocomplete="family-name"
placeholder="Dupont"
aria-labelledby="field-lastname-label"
required
autofocus
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
@ -96,6 +97,7 @@
<div class="input-group align-items-center">
<input
type="text"
pattern="^([0][1-9]|[1-2][0-9]|30|31)/([0][1-9]|10|11|12)/(19[0-9][0-9]|20[0-1][0-9]|2020)"
inputmode="numeric"
class="form-control"
id="field-birthday"
@ -103,10 +105,9 @@
autocomplete="bday"
placeholder="01/01/1970"
maxlength="10"
aria-labelledby="field-birthday-label"
required
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
@ -119,11 +120,10 @@
class="form-control"
id="field-lieunaissance"
name="lieunaissance"
aria-labelledby="field-lieunaissance-label"
placeholder="Lyon"
required
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
@ -137,11 +137,10 @@
id="field-address"
name="address"
autocomplete="address-line1"
aria-labelledby="field-address-label"
placeholder="999 avenue de france"
required
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
@ -155,11 +154,10 @@
id="field-town"
name="town"
autocomplete="address-level1"
aria-labelledby="field-town-label"
placeholder="Paris"
required
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
@ -168,85 +166,91 @@
<label for="field-zipcode" id="field-zipcode-label" >Code Postal</label>
<div class="input-group align-items-center">
<input
type="number"
type="text"
inputmode="numeric"
pattern="[0-9]{5}"
min="00000"
max="99999"
class="form-control"
id="field-zipcode"
name="zipcode"
autocomplete="zipcode"
autocomplete="postal-code"
minlength="4"
maxlength="5"
aria-labelledby="field-zipcode-label"
placeholder="75001"
required
>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
<p class="exemple"></p>
</div>
<h3>Choisissez un motif de sortie</h3>
<fieldset>
<legend class="title-3">Choisissez le ou les motif(s) de sortie</legend>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-travail" value="travail">
<label class="form-check-label" for="checkbox-travail">Déplacements entre le domicile et le lieu dexercice de lactivité professionnelle, lorsqu'ils sont indispensables à l'exercice dactivités ne pouvant être organisées sous forme de télétravail ou déplacements professionnels ne pouvant être différés.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-travail" value="travail">
<label class="form-check-label" for="checkbox-travail">Déplacements entre le domicile et le lieu dexercice de lactivité professionnelle, lorsqu'ils sont indispensables à l'exercice dactivités ne pouvant être organisées sous forme de télétravail ou déplacements professionnels ne pouvant être différés.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-courses" value="courses">
<label class="form-check-label" for="checkbox-courses">Déplacements pour effectuer des achats de fournitures nécessaires à lactivité professionnelle et des achats de première nécessité dans des établissements dont les activités demeurent autorisées <a href="https://www.service-public.fr/particuliers/actualites/A13921" target="_blank">(liste sur gouvernement.fr)</a>.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-courses" value="courses">
<label class="form-check-label" for="checkbox-courses">Déplacements pour effectuer des achats de fournitures nécessaires à lactivité professionnelle et des achats de première nécessité dans des établissements dont les activités demeurent autorisées <a href="https://www.service-public.fr/particuliers/actualites/A13921" target="_blank">(liste sur gouvernement.fr)</a>.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sante" value="sante">
<label class="form-check-label" for="checkbox-sante">Consultations et soins ne pouvant être assurés à distance et ne pouvant être différés ; consultations et soins des patients atteints d'une affection de longue durée.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sante" value="sante">
<label class="form-check-label" for="checkbox-sante">Consultations et soins ne pouvant être assurés à distance et ne pouvant être différés ; consultations et soins des patients atteints d'une affection de longue durée.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-famille" value="famille">
<label class="form-check-label" for="checkbox-famille">Déplacements pour motif familial impérieux, pour lassistance aux personnes vulnérables ou la garde denfants.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-famille" value="famille">
<label class="form-check-label" for="checkbox-famille">Déplacements pour motif familial impérieux, pour lassistance aux personnes vulnérables ou la garde denfants.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sport" value="sport">
<label class="form-check-label" for="checkbox-sport">Déplacements brefs, dans la limite d'une heure quotidienne et dans un rayon maximal d'un kilomètre autour du domicile, liés soit à l'activité physique individuelle des personnes, à l'exclusion de toute pratique sportive collective et de toute proximité avec d'autres personnes, soit à la promenade avec les seules personnes regroupées dans un même domicile, soit aux besoins des animaux de compagnie.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-judiciaire" value="judiciaire">
<label class="form-check-label" for="checkbox-judiciaire">Convocation judiciaire ou administrative.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-missions" value="missions">
<label class="form-check-label" for="checkbox-missions">Participation à des missions dintérêt général sur demande de lautorité administrative.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-sport" value="sport">
<label class="form-check-label" for="checkbox-sport">Déplacements brefs, dans la limite d'une heure quotidienne et dans un rayon maximal d'un kilomètre autour du domicile, liés soit à l'activité physique individuelle des personnes, à l'exclusion de toute pratique sportive collective et de toute proximité avec d'autres personnes, soit à la promenade avec les seules personnes regroupées dans un même domicile, soit aux besoins des animaux de compagnie.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-judiciaire" value="judiciaire">
<label class="form-check-label" for="checkbox-judiciaire">Convocation judiciaire ou administrative.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="field-reason" id="checkbox-missions" value="missions">
<label class="form-check-label" for="checkbox-missions">Participation à des missions dintérêt général sur demande de lautorité administrative.</label>
</div>
</fieldset>
<div class="form-group">
<label for="field-datesortie">Date de sortie</label>
<div class="input-group align-items-center">
<input type="date" class="form-control" id="field-datesortie" name="datesortie" placeholder="JJ/MM/YYYY" required>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
</div>
<div class="form-group">
<label for="field-heure">Heure de sortie</label>
<label for="field-heuresortie">Heure de sortie</label>
<div class="input-group align-items-center">
<input type="time" class="form-control" id="field-heuresortie" name="heure" required>
<span class="validity"></span>
<span class="validity" aria-hidden="true"></span>
</div>
</div>
<p class="text-center mt-5">
<button type="submit" class="btn btn-primary btn-attestation"> <span class="btn-text">Générer mon attestation</span></button>
<button type="button" id="generate-btn" class="btn btn-primary btn-attestation"><span ><i class="fa fa-file-pdf inline-block mr-1"></i> Générer mon attestation</span></button>
</p>
<div class="bg-primary d-none" id="snackbar">
L'attestation est téléchargée sur votre appareil.
</div>
</form>
</div>
<div class="">
<p class="github">
Le code source de ce service est consultable sur <a href="https://github.com/LAB-MI/deplacement-covid-19" class="github-link">Github</a>.
</p>
<p class="label-mi">
Ministère de l'Intérieur - DNUM - SDIT
</p>
@ -263,9 +267,11 @@
</div>
</footer>
<div class="alert alert-danger d-none" role="alert" id="alert-facebook">
ATTENTION !! Vous utilisez actuellement le navigateur Facebook, ce générateur ne fonctionne pas correctement au
sein de ce navigateur ! Merci d'ouvrir Chrome sur Android ou bien Safari sur iOS.
<div class="alert alert-info d-none" id="update-alert">
Une nouvelle version est disponible. Cliquer sur le bouton pour l'obtenir.
<p class="text-right">
<button id="reload-btn" class="btn btn-info">Mettre à jour</button>
</p>
</div>
<script src="./certificate.js"></script>

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -64,6 +64,10 @@ canvas {
border-radius: .25rem;
}
a {
color: #000191;
}
.wrapper {
max-width: 800px;
margin-left: auto;
@ -187,7 +191,7 @@ input:valid+span:after {
.btn-attestation {
padding: 0.8em;
padding: 0.9em;
font-size: 1.2em;
font-weight: bold;
color: #ffffff;
@ -195,6 +199,10 @@ input:valid+span:after {
border-radius: 0.5em;
}
.btn-attestation:hover {
background-color: #3031C1;
}
.github {
font-size: 0.7em;
text-align: center;
@ -360,8 +368,23 @@ input:valid+span:after {
opacity: 1;
}
@media only screen and (min-width:600px) {
.hide-on-desktop, * [aria-labelledby='hide-on-desktop'] {
display: none;
max-height: 0;
overflow: hidden;
}
}
.center {
display: block;
margin-left: auto;
margin-right: auto;
}
#update-alert {
position: fixed;
bottom: 1em;
left: 50%;
transform: translateX(-50%);
}

1
src/robots.txt Normal file
View File

@ -0,0 +1 @@
Sitemap: https://media.interieur.gouv.fr/deplacement-covid-19/sitemap.xml

27
src/sitemap.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<!-- created with Free Online Sitemap Generator www.xml-sitemaps.com -->
<url>
<loc>https://media.interieur.gouv.fr/deplacement-covid-19/</loc>
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://media.interieur.gouv.fr/deplacement-covid-19/confidentialite.html</loc>
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://media.interieur.gouv.fr/deplacement-covid-19/index.html</loc>
<lastmod>2020-04-06T04:22:03+00:00</lastmod>
<priority>0.64</priority>
</url>
</urlset>