@fulldecent/nice-checkers-plugin 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -119,6 +119,13 @@ This rule has no configurable options.
119
119
 
120
120
  Validates that all external links are live and accessible. This rule helps maintain website quality by catching broken external links before they go live, improving user experience and SEO.
121
121
 
122
+ **Note:** This rule automatically skips validation of:
123
+
124
+ - `<link rel="canonical">` - Canonical URLs point to the site itself and may not be published yet during development/preview
125
+ - `<link rel="alternate">` - Alternate language URLs also point to the site itself and may not exist during development
126
+
127
+ This allows you to validate your HTML before publishing, even when the canonical and alternate URLs reference the final production URLs.
128
+
122
129
  ```diff
123
130
  - <a href="https://wrong-subdomain.example.com">This link is broken</a>
124
131
  + <a href="https://example.com/nonexistent-page">This link works</a>
@@ -147,15 +154,40 @@ Validates that all external links are live and accessible. This rule helps maint
147
154
 
148
155
  ### Configuration options
149
156
 
150
- | Option | Type | Default | Description |
151
- | ---------------------------- | ---------- | --------------------------------------------------------- | ------------------------------------------------------- |
152
- | `proxyUrl` | `string` | `""` | Proxy URL to use for HTTP requests |
153
- | `skipRegexes` | `string[]` | `[]` | Array of regex patterns for URLs to skip checking |
154
- | `cacheExpiryFoundSeconds` | `number` | `2592000` | Cache duration for successful checks (default: 30 days) |
155
- | `cacheExpiryNotFoundSeconds` | `number` | `259200` | Cache duration for failed checks (default: 3 days) |
156
- | `timeoutSeconds` | `number` | `5` | Request timeout in seconds |
157
- | `cacheDatabasePath` | `string` | `"cache/external-links.db"` | Path to the cache database file |
158
- | `userAgent` | `string` | `"Mozilla/5.0 (compatible; html-validate-nice-checkers)"` | User agent string for HTTP requests |
157
+ | Option | Type | Default | Description |
158
+ | ------------------------------- | ---------- | --------------------------------------------------------- | ---------------------------------------------------------- |
159
+ | `proxyUrl` | `string` | `""` | Proxy URL to use for HTTP requests |
160
+ | `skipRegexes` | `string[]` | `[]` | Array of regex patterns for URLs to skip checking |
161
+ | `cacheExpiryFoundSeconds` | `number` | `2592000` | Cache duration for successful checks (default: 30 days) |
162
+ | `cacheExpiryNotFoundSeconds` | `number` | `259200` | Cache duration for failed checks (default: 3 days) |
163
+ | `timeoutSeconds` | `number` | `5` | Request timeout in seconds |
164
+ | `cacheDatabasePath` | `string` | `"cache/external-links.db"` | Path to the cache database file |
165
+ | `userAgent` | `string` | `"Mozilla/5.0 (compatible; html-validate-nice-checkers)"` | User agent string for HTTP requests |
166
+ | `manuallyReviewedPath` | `string` | `""` | Path to CSV file with manually reviewed URLs (see below) |
167
+ | `manuallyReviewedExpirySeconds` | `number` | `31536000` | Expiry time for manually reviewed URLs (default: 365 days) |
168
+
169
+ #### Manually reviewed URLs
170
+
171
+ Some websites resist automated checking (anti-scraping, rate limiting, etc.). You can maintain a CSV file of manually reviewed URLs that should be treated as valid:
172
+
173
+ **CSV format:**
174
+
175
+ ```csv
176
+ url,last_approved_timestamp
177
+ https://anti-scraping-site.example.com/page,1764877136
178
+ https://example.com/manually-verified,1764877136
179
+ ```
180
+
181
+ - The first line must be the header: `url,last_approved_timestamp`
182
+ - `url`: The exact URL to approve (must match exactly, including protocol and path)
183
+ - `last_approved_timestamp`: Unix timestamp (seconds since epoch) when you last verified the URL
184
+
185
+ URLs in this file are approved if:
186
+
187
+ 1. The URL matches exactly
188
+ 2. Current time < (last_approved_timestamp + manuallyReviewedExpirySeconds)
189
+
190
+ This allows time-limited manual approvals that automatically expire, ensuring you periodically re-verify that URLs still exist.
159
191
 
160
192
  ### `nice-checkers/https-links`
161
193
 
@@ -199,11 +231,15 @@ Reports insecure HTTP links that are accessible via HTTPS, encouraging the use o
199
231
 
200
232
  Validates that all internal links point to existing files in your project. This rule prevents broken internal navigation and missing resource references.
201
233
 
234
+ **Case-sensitive checking:** This rule performs case-sensitive file matching even on case-insensitive file systems (like macOS default). A link to `/abc.webp` will fail if the actual file is `/AbC.webp`, ensuring your code works correctly on Linux servers where case matters.
235
+
202
236
  ```diff
203
237
  - <a href="/nonexistent-page">Broken internal link</a>
204
238
  - <img src="../images/missing.webp" alt="Missing image" />
239
+ - <a href="/Logo.png">Wrong case (actual file: logo.png)</a>
205
240
  + <a href="/about">Working internal link</a>
206
241
  + <img src="../images/logo.webp" alt="Company logo" />
242
+ + <a href="/logo.png">Correct case</a>
207
243
  ```
208
244
 
209
245
  ### Configuration
@@ -326,6 +362,42 @@ If you are still using jQuery after 2022, please try to open your favorite chatb
326
362
 
327
363
  This rule has no configurable options.
328
364
 
365
+ ### `nice-checkers/alternate-language-links`
366
+
367
+ This rule enforces best practices for alternate language links (`<link rel="alternate" hreflang="...">`) in the `<head>` of HTML documents, as recommended by authoritative and established sources:
368
+
369
+ - [Google Search Central: specify alternate language pages](https://developers.google.com/search/docs/specialty/international/localized-versions)
370
+ - [W3C HTML Standard: link types](https://html.spec.whatwg.org/multipage/links.html#link-type-alternate)
371
+
372
+ Note that these sources we reference have a conflict. One says that you may use relative URLs and the other says you must use fully qualified URLs. To be conservative, we require fully qualified URLs.
373
+
374
+ **Activation:** this checker is only active if one or more `<link rel="alternate" hreflang="...">` elements exist in the document `<head>`.
375
+
376
+ **Checks performed:**
377
+
378
+ 1. **Self-link requirement:**
379
+ - There must be at least one `<link rel="alternate" hreflang="...">` whose `href` exactly matches the canonical URL of the page.
380
+ - The `hreflang` of this self-link must match the page's `<html lang="...">` attribute, if set.
381
+ - The canonical URL must exist (enforced by another checker).
382
+
383
+ 2. **Fully qualified URLs:**
384
+ - Every alternate language link must use a fully qualified URL (must include a scheme, e.g., `https://`).
385
+
386
+ 3. **Reciprocal linking:**
387
+ - Every alternate language page linked out to must reciprocate by linking back to the current page's canonical URL via its own `<link rel="alternate" hreflang="...">`.
388
+ - The `hreflang` of the reciprocal link on the remote page must match the `<html lang="...">` of the current page (if set).
389
+ - This is enforced by fetching the remote page and verifying its `<head>` contains the correct reciprocal link.
390
+
391
+ **Example:**
392
+
393
+ - The English page must link to itself and to the French page.
394
+ - The French page (`https://example.com/page-fr`) must link back to the English canonical page, and the `hreflang` must match the English page’s `<html lang="en">`.
395
+
396
+ **References:**
397
+
398
+ - [Google: specify alternate language pages](https://developers.google.com/search/docs/specialty/international/localized-versions)
399
+ - [W3C: link type 'alternate'](https://html.spec.whatwg.org/multipage/links.html#link-type-alternate)
400
+
329
401
  ## Development
330
402
 
331
403
  This package is built with TypeScript and supports both ESM and CommonJS module systems. Thank you for contributing improvements to this project!
@@ -369,6 +441,10 @@ and YES, use workspace TypeScript version.
369
441
  - `yarn lint` runs TypeScript type checking
370
442
  - `yarn format` formats all source files with Prettier
371
443
 
444
+ ### Testing notes
445
+
446
+ When running `yarn test` to test Nice Checkers itself, you may see two warnings about missing "root" paths. These come from the mock HTTP server (`@jaredwray/mockhttp`) which is only used in our test suite. The warnings are harmless and do not affect test results. We consider this an error in the upstream mock HTTP server package. These warnings do not appear for downstream users who install Nice Checkers to validate their own websites.
447
+
372
448
  ## Publishing to [npm registry](https://www.npmjs.com/package/@fulldecent/nice-checkers-plugin)
373
449
 
374
450
  @fulldecent will periodically create a GitHub release and this triggers [the npm publish workflow](https://github.com/fulldecent/html-validate-nice-checkers/blob/main/.github/workflows/publish.yml).
package/dist/index.cjs CHANGED
@@ -1,21 +1,24 @@
1
- 'use strict';var htmlValidate=require('html-validate'),U=require('better-sqlite3'),h=require('fs'),child_process=require('child_process'),shellQuote=require('shell-quote'),l=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var U__default=/*#__PURE__*/_interopDefault(U);var h__default=/*#__PURE__*/_interopDefault(h);var l__default=/*#__PURE__*/_interopDefault(l);var B=Object.defineProperty;var c=(C,s)=>B(C,"name",{value:s,configurable:true});var A=class A extends htmlValidate.Rule{documentation(){return {description:'Requires <link rel="alternate" hreflang="..."> to use fully qualified URLs with protocol (https://).',url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("dom:ready",s=>{this.domReady(s);});}domReady(s){let{document:e}=s,a=e.querySelectorAll('link[rel="alternate"][hreflang]');for(let t of a){let i=t.getAttribute("href")?.value;typeof i!="string"||!i||i.includes("://")||this.report({node:t,message:`Alternate language link must use a fully qualified URL (with protocol://), found: ${i}`});}}};c(A,"AlternateLanguageUrlRule");var f=A;var w=class w extends htmlValidate.Rule{documentation(){return {description:"Requires a <link rel='canonical'> in the <head> with a specific href format.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("dom:ready",s=>{this.domReady(s);});}domReady(s){let{document:e}=s,a=e.querySelector('head > link[rel="canonical"]');if(a===null){this.report({node:e.querySelector("head"),message:'<head> is missing <link rel="canonical" ...>'});return}let t=a.getAttribute("href")?.value;if(typeof t!="string"||!t){this.report({node:a,message:"<link rel='canonical'> is missing a non-empty href attribute"});return}/\.\w+$/.test(t)&&this.report({node:a,message:"Canonical link href should be extensionless (no .html, .php, etc.)"}),t.toLowerCase().endsWith("/index")&&this.report({node:a,message:'Canonical link href should be "/" instead of ending with "/index"'});}};c(w,"CanonicalLinkRule");var p=w;var j={proxyUrl:"",skipRegexes:[],cacheExpiryFoundSeconds:720*60*60,cacheExpiryNotFoundSeconds:4320*60,timeoutSeconds:5,cacheDatabasePath:"cache/external-links.db",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.999 Safari/537.36"};function L(C){try{let s=new URL(C);return s.hostname=s.hostname.toLowerCase(),s.toString()}catch{return C}}c(L,"normalizeUrl");var S=class S extends htmlValidate.Rule{constructor(s){super({...j,...s}),this.skipRegexesCompiled=this.compileRegexes(this.options.skipRegexes);}static schema(){return {proxyUrl:{type:"string",description:"URL of a proxy server to check external links."},skipRegexes:{type:"array",items:{type:"string"},description:"An array of regex patterns (as strings) for URLs to skip."},cacheExpiryFoundSeconds:{type:"number",description:"Number of seconds to cache successful (2xx) responses."},cacheExpiryNotFoundSeconds:{type:"number",description:"Number of seconds to cache failed (non-2xx) responses."},timeoutSeconds:{type:"number",description:"Maximum time in seconds to wait for a response."},cacheDatabasePath:{type:"string",description:"File path for the SQLite cache database."},userAgent:{type:"string",description:"User-Agent string to use for requests."}}}documentation(){return {description:"Require all external links to be live.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.db=this.setupDatabase(),this.on("tag:ready",s=>this.tagReady(s));}setupDatabase(){let s=l__default.default.dirname(this.options.cacheDatabasePath);h__default.default.mkdirSync(s,{recursive:true});let e=new U__default.default(this.options.cacheDatabasePath);return e.exec(`
1
+ 'use strict';var htmlValidate=require('html-validate'),child_process=require('child_process'),shellQuote=require('shell-quote'),J=require('cheerio'),te=require('better-sqlite3'),g=require('fs'),p=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var J__namespace=/*#__PURE__*/_interopNamespace(J);var te__default=/*#__PURE__*/_interopDefault(te);var g__default=/*#__PURE__*/_interopDefault(g);var p__default=/*#__PURE__*/_interopDefault(p);var $=Object.defineProperty;var d=(f,s)=>$(f,"name",{value:s,configurable:true});var B=class B extends htmlValidate.Rule{documentation(){return {description:'Requires <link rel="alternate" hreflang="..."> to use fully qualified URLs with protocol (https://).',url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("dom:ready",s=>{this.domReady(s);});}domReady(s){let{document:e}=s,a=e.querySelectorAll('link[rel="alternate"][hreflang]');for(let t of a){let r=t.getAttribute("href")?.value;typeof r!="string"||!r||r.includes("://")||this.report({node:t,message:`Alternate language link must use a fully qualified URL (with protocol://), found: ${r}`});}}};d(B,"AlternateLanguageUrlRule");var b=B;function v(f,s={}){let{timeoutSeconds:e=5,userAgent:a="Mozilla/5.0 (compatible; html-validate-nice-checkers)",headOnly:t=false,maxRedirs:r=0,failOnError:i=false,headers:o={}}=s,c=shellQuote.quote([f]),n=["curl","--silent","--max-time",String(e),"--max-redirs",String(r),"--user-agent",shellQuote.quote([a])];t&&n.push("--head"),i&&n.push("--fail");for(let[m,h]of Object.entries(o))n.push("--header",shellQuote.quote([`${m}: ${h}`]));n.push("--dump-header","-"),t&&n.push("--output","/dev/null"),n.push(c);let l=n.join(" ")+" || true";try{let m=child_process.execSync(l).toString(),h=m.match(/^HTTP\/[0-9.]+ (\d{3})/m),u=h?.[1]?parseInt(h[1],10):void 0,L=m.match(/^Location: (.+)/im)?.[1]?.trim(),y=m.split(/\r?\n\r?\n/),Q=y[0]||"",x=y.slice(1).join(`
2
+
3
+ `);return {success:u!==void 0&&u>=200&&u<400,statusCode:u,headers:Q,body:t?void 0:x,redirectTo:L}}catch(m){return {success:false,error:m instanceof Error?m.message:String(m)}}}d(v,"syncFetch");function R(f,s={}){return v(f,{...s,headOnly:true})}d(R,"syncHead");var F=class F extends htmlValidate.Rule{static schema(){return {}}documentation(){return {description:'Enforces best practices for alternate language links (rel="alternate" hreflang) in the <head> of HTML documents.',url:"https://developers.google.com/search/docs/specialty/international/localized-versions"}}setup(){this.on("dom:ready",s=>this.domReady(s));}domReady(s){let{document:e}=s,a=e.querySelector("html");a&&this.checkAlternateLanguageLinks(a);}checkAlternateLanguageLinks(s){let e=s.querySelector("head");if(!e)return;let a=e.querySelectorAll('link[rel="alternate"][hreflang]');if(a.length===0)return;let t=e.querySelector('link[rel="canonical"]');if(!t)return;let r=t.getAttribute("href")?.value;if(!r||typeof r!="string")return;let i=r,o=s.getAttribute("lang")?.value,c=false;for(let n of a){let l=n.getAttribute("href")?.value,m=typeof l=="string"?l:void 0,h=n.getAttribute("hreflang")?.value;m===i&&(c=true,o&&h!==o&&this.report({node:n,message:`Self alternate link hreflang (${h}) does not match page lang (${o})`})),(!m||!/^https?:\/\//.test(m))&&this.report({node:n,message:`Alternate link href must be fully qualified: ${m??"(empty)"}`});}c||this.report({node:e,message:"No self-link found in alternate language links (must match canonical URL)"});for(let n of a){let l=n.getAttribute("href")?.value,m=typeof l=="string"?l:void 0;if(!m||m===i)continue;let h=v(m,{timeoutSeconds:10,maxRedirs:5});if(!h.success||!h.body){this.report({node:n,message:`Alternate page ${m} is not accessible`});continue}let u=o??"",E=J__namespace.load(h.body),L=E('link[rel="alternate"][hreflang]'),y=false;L.each((Q,x)=>{let Y=E(x).attr("href"),Z=E(x).attr("hreflang");if(Y===i&&Z===u)return y=true,false}),y||this.report({node:n,message:`Reciprocal alternate link not found on ${m} (must link back to ${i} with hreflang="${u}")`});}}};d(F,"AlternateLanguageLinksRule");var P=F;var W=class W extends htmlValidate.Rule{documentation(){return {description:"Requires a <link rel='canonical'> in the <head> with a specific href format.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("dom:ready",s=>{this.domReady(s);});}domReady(s){let{document:e}=s,a=e.querySelector('head > link[rel="canonical"]');if(a===null){this.report({node:e.querySelector("head"),message:'<head> is missing <link rel="canonical" ...>'});return}let t=a.getAttribute("href")?.value;if(typeof t!="string"||!t){this.report({node:a,message:"<link rel='canonical'> is missing a non-empty href attribute"});return}/\.\w+$/.test(t)&&this.report({node:a,message:"Canonical link href should be extensionless (no .html, .php, etc.)"}),t.toLowerCase().endsWith("/index")&&this.report({node:a,message:'Canonical link href should be "/" instead of ending with "/index"'});}};d(W,"CanonicalLinkRule");var I=W;var re={proxyUrl:"",skipRegexes:[],cacheExpiryFoundSeconds:720*60*60,cacheExpiryNotFoundSeconds:4320*60,timeoutSeconds:5,cacheDatabasePath:"cache/external-links.db",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.999 Safari/537.36",manuallyReviewedPath:"",manuallyReviewedExpirySeconds:365*24*60*60};function M(f){try{let s=new URL(f);return s.hostname=s.hostname.toLowerCase(),s.toString()}catch{return f}}d(M,"normalizeUrl");var z=class z extends htmlValidate.Rule{constructor(s){super({...re,...s}),this.skipRegexesCompiled=this.compileRegexes(this.options.skipRegexes),this.manuallyReviewedUrls=this.loadManuallyReviewedUrls();}static schema(){return {proxyUrl:{type:"string",description:"URL of a proxy server to check external links."},skipRegexes:{type:"array",items:{type:"string"},description:"An array of regex patterns (as strings) for URLs to skip."},cacheExpiryFoundSeconds:{type:"number",description:"Number of seconds to cache successful (2xx) responses."},cacheExpiryNotFoundSeconds:{type:"number",description:"Number of seconds to cache failed (non-2xx) responses."},timeoutSeconds:{type:"number",description:"Maximum time in seconds to wait for a response."},cacheDatabasePath:{type:"string",description:"File path for the SQLite cache database."},userAgent:{type:"string",description:"User-Agent string to use for requests."},manuallyReviewedPath:{type:"string",description:"Path to CSV file containing manually reviewed URLs with approval timestamps."},manuallyReviewedExpirySeconds:{type:"number",description:"Number of seconds before a manually reviewed URL approval expires (default: 365 days)."}}}documentation(){return {description:"Require all external links to be live.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.db=this.setupDatabase(),this.on("tag:ready",s=>this.tagReady(s));}setupDatabase(){let s=p__default.default.dirname(this.options.cacheDatabasePath);g__default.default.mkdirSync(s,{recursive:true});let e=new te__default.default(this.options.cacheDatabasePath);return e.exec(`
2
4
  CREATE TABLE IF NOT EXISTS urls (url TEXT UNIQUE NOT NULL, status INTEGER, redirect_to TEXT, time INTEGER);
3
5
  CREATE INDEX IF NOT EXISTS time_idx ON urls (time);
4
- `),e.exec(`DELETE FROM urls WHERE status >= 200 AND status < 300 AND time < unixepoch() - ${this.options.cacheExpiryFoundSeconds}`),e.exec(`DELETE FROM urls WHERE (status < 200 OR status >= 300) AND time < unixepoch() - ${this.options.cacheExpiryNotFoundSeconds}`),e}compileRegexes(s){return s.map(e=>{try{return new RegExp(e)}catch{return console.error(`[html-validate-nice-checkers] Invalid regex pattern in 'skipRegexes' configuration: "${e}"`),null}}).filter(e=>e!==null)}performCheck(s,e,a){let t=L(s),i=shellQuote.quote([a??s]),r=`curl --head --silent --max-time ${this.options.timeoutSeconds} --max-redirs 0 --user-agent "${this.options.userAgent}" --header "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" --dump-header - --output /dev/null ${i} || true`,o=child_process.execSync(r).toString(),n=o.match(/^HTTP\/[0-9.]+ (\d{3})/m),d=n&&n[1]?parseInt(n[1],10):500,m=o.match(/^Location: (.+)/im),T=m&&m[1]?m[1].trim():null;this.db.prepare("REPLACE INTO urls (url, status, redirect_to, time) VALUES (?, ?, ?, unixepoch())").run(t,d,T),(d<200||d>=300)&&(T?this.report({node:e,message:`External link ${s} redirects to: ${T}`}):this.report({node:e,message:`External link is broken with status ${d}: ${s}`}));}tagReady(s){let{target:e}=s,{tagName:a}=e,t=null;if(a==="a"||a==="link")t="href";else if(a==="script"||a==="img")t="src";else return;let i=e.getAttribute(t)?.value;if(typeof i!="string"||!i)return;let r=i.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">");if(!r.startsWith("http")||this.skipRegexesCompiled.some(d=>d.test(r)))return;let o=L(r),n=this.db.prepare("SELECT * FROM urls WHERE url = ?").get(o);if(n){if(n.status>=200&&n.status<300)return;n.redirect_to?this.report({node:e,message:`External link ${r} redirects to: ${n.redirect_to}`}):this.report({node:e,message:`External link is broken with status ${n.status}: ${r}`});return}this.options.proxyUrl?this.performCheck(r,e,`${this.options.proxyUrl}?url=${encodeURIComponent(r)}`):this.performCheck(r,e);}};c(S,"ExternalLinksRule");var u=S;var K={cacheExpiryFoundSeconds:720*60*60,cacheExpiryNotFoundSeconds:4320*60,timeoutSeconds:5,cacheDatabasePath:"cache/https-availability.db"},k=class k extends htmlValidate.Rule{constructor(s){super({...K,...s});}static schema(){return {cacheExpiryFoundSeconds:{type:"number",description:"Number of seconds to cache that a URL is available over HTTPS."},cacheExpiryNotFoundSeconds:{type:"number",description:"Number of seconds to cache that a URL is NOT available over HTTPS."},timeoutSeconds:{type:"number",description:"Maximum time in seconds to wait for a response when checking for HTTPS."},cacheDatabasePath:{type:"string",description:"File path for the SQLite cache database."}}}documentation(){return {description:"Report insecure HTTP links that are accessible via HTTPS.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.db=this.setupDatabase(),this.on("tag:ready",s=>this.tagReady(s));}setupDatabase(){let s=l__default.default.dirname(this.options.cacheDatabasePath);h__default.default.mkdirSync(s,{recursive:true});let e=new U__default.default(this.options.cacheDatabasePath);return e.exec(`
6
+ `),e.exec(`DELETE FROM urls WHERE status >= 200 AND status < 300 AND time < unixepoch() - ${this.options.cacheExpiryFoundSeconds}`),e.exec(`DELETE FROM urls WHERE (status < 200 OR status >= 300) AND time < unixepoch() - ${this.options.cacheExpiryNotFoundSeconds}`),e}compileRegexes(s){return s.map(e=>{try{return new RegExp(e)}catch{return console.error(`[html-validate-nice-checkers] Invalid regex pattern in 'skipRegexes' configuration: "${e}"`),null}}).filter(e=>e!==null)}loadManuallyReviewedUrls(){let s=new Map;if(!this.options.manuallyReviewedPath)return s;try{if(!this.options.manuallyReviewedPath||!g__default.default.existsSync(this.options.manuallyReviewedPath))return s;let a=g__default.default.readFileSync(this.options.manuallyReviewedPath,"utf-8").split(`
7
+ `);for(let t=1;t<a.length;t++){let r=a[t];if(!r)continue;let i=r.trim();if(!i)continue;let o=i.split(",");if(o.length<2)continue;let c=o[0],n=o[1];if(!c||!n)continue;let l=c.replace(/^"|"$/g,"").trim(),m=n.replace(/^"|"$/g,"").trim();if(!l||!m)continue;let h=parseInt(m,10);isNaN(h)||s.set(M(l),h);}}catch(e){console.error(`[html-validate-nice-checkers] Error reading manually reviewed URLs from "${this.options.manuallyReviewedPath}": ${e}`);}return s}isManuallyApproved(s){let e=M(s),a=this.manuallyReviewedUrls.get(e);if(!a)return false;let t=Math.floor(Date.now()/1e3),r=a+this.options.manuallyReviewedExpirySeconds;return t<r}performCheck(s,e,a){let t=M(s),i=R(a??s,{timeoutSeconds:this.options.timeoutSeconds,userAgent:this.options.userAgent,maxRedirs:0,headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"}}),o=i.statusCode??500,c=i.redirectTo??null;this.db.prepare("REPLACE INTO urls (url, status, redirect_to, time) VALUES (?, ?, ?, unixepoch())").run(t,o,c),(o<200||o>=300)&&(c?this.report({node:e,message:`External link ${s} redirects to: ${c}`}):this.report({node:e,message:`External link is broken with status ${o}: ${s}`}));}tagReady(s){let{target:e}=s,{tagName:a}=e,t=null;if(a==="a"||a==="link")t="href";else if(a==="script"||a==="img")t="src";else return;if(a==="link"){let n=e.getAttribute("rel")?.value;if(n==="canonical"||n==="alternate")return}let r=e.getAttribute(t)?.value;if(typeof r!="string"||!r)return;let i=r.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">");if(!i.startsWith("http")||this.skipRegexesCompiled.some(n=>n.test(i))||this.isManuallyApproved(i))return;let o=M(i),c=this.db.prepare("SELECT * FROM urls WHERE url = ?").get(o);if(c){if(c.status>=200&&c.status<300)return;c.redirect_to?this.report({node:e,message:`External link ${i} redirects to: ${c.redirect_to}`}):this.report({node:e,message:`External link is broken with status ${c.status}: ${i}`});return}this.options.proxyUrl?this.performCheck(i,e,`${this.options.proxyUrl}?url=${encodeURIComponent(i)}`):this.performCheck(i,e);}};d(z,"ExternalLinksRule");var C=z;var me={cacheExpiryFoundSeconds:720*60*60,cacheExpiryNotFoundSeconds:4320*60,timeoutSeconds:5,cacheDatabasePath:"cache/https-availability.db"},V=class V extends htmlValidate.Rule{constructor(s){super({...me,...s});}static schema(){return {cacheExpiryFoundSeconds:{type:"number",description:"Number of seconds to cache that a URL is available over HTTPS."},cacheExpiryNotFoundSeconds:{type:"number",description:"Number of seconds to cache that a URL is NOT available over HTTPS."},timeoutSeconds:{type:"number",description:"Maximum time in seconds to wait for a response when checking for HTTPS."},cacheDatabasePath:{type:"string",description:"File path for the SQLite cache database."}}}documentation(){return {description:"Report insecure HTTP links that are accessible via HTTPS.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.md#rules"}}setup(){this.db=this.setupDatabase(),this.on("tag:ready",s=>this.tagReady(s));}setupDatabase(){let s=p__default.default.dirname(this.options.cacheDatabasePath);g__default.default.mkdirSync(s,{recursive:true});let e=new te__default.default(this.options.cacheDatabasePath);return e.exec(`
5
8
  CREATE TABLE IF NOT EXISTS urls (
6
9
  url TEXT UNIQUE NOT NULL,
7
10
  found INTEGER NOT NULL,
8
11
  time INTEGER NOT NULL
9
12
  );
10
13
  CREATE INDEX IF NOT EXISTS time_idx ON urls (time);
11
- `),e.exec(`DELETE FROM urls WHERE found = 1 AND time < unixepoch() - ${this.options.cacheExpiryFoundSeconds}`),e.exec(`DELETE FROM urls WHERE found = 0 AND time < unixepoch() - ${this.options.cacheExpiryNotFoundSeconds}`),e}performHttpsCheck(s,e){let a=s.replace(/^http:/,"https:"),t=shellQuote.quote([a]),i=`curl --head --silent --fail --no-location --max-time ${this.options.timeoutSeconds} ${t} > /dev/null 2>&1`;try{child_process.execSync(i),this.db.prepare("REPLACE INTO urls (url, found, time) VALUES (?, 1, unixepoch())").run(s),this.report({node:e,message:`Insecure link can be upgraded to HTTPS: ${s}`});}catch{this.db.prepare("REPLACE INTO urls (url, found, time) VALUES (?, 0, unixepoch())").run(s);}}tagReady(s){let{target:e}=s,{tagName:a}=e,t=null;if(a==="a"||a==="link")t="href";else if(a==="script"||a==="img")t="src";else return;let i=e.getAttribute(t)?.value;if(typeof i!="string"||!i.startsWith("http://"))return;let r=i.replace(/&amp;/g,"&"),o=this.db.prepare("SELECT found FROM urls WHERE url = ?").get(r);if(o){o.found===1&&this.report({node:e,message:`Insecure link can be upgraded to HTTPS: ${r}`});return}this.performHttpsCheck(r,e);}};c(k,"HttpsLinksRule");var g=k;var $={webRoot:l__default.default.join(process.cwd(),"build"),alternativeExtensions:[".html",".php"],indexFile:"index.html"},E=class E extends htmlValidate.Rule{constructor(e){super({...$,...e});this.fileExistsCache=new Map;}static schema(){return {webRoot:{type:"string",description:'The root directory for resolving absolute links (starting with "/").'},alternativeExtensions:{type:"array",items:{type:"string"},description:'List of file extensions to check for extensionless links (e.g., [".html"]).'},indexFile:{type:"string",description:"The file to look for in directory links (e.g., 'index.html')."}}}documentation(){return {description:"Validate that all internal links point to existing files.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("tag:ready",e=>this.tagReady(e));}doesFileExist(e){if(this.fileExistsCache.has(e))return this.fileExistsCache.get(e);let a=h__default.default.existsSync(e)&&h__default.default.lstatSync(e).isFile();return this.fileExistsCache.set(e,a),a}checkLink(e,a){let t;if(e.startsWith("/"))t=l__default.default.join(this.options.webRoot,e);else {let i=l__default.default.dirname(a.location.filename);t=l__default.default.resolve(i,e);}if(e.endsWith("/")){let i=t;if(h__default.default.existsSync(i)&&h__default.default.lstatSync(i).isDirectory()){let r=l__default.default.join(i,this.options.indexFile);if(this.doesFileExist(r))return}}else {if(this.doesFileExist(t)||this.options.alternativeExtensions.some(i=>this.doesFileExist(t+i)))return;if(h__default.default.existsSync(t)&&h__default.default.lstatSync(t).isDirectory()){let i=l__default.default.join(t,this.options.indexFile);if(this.doesFileExist(i))return}}this.report({node:a,message:`Internal link to "${e}" is broken.`});}tagReady(e){let{target:a}=e,{tagName:t}=a,i=null;if(t==="a"||t==="link")i="href";else if(t==="script"||t==="img")i="src";else return;let r=a.getAttribute(i)?.value;if(typeof r!="string"||!r)return;let o=r.replace(/[#?].*$/,"");o&&(/^[a-z][a-z0-9+.-]*:/i.test(o)||this.checkLink(o,a));}};c(E,"InternalLinksRule");var y=E;var oe={cacheExpirySeconds:2880*60,timeoutSeconds:5,cacheDatabasePath:"cache/latest-packages.db",skipUrlPatterns:["googletagmanager.com"]},x=class x extends htmlValidate.Rule{constructor(s){super({...oe,...s});}static schema(){return {cacheExpirySeconds:{type:"number",description:"Number of seconds to cache package version lookup results."},timeoutSeconds:{type:"number",description:"Maximum time in seconds to wait for a response from the package registry API."},cacheDatabasePath:{type:"string",description:"File path for the SQLite cache database."},skipUrlPatterns:{type:"array",items:{type:"string"},description:"An array of substrings to identify URLs that should be ignored."}}}documentation(){return {description:"Ensures that package assets loaded from a CDN are the latest version and have SRI attributes.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.db=this.setupDatabase(),this.on("tag:ready",s=>this.tagReady(s));}setupDatabase(){let s=l__default.default.dirname(this.options.cacheDatabasePath);h__default.default.mkdirSync(s,{recursive:true});let e=new U__default.default(this.options.cacheDatabasePath);return e.exec(`
14
+ `),e.exec(`DELETE FROM urls WHERE found = 1 AND time < unixepoch() - ${this.options.cacheExpiryFoundSeconds}`),e.exec(`DELETE FROM urls WHERE found = 0 AND time < unixepoch() - ${this.options.cacheExpiryNotFoundSeconds}`),e}performHttpsCheck(s,e){let a=s.replace(/^http:/,"https:");R(a,{timeoutSeconds:this.options.timeoutSeconds,maxRedirs:0}).success?(this.db.prepare("REPLACE INTO urls (url, found, time) VALUES (?, 1, unixepoch())").run(s),this.report({node:e,message:`Insecure link can be upgraded to HTTPS: ${s}`})):this.db.prepare("REPLACE INTO urls (url, found, time) VALUES (?, 0, unixepoch())").run(s);}tagReady(s){let{target:e}=s,{tagName:a}=e,t=null;if(a==="a"||a==="link")t="href";else if(a==="script"||a==="img")t="src";else return;let r=e.getAttribute(t)?.value;if(typeof r!="string"||!r.startsWith("http://"))return;let i=r.replace(/&amp;/g,"&"),o=this.db.prepare("SELECT found FROM urls WHERE url = ?").get(i);if(o){o.found===1&&this.report({node:e,message:`Insecure link can be upgraded to HTTPS: ${i}`});return}this.performHttpsCheck(i,e);}};d(V,"HttpsLinksRule");var O=V;var le={webRoot:p__default.default.join(process.cwd(),"build"),alternativeExtensions:[".html",".php"],indexFile:"index.html"},G=class G extends htmlValidate.Rule{constructor(e){super({...le,...e});this.fileExistsCache=new Map;}static schema(){return {webRoot:{type:"string",description:'The root directory for resolving absolute links (starting with "/").'},alternativeExtensions:{type:"array",items:{type:"string"},description:'List of file extensions to check for extensionless links (e.g., [".html"]).'},indexFile:{type:"string",description:"The file to look for in directory links (e.g., 'index.html')."}}}documentation(){return {description:"Validate that all internal links point to existing files.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.md#rules"}}setup(){this.on("tag:ready",e=>this.tagReady(e));}doesFileExist(e){if(this.fileExistsCache.has(e))return this.fileExistsCache.get(e);if(!g__default.default.existsSync(e))return this.fileExistsCache.set(e,false),false;if(!g__default.default.lstatSync(e).isFile())return this.fileExistsCache.set(e,false),false;try{let a=p__default.default.dirname(e),t=p__default.default.basename(e),i=g__default.default.readdirSync(a).includes(t);return this.fileExistsCache.set(e,i),i}catch{return this.fileExistsCache.set(e,false),false}}checkLink(e,a){let t;if(e.startsWith("/"))t=p__default.default.join(this.options.webRoot,e);else {let r=p__default.default.dirname(a.location.filename);t=p__default.default.resolve(r,e);}if(e.endsWith("/")){let r=t;if(g__default.default.existsSync(r)&&g__default.default.lstatSync(r).isDirectory()){let i=p__default.default.join(r,this.options.indexFile);if(this.doesFileExist(i))return}}else {if(this.doesFileExist(t)||this.options.alternativeExtensions.some(r=>this.doesFileExist(t+r)))return;if(g__default.default.existsSync(t)&&g__default.default.lstatSync(t).isDirectory()){let r=p__default.default.join(t,this.options.indexFile);if(this.doesFileExist(r))return}}this.report({node:a,message:`Internal link to "${e}" is broken.`});}tagReady(e){let{target:a}=e,{tagName:t}=a,r=null;if(t==="a"||t==="link")r="href";else if(t==="script"||t==="img")r="src";else return;let i=a.getAttribute(r)?.value;if(typeof i!="string"||!i)return;let o=i.replace(/[#?].*$/,"");o&&(/^[a-z][a-z0-9+.-]*:/i.test(o)||this.checkLink(o,a));}};d(G,"InternalLinksRule");var T=G;var ye={cacheExpirySeconds:2880*60,timeoutSeconds:5,cacheDatabasePath:"cache/latest-packages.db",skipUrlPatterns:["googletagmanager.com"]},H=class H extends htmlValidate.Rule{constructor(s){super({...ye,...s});}static schema(){return {cacheExpirySeconds:{type:"number",description:"Number of seconds to cache package version lookup results."},timeoutSeconds:{type:"number",description:"Maximum time in seconds to wait for a response from the package registry API."},cacheDatabasePath:{type:"string",description:"File path for the SQLite cache database."},skipUrlPatterns:{type:"array",items:{type:"string"},description:"An array of substrings to identify URLs that should be ignored."}}}documentation(){return {description:"Ensures that package assets loaded from a CDN are the latest version and have SRI attributes.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.db=this.setupDatabase(),this.on("tag:ready",s=>this.tagReady(s));}setupDatabase(){let s=p__default.default.dirname(this.options.cacheDatabasePath);g__default.default.mkdirSync(s,{recursive:true});let e=new te__default.default(this.options.cacheDatabasePath);return e.exec(`
12
15
  CREATE TABLE IF NOT EXISTS latest_packages (
13
16
  url TEXT UNIQUE NOT NULL,
14
17
  current INTEGER NOT NULL,
15
18
  time INTEGER NOT NULL
16
19
  );
17
20
  CREATE INDEX IF NOT EXISTS time_idx ON latest_packages (time);
18
- `),e.exec(`DELETE FROM latest_packages WHERE time < unixepoch() - ${this.options.cacheExpirySeconds}`),e}performPackageCheck(s,e){let a=s.match(/cdn\.jsdelivr\.net\/npm\/(@?[^/]+)@([^/]+)/);if(!a)return;let t=a[1],i=a[2],r=`https://data.jsdelivr.com/v1/package/npm/${t}`,o=shellQuote.quote([r]),n=`curl --silent --fail --no-location --max-time ${this.options.timeoutSeconds} ${o}`;try{let d=child_process.execSync(n).toString(),m=JSON.parse(d);m&&m.tags&&Object.values(m.tags).includes(i)?this.db.prepare("REPLACE INTO latest_packages (url, current, time) VALUES (?, 1, unixepoch())").run(s):(this.db.prepare("REPLACE INTO latest_packages (url, current, time) VALUES (?, 0, unixepoch())").run(s),this.report({node:e,message:`Package "${t}" is not using a current version tag. Found "${i}", but latest is "${m.tags.latest}".`}));}catch(d){console.error(`[html-validate-latest-packages] Error checking package version for ${s}: ${d.message}`);}}tagReady(s){let{target:e}=s,{tagName:a}=s.target,t;if(a==="script"){let r=e.getAttribute("src")?.value;typeof r=="string"&&(t=r);}else if(a==="link"&&e.getAttribute("rel")?.value==="stylesheet"){let r=e.getAttribute("href")?.value;typeof r=="string"&&(t=r);}else return;if(!t||!t.startsWith("http")||this.options.skipUrlPatterns.some(r=>t.includes(r)))return;(!e.hasAttribute("integrity")||!e.hasAttribute("crossorigin"))&&this.report({node:e,message:`Package resource is missing required "integrity" and "crossorigin" attributes: ${t}`});let i=this.db.prepare("SELECT current FROM latest_packages WHERE url = ?").get(t);if(i){i.current===0&&this.report({node:e,message:`Package is using an outdated version: ${t}`});return}this.performPackageCheck(t,e);}};c(x,"LatestPackagesRule");var b=x;var ne={requiredParameters:["subject","body"]},R=class R extends htmlValidate.Rule{constructor(s){super({...ne,...s});}static schema(){return {requiredParameters:{type:"array",items:{type:"string"},description:'An array of parameters that must be present in a mailto: link (e.g., ["subject", "body"]).'}}}documentation(){return {description:"Enforce that mailto: links contain specific parameters.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("tag:ready",s=>this.tagReady(s));}tagReady(s){let{target:e}=s;if(e.tagName!=="a")return;let a=e.getAttribute("href")?.value;if(!(typeof a!="string"||!a)&&a.startsWith("mailto:"))try{let t=a.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">"),i=new URL(t),r=this.options.requiredParameters.filter(o=>!i.searchParams.has(o));r.length>0&&this.report({node:e,message:`mailto: link is missing required parameters: ${r.join(", ")}`});}catch{this.report({node:e,message:"mailto: link is malformed and could not be parsed."});}}};c(R,"MailtoAwesomeRule");var v=R;var M=class M extends htmlValidate.Rule{documentation(){return {description:"Disallow script tag with src including jQuery",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("tag:ready",s=>{let{target:e}=s;if(e.tagName!=="script")return;let a=e.getAttribute("src")?.value;typeof a!="string"||!a||a.toLowerCase().includes("jquery")&&this.report({node:e,message:"Script tag with src including jQuery is not allowed"});});}};c(M,"NoJqueryRule");var P=M;var O={"@context":{brick:"https://brickschema.org/schema/Brick#",csvw:"http://www.w3.org/ns/csvw#",dc:"http://purl.org/dc/elements/1.1/",dcam:"http://purl.org/dc/dcam/",dcat:"http://www.w3.org/ns/dcat#",dcmitype:"http://purl.org/dc/dcmitype/",dcterms:"http://purl.org/dc/terms/",doap:"http://usefulinc.com/ns/doap#",foaf:"http://xmlns.com/foaf/0.1/",odrl:"http://www.w3.org/ns/odrl/2/",org:"http://www.w3.org/ns/org#",owl:"http://www.w3.org/2002/07/owl#",prof:"http://www.w3.org/ns/dx/prof/",prov:"http://www.w3.org/ns/prov#",qb:"http://purl.org/linked-data/cube#",rdf:"http://www.w3.org/1999/02/22-rdf-syntax-ns#",rdfs:"http://www.w3.org/2000/01/rdf-schema#",schema:"https://schema.org/",sh:"http://www.w3.org/ns/shacl#",skos:"http://www.w3.org/2004/02/skos/core#",sosa:"http://www.w3.org/ns/sosa/",ssn:"http://www.w3.org/ns/ssn/",time:"http://www.w3.org/2006/time#",vann:"http://purl.org/vocab/vann/",void:"http://rdfs.org/ns/void#",xsd:"http://www.w3.org/2001/XMLSchema#"},"@graph":[{"@id":"schema:option","@type":"rdf:Property","rdfs:comment":"A sub property of object. The options subject to this action.","rdfs:label":"option","rdfs:subPropertyOf":{"@id":"schema:object"},"schema:domainIncludes":{"@id":"schema:ChooseAction"},"schema:rangeIncludes":[{"@id":"schema:Text"},{"@id":"schema:Thing"}],"schema:supersededBy":{"@id":"schema:actionOption"}},{"@id":"schema:minimumPaymentDue","@type":"rdf:Property","rdfs:comment":"The minimum payment required at this time.","rdfs:label":"minimumPaymentDue","schema:domainIncludes":{"@id":"schema:Invoice"},"schema:rangeIncludes":[{"@id":"schema:PriceSpecification"},{"@id":"schema:MonetaryAmount"}]},{"@id":"schema:DJMixAlbum","@type":"schema:MusicAlbumProductionType","rdfs:comment":"DJMixAlbum.","rdfs:label":"DJMixAlbum","schema:contributor":{"@id":"https://schema.org/docs/collab/MBZ"}},{"@id":"schema:Nonprofit501c27","@type":"schema:USNonprofitType","rdfs:comment":"Nonprofit501c27: Non-profit type referring to State-Sponsored Workers' Compensation Reinsurance Organizations.","rdfs:label":"Nonprofit501c27","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:includedDataCatalog","@type":"rdf:Property","rdfs:comment":"A data catalog which contains this dataset (this property was previously 'catalog', preferred name is now 'includedInDataCatalog').","rdfs:label":"includedDataCatalog","schema:domainIncludes":{"@id":"schema:Dataset"},"schema:rangeIncludes":{"@id":"schema:DataCatalog"},"schema:supersededBy":{"@id":"schema:includedInDataCatalog"}},{"@id":"schema:UKTrust","@type":"schema:UKNonprofitType","rdfs:comment":"UKTrust: Non-profit type referring to a UK trust.","rdfs:label":"UKTrust","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:discount","@type":"rdf:Property","rdfs:comment":"Any discount applied (to an Order).","rdfs:label":"discount","schema:domainIncludes":{"@id":"schema:Order"},"schema:rangeIncludes":[{"@id":"schema:Text"},{"@id":"schema:Number"}]},{"@id":"schema:printPage","@type":"rdf:Property","rdfs:comment":"If this NewsArticle appears in print, this field indicates the name of the page on which the article is found. Please note that this field is intended for the exact page name (e.g. A5, B18).","rdfs:label":"printPage","schema:domainIncludes":{"@id":"schema:NewsArticle"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:muscleAction","@type":"rdf:Property","rdfs:comment":"The movement the muscle generates.","rdfs:label":"muscleAction","schema:domainIncludes":{"@id":"schema:Muscle"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:illustrator","@type":"rdf:Property","rdfs:comment":"The illustrator of the book.","rdfs:label":"illustrator","schema:domainIncludes":{"@id":"schema:Book"},"schema:rangeIncludes":{"@id":"schema:Person"}},{"@id":"schema:contentType","@type":"rdf:Property","rdfs:comment":"The supported content type(s) for an EntryPoint response.","rdfs:label":"contentType","schema:domainIncludes":{"@id":"schema:EntryPoint"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:servesCuisine","@type":"rdf:Property","rdfs:comment":"The cuisine of the restaurant.","rdfs:label":"servesCuisine","schema:domainIncludes":{"@id":"schema:FoodEstablishment"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:distance","@type":"rdf:Property","rdfs:comment":"The distance travelled, e.g. exercising or travelling.","rdfs:label":"distance","schema:domainIncludes":[{"@id":"schema:ExerciseAction"},{"@id":"schema:TravelAction"}],"schema:rangeIncludes":{"@id":"schema:Distance"}},{"@id":"schema:containedInPlace","@type":"rdf:Property","rdfs:comment":"The basic containment relation between a place and one that contains it.","rdfs:label":"containedInPlace","schema:domainIncludes":{"@id":"schema:Place"},"schema:inverseOf":{"@id":"schema:containsPlace"},"schema:rangeIncludes":{"@id":"schema:Place"}},{"@id":"schema:ReturnAtKiosk","@type":"schema:ReturnMethodEnumeration","rdfs:comment":"Specifies that product returns must be made at a kiosk.","rdfs:label":"ReturnAtKiosk","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:broker","@type":"rdf:Property","rdfs:comment":"An entity that arranges for an exchange between a buyer and a seller. In most cases a broker never acquires or releases ownership of a product or service involved in an exchange. If it is not clear whether an entity is a broker, seller, or buyer, the latter two terms are preferred.","rdfs:label":"broker","schema:domainIncludes":[{"@id":"schema:Order"},{"@id":"schema:Reservation"},{"@id":"schema:Service"},{"@id":"schema:Invoice"}],"schema:rangeIncludes":[{"@id":"schema:Person"},{"@id":"schema:Organization"}]},{"@id":"schema:permittedUsage","@type":"rdf:Property","rdfs:comment":"Indications regarding the permitted usage of the accommodation.","rdfs:label":"permittedUsage","schema:contributor":{"@id":"https://schema.org/docs/collab/STI_Accommodation_Ontology"},"schema:domainIncludes":{"@id":"schema:Accommodation"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:AerobicActivity","@type":"schema:PhysicalActivityCategory","rdfs:comment":"Physical activity of relatively low intensity that depends primarily on the aerobic energy-generating process; during activity, the aerobic metabolism uses oxygen to adequately meet energy demands during exercise.","rdfs:label":"AerobicActivity","schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:totalHistoricalEnrollment","@type":"rdf:Property","rdfs:comment":"The total number of students that have enrolled in the history of the course.","rdfs:label":"totalHistoricalEnrollment","schema:domainIncludes":{"@id":"schema:Course"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Integer"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3281"}},{"@id":"schema:WearableMeasurementHeight","@type":"schema:WearableMeasurementTypeEnumeration","rdfs:comment":"Measurement of the height, for example the heel height of a shoe.","rdfs:label":"WearableMeasurementHeight","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2811"}},{"@id":"schema:Clip","@type":"rdfs:Class","rdfs:comment":"A short TV or radio program or a segment/part of a program.","rdfs:label":"Clip","rdfs:subClassOf":{"@id":"schema:CreativeWork"}},{"@id":"schema:WatchAction","@type":"rdfs:Class","rdfs:comment":"The act of consuming dynamic/moving visual content.","rdfs:label":"WatchAction","rdfs:subClassOf":{"@id":"schema:ConsumeAction"}},{"@id":"schema:issuedBy","@type":"rdf:Property","rdfs:comment":"The organization issuing the item, for example a [[Permit]], [[Ticket]], or [[Certification]].","rdfs:label":"issuedBy","schema:domainIncludes":[{"@id":"schema:Permit"},{"@id":"schema:Ticket"},{"@id":"schema:Certification"}],"schema:rangeIncludes":{"@id":"schema:Organization"}},{"@id":"schema:DemoAlbum","@type":"schema:MusicAlbumProductionType","rdfs:comment":"DemoAlbum.","rdfs:label":"DemoAlbum","schema:contributor":{"@id":"https://schema.org/docs/collab/MBZ"}},{"@id":"schema:hasMemberProgram","@type":"rdf:Property","rdfs:comment":"MemberProgram offered by an Organization, for example an eCommerce merchant or an airline.","rdfs:label":"hasMemberProgram","schema:domainIncludes":{"@id":"schema:Organization"},"schema:rangeIncludes":{"@id":"schema:MemberProgram"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3563"}},{"@id":"schema:hasEnergyEfficiencyCategory","@type":"rdf:Property","rdfs:comment":"Defines the energy efficiency Category (which could be either a rating out of range of values or a yes/no certification) for a product according to an international energy efficiency standard.","rdfs:label":"hasEnergyEfficiencyCategory","schema:domainIncludes":{"@id":"schema:EnergyConsumptionDetails"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:EnergyEfficiencyEnumeration"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2670"}},{"@id":"schema:rangeIncludes","@type":"rdf:Property","rdfs:comment":"Relates a property to a class that constitutes (one of) the expected type(s) for values of the property.","rdfs:label":"rangeIncludes","schema:domainIncludes":{"@id":"schema:Property"},"schema:isPartOf":{"@id":"https://meta.schema.org"},"schema:rangeIncludes":{"@id":"schema:Class"}},{"@id":"schema:contentLocation","@type":"rdf:Property","rdfs:comment":"The location depicted or described in the content. For example, the location in a photograph or painting.","rdfs:label":"contentLocation","schema:domainIncludes":{"@id":"schema:CreativeWork"},"schema:rangeIncludes":{"@id":"schema:Place"}},{"@id":"schema:NonprofitANBI","@type":"schema:NLNonprofitType","rdfs:comment":"NonprofitANBI: Non-profit type referring to a Public Benefit Organization (NL).","rdfs:label":"NonprofitANBI","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:ExerciseGym","@type":"rdfs:Class","rdfs:comment":"A gym.","rdfs:label":"ExerciseGym","rdfs:subClassOf":{"@id":"schema:SportsActivityLocation"}},{"@id":"schema:MusicPlaylist","@type":"rdfs:Class","rdfs:comment":"A collection of music tracks in playlist form.","rdfs:label":"MusicPlaylist","rdfs:subClassOf":{"@id":"schema:CreativeWork"}},{"@id":"schema:weightPercentage","@type":"rdf:Property","rdfs:comment":"Fraction of the weight that is used to compute the shipping price.","rdfs:label":"weightPercentage","schema:domainIncludes":{"@id":"schema:ShippingRateSettings"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Number"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3617"}},{"@id":"schema:landlord","@type":"rdf:Property","rdfs:comment":"A sub property of participant. The owner of the real estate property.","rdfs:label":"landlord","rdfs:subPropertyOf":{"@id":"schema:participant"},"schema:domainIncludes":{"@id":"schema:RentAction"},"schema:rangeIncludes":[{"@id":"schema:Organization"},{"@id":"schema:Person"}]},{"@id":"schema:recordedAt","@type":"rdf:Property","rdfs:comment":"The Event where the CreativeWork was recorded. The CreativeWork may capture all or part of the event.","rdfs:label":"recordedAt","schema:domainIncludes":{"@id":"schema:CreativeWork"},"schema:inverseOf":{"@id":"schema:recordedIn"},"schema:rangeIncludes":{"@id":"schema:Event"}},{"@id":"schema:DigitalFormat","@type":"schema:MusicReleaseFormatType","rdfs:comment":"DigitalFormat.","rdfs:label":"DigitalFormat","schema:contributor":{"@id":"https://schema.org/docs/collab/MBZ"}},{"@id":"schema:EmployeeRole","@type":"rdfs:Class","rdfs:comment":"A subclass of OrganizationRole used to describe employee relationships.","rdfs:label":"EmployeeRole","rdfs:subClassOf":{"@id":"schema:OrganizationRole"}},{"@id":"schema:RejectAction","@type":"rdfs:Class","rdfs:comment":"The act of rejecting to/adopting an object.\\n\\nRelated actions:\\n\\n* [[AcceptAction]]: The antonym of RejectAction.","rdfs:label":"RejectAction","rdfs:subClassOf":{"@id":"schema:AllocateAction"}},{"@id":"schema:MerchantReturnFiniteReturnWindow","@type":"schema:MerchantReturnEnumeration","rdfs:comment":"Specifies that there is a finite window for product returns.","rdfs:label":"MerchantReturnFiniteReturnWindow","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2288"}},{"@id":"schema:ActiveActionStatus","@type":"schema:ActionStatusType","rdfs:comment":"An in-progress action (e.g., while watching the movie, or driving to a location).","rdfs:label":"ActiveActionStatus"},{"@id":"schema:EventVenue","@type":"rdfs:Class","rdfs:comment":"An event venue.","rdfs:label":"EventVenue","rdfs:subClassOf":{"@id":"schema:CivicStructure"}},{"@id":"schema:FreeReturn","@type":"schema:ReturnFeesEnumeration","rdfs:comment":"Specifies that product returns are free of charge for the customer.","rdfs:label":"FreeReturn","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:frequency","@type":"rdf:Property","rdfs:comment":"How often the dose is taken, e.g. 'daily'.","rdfs:label":"frequency","schema:domainIncludes":{"@id":"schema:DoseSchedule"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:polygon","@type":"rdf:Property","rdfs:comment":"A polygon is the area enclosed by a point-to-point path for which the starting and ending points are the same. A polygon is expressed as a series of four or more space delimited points where the first and final points are identical.","rdfs:label":"polygon","schema:domainIncludes":{"@id":"schema:GeoShape"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:isTierOf","@type":"rdf:Property","rdfs:comment":"The member program this tier is a part of.","rdfs:label":"isTierOf","schema:domainIncludes":{"@id":"schema:MemberProgramTier"},"schema:inverseOf":{"@id":"schema:hasTiers"},"schema:rangeIncludes":{"@id":"schema:MemberProgram"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3563"}},{"@id":"schema:ScheduleAction","@type":"rdfs:Class","rdfs:comment":"Scheduling future actions, events, or tasks.\\n\\nRelated actions:\\n\\n* [[ReserveAction]]: Unlike ReserveAction, ScheduleAction allocates future actions (e.g. an event, a task, etc) towards a time slot / spatial allocation.","rdfs:label":"ScheduleAction","rdfs:subClassOf":{"@id":"schema:PlanAction"}},{"@id":"schema:itemDefectReturnLabelSource","@type":"rdf:Property","rdfs:comment":"The method (from an enumeration) by which the customer obtains a return shipping label for a defect product.","rdfs:label":"itemDefectReturnLabelSource","schema:domainIncludes":{"@id":"schema:MerchantReturnPolicy"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:ReturnLabelSourceEnumeration"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:MedicalCode","@type":"rdfs:Class","rdfs:comment":"A code for a medical entity.","rdfs:label":"MedicalCode","rdfs:subClassOf":[{"@id":"schema:MedicalIntangible"},{"@id":"schema:CategoryCode"}],"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:isInvolvedInBiologicalProcess","@type":"rdf:Property","rdfs:comment":"Biological process this BioChemEntity is involved in; please use PropertyValue if you want to include any evidence.","rdfs:label":"isInvolvedInBiologicalProcess","schema:domainIncludes":{"@id":"schema:BioChemEntity"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":[{"@id":"schema:PropertyValue"},{"@id":"schema:URL"},{"@id":"schema:DefinedTerm"}],"schema:source":{"@id":"http://www.bioschemas.org/BioChemEntity"}},{"@id":"schema:productionDate","@type":"rdf:Property","rdfs:comment":"The date of production of the item, e.g. vehicle.","rdfs:label":"productionDate","schema:contributor":{"@id":"https://schema.org/docs/collab/Automotive_Ontology_Working_Group"},"schema:domainIncludes":[{"@id":"schema:Product"},{"@id":"schema:Vehicle"}],"schema:rangeIncludes":{"@id":"schema:Date"}},{"@id":"schema:emissionsCO2","@type":"rdf:Property","rdfs:comment":'The CO2 emissions in g/km. When used in combination with a QuantitativeValue, put "g/km" into the unitText property of that value, since there is no UN/CEFACT Common Code for "g/km".',"rdfs:label":"emissionsCO2","schema:contributor":{"@id":"https://schema.org/docs/collab/Automotive_Ontology_Working_Group"},"schema:domainIncludes":{"@id":"schema:Vehicle"},"schema:isPartOf":{"@id":"https://auto.schema.org"},"schema:rangeIncludes":{"@id":"schema:Number"}},{"@id":"schema:legislationDateVersion","@type":"rdf:Property","owl:equivalentProperty":{"@id":"http://data.europa.eu/eli/ontology#version_date"},"rdfs:comment":'The point-in-time at which the provided description of the legislation is valid (e.g.: when looking at the law on the 2016-04-07 (= dateVersion), I get the consolidation of 2015-04-12 of the "National Insurance Contributions Act 2015")',"rdfs:label":"legislationDateVersion","schema:contributor":{"@id":"https://schema.org/docs/collab/ELI"},"schema:domainIncludes":{"@id":"schema:Legislation"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Date"},"schema:source":[{"@id":"https://op.europa.eu/en/web/eu-vocabularies/model/-/resource/dataset/eli"},{"@id":"https://github.com/schemaorg/schemaorg/issues/1156"}],"skos:exactMatch":{"@id":"http://data.europa.eu/eli/ontology#version_date"}},{"@id":"schema:Hotel","@type":"rdfs:Class","rdfs:comment":`A hotel is an establishment that provides lodging paid on a short-term basis (source: Wikipedia, the free encyclopedia, see http://en.wikipedia.org/wiki/Hotel).
21
+ `),e.exec(`DELETE FROM latest_packages WHERE time < unixepoch() - ${this.options.cacheExpirySeconds}`),e}performPackageCheck(s,e){let a=s.match(/cdn\.jsdelivr\.net\/npm\/(@?[^/]+)@([^/]+)/);if(!a)return;let t=a[1],r=a[2],i=`https://data.jsdelivr.com/v1/package/npm/${t}`,o=v(i,{timeoutSeconds:this.options.timeoutSeconds,maxRedirs:0});if(!o.success||!o.body){console.error(`[html-validate-latest-packages] Error checking package version for ${s}: ${o.error??"Unknown error"}`);return}try{let c=JSON.parse(o.body);c&&c.tags&&Object.values(c.tags).includes(r)?this.db.prepare("REPLACE INTO latest_packages (url, current, time) VALUES (?, 1, unixepoch())").run(s):(this.db.prepare("REPLACE INTO latest_packages (url, current, time) VALUES (?, 0, unixepoch())").run(s),this.report({node:e,message:`Package "${t}" is not using a current version tag. Found "${r}", but latest is "${c.tags.latest}".`}));}catch(c){let n=c instanceof Error?c.message:String(c);console.error(`[html-validate-latest-packages] Error parsing package info for ${s}: ${n}`);}}tagReady(s){let{target:e}=s,{tagName:a}=s.target,t;if(a==="script"){let i=e.getAttribute("src")?.value;typeof i=="string"&&(t=i);}else if(a==="link"&&e.getAttribute("rel")?.value==="stylesheet"){let i=e.getAttribute("href")?.value;typeof i=="string"&&(t=i);}else return;if(!t||!t.startsWith("http")||this.options.skipUrlPatterns.some(i=>t.includes(i)))return;(!e.hasAttribute("integrity")||!e.hasAttribute("crossorigin"))&&this.report({node:e,message:`Package resource is missing required "integrity" and "crossorigin" attributes: ${t}`});let r=this.db.prepare("SELECT current FROM latest_packages WHERE url = ?").get(t);if(r){r.current===0&&this.report({node:e,message:`Package is using an outdated version: ${t}`});return}this.performPackageCheck(t,e);}};d(H,"LatestPackagesRule");var A=H;var ve={requiredParameters:["subject","body"]},j=class j extends htmlValidate.Rule{constructor(s){super({...ve,...s});}static schema(){return {requiredParameters:{type:"array",items:{type:"string"},description:'An array of parameters that must be present in a mailto: link (e.g., ["subject", "body"]).'}}}documentation(){return {description:"Enforce that mailto: links contain specific parameters.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("tag:ready",s=>this.tagReady(s));}tagReady(s){let{target:e}=s;if(e.tagName!=="a")return;let a=e.getAttribute("href")?.value;if(!(typeof a!="string"||!a)&&a.startsWith("mailto:"))try{let t=a.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">"),r=new URL(t),i=this.options.requiredParameters.filter(o=>!r.searchParams.has(o));i.length>0&&this.report({node:e,message:`mailto: link is missing required parameters: ${i.join(", ")}`});}catch{this.report({node:e,message:"mailto: link is malformed and could not be parsed."});}}};d(j,"MailtoAwesomeRule");var w=j;var q=class q extends htmlValidate.Rule{documentation(){return {description:"Disallow script tag with src including jQuery",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.m#rules"}}setup(){this.on("tag:ready",s=>{let{target:e}=s;if(e.tagName!=="script")return;let a=e.getAttribute("src")?.value;typeof a!="string"||!a||a.toLowerCase().includes("jquery")&&this.report({node:e,message:"Script tag with src including jQuery is not allowed"});});}};d(q,"NoJqueryRule");var S=q;var D={"@context":{brick:"https://brickschema.org/schema/Brick#",csvw:"http://www.w3.org/ns/csvw#",dc:"http://purl.org/dc/elements/1.1/",dcam:"http://purl.org/dc/dcam/",dcat:"http://www.w3.org/ns/dcat#",dcmitype:"http://purl.org/dc/dcmitype/",dcterms:"http://purl.org/dc/terms/",doap:"http://usefulinc.com/ns/doap#",foaf:"http://xmlns.com/foaf/0.1/",odrl:"http://www.w3.org/ns/odrl/2/",org:"http://www.w3.org/ns/org#",owl:"http://www.w3.org/2002/07/owl#",prof:"http://www.w3.org/ns/dx/prof/",prov:"http://www.w3.org/ns/prov#",qb:"http://purl.org/linked-data/cube#",rdf:"http://www.w3.org/1999/02/22-rdf-syntax-ns#",rdfs:"http://www.w3.org/2000/01/rdf-schema#",schema:"https://schema.org/",sh:"http://www.w3.org/ns/shacl#",skos:"http://www.w3.org/2004/02/skos/core#",sosa:"http://www.w3.org/ns/sosa/",ssn:"http://www.w3.org/ns/ssn/",time:"http://www.w3.org/2006/time#",vann:"http://purl.org/vocab/vann/",void:"http://rdfs.org/ns/void#",xsd:"http://www.w3.org/2001/XMLSchema#"},"@graph":[{"@id":"schema:option","@type":"rdf:Property","rdfs:comment":"A sub property of object. The options subject to this action.","rdfs:label":"option","rdfs:subPropertyOf":{"@id":"schema:object"},"schema:domainIncludes":{"@id":"schema:ChooseAction"},"schema:rangeIncludes":[{"@id":"schema:Text"},{"@id":"schema:Thing"}],"schema:supersededBy":{"@id":"schema:actionOption"}},{"@id":"schema:minimumPaymentDue","@type":"rdf:Property","rdfs:comment":"The minimum payment required at this time.","rdfs:label":"minimumPaymentDue","schema:domainIncludes":{"@id":"schema:Invoice"},"schema:rangeIncludes":[{"@id":"schema:PriceSpecification"},{"@id":"schema:MonetaryAmount"}]},{"@id":"schema:DJMixAlbum","@type":"schema:MusicAlbumProductionType","rdfs:comment":"DJMixAlbum.","rdfs:label":"DJMixAlbum","schema:contributor":{"@id":"https://schema.org/docs/collab/MBZ"}},{"@id":"schema:Nonprofit501c27","@type":"schema:USNonprofitType","rdfs:comment":"Nonprofit501c27: Non-profit type referring to State-Sponsored Workers' Compensation Reinsurance Organizations.","rdfs:label":"Nonprofit501c27","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:includedDataCatalog","@type":"rdf:Property","rdfs:comment":"A data catalog which contains this dataset (this property was previously 'catalog', preferred name is now 'includedInDataCatalog').","rdfs:label":"includedDataCatalog","schema:domainIncludes":{"@id":"schema:Dataset"},"schema:rangeIncludes":{"@id":"schema:DataCatalog"},"schema:supersededBy":{"@id":"schema:includedInDataCatalog"}},{"@id":"schema:UKTrust","@type":"schema:UKNonprofitType","rdfs:comment":"UKTrust: Non-profit type referring to a UK trust.","rdfs:label":"UKTrust","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:discount","@type":"rdf:Property","rdfs:comment":"Any discount applied (to an Order).","rdfs:label":"discount","schema:domainIncludes":{"@id":"schema:Order"},"schema:rangeIncludes":[{"@id":"schema:Text"},{"@id":"schema:Number"}]},{"@id":"schema:printPage","@type":"rdf:Property","rdfs:comment":"If this NewsArticle appears in print, this field indicates the name of the page on which the article is found. Please note that this field is intended for the exact page name (e.g. A5, B18).","rdfs:label":"printPage","schema:domainIncludes":{"@id":"schema:NewsArticle"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:muscleAction","@type":"rdf:Property","rdfs:comment":"The movement the muscle generates.","rdfs:label":"muscleAction","schema:domainIncludes":{"@id":"schema:Muscle"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:illustrator","@type":"rdf:Property","rdfs:comment":"The illustrator of the book.","rdfs:label":"illustrator","schema:domainIncludes":{"@id":"schema:Book"},"schema:rangeIncludes":{"@id":"schema:Person"}},{"@id":"schema:contentType","@type":"rdf:Property","rdfs:comment":"The supported content type(s) for an EntryPoint response.","rdfs:label":"contentType","schema:domainIncludes":{"@id":"schema:EntryPoint"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:servesCuisine","@type":"rdf:Property","rdfs:comment":"The cuisine of the restaurant.","rdfs:label":"servesCuisine","schema:domainIncludes":{"@id":"schema:FoodEstablishment"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:distance","@type":"rdf:Property","rdfs:comment":"The distance travelled, e.g. exercising or travelling.","rdfs:label":"distance","schema:domainIncludes":[{"@id":"schema:ExerciseAction"},{"@id":"schema:TravelAction"}],"schema:rangeIncludes":{"@id":"schema:Distance"}},{"@id":"schema:containedInPlace","@type":"rdf:Property","rdfs:comment":"The basic containment relation between a place and one that contains it.","rdfs:label":"containedInPlace","schema:domainIncludes":{"@id":"schema:Place"},"schema:inverseOf":{"@id":"schema:containsPlace"},"schema:rangeIncludes":{"@id":"schema:Place"}},{"@id":"schema:ReturnAtKiosk","@type":"schema:ReturnMethodEnumeration","rdfs:comment":"Specifies that product returns must be made at a kiosk.","rdfs:label":"ReturnAtKiosk","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:broker","@type":"rdf:Property","rdfs:comment":"An entity that arranges for an exchange between a buyer and a seller. In most cases a broker never acquires or releases ownership of a product or service involved in an exchange. If it is not clear whether an entity is a broker, seller, or buyer, the latter two terms are preferred.","rdfs:label":"broker","schema:domainIncludes":[{"@id":"schema:Order"},{"@id":"schema:Reservation"},{"@id":"schema:Service"},{"@id":"schema:Invoice"}],"schema:rangeIncludes":[{"@id":"schema:Person"},{"@id":"schema:Organization"}]},{"@id":"schema:permittedUsage","@type":"rdf:Property","rdfs:comment":"Indications regarding the permitted usage of the accommodation.","rdfs:label":"permittedUsage","schema:contributor":{"@id":"https://schema.org/docs/collab/STI_Accommodation_Ontology"},"schema:domainIncludes":{"@id":"schema:Accommodation"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:AerobicActivity","@type":"schema:PhysicalActivityCategory","rdfs:comment":"Physical activity of relatively low intensity that depends primarily on the aerobic energy-generating process; during activity, the aerobic metabolism uses oxygen to adequately meet energy demands during exercise.","rdfs:label":"AerobicActivity","schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:totalHistoricalEnrollment","@type":"rdf:Property","rdfs:comment":"The total number of students that have enrolled in the history of the course.","rdfs:label":"totalHistoricalEnrollment","schema:domainIncludes":{"@id":"schema:Course"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Integer"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3281"}},{"@id":"schema:WearableMeasurementHeight","@type":"schema:WearableMeasurementTypeEnumeration","rdfs:comment":"Measurement of the height, for example the heel height of a shoe.","rdfs:label":"WearableMeasurementHeight","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2811"}},{"@id":"schema:Clip","@type":"rdfs:Class","rdfs:comment":"A short TV or radio program or a segment/part of a program.","rdfs:label":"Clip","rdfs:subClassOf":{"@id":"schema:CreativeWork"}},{"@id":"schema:WatchAction","@type":"rdfs:Class","rdfs:comment":"The act of consuming dynamic/moving visual content.","rdfs:label":"WatchAction","rdfs:subClassOf":{"@id":"schema:ConsumeAction"}},{"@id":"schema:issuedBy","@type":"rdf:Property","rdfs:comment":"The organization issuing the item, for example a [[Permit]], [[Ticket]], or [[Certification]].","rdfs:label":"issuedBy","schema:domainIncludes":[{"@id":"schema:Permit"},{"@id":"schema:Ticket"},{"@id":"schema:Certification"}],"schema:rangeIncludes":{"@id":"schema:Organization"}},{"@id":"schema:DemoAlbum","@type":"schema:MusicAlbumProductionType","rdfs:comment":"DemoAlbum.","rdfs:label":"DemoAlbum","schema:contributor":{"@id":"https://schema.org/docs/collab/MBZ"}},{"@id":"schema:hasMemberProgram","@type":"rdf:Property","rdfs:comment":"MemberProgram offered by an Organization, for example an eCommerce merchant or an airline.","rdfs:label":"hasMemberProgram","schema:domainIncludes":{"@id":"schema:Organization"},"schema:rangeIncludes":{"@id":"schema:MemberProgram"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3563"}},{"@id":"schema:hasEnergyEfficiencyCategory","@type":"rdf:Property","rdfs:comment":"Defines the energy efficiency Category (which could be either a rating out of range of values or a yes/no certification) for a product according to an international energy efficiency standard.","rdfs:label":"hasEnergyEfficiencyCategory","schema:domainIncludes":{"@id":"schema:EnergyConsumptionDetails"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:EnergyEfficiencyEnumeration"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2670"}},{"@id":"schema:rangeIncludes","@type":"rdf:Property","rdfs:comment":"Relates a property to a class that constitutes (one of) the expected type(s) for values of the property.","rdfs:label":"rangeIncludes","schema:domainIncludes":{"@id":"schema:Property"},"schema:isPartOf":{"@id":"https://meta.schema.org"},"schema:rangeIncludes":{"@id":"schema:Class"}},{"@id":"schema:contentLocation","@type":"rdf:Property","rdfs:comment":"The location depicted or described in the content. For example, the location in a photograph or painting.","rdfs:label":"contentLocation","schema:domainIncludes":{"@id":"schema:CreativeWork"},"schema:rangeIncludes":{"@id":"schema:Place"}},{"@id":"schema:NonprofitANBI","@type":"schema:NLNonprofitType","rdfs:comment":"NonprofitANBI: Non-profit type referring to a Public Benefit Organization (NL).","rdfs:label":"NonprofitANBI","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:ExerciseGym","@type":"rdfs:Class","rdfs:comment":"A gym.","rdfs:label":"ExerciseGym","rdfs:subClassOf":{"@id":"schema:SportsActivityLocation"}},{"@id":"schema:MusicPlaylist","@type":"rdfs:Class","rdfs:comment":"A collection of music tracks in playlist form.","rdfs:label":"MusicPlaylist","rdfs:subClassOf":{"@id":"schema:CreativeWork"}},{"@id":"schema:weightPercentage","@type":"rdf:Property","rdfs:comment":"Fraction of the weight that is used to compute the shipping price.","rdfs:label":"weightPercentage","schema:domainIncludes":{"@id":"schema:ShippingRateSettings"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Number"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3617"}},{"@id":"schema:landlord","@type":"rdf:Property","rdfs:comment":"A sub property of participant. The owner of the real estate property.","rdfs:label":"landlord","rdfs:subPropertyOf":{"@id":"schema:participant"},"schema:domainIncludes":{"@id":"schema:RentAction"},"schema:rangeIncludes":[{"@id":"schema:Organization"},{"@id":"schema:Person"}]},{"@id":"schema:recordedAt","@type":"rdf:Property","rdfs:comment":"The Event where the CreativeWork was recorded. The CreativeWork may capture all or part of the event.","rdfs:label":"recordedAt","schema:domainIncludes":{"@id":"schema:CreativeWork"},"schema:inverseOf":{"@id":"schema:recordedIn"},"schema:rangeIncludes":{"@id":"schema:Event"}},{"@id":"schema:DigitalFormat","@type":"schema:MusicReleaseFormatType","rdfs:comment":"DigitalFormat.","rdfs:label":"DigitalFormat","schema:contributor":{"@id":"https://schema.org/docs/collab/MBZ"}},{"@id":"schema:EmployeeRole","@type":"rdfs:Class","rdfs:comment":"A subclass of OrganizationRole used to describe employee relationships.","rdfs:label":"EmployeeRole","rdfs:subClassOf":{"@id":"schema:OrganizationRole"}},{"@id":"schema:RejectAction","@type":"rdfs:Class","rdfs:comment":"The act of rejecting to/adopting an object.\\n\\nRelated actions:\\n\\n* [[AcceptAction]]: The antonym of RejectAction.","rdfs:label":"RejectAction","rdfs:subClassOf":{"@id":"schema:AllocateAction"}},{"@id":"schema:MerchantReturnFiniteReturnWindow","@type":"schema:MerchantReturnEnumeration","rdfs:comment":"Specifies that there is a finite window for product returns.","rdfs:label":"MerchantReturnFiniteReturnWindow","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2288"}},{"@id":"schema:ActiveActionStatus","@type":"schema:ActionStatusType","rdfs:comment":"An in-progress action (e.g., while watching the movie, or driving to a location).","rdfs:label":"ActiveActionStatus"},{"@id":"schema:EventVenue","@type":"rdfs:Class","rdfs:comment":"An event venue.","rdfs:label":"EventVenue","rdfs:subClassOf":{"@id":"schema:CivicStructure"}},{"@id":"schema:FreeReturn","@type":"schema:ReturnFeesEnumeration","rdfs:comment":"Specifies that product returns are free of charge for the customer.","rdfs:label":"FreeReturn","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:frequency","@type":"rdf:Property","rdfs:comment":"How often the dose is taken, e.g. 'daily'.","rdfs:label":"frequency","schema:domainIncludes":{"@id":"schema:DoseSchedule"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:polygon","@type":"rdf:Property","rdfs:comment":"A polygon is the area enclosed by a point-to-point path for which the starting and ending points are the same. A polygon is expressed as a series of four or more space delimited points where the first and final points are identical.","rdfs:label":"polygon","schema:domainIncludes":{"@id":"schema:GeoShape"},"schema:rangeIncludes":{"@id":"schema:Text"}},{"@id":"schema:isTierOf","@type":"rdf:Property","rdfs:comment":"The member program this tier is a part of.","rdfs:label":"isTierOf","schema:domainIncludes":{"@id":"schema:MemberProgramTier"},"schema:inverseOf":{"@id":"schema:hasTiers"},"schema:rangeIncludes":{"@id":"schema:MemberProgram"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3563"}},{"@id":"schema:ScheduleAction","@type":"rdfs:Class","rdfs:comment":"Scheduling future actions, events, or tasks.\\n\\nRelated actions:\\n\\n* [[ReserveAction]]: Unlike ReserveAction, ScheduleAction allocates future actions (e.g. an event, a task, etc) towards a time slot / spatial allocation.","rdfs:label":"ScheduleAction","rdfs:subClassOf":{"@id":"schema:PlanAction"}},{"@id":"schema:itemDefectReturnLabelSource","@type":"rdf:Property","rdfs:comment":"The method (from an enumeration) by which the customer obtains a return shipping label for a defect product.","rdfs:label":"itemDefectReturnLabelSource","schema:domainIncludes":{"@id":"schema:MerchantReturnPolicy"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:ReturnLabelSourceEnumeration"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:MedicalCode","@type":"rdfs:Class","rdfs:comment":"A code for a medical entity.","rdfs:label":"MedicalCode","rdfs:subClassOf":[{"@id":"schema:MedicalIntangible"},{"@id":"schema:CategoryCode"}],"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:isInvolvedInBiologicalProcess","@type":"rdf:Property","rdfs:comment":"Biological process this BioChemEntity is involved in; please use PropertyValue if you want to include any evidence.","rdfs:label":"isInvolvedInBiologicalProcess","schema:domainIncludes":{"@id":"schema:BioChemEntity"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":[{"@id":"schema:PropertyValue"},{"@id":"schema:URL"},{"@id":"schema:DefinedTerm"}],"schema:source":{"@id":"http://www.bioschemas.org/BioChemEntity"}},{"@id":"schema:productionDate","@type":"rdf:Property","rdfs:comment":"The date of production of the item, e.g. vehicle.","rdfs:label":"productionDate","schema:contributor":{"@id":"https://schema.org/docs/collab/Automotive_Ontology_Working_Group"},"schema:domainIncludes":[{"@id":"schema:Product"},{"@id":"schema:Vehicle"}],"schema:rangeIncludes":{"@id":"schema:Date"}},{"@id":"schema:emissionsCO2","@type":"rdf:Property","rdfs:comment":'The CO2 emissions in g/km. When used in combination with a QuantitativeValue, put "g/km" into the unitText property of that value, since there is no UN/CEFACT Common Code for "g/km".',"rdfs:label":"emissionsCO2","schema:contributor":{"@id":"https://schema.org/docs/collab/Automotive_Ontology_Working_Group"},"schema:domainIncludes":{"@id":"schema:Vehicle"},"schema:isPartOf":{"@id":"https://auto.schema.org"},"schema:rangeIncludes":{"@id":"schema:Number"}},{"@id":"schema:legislationDateVersion","@type":"rdf:Property","owl:equivalentProperty":{"@id":"http://data.europa.eu/eli/ontology#version_date"},"rdfs:comment":'The point-in-time at which the provided description of the legislation is valid (e.g.: when looking at the law on the 2016-04-07 (= dateVersion), I get the consolidation of 2015-04-12 of the "National Insurance Contributions Act 2015")',"rdfs:label":"legislationDateVersion","schema:contributor":{"@id":"https://schema.org/docs/collab/ELI"},"schema:domainIncludes":{"@id":"schema:Legislation"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Date"},"schema:source":[{"@id":"https://op.europa.eu/en/web/eu-vocabularies/model/-/resource/dataset/eli"},{"@id":"https://github.com/schemaorg/schemaorg/issues/1156"}],"skos:exactMatch":{"@id":"http://data.europa.eu/eli/ontology#version_date"}},{"@id":"schema:Hotel","@type":"rdfs:Class","rdfs:comment":`A hotel is an establishment that provides lodging paid on a short-term basis (source: Wikipedia, the free encyclopedia, see http://en.wikipedia.org/wiki/Hotel).
19
22
  <br /><br />
20
23
  See also the <a href="/docs/hotels.html">dedicated document on the use of schema.org for marking up hotels and other forms of accommodations</a>.
21
24
  `,"rdfs:label":"Hotel","rdfs:subClassOf":{"@id":"schema:LodgingBusiness"},"schema:contributor":{"@id":"https://schema.org/docs/collab/STI_Accommodation_Ontology"}},{"@id":"schema:diet","@type":"rdf:Property","rdfs:comment":"A sub property of instrument. The diet used in this action.","rdfs:label":"diet","rdfs:subPropertyOf":{"@id":"schema:instrument"},"schema:domainIncludes":{"@id":"schema:ExerciseAction"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"},"schema:rangeIncludes":{"@id":"schema:Diet"}},{"@id":"schema:maximumVirtualAttendeeCapacity","@type":"rdf:Property","rdfs:comment":"The maximum virtual attendee capacity of an [[Event]] whose [[eventAttendanceMode]] is [[OnlineEventAttendanceMode]] (or the online aspects, in the case of a [[MixedEventAttendanceMode]]). ","rdfs:label":"maximumVirtualAttendeeCapacity","schema:domainIncludes":{"@id":"schema:Event"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Integer"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/1842"}},{"@id":"schema:Neuro","@type":"schema:PhysicalExam","rdfs:comment":"Neurological system clinical examination.","rdfs:label":"Neuro","schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:comment","@type":"rdf:Property","rdfs:comment":"Comments, typically from users.","rdfs:label":"comment","schema:domainIncludes":[{"@id":"schema:RsvpAction"},{"@id":"schema:CreativeWork"}],"schema:rangeIncludes":{"@id":"schema:Comment"}},{"@id":"schema:UnemploymentSupport","@type":"schema:GovernmentBenefitsType","rdfs:comment":"UnemploymentSupport: this is a benefit for unemployment support.","rdfs:label":"UnemploymentSupport","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2534"}},{"@id":"schema:hoursAvailable","@type":"rdf:Property","rdfs:comment":"The hours during which this service or contact is available.","rdfs:label":"hoursAvailable","schema:domainIncludes":[{"@id":"schema:Service"},{"@id":"schema:LocationFeatureSpecification"},{"@id":"schema:ContactPoint"}],"schema:rangeIncludes":{"@id":"schema:OpeningHoursSpecification"}},{"@id":"schema:PaymentMethod","@type":"rdfs:Class","rdfs:comment":`A payment method is a standardized procedure for transferring the monetary amount for a purchase. Payment methods are characterized by the legal and technical structures used, and by the organization or group carrying out the transaction. The following legacy values should be accepted:
@@ -516,6 +519,6 @@ E.g. Cheaper and slower: $5 in 5-7 days
516
519
  or Fast and expensive: $15 in 1-2 days.`,"rdfs:label":"OfferShippingDetails","rdfs:subClassOf":{"@id":"schema:StructuredValue"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2506"}},{"@id":"schema:SurgicalProcedure","@type":"rdfs:Class","owl:equivalentClass":{"@id":"http://purl.bioontology.org/ontology/SNOMEDCT/387713003"},"rdfs:comment":"A medical procedure involving an incision with instruments; performed for diagnose, or therapeutic purposes.","rdfs:label":"SurgicalProcedure","rdfs:subClassOf":{"@id":"schema:MedicalProcedure"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:hasVariant","@type":"rdf:Property","rdfs:comment":"Indicates a [[Product]] that is a member of this [[ProductGroup]] (or [[ProductModel]]).","rdfs:label":"hasVariant","schema:domainIncludes":{"@id":"schema:ProductGroup"},"schema:inverseOf":{"@id":"schema:isVariantOf"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Product"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/1797"}},{"@id":"schema:remainingAttendeeCapacity","@type":"rdf:Property","rdfs:comment":"The number of attendee places for an event that remain unallocated.","rdfs:label":"remainingAttendeeCapacity","schema:domainIncludes":{"@id":"schema:Event"},"schema:rangeIncludes":{"@id":"schema:Integer"}},{"@id":"schema:paymentMethod","@type":"rdf:Property","rdfs:comment":"The name of the credit card or other method of payment for the order.","rdfs:label":"paymentMethod","schema:domainIncludes":[{"@id":"schema:Order"},{"@id":"schema:Invoice"}],"schema:rangeIncludes":[{"@id":"schema:Text"},{"@id":"schema:PaymentMethod"}],"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/3537"}},{"@id":"schema:ListPrice","@type":"schema:PriceTypeEnumeration","rdfs:comment":"Represents the list price of an offered product. Typically the same as the [MSRP](https://schema.org/MSRP).","rdfs:label":"ListPrice","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2712"}},{"@id":"schema:acceptsReservations","@type":"rdf:Property","rdfs:comment":"Indicates whether a FoodEstablishment accepts reservations. Values can be Boolean, an URL at which reservations can be made or (for backwards compatibility) the strings ```Yes``` or ```No```.","rdfs:label":"acceptsReservations","schema:domainIncludes":{"@id":"schema:FoodEstablishment"},"schema:rangeIncludes":[{"@id":"schema:Text"},{"@id":"schema:URL"},{"@id":"schema:Boolean"}]},{"@id":"schema:PatientExperienceHealthAspect","@type":"schema:HealthAspectEnumeration","rdfs:comment":"Content about the real life experience of patients or people that have lived a similar experience about the topic. May be forums, topics, Q-and-A and related material.","rdfs:label":"PatientExperienceHealthAspect","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2374"}},{"@id":"schema:customerRemorseReturnFees","@type":"rdf:Property","rdfs:comment":"The type of return fees if the product is returned due to customer remorse.","rdfs:label":"customerRemorseReturnFees","schema:domainIncludes":{"@id":"schema:MerchantReturnPolicy"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:ReturnFeesEnumeration"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2880"}},{"@id":"schema:renegotiableLoan","@type":"rdf:Property","rdfs:comment":"Whether the terms for payment of interest can be renegotiated during the life of the loan.","rdfs:label":"renegotiableLoan","schema:contributor":{"@id":"https://schema.org/docs/collab/FIBO"},"schema:domainIncludes":{"@id":"schema:LoanOrCredit"},"schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:rangeIncludes":{"@id":"schema:Boolean"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/1253"}},{"@id":"schema:BodyMeasurementNeck","@type":"schema:BodyMeasurementTypeEnumeration","rdfs:comment":"Girth of neck. Used, for example, to fit shirts.","rdfs:label":"BodyMeasurementNeck","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2811"}},{"@id":"schema:warranty","@type":"rdf:Property","rdfs:comment":"The warranty promise(s) included in the offer.","rdfs:label":"warranty","schema:contributor":{"@id":"https://schema.org/docs/collab/GoodRelationsTerms"},"schema:domainIncludes":[{"@id":"schema:Offer"},{"@id":"schema:Demand"}],"schema:rangeIncludes":{"@id":"schema:WarrantyPromise"}},{"@id":"schema:broadcastOfEvent","@type":"rdf:Property","rdfs:comment":"The event being broadcast such as a sporting event or awards ceremony.","rdfs:label":"broadcastOfEvent","schema:domainIncludes":{"@id":"schema:BroadcastEvent"},"schema:rangeIncludes":{"@id":"schema:Event"}},{"@id":"schema:DrugCostCategory","@type":"rdfs:Class","rdfs:comment":"Enumerated categories of medical drug costs.","rdfs:label":"DrugCostCategory","rdfs:subClassOf":{"@id":"schema:MedicalEnumeration"},"schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}},{"@id":"schema:LimitedByGuaranteeCharity","@type":"schema:UKNonprofitType","rdfs:comment":"LimitedByGuaranteeCharity: Non-profit type referring to a charitable company that is limited by guarantee (UK).","rdfs:label":"LimitedByGuaranteeCharity","schema:isPartOf":{"@id":"https://pending.schema.org"},"schema:source":{"@id":"https://github.com/schemaorg/schemaorg/issues/2543"}},{"@id":"schema:director","@type":"rdf:Property","rdfs:comment":"A director of e.g. TV, radio, movie, video gaming etc. content, or of an event. Directors can be associated with individual items or with a series, episode, clip.","rdfs:label":"director","schema:domainIncludes":[{"@id":"schema:Clip"},{"@id":"schema:CreativeWorkSeason"},{"@id":"schema:Event"},{"@id":"schema:VideoObject"},{"@id":"schema:MovieSeries"},{"@id":"schema:TVSeries"},{"@id":"schema:VideoGame"},{"@id":"schema:Movie"},{"@id":"schema:RadioSeries"},{"@id":"schema:Episode"},{"@id":"schema:VideoGameSeries"}],"schema:rangeIncludes":{"@id":"schema:Person"}},{"@id":"schema:AutomatedTeller","@type":"rdfs:Class","rdfs:comment":"ATM/cash machine.","rdfs:label":"AutomatedTeller","rdfs:subClassOf":{"@id":"schema:FinancialService"}},{"@id":"schema:HotelRoom","@type":"rdfs:Class","rdfs:comment":`A hotel room is a single room in a hotel.
517
520
  <br /><br />
518
521
  See also the <a href="/docs/hotels.html">dedicated document on the use of schema.org for marking up hotels and other forms of accommodations</a>.
519
- `,"rdfs:label":"HotelRoom","rdfs:subClassOf":{"@id":"schema:Room"},"schema:contributor":{"@id":"https://schema.org/docs/collab/STI_Accommodation_Ontology"}},{"@id":"schema:PercutaneousProcedure","@type":"schema:MedicalProcedureType","rdfs:comment":"A type of medical procedure that involves percutaneous techniques, where access to organs or tissue is achieved via needle-puncture of the skin. For example, catheter-based procedures like stent delivery.","rdfs:label":"PercutaneousProcedure","schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}}]};var D=class D extends htmlValidate.Rule{constructor(){super();this.validClasses=new Set;this.validProperties=new Set;try{let e=O["@context"],a=e.rdfs+"Class",t=e.rdf+"Property";if(O["@graph"])for(let i of O["@graph"]){let r=Array.isArray(i["@type"])?i["@type"]:[i["@type"]];(r.includes("rdfs:Class")||r.includes(a))&&this.validClasses.add(i["rdfs:label"]),(r.includes("rdf:Property")||r.includes(t))&&this.validProperties.add(i["rdfs:label"]);}if(this.validClasses.size===0||this.validProperties.size===0)throw new Error("Schema loading resulted in zero classes or properties. The vocabulary format may have changed.")}catch(e){throw new Error(`[SchemaOrgJsonLdRule] CRITICAL: Failed to load or process schema: ${e.message}`)}}documentation(){return {description:"Validates JSON-LD against the bundled Schema.org vocabulary.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.md#schema-org-json-ld"}}setup(){this.on("element:ready",e=>this.elementReady(e));}elementReady(e){let{target:a}=e;if(a.tagName!=="script"||a.getAttribute("type")?.value!=="application/ld+json")return;let i=a.textContent;if(!i||i.trim()==="")return;let r;try{r=JSON.parse(i);}catch(o){this.report({node:a,message:`Invalid JSON syntax: ${o.message}`});return}this.validateSchemaObject(r,a,true);}validateSchemaObject(e,a,t){if(Array.isArray(e)){e.forEach(r=>this.validateSchemaObject(r,a,t));return}if(typeof e!="object"||e===null)return;if(t&&e["@graph"]&&Array.isArray(e["@graph"])){this.validateSchemaObject(e["@graph"],a,true);return}let i=e["@type"];if(t&&!i){this.report({node:a,message:'Top-level JSON-LD object is missing required "@type" property.'});return}if(i){let r=Array.isArray(i)?i:[i];for(let o of r)this.validClasses.has(o)||this.report({node:a,message:`Schema.org type "${o}" is not a valid class.`});}for(let r in e){if(r.startsWith("@"))continue;this.validProperties.has(r)||this.report({node:a,message:`Schema.org property "${r}" is not a valid property.`});let o=e[r];typeof o=="object"&&o!==null&&this.validateSchemaObject(o,a,false);}}};c(D,"SchemaOrgJsonLdRule");var I=D;var le={name:"nice-checkers-plugin",rules:{"nice-checkers/alternate-language-url":f,"nice-checkers/canonical-link":p,"nice-checkers/external-links":u,"nice-checkers/https-links":g,"nice-checkers/internal-links":y,"nice-checkers/latest-packages":b,"nice-checkers/mailto-awesome":v,"nice-checkers/no-jquery":P,"nice-checkers/schema-org-json-ld":I},configs:{recommended:{rules:{"nice-checkers/alternate-language-url":["error"],"nice-checkers/canonical-link":["error"],"nice-checkers/external-links":["error"],"nice-checkers/https-links":["error"],"nice-checkers/internal-links":["error"],"nice-checkers/latest-packages":["warn"],"nice-checkers/mailto-awesome":["error"],"nice-checkers/no-jquery":["error"],"nice-checkers/schema-org-json-ld":["error"]}}}},us=le;
520
- module.exports=us;//# sourceMappingURL=index.cjs.map
522
+ `,"rdfs:label":"HotelRoom","rdfs:subClassOf":{"@id":"schema:Room"},"schema:contributor":{"@id":"https://schema.org/docs/collab/STI_Accommodation_Ontology"}},{"@id":"schema:PercutaneousProcedure","@type":"schema:MedicalProcedureType","rdfs:comment":"A type of medical procedure that involves percutaneous techniques, where access to organs or tissue is achieved via needle-puncture of the skin. For example, catheter-based procedures like stent delivery.","rdfs:label":"PercutaneousProcedure","schema:isPartOf":{"@id":"https://health-lifesci.schema.org"}}]};var _=class _ extends htmlValidate.Rule{constructor(){super();this.validClasses=new Set;this.validProperties=new Set;try{let e=D["@context"],a=e.rdfs+"Class",t=e.rdf+"Property";if(D["@graph"])for(let r of D["@graph"]){let i=Array.isArray(r["@type"])?r["@type"]:[r["@type"]];(i.includes("rdfs:Class")||i.includes(a))&&this.validClasses.add(r["rdfs:label"]),(i.includes("rdf:Property")||i.includes(t))&&this.validProperties.add(r["rdfs:label"]);}if(this.validClasses.size===0||this.validProperties.size===0)throw new Error("Schema loading resulted in zero classes or properties. The vocabulary format may have changed.")}catch(e){throw new Error(`[SchemaOrgJsonLdRule] CRITICAL: Failed to load or process schema: ${e.message}`)}}documentation(){return {description:"Validates JSON-LD against the bundled Schema.org vocabulary.",url:"https://github.com/fulldecent/html-validate-nice-checkers/blob/main/README.md#schema-org-json-ld"}}setup(){this.on("element:ready",e=>this.elementReady(e));}elementReady(e){let{target:a}=e;if(a.tagName!=="script"||a.getAttribute("type")?.value!=="application/ld+json")return;let r=a.textContent;if(!r||r.trim()==="")return;let i;try{i=JSON.parse(r);}catch(o){this.report({node:a,message:`Invalid JSON syntax: ${o.message}`});return}this.validateSchemaObject(i,a,true);}validateSchemaObject(e,a,t){if(Array.isArray(e)){e.forEach(i=>this.validateSchemaObject(i,a,t));return}if(typeof e!="object"||e===null)return;if(t&&e["@graph"]&&Array.isArray(e["@graph"])){this.validateSchemaObject(e["@graph"],a,true);return}let r=e["@type"];if(t&&!r){this.report({node:a,message:'Top-level JSON-LD object is missing required "@type" property.'});return}if(r){let i=Array.isArray(r)?r:[r];for(let o of i)this.validClasses.has(o)||this.report({node:a,message:`Schema.org type "${o}" is not a valid class.`});}for(let i in e){if(i.startsWith("@"))continue;this.validProperties.has(i)||this.report({node:a,message:`Schema.org property "${i}" is not a valid property.`});let o=e[i];typeof o=="object"&&o!==null&&this.validateSchemaObject(o,a,false);}}};d(_,"SchemaOrgJsonLdRule");var k=_;var Oe={name:"nice-checkers-plugin",rules:{"nice-checkers/alternate-language-url":b,"nice-checkers/alternate-language-links":P,"nice-checkers/canonical-link":I,"nice-checkers/external-links":C,"nice-checkers/https-links":O,"nice-checkers/internal-links":T,"nice-checkers/latest-packages":A,"nice-checkers/mailto-awesome":w,"nice-checkers/no-jquery":S,"nice-checkers/schema-org-json-ld":k},configs:{recommended:{rules:{"nice-checkers/alternate-language-url":["error"],"nice-checkers/alternate-language-links":["error"],"nice-checkers/canonical-link":["error"],"nice-checkers/external-links":["error"],"nice-checkers/https-links":["error"],"nice-checkers/internal-links":["error"],"nice-checkers/latest-packages":["warn"],"nice-checkers/mailto-awesome":["error"],"nice-checkers/no-jquery":["error"],"nice-checkers/schema-org-json-ld":["error"]}}}},Ms=Oe;
523
+ module.exports=Ms;//# sourceMappingURL=index.cjs.map
521
524
  //# sourceMappingURL=index.cjs.map