Skip to content

fix(metadata): lenient RDFa extraction for unbound XML prefixes (#4) - #37

Merged
natechadwick merged 2 commits into
mainfrom
fix/issue-4-unbound-prefix-metadata
Aug 13, 2026
Merged

fix(metadata): lenient RDFa extraction for unbound XML prefixes (#4)#37
natechadwick merged 2 commits into
mainfrom
fix/issue-4-unbound-prefix-metadata

Conversation

@vijaya-boddipudi

Copy link
Copy Markdown
Collaborator

Fixes #4

Summary

PSMetadataExtractorService (RDFa / Semargl parse over published HTML) previously threw SAXParseException: The prefix "gcse" for element "gcse:search" is not bound when published pages included vendor embeds such as Google Custom Search without an xmlns:gcse declaration. That failure aborted the whole page's metadata delivery in PSMetadataDeliveryHandler with ERROR even when file publish itself succeeded.

This change makes extraction lenient:

  1. Pre-sanitize HTML before RDFa parse: collect xmlns:* declarations on the document, strip elements and attributes whose prefixes are not declared (deepest-first via Jsoup.unwrap, so children move to the parent before the parent is unwrapped), and rewrite non-XML named entities to numeric character references.
  2. Defensive fallback: wrap the RDFa parse in a try/catch that detects SAXParseException / unbound-prefix parse messages and logs WARN with the page path and the offending prefix when extractable, so the rest of the page metadata still flows through.
  3. Normal dcterms:* / og:* / perc:* metadata is unchanged because the metadata of interest lives in attribute values, not in unbound element/attribute names.

Acceptance criteria

  • HTML with unbound gcse:search (and similar) does not throw out of PSMetadataExtractorService / fail the delivery handler with ERROR for that reason alone.
  • WARN identifies page path + problem prefix for support (the pre-sanitize WARN and the catch-around-parse WARN).
  • Normal RDFa / dcterms metadata still extracted when present (verified by the new testUnboundPrefixGcseSearch fixture, which also asserts dcterms:source, dcterms:title, dcterms:description, dcterms:abstract).
  • Unit test covering gcse (and generic unbound prefix) fixtures.

Files changed

  • system/business/src/com/percussion/delivery/metadata/PSMetadataExtractorService.java — pre-sanitize (stripUnboundPrefixedMarkup, rewriteNonXmlNamedEntities) + lenient catch around RDFa parse (isUnboundPrefixParseFailure, extractUnboundPrefix).
  • system/Testing/src/com/percussion/delivery/PSMetadataExtractorServiceTests.java — 4 new tests (full fixture, minimal inline HTML, positive / negative helper coverage).
  • system/UnitTestResources/com/percussion/delivery/unbound-prefix-gcse.html — new fixture with <gcse:search>, vendor:data-id attribute, and dcterms:* / og:* metadata.
  • CHANGELOG.md — new 8.1.8 / GH_POST_PR_COMMIT_RUN_ID entry per AGENTS.md guidelines.
  • AGENTS.md — point at main (the active branch), not the non-existent development-8.1.x.

Verification

./mvn-env.sh test -pl system -am -Dtest=PSMetadataExtractorServiceTests -Dsurefire.failIfNoSpecifiedTests=false

Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.949 s
  - testUnboundPrefixGcseSearch                       (new)
  - testUnboundPrefixOnlyDoesNotThrow                 (new)
  - testIsUnboundPrefixParseFailureDetectsGcse        (new)
  - testIsUnboundPrefixParseFailureIgnoresOtherErrors (new)
  - testEntityAndScriptHandling, testbwcorona, testNoAbstract,
    testgetPlainProperty, testgetNamespace             (existing, all pass)

Spotless check on the modified files: clean. Checkstyle: pre-existing config issue on main (JavadocMethod.scope property) — not introduced by this change.

Related

@natechadwick-intsof natechadwick-intsof left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This is a solid, JDK 8-compatible backport of the already-merged sister fix (percussioncms#2375). PSMetadataExtractorService now unwraps undeclared XML-style prefixed elements/attributes (the gcse:search case) before XHTML serialization and, if SAX still reports an unbound prefix, swallows that parse failure so DTS metadata delivery no longer ERRORs the whole page. The strip logic matches Jsoup 1.23 (tagName() is the qualified name, e.g. gcse:search), reserved xml/xmlns prefixes are kept, and dcterms/og values live in unprefixed attribute values so they are not stripped. Dominant residual risk is that the new fixture places every asserted RDFa node before the unbound markup, so a broken sanitizer plus the new catch would still go green; the fallback matcher is also a bit broader than the sister PR.

Issue counts by severity

  • bugs: 0
  • suggestions: 2
  • nits: 0

vijaya-boddipudi pushed a commit that referenced this pull request Aug 13, 2026
…fter-markup test)

Review feedback on PR #37 (natechadwick-intsof):

1) testUnboundPrefixGcseSearch could not tell a working strip from a
   failed strip + parse catch. All asserted dcterms:* meta tags sat
   BEFORE the unbound markup, so even a no-op sanitizer + the catch
   would still produce a green test. Move the dcterms:source meta to
   AFTER the gcse:search / vendor:data-id in unbound-prefix-gcse.html
   so the assertion can only succeed if the sanitizer actually ran.
   Also add a direct unit test
   (testStripUnboundPrefixedMarkupRemovesGcseAndVendorButKeepsDcterms)
   that drives stripUnboundPrefixedMarkup directly and walks the Jsoup
   tree to assert gcse:search / vendor:data-id are gone, a declared
   xmlns:foo element survives, and dcterms:* metadata is intact.

2) isUnboundPrefixParseFailure previously trusted a bare
   prefix "x" for element|attribute pattern, which could swallow
   non-unbound SAX diagnostics that happen to mention the same words
   and silently drop every subsequent RDFa triple on the page.
   Tighten the detector: only SAXParseException (or SAX-typed
   throwable) + a message containing 'not bound' is treated as
   ignorable. The looser UNBOUND_PREFIX_MESSAGE pattern is now only
   used by extractUnboundPrefix(Throwable) for WARN log labeling --
   never to decide whether to swallow a parse error.

   Update the existing
   testIsUnboundPrefixParseFailureDetectsGcse to use a real
   SAXParseException, and add
   testIsUnboundPrefixParseFailureIgnoresUntypedThrowableWithSameMessage
   (negative) and
   testIsUnboundPrefixParseFailureIgnoresUnboundPrefixInCauseChainOfUntypedThrowable
   (cause-chain positive).

Bump stripUnboundPrefixedMarkup visibility from package-private to
public so it can be tested from com.percussion.delivery (the test
package lives one level above com.percussion.delivery.metadata).

Verification: 12/12 tests pass.
Pre-sanitize published HTML in PSMetadataExtractorService before RDFa
parse so vendor embeds like Google CSE <gcse:search> without an xmlns:gcse
declaration no longer throw SAXParseException and fail the whole page's
metadata delivery. Collect xmlns:* declarations on the document, strip
elements/attributes whose prefixes are not declared (deepest-first via
Jsoup.unwrap), and rewrite non-XML named entities to numeric character
references. As a defensive fallback the RDFa parse is wrapped in a
try/catch that detects unbound-prefix parse messages and logs WARN with
the page path and offending prefix so non-RDFa fields still flow through.

dcterms:*/og:*/perc:* metadata is unaffected because the metadata of
interest lives in attribute values, not in unbound element/attribute names.

Adds:
  - PSMetadataExtractorService.stripUnboundPrefixedMarkup(Document, String)
  - PSMetadataExtractorService.isUnboundPrefixParseFailure(Throwable)
  - PSMetadataExtractorService.extractUnboundPrefix(Throwable)
  - tests: testUnboundPrefixGcseSearch, testUnboundPrefixOnlyDoesNotThrow,
    testIsUnboundPrefixParseFailureDetectsGcse,
    testIsUnboundPrefixParseFailureIgnoresOtherErrors
  - fixture: system/UnitTestResources/com/percussion/delivery/unbound-prefix-gcse.html
  - CHANGELOG entry under 8.1.8 / GH_POST_PR_COMMIT_RUN_ID placeholder

Also updates AGENTS.md to point at the main branch (not development-8.1.x).

Signed-off-by: Vijaya Boddipudi <216913149+vijaya-boddipudi@users.noreply.github.com>
…fter-markup test)

Review feedback on PR #37 (natechadwick-intsof):

1) testUnboundPrefixGcseSearch could not tell a working strip from a
   failed strip + parse catch. All asserted dcterms:* meta tags sat
   BEFORE the unbound markup, so even a no-op sanitizer + the catch
   would still produce a green test. Move the dcterms:source meta to
   AFTER the gcse:search / vendor:data-id in unbound-prefix-gcse.html
   so the assertion can only succeed if the sanitizer actually ran.
   Also add a direct unit test
   (testStripUnboundPrefixedMarkupRemovesGcseAndVendorButKeepsDcterms)
   that drives stripUnboundPrefixedMarkup directly and walks the Jsoup
   tree to assert gcse:search / vendor:data-id are gone, a declared
   xmlns:foo element survives, and dcterms:* metadata is intact.

2) isUnboundPrefixParseFailure previously trusted a bare
   prefix "x" for element|attribute pattern, which could swallow
   non-unbound SAX diagnostics that happen to mention the same words
   and silently drop every subsequent RDFa triple on the page.
   Tighten the detector: only SAXParseException (or SAX-typed
   throwable) + a message containing 'not bound' is treated as
   ignorable. The looser UNBOUND_PREFIX_MESSAGE pattern is now only
   used by extractUnboundPrefix(Throwable) for WARN log labeling --
   never to decide whether to swallow a parse error.

   Update the existing
   testIsUnboundPrefixParseFailureDetectsGcse to use a real
   SAXParseException, and add
   testIsUnboundPrefixParseFailureIgnoresUntypedThrowableWithSameMessage
   (negative) and
   testIsUnboundPrefixParseFailureIgnoresUnboundPrefixInCauseChainOfUntypedThrowable
   (cause-chain positive).

Bump stripUnboundPrefixedMarkup visibility from package-private to
public so it can be tested from com.percussion.delivery (the test
package lives one level above com.percussion.delivery.metadata).

Verification: 12/12 tests pass.
Signed-off-by: Vijaya Boddipudi <216913149+vijaya-boddipudi@users.noreply.github.com>
@vijaya-boddipudi
vijaya-boddipudi force-pushed the fix/issue-4-unbound-prefix-metadata branch from 5b4e53c to d194e6c Compare August 13, 2026 16:10
@natechadwick
natechadwick merged commit fb72735 into main Aug 13, 2026
3 checks passed
@natechadwick
natechadwick deleted the fix/issue-4-unbound-prefix-metadata branch August 13, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metadata extraction fails on unbound prefixes (e.g. gcse:search) during DTS publish

3 participants