Skip to content

bridge: be tolerant to missing references #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions MIGRATING_FROM_BATIK.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Migrating from Apache Batik

There are several reasons why EchoSVG is not a drop-in replacement for Apache
Batik:

- Avoid using Apache Software Foundation package names and trademarks.

- Improved conformance to the SVG and CSS specifications.

- Better user experience (_e.g._ the `codec`-`transcoder` merge).

- A circularity with Apache FOP. To render PDF images, Batik uses FOP which in
turn uses Batik, so depending on FOP would imply mixing EchoSVG and Batik. See
issue [#10](https://github.com/css4j/echosvg/issues/10).

- Changes in the Java™ Platform.

<br/>

## Migration checklist

To migrate from Apache Batik, you generally have to deal with the following:

1) Package names are now prefixed by `io.sf.carte.echosvg` instead of
Expand Down Expand Up @@ -40,3 +59,6 @@
`ApplicationSecurityEnforcer.createSecurityEnforcer(Class, String)` instead.

9) `BridgeContext` (in the `bridge` module) gained a `close` method.

10) `SVGAnimationElementBridge.initializeAnimation()` gained a `BridgeContext`
argument and now returns a `boolean`.
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ protected static List<Stop> extractStop(Element paintElement, float opacity, Bri
throw new BridgeException(ctx, paintElement, ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
new Object[] { uri });
}
refs.add(purl);
paintElement = ctx.getReferencedElement(paintElement, uri);
if (paintElement == null) {
return null; // Missing reference
}
refs.add(purl);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,12 @@ public Node getReferencedNode(Element e, String uri) {
URIResolver ur = createURIResolver(document, documentLoader);
Node ref = ur.getNode(uri, e);
if (ref == null) {
throw new BridgeException(this, e, ERR_URI_BAD_TARGET, new Object[] { uri });
BridgeException ex = new BridgeException(this, e, ERR_URI_BAD_TARGET, new Object[] { uri });
if (userAgent != null) {
userAgent.displayError(ex);
} else {
throw ex;
}
} else {
SVGOMDocument refDoc = (SVGOMDocument) (ref.getNodeType() == Node.DOCUMENT_NODE ? ref
: ref.getOwnerDocument());
Expand All @@ -763,8 +768,8 @@ public Node getReferencedNode(Element e, String uri) {
if (refDoc != document) {
createSubBridgeContext(refDoc);
}
return ref;
}
return ref;
} catch (MalformedURLException ex) {
throw new BridgeException(this, e, ex, ERR_URI_MALFORMED, new Object[] { uri });
} catch (InterruptedIOException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,23 @@ public static Filter convertFilter(Element filteredElement, GraphicsNode filtere
case CSSPrimitiveValue.CSS_URI:
String uri = v.getStringValue();
Element filter = ctx.getReferencedElement(filteredElement, uri);
if (filter == null) {
return null; // Missing reference
}
Bridge bridge = ctx.getBridge(filter);
if (bridge == null || !(bridge instanceof FilterBridge)) {
throw new BridgeException(ctx, filteredElement, ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
if (bridge == null) {
// Assume that the cause of this was already reported
return null;
}
if (!(bridge instanceof FilterBridge)) {
BridgeException ex = new BridgeException(ctx, filteredElement,
ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return null;
}
return ((FilterBridge) bridge).createFilter(ctx, filter, filteredElement, filteredNode);
default:
Expand Down Expand Up @@ -660,9 +674,23 @@ public static ClipRable convertClipPath(Element clippedElement, GraphicsNode cli
case CSSPrimitiveValue.CSS_URI:
String uri = v.getStringValue();
Element cp = ctx.getReferencedElement(clippedElement, uri);
if (cp == null) {
return null; // Missing reference
}
Bridge bridge = ctx.getBridge(cp);
if (bridge == null || !(bridge instanceof ClipBridge)) {
throw new BridgeException(ctx, clippedElement, ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
if (bridge == null) {
// Assume that the cause of this was already reported
return null;
}
if (!(bridge instanceof ClipBridge)) {
BridgeException ex = new BridgeException(ctx, clippedElement,
ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return null;
}
return ((ClipBridge) bridge).createClip(ctx, cp, clippedElement, clippedNode);
default:
Expand Down Expand Up @@ -703,9 +731,23 @@ public static Mask convertMask(Element maskedElement, GraphicsNode maskedNode, B
case CSSPrimitiveValue.CSS_URI:
String uri = v.getStringValue();
Element m = ctx.getReferencedElement(maskedElement, uri);
if (m == null) {
return null; // Missing reference
}
Bridge bridge = ctx.getBridge(m);
if (bridge == null || !(bridge instanceof MaskBridge)) {
throw new BridgeException(ctx, maskedElement, ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
if (bridge == null) {
// Assume that the cause of this was already reported
return null;
}
if (!(bridge instanceof MaskBridge)) {
BridgeException ex = new BridgeException(ctx, maskedElement, ERR_CSS_URI_BAD_TARGET,
new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return null;
}
return ((MaskBridge) bridge).createMask(ctx, m, maskedElement, maskedNode);
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,10 @@ protected Filter cursorHrefToFilter(Element cursorElement, ParsedURL purl, Point
}
GraphicsNode node = ctx.getGVTBuilder().build(ctx, rootElement);

if (node == null) {
return null;
}

//
// The cursorSize define the viewport into which the
// cursor is displayed. That viewport is platform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ protected GVTFontFamily getFontFamily(BridgeContext ctx, ParsedURL purl) {
if (purl.getRef() != null) {
// Reference must be to a SVGFont.
Element ref = ctx.getReferencedElement(e, purlStr);
if (!ref.getNamespaceURI().equals(SVG_NAMESPACE_URI) || !ref.getLocalName().equals(SVG_FONT_TAG)) {
if (ref == null || !ref.getNamespaceURI().equals(SVG_NAMESPACE_URI)
|| !ref.getLocalName().equals(SVG_FONT_TAG)) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,10 @@ public GraphicsNode getGraphicsNode(int idx) {

try {
GVTBuilder builder = ctx.getGVTBuilder();
GraphicsNode gn;
gn = builder.build(ctx, srcElems[idx]);
srcs[idx] = new SoftReference<>(gn);
GraphicsNode gn = builder.build(ctx, srcElems[idx]);
if (gn != null) {
srcs[idx] = new SoftReference<>(gn);
}
return gn;
} catch (Exception ex) {
ex.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,22 @@ public static Marker convertMarker(Element e, Value v, BridgeContext ctx) {
} else {
String uri = v.getStringValue();
Element markerElement = ctx.getReferencedElement(e, uri);
if (markerElement == null) {
return null; // Missing reference
}
Bridge bridge = ctx.getBridge(markerElement);
if (bridge == null || !(bridge instanceof MarkerBridge)) {
throw new BridgeException(ctx, e, ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
if (bridge == null) {
// Assume that the cause of this was already reported
return null;
}
if (!(bridge instanceof MarkerBridge)) {
BridgeException ex = new BridgeException(ctx, e, ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return null;
}
return ((MarkerBridge) bridge).createMarker(ctx, markerElement, e);
}
Expand Down Expand Up @@ -315,10 +328,24 @@ public static Paint convertURIPaint(Element paintedElement, GraphicsNode painted

String uri = paintDef.getStringValue();
Element paintElement = ctx.getReferencedElement(paintedElement, uri);
if (paintElement == null) {
return null; // Missing reference
}

Bridge bridge = ctx.getBridge(paintElement);
if (bridge == null || !(bridge instanceof PaintBridge)) {
throw new BridgeException(ctx, paintedElement, ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
if (bridge == null) {
// Assume that the cause of this was already reported
return null;
}
if (!(bridge instanceof PaintBridge)) {
BridgeException ex = new BridgeException(ctx, paintedElement, ERR_CSS_URI_BAD_TARGET,
new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return null;
}
return ((PaintBridge) bridge).createPaint(ctx, paintElement, paintedElement, paintedNode, opacity);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ protected ExtendedGeneralPath parsePath() {
&& SVG_MPATH_TAG.equals(n.getLocalName())) {
String uri = XLinkSupport.getXLinkHref((Element) n);
Element path = ctx.getReferencedElement(element, uri);
if (!SVG_NAMESPACE_URI.equals(path.getNamespaceURI()) || !SVG_PATH_TAG.equals(path.getLocalName())) {
throw new BridgeException(ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
if (path == null || !SVG_NAMESPACE_URI.equals(path.getNamespaceURI())
|| !SVG_PATH_TAG.equals(path.getLocalName())) {
throw new BridgeException(ctx, element, ErrorConstants.ERR_URI_BAD_TARGET,
new Object[] { uri });
}
SVGOMPathElement pathElt = (SVGOMPathElement) path;
AWTPathProducer app = new AWTPathProducer();
Expand Down Expand Up @@ -295,18 +297,27 @@ public AnimatableValue getUnderlyingValue() {
/**
* Parses the animation element's target attributes and adds it to the
* document's AnimationEngine.
* @param ctx the bridge context to use in error reporting.
* @return true if the initialization was successful.
*/
@Override
protected void initializeAnimation() {
protected boolean initializeAnimation(BridgeContext ctx) {
// Determine the target element.
String uri = XLinkSupport.getXLinkHref(element);
Node t;
if (uri.length() == 0) {
t = element.getParentNode();
} else {
t = ctx.getReferencedElement(element, uri);
if (t.getOwnerDocument() != element.getOwnerDocument()) {
throw new BridgeException(ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
if (t == null || t.getOwnerDocument() != element.getOwnerDocument()) {
BridgeException ex = new BridgeException(ctx, element,
ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return false;
}
}
animationTarget = null;
Expand All @@ -315,13 +326,21 @@ protected void initializeAnimation() {
animationTarget = targetElement;
}
if (animationTarget == null) {
throw new BridgeException(ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
BridgeException ex = new BridgeException(ctx, element,
ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return false;
}

// Add the animation.
timedElement = createTimedElement();
animation = createAnimation(animationTarget);
eng.addAnimation(animationTarget, AnimationEngine.ANIM_TYPE_OTHER, attributeNamespaceURI, attributeLocalName,
animation);
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ public void handleElement(BridgeContext ctx, Element e) {
b.eng = ctx.getAnimationEngine();
b.element.setSVGContext(b);
if (b.eng.hasStarted()) {
b.initializeAnimation();
b.initializeTimedElement();
if (b.initializeAnimation(ctx)) {
b.initializeTimedElement();
}
} else {
b.eng.addInitialBridge(b);
}
Expand All @@ -155,17 +156,26 @@ public void handleElement(BridgeContext ctx, Element e) {
/**
* Parses the animation element's target attributes and adds it to the
* document's AnimationEngine.
* @param ctx the bridge context to use in error reporting.
* @return true if the initialization was successful.
*/
protected void initializeAnimation() {
protected boolean initializeAnimation(BridgeContext ctx) {
// Determine the target element.
String uri = XLinkSupport.getXLinkHref(element);
Node t;
if (uri.length() == 0) {
t = element.getParentNode();
} else {
t = ctx.getReferencedElement(element, uri);
if (t.getOwnerDocument() != element.getOwnerDocument()) {
throw new BridgeException(ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
if (t == null || t.getOwnerDocument() != element.getOwnerDocument()) {
Copy link

Choose a reason for hiding this comment

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

Won't this still throw an exception when the referenced element cannot be found?

Copy link
Member Author

Choose a reason for hiding this comment

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

Won't this still throw an exception when the referenced element cannot be found?

Yes, and it's the only thing that can be done with the current API, unless you want to silently ignore the problem.

I could change the API so the error can be processed, but that should be a different commit.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in ba1fbcd

BridgeException ex = new BridgeException(ctx, element,
ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return false;
}
}
animationTarget = null;
Expand All @@ -174,7 +184,14 @@ protected void initializeAnimation() {
animationTarget = targetElement;
}
if (animationTarget == null) {
throw new BridgeException(ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
BridgeException ex = new BridgeException(ctx, element,
ErrorConstants.ERR_URI_BAD_TARGET, new Object[] { uri });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return false;
}

// Get the attribute/property name.
Expand All @@ -197,8 +214,14 @@ protected void initializeAnimation() {
if (animationType == AnimationEngine.ANIM_TYPE_CSS && !targetElement.isPropertyAnimatable(attributeLocalName)
|| animationType == AnimationEngine.ANIM_TYPE_XML
&& !targetElement.isAttributeAnimatable(attributeNamespaceURI, attributeLocalName)) {
throw new BridgeException(ctx, element, "attribute.not.animatable",
BridgeException ex = new BridgeException(ctx, element, "attribute.not.animatable",
new Object[] { targetElement.getNodeName(), an });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return false;
}

// Check that the attribute/property is animatable with this
Expand All @@ -210,14 +233,21 @@ protected void initializeAnimation() {
type = targetElement.getAttributeType(attributeNamespaceURI, attributeLocalName);
}
if (!canAnimateType(type)) {
throw new BridgeException(ctx, element, "type.not.animatable",
BridgeException ex = new BridgeException(ctx, element, "type.not.animatable",
new Object[] { targetElement.getNodeName(), an, element.getNodeName() });
UserAgent userAgent = ctx.getUserAgent();
if (userAgent == null) {
throw ex;
}
userAgent.displayError(ex);
return false;
}

// Add the animation.
timedElement = createTimedElement();
animation = createAnimation(animationTarget);
eng.addAnimation(animationTarget, animationType, attributeNamespaceURI, attributeLocalName, animation);
return true;
}

/**
Expand Down
Loading