Repository: crawler-commons/crawler-commons
Pull request #438 (2023-09-18 20:02:11)

Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.5.0 to 3.6.0
Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.5.0 to 3.6.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/apache/maven-javadoc-plugin/releases">org.apache.maven.plugins:maven-javadoc-plugin's releases</a>.</em></p>
<blockquote>
<h2>3.6.0</h2>
<!-- raw HTML omitted -->
<h2>🚀 New features and improvements</h2>
<ul>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-642">[MJAVADOC-642]</a> - Make offline mode configurable (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/238">#238</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-642">[MJAVADOC-642]</a> - Make offline mode configurable (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/232">#232</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-770">[MJAVADOC-770]</a> - Implement legacy mode for Java 9+ (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/228">#228</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-743">[MJAVADOC-743]</a> - Drop deprecated mojo parameter (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/185">#185</a>) <a href="https://github.com/cstamas"><code>@​cstamas</code></a></li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-742">[MJAVADOC-742]</a> - Fix resolution of docletArtifacts (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/186">#186</a>) <a href="https://github.com/cstamas"><code>@​cstamas</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-758">[MJAVADOC-758]</a> - correct exception in JavadocUtil (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/230">#230</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-769">[MJAVADOC-769]</a> - fix for transitive filename based modules (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/227">#227</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-767">[MJAVADOC-767]</a> - javadoc creates invalid --patch-module statements (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/225">#225</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-763">[MJAVADOC-763]</a> - remove test that doesn't test what it claims (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/219">#219</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-757">[MJAVADOC-757]</a> - fix Javadoc warnings (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/212">#212</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>Fix UmlGraph link in site example (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/198">#198</a>) <a href="https://github.com/ParkerM"><code>@​ParkerM</code></a></li>
</ul>
<h2>📦 Dependency updates</h2>
<ul>
<li><a href="https://issues.apache.org/jira/browse/JAVADOC-771">[JAVADOC-771]</a> - Upgrade Parent to 40 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/234">#234</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li>Bump org.apache.maven:maven-core from 3.6.0 to 3.8.1 in /src/it/projects/MJAVADOC-568_manifest-splitpackage/mojo (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/226">#226</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li>Bump com.google.guava:guava from 31.1-jre to 32.0.0-jre in /src/it/projects/MJAVADOC-769 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/229">#229</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-766">[MJAVADOC-766]</a> - Bump org.codehaus.plexus:plexus-archiver from 4.7.1 to 4.8.0 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/222">#222</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li>Bump hamcrest-core from 1.3 to 2.2 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/221">#221</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li>Bump guava from 27.0.1-jre to 32.0.0-jre in /src/it/projects/MJAVADOC-555_link-automatic-modules (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/207">#207</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li>Bump javax.servlet-api from 3.1.0 to 4.0.1 (test dependency) (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/220">#220</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-761">[MJAVADOC-761]</a> - commons-io 2.13.0 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/215">#215</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>Bump plexus-utils from 3.5.0 to 3.5.1 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/201">#201</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li>Bump mockito-core from 4.4.0 to 4.11.0 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/196">#196</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
</ul>
<h2>👻 Maintenance</h2>
<ul>
<li>fix jenkins link (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/237">#237</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li>Fix build on jenkins (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/236">#236</a>) <a href="https://github.com/slawekjaranowski"><code>@​slawekjaranowski</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-772">[MJAVADOC-772]</a> - Refresh download page (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/235">#235</a>) <a href="https://github.com/slawekjaranowski"><code>@​slawekjaranowski</code></a></li>
<li>Use 3.6.0 as release version (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/233">#233</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li>Remove superflous whitespace from PR template (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/231">#231</a>) <a href="https://github.com/hgschmie"><code>@​hgschmie</code></a></li>
<li>Bump jetty from 9.4.50.v20221201 to 9.4.51.v20230217 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/224">#224</a>) <a href="https://github.com/slachiewicz"><code>@​slachiewicz</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-765">[MJAVADOC-765]</a> - Fix build with Java 20 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/223">#223</a>) <a href="https://github.com/slachiewicz"><code>@​slachiewicz</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-760">[MJAVADOC-760]</a> - declare test dependencies (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/217">#217</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-760">[MJAVADOC-760]</a> - explicitly declare shared utility dependencies (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/216">#216</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>remove unused private method (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/213">#213</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li>replace plexus with maven shared utils (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/211">#211</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-755">[MJAVADOC-755]</a> - replace IOUtil.close with try with resources (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/209">#209</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MJAVADOC-755">[MJAVADOC-755]</a> - Use java.lang.String instead of StringUtils (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/208">#208</a>) <a href="https://github.com/elharo"><code>@​elharo</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MNG-6829">[MNG-6829]</a> - Replace StringUtils#isEmpty(String) and #isNotEmpty(String) (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/205">#205</a>) <a href="https://github.com/timtebeek"><code>@​timtebeek</code></a></li>
<li>Bump mockito-core from 4.4.0 to 4.11.0 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/196">#196</a>) <a href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li><a href="https://issues.apache.org/jira/browse/MNG-6825">[MNG-6825]</a> - Replace Maven shared StringUtils with Commons Lang3 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/pull/199">#199</a>) <a href="https://github.com/timtebeek"><code>@​timtebeek</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/7548066d25e6c01055400cca9acf26567c7a74e6"><code>7548066</code></a> [maven-release-plugin] prepare release maven-javadoc-plugin-3.6.0</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/77adc4747e29bbb1873ae812347a77c0e7648565"><code>77adc47</code></a> [MJAVADOC-642] Make offline mode configurable (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/238">#238</a>)</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/24362d288bc70c658cdcced4cc0b60b648180dc7"><code>24362d2</code></a> [MJAVADOC-742] Fix resolution of docletArtifacts (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/186">#186</a>)</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/bee41974b1768e779d9c21c6426776f6a4207b20"><code>bee4197</code></a> fix jenkins link (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/237">#237</a>)</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/9830bdcb5309db68f86401d7f14c62041ea4fae7"><code>9830bdc</code></a> Fix build on jenkins</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/6f30bed49a05dc548b4a6c1cffde92204aca14c6"><code>6f30bed</code></a> [MJAVADOC-642] Make offline mode configurable (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/232">#232</a>)</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/e4023d09cbbd99e27130acefbaa7bd9d0ba6c54f"><code>e4023d0</code></a> [JAVADOC-771] Upgrade Parent to 40 (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/234">#234</a>)</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/7904e45c82f3385771953896b917e2b589d9c19d"><code>7904e45</code></a> [MJAVADOC-772] Refresh download page</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/87c24242899b5a1bf01f591533fdbba25dbd238a"><code>87c2424</code></a> Bump org.apache.maven:maven-core (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/226">#226</a>)</li>
<li><a href="https://github.com/apache/maven-javadoc-plugin/commit/83ab01beea0a9cadafa4b2b05b3da35bd0d7dab5"><code>83ab01b</code></a> Use 3.6.0 as release version (<a href="https://redirect.github.com/apache/maven-javadoc-plugin/issues/233">#233</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.5.0...maven-javadoc-plugin-3.6.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-javadoc-plugin&package-manager=maven&previous-version=3.5.0&new-version=3.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.



Patch:
diff --git a/pom.xml b/pom.xml
index 436010d..587cd6d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -392,8 +392,8 @@
 	<properties>
 		<!-- Dependencies -->
 
-		<commons-io.version>2.13.0</commons-io.version>
-		<slf4j-api.version>2.0.7</slf4j-api.version>
+		<commons-io.version>2.15.0</commons-io.version>
+		<slf4j-api.version>2.0.9</slf4j-api.version>
 		<junit.version>5.9.3</junit.version>
 		<slf4j-log4j12.version>1.7.33</slf4j-log4j12.version>
 
@@ -404,13 +404,13 @@
 		<maven-surfire-plugin.version>3.1.2</maven-surfire-plugin.version>
 		<maven-release-plugin.version>3.0.1</maven-release-plugin.version>
 		<maven-source-plugin.version>3.3.0</maven-source-plugin.version>
-		<maven-javadoc-plugin.version>3.5.0</maven-javadoc-plugin.version>
+		<maven-javadoc-plugin.version>3.6.0</maven-javadoc-plugin.version>
 		<maven-gpg-plugin.version>3.1.0</maven-gpg-plugin.version>
 		<apache-rat-plugin.version>0.8</apache-rat-plugin.version>
 		<maven-assembly-plugin.version>2.2.2</maven-assembly-plugin.version>
 		<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
 		<checksum-maven-plugin.version>1.4</checksum-maven-plugin.version>
-		<maven.download.plugin.version>1.7.1</maven.download.plugin.version>
+		<maven.download.plugin.version>1.6.8</maven.download.plugin.version>
 
 		<!-- General Properties -->
 		<implementation.build>${scmBranch}@r${buildNumber}</implementation.build>
diff --git a/src/main/java/crawlercommons/sitemaps/Namespace.java b/src/main/java/crawlercommons/sitemaps/Namespace.java
index be554c3..93a1d9a 100644
--- a/src/main/java/crawlercommons/sitemaps/Namespace.java
+++ b/src/main/java/crawlercommons/sitemaps/Namespace.java
@@ -72,6 +72,8 @@ public class Namespace {
 
     public static final String LINKS = "http://www.w3.org/1999/xhtml";
 
+    public static final String PAGEMAPS = "http://www.google.com/schemas/sitemap-pagemap/1.0";
+
     /**
      * In contradiction to the protocol specification ("The Sitemap must ...
      * [s]pecify the namespace (protocol standard) within the &lt;urlset&gt;
@@ -101,6 +103,7 @@ public class Namespace {
         SITEMAP_SUPPORTED_NAMESPACES.addAll(Arrays.asList(VIDEO));
         SITEMAP_SUPPORTED_NAMESPACES.addAll(Arrays.asList(NEWS));
         SITEMAP_SUPPORTED_NAMESPACES.add(LINKS);
+        SITEMAP_SUPPORTED_NAMESPACES.add(PAGEMAPS);
     }
 
     /**
@@ -120,5 +123,6 @@ public class Namespace {
         SITEMAP_EXTENSION_NAMESPACES.put(Extension.VIDEO, Arrays.asList(VIDEO));
         SITEMAP_EXTENSION_NAMESPACES.put(Extension.MOBILE, Arrays.asList(MOBILE));
         SITEMAP_EXTENSION_NAMESPACES.put(Extension.LINKS, Arrays.asList(LINKS));
+        SITEMAP_EXTENSION_NAMESPACES.put(Extension.PAGEMAPS, Arrays.asList(PAGEMAPS));
     }
 }
diff --git a/src/main/java/crawlercommons/sitemaps/extension/Extension.java b/src/main/java/crawlercommons/sitemaps/extension/Extension.java
index 2e8a49a..0d90e7a 100644
--- a/src/main/java/crawlercommons/sitemaps/extension/Extension.java
+++ b/src/main/java/crawlercommons/sitemaps/extension/Extension.java
@@ -44,5 +44,11 @@ public enum Extension {
      * URL as having mobile content</cite>, cf.
      * http://www.google.com/schemas/sitemap-mobile/1.0
      */
-    MOBILE
+    MOBILE,
+    /**
+     * <cite>PageMaps is a structured data format that Google created to enable
+     * website creators to embed data and notes in their webpages.</cite>, cf.
+     * https://support.google.com/programmable-search/answer/1628213
+     */
+    PAGEMAPS
 }
diff --git a/src/main/java/crawlercommons/sitemaps/extension/PageMap.java b/src/main/java/crawlercommons/sitemaps/extension/PageMap.java
new file mode 100644
index 0000000..036f887
--- /dev/null
+++ b/src/main/java/crawlercommons/sitemaps/extension/PageMap.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2023 Crawler-Commons
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package crawlercommons.sitemaps.extension;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+
+/**
+ * Data model for the <a href=
+ * "https://support.google.com/programmable-search/answer/1628213">PageMaps</a>
+ * extension to the sitemap protocol used for Google's Programmable Search
+ * Engine.
+ * 
+ * A PageMap holds a list of {@link PageMapDataObject}s, each PageMapDataObject
+ * a map of attributes (pairs of name and value).
+ */
+@SuppressWarnings("serial")
+public class PageMap extends ExtensionMetadata {
+
+    private List<PageMapDataObject> dataObjects = new ArrayList<>();
+
+    public List<PageMapDataObject> getPageMapDataObjects() {
+        return dataObjects;
+    }
+
+    public void addDataObject(PageMapDataObject d) {
+        dataObjects.add(d);
+    }
+
+    @Override
+    public Map<String, String[]> asMap() {
+        Map<String, String[]> map = new LinkedHashMap<>();
+        for (PageMapDataObject dobj : dataObjects) {
+            for (Entry<String, String[]> e : dobj.asMap().entrySet()) {
+                map.put(e.getKey(), e.getValue());
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("PageMap: [");
+        if (!dataObjects.isEmpty()) {
+            sb.append('\n');
+        }
+        for (PageMapDataObject dobj : dataObjects) {
+            sb.append(dobj.toString()).append(",\n");
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(dataObjects);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        PageMap other = (PageMap) obj;
+        return Objects.equals(dataObjects, other.dataObjects);
+    }
+
+}
diff --git a/src/main/java/crawlercommons/sitemaps/extension/PageMapDataObject.java b/src/main/java/crawlercommons/sitemaps/extension/PageMapDataObject.java
new file mode 100644
index 0000000..9cb38f2
--- /dev/null
+++ b/src/main/java/crawlercommons/sitemaps/extension/PageMapDataObject.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2023 Crawler-Commons
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package crawlercommons.sitemaps.extension;
+
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Map.Entry;
+
+@SuppressWarnings("serial")
+public class PageMapDataObject extends ExtensionMetadata {
+    private String type;
+    private String id;
+    private Map<String, String> attributes;
+
+    public PageMapDataObject(String type, String id) {
+        this.type = type;
+        this.id = id;
+        attributes = new LinkedHashMap<>();
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+
+    public boolean hasAttribute(String name) {
+        return attributes.containsKey(name);
+    }
+
+    public String getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    public String addAttribute(String name, String value) {
+        return attributes.put(name, value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        PageMapDataObject other = (PageMapDataObject) obj;
+        return Objects.equals(attributes, other.attributes) && Objects.equals(id, other.id) && Objects.equals(type, other.type);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{type = ").append(type);
+        sb.append(", id = ").append(id);
+        sb.append(", attributes = ").append(attributes);
+        sb.append("}");
+        return sb.toString();
+    }
+
+    @Override
+    public Map<String, String[]> asMap() {
+        String keyFormat = "%s::%s";
+        String valueFormat = "%s: %s";
+        String key = String.format(Locale.ROOT, keyFormat, (getType() == null ? "" : getType()), (getId() == null ? "" : getId()));
+        String[] values = getAttributes().entrySet().stream().map((Entry<String, String> e) -> String.format(Locale.ROOT, valueFormat, e.getKey(), e.getValue())).toArray(String[]::new);
+        return Map.of(key, values);
+    }
+}
diff --git a/src/main/java/crawlercommons/sitemaps/sax/extension/ExtensionHandler.java b/src/main/java/crawlercommons/sitemaps/sax/extension/ExtensionHandler.java
index 4f4b454..14e4d3a 100644
--- a/src/main/java/crawlercommons/sitemaps/sax/extension/ExtensionHandler.java
+++ b/src/main/java/crawlercommons/sitemaps/sax/extension/ExtensionHandler.java
@@ -55,6 +55,8 @@ public abstract class ExtensionHandler extends DefaultHandler {
                 return new LinksHandler();
             case MOBILE:
                 return new MobileHandler();
+            case PAGEMAPS:
+                return new PageMapsHandler();
             default:
                 return null;
         }
diff --git a/src/main/java/crawlercommons/sitemaps/sax/extension/PageMapsHandler.java b/src/main/java/crawlercommons/sitemaps/sax/extension/PageMapsHandler.java
new file mode 100644
index 0000000..bd3cbc1
--- /dev/null
+++ b/src/main/java/crawlercommons/sitemaps/sax/extension/PageMapsHandler.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright 2023 Crawler-Commons
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package crawlercommons.sitemaps.sax.extension;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import crawlercommons.sitemaps.extension.PageMap;
+import crawlercommons.sitemaps.extension.PageMapDataObject;
+
+/**
+ * Handle SAX events in the Google's Programmable Search Engine <a href=
+ * "https://support.google.com/programmable-search/answer/1628213">PageMaps</a>
+ * extension namespace.
+ */
+public class PageMapsHandler extends ExtensionHandler {
+
+    private PageMap currPageMap;
+    private PageMapDataObject currDataObj;
+    private String currAttrName;
+    private StringBuilder currAttrVal = new StringBuilder();
+    private String currAttrValFromAttr;
+
+    public PageMapsHandler() {
+        reset();
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        resetCurrent();
+    }
+
+    private void resetCurrent() {
+        currDataObj = null;
+        currAttrName = null;
+        currAttrVal.setLength(0);
+        currAttrValFromAttr = null;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+        switch (localName) {
+            case "PageMap":
+            currPageMap = new PageMap();
+                break;
+            case "DataObject":
+            currDataObj = new PageMapDataObject(attributes.getValue("type"), attributes.getValue("id"));
+            if (currPageMap == null) {
+                // ignore lonesome DataObject elements
+            } else {
+                currPageMap.addDataObject(currDataObj);
+            }
+                break;
+            case "Attribute":
+            currAttrVal.setLength(0);
+            currAttrName = attributes.getValue("name");
+            if (attributes.getValue("value") != null) {
+                /*
+                 * The PageMaps specification
+                 * (https://support.google.com/programmable-search/answer/
+                 * 1628213) describes for PageMaps embedded in HTML that the
+                 * attribute value is given as element attribute named "value".
+                 * For sitemaps it should be given as character data. However,
+                 * some PageMaps sitemaps in the wild also use the HTML
+                 * mechanism. We fall back to the HTML mechanism if there is no
+                 * or white space only character data.
+                 */
+                currAttrValFromAttr = attributes.getValue("value");
+            }
+                break;
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        switch (localName) {
+            case "PageMap":
+            attributes.add(currPageMap);
+                break;
+            case "DataObject":
+                break;
+            case "Attribute":
+            String currAttrValStr = currAttrVal.toString().trim();
+            if (currDataObj == null) {
+                // ignore lonesome attributes
+            } else if (currAttrValStr.isEmpty() && currAttrValFromAttr != null) {
+                /*
+                 * If there is no or white space only character data, fall back
+                 * to the HTML mechanism and use the content of the attribute
+                 * "value".
+                 */
+                currDataObj.addAttribute(currAttrName, currAttrValFromAttr);
+            } else {
+                currDataObj.addAttribute(currAttrName, currAttrValStr);
+            }
+                break;
+        }
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        currAttrVal.append(String.valueOf(ch, start, length));
+    }
+
+}


Test patch:
diff --git a/src/test/java/crawlercommons/sitemaps/SiteMapParserExtensionTest.java b/src/test/java/crawlercommons/sitemaps/SiteMapParserExtensionTest.java
index 0bc9f50..322530a 100644
--- a/src/test/java/crawlercommons/sitemaps/SiteMapParserExtensionTest.java
+++ b/src/test/java/crawlercommons/sitemaps/SiteMapParserExtensionTest.java
@@ -16,17 +16,32 @@
 
 package crawlercommons.sitemaps;
 
-import crawlercommons.sitemaps.extension.*;
-import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.time.ZonedDateTime;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.*;
+import crawlercommons.sitemaps.extension.Extension;
+import crawlercommons.sitemaps.extension.ExtensionMetadata;
+import crawlercommons.sitemaps.extension.ImageAttributes;
+import crawlercommons.sitemaps.extension.LinkAttributes;
+import crawlercommons.sitemaps.extension.MobileAttributes;
+import crawlercommons.sitemaps.extension.NewsAttributes;
+import crawlercommons.sitemaps.extension.PageMap;
+import crawlercommons.sitemaps.extension.PageMapDataObject;
+import crawlercommons.sitemaps.extension.VideoAttributes;
 
 public class SiteMapParserExtensionTest {
 
@@ -257,4 +272,56 @@ public class SiteMapParserExtensionTest {
         SiteMap sm = (SiteMap) asm;
         assertEquals(74, sm.getSiteMapUrls().size());
     }
+
+    @Test
+    public void testPageMapSitemap() throws UnknownFormatException, IOException {
+        SiteMapParser parser = new SiteMapParser();
+        parser.setStrictNamespace(true);
+        parser.enableExtension(Extension.PAGEMAPS);
+
+        String urlStr = "http://www.example.com/pagemaps-sitemap.xml";
+        URL url = new URL(urlStr);
+        AbstractSiteMap asm = parse(parser, "src/test/resources/sitemaps/extension/pagemaps-sitemap.xml", url);
+
+        assertEquals(false, asm.isIndex());
+        assertEquals(true, asm instanceof SiteMap);
+        SiteMap sm = (SiteMap) asm;
+        assertEquals(2, sm.getSiteMapUrls().size());
+        assertEquals(urlStr, sm.getUrl().toString());
+        // System.out.println(sm.toString());
+        for (SiteMapURL u : sm.getSiteMapUrls()) {
+            System.out.println(u.toString());
+            for (Entry<Extension, ExtensionMetadata[]> x : u.getAttributes().entrySet()) {
+                assertEquals(Extension.PAGEMAPS, x.getKey());
+                System.out.println(x.getValue().getClass());
+                PageMap pageMap = (PageMap) x.getValue()[0];
+                List<PageMapDataObject> dataObjects = pageMap.getPageMapDataObjects();
+                PageMapDataObject dataObject;
+                switch (u.getUrl().toString()) {
+                    case "http://www.example.com/foo":
+                    assertEquals(2, dataObjects.size());
+                    dataObject = dataObjects.get(0);
+                    assertEquals("document", dataObject.getType());
+                    assertEquals("one", dataObject.getId());
+                    assertEquals("Doc One", dataObject.getAttribute("name"));
+                    assertEquals("3.5", dataObject.getAttribute("review"));
+                    dataObject = dataObjects.get(1);
+                    assertEquals("image", dataObject.getType());
+                    assertNull(dataObject.getId());
+                    assertEquals("http://www.example.com/foo.gif", dataObject.getAttribute("image_src"));
+                        break;
+                    case "http://www.example.com/bar":
+                    assertEquals(1, dataObjects.size());
+                    dataObject = dataObjects.get(0);
+                    assertEquals("document", dataObject.getType());
+                    assertEquals("two", dataObject.getId());
+                    assertEquals("Doc Two", dataObject.getAttribute("name"));
+                    assertEquals("4.0", dataObject.getAttribute("review"));
+                        break;
+                }
+                System.out.println(x.getKey() + ": " + Arrays.toString(x.getValue()));
+                System.out.println(x.getValue().length);
+            }
+        }
+    }
 }
diff --git a/src/test/java/crawlercommons/sitemaps/extension/PageMapsTest.java b/src/test/java/crawlercommons/sitemaps/extension/PageMapsTest.java
new file mode 100644
index 0000000..9a72837
--- /dev/null
+++ b/src/test/java/crawlercommons/sitemaps/extension/PageMapsTest.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2023 Crawler-Commons
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package crawlercommons.sitemaps.extension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+public class PageMapsTest {
+
+    @Test
+    public void testPageMapAttributesEquals() {
+        PageMap a = new PageMap();
+        PageMapDataObject da = new PageMapDataObject("test", "a");
+        da.addAttribute("foo", "bar");
+        a.addDataObject(da);
+        assertEquals(a, a);
+        assertNotNull(a.toString());
+        assertEquals(1, a.getPageMapDataObjects().size());
+        assertEquals("test", a.getPageMapDataObjects().get(0).getType());
+        assertEquals("a", a.getPageMapDataObjects().get(0).getId());
+        assertEquals("bar", a.getPageMapDataObjects().get(0).getAttribute("foo"));
+
+        PageMap b = new PageMap();
+        PageMapDataObject db = new PageMapDataObject("test", "a");
+        db.addAttribute("foo", "bar");
+        b.addDataObject(db);
+        assertEquals(da, db);
+        assertEquals(a, b);
+        db.addAttribute("hello", "world");
+        assertNotEquals(da, db);
+        assertNotEquals(a, b);
+        assertEquals(b, b);
+        assertNotNull(a.toString());
+        assertEquals(1, b.getPageMapDataObjects().size());
+        assertEquals("test", b.getPageMapDataObjects().get(0).getType());
+        assertEquals("a", b.getPageMapDataObjects().get(0).getId());
+        assertEquals("bar", b.getPageMapDataObjects().get(0).getAttribute("foo"));
+        assertEquals("world", b.getPageMapDataObjects().get(0).getAttribute("hello"));
+        assertEquals(1, b.asMap().size());
+        assertNotNull(b.asMap().get("test::a"));
+        assertEquals(2, b.asMap().get("test::a").length);
+
+        PageMap c = new PageMap();
+        PageMapDataObject dc = new PageMapDataObject("test", "c");
+        dc.addAttribute("abc", "xyz");
+        c.addDataObject(dc);
+        assertEquals(c, c);
+        assertNotEquals(a, c);
+        assertNotNull(a.toString());
+        assertEquals(1, c.getPageMapDataObjects().size());
+        assertEquals("test", c.getPageMapDataObjects().get(0).getType());
+        assertEquals("c", c.getPageMapDataObjects().get(0).getId());
+        assertEquals("xyz", c.getPageMapDataObjects().get(0).getAttribute("abc"));
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/sitemaps/extension/pagemaps-sitemap.xml b/src/test/resources/sitemaps/extension/pagemaps-sitemap.xml
new file mode 100644
index 0000000..5306bde
--- /dev/null
+++ b/src/test/resources/sitemaps/extension/pagemaps-sitemap.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
+        xmlns:content="http://www.google.com/schemas/sitemap-content/1.0">
+  <!-- source: Google's Programmable Search Engine documentation - PageMaps
+       https://support.google.com/programmable-search/answer/1628213
+       and Providing Structured Data
+       https://developers.google.com/custom-search/docs/structured_data
+  -->
+  <url>
+    <loc>http://www.example.com/foo</loc>
+    <PageMap xmlns="http://www.google.com/schemas/sitemap-pagemap/1.0">
+      <DataObject type="document" id="one">
+        <Attribute name="name">Doc One</Attribute>
+        <Attribute name="review">3.5</Attribute>
+      </DataObject>
+      <DataObject type="image">
+        <Attribute name="image_src">http://www.example.com/foo.gif</Attribute>
+      </DataObject>
+    </PageMap>
+  </url>
+  <url>
+    <loc>http://www.example.com/bar</loc>
+    <PageMap xmlns="http://www.google.com/schemas/sitemap-pagemap/1.0">
+      <DataObject type="document" id="two">
+        <Attribute name="name">Doc Two</Attribute>
+        <Attribute name="review">4.0</Attribute>
+      </DataObject>
+    </PageMap>
+  </url>
+</urlset>
\ No newline at end of file