--- /dev/null
+org.apache.fop.render.ps.extensions.PSExtensionHandlerFactory
\ No newline at end of file
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.ElementMappingRegistry;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.image.FopImage;
import org.apache.fop.image.ImageFactory;
import org.apache.fop.traits.BorderProps;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.ContentHandlerFactoryRegistry;
import org.apache.fop.util.DefaultErrorListener;
import org.w3c.dom.DOMImplementation;
delegateStack.push(qName);
delegate.startElement(uri, localName, qName, attributes);
} else if (domImplementation != null) {
+ //domImplementation is set so we need to start a new DOM building sub-process
TransformerHandler handler;
try {
handler = tFactory.newTransformerHandler();
((ForeignObject)parent).setDocument(doc);
//activate delegate for nested foreign document
+ domImplementation = null; //Not needed anymore now
this.delegate = handler;
delegateStack.push(qName);
delegate.startDocument();
setTraits(attributes, foreign);
getCurrentViewport().setContent(foreign);
areaStack.push(foreign);
+ } else if ("extension-attachments".equals(localName)) {
+ //TODO implement me
} else {
handled = false;
}
} else {
- handled = false;
+ ContentHandlerFactory factory
+ = ContentHandlerFactoryRegistry.getInstance().getFactory(uri);
+ if (factory != null) {
+ delegate = factory.createContentHandler();
+ delegateStack.push(qName);
+ delegate.startDocument();
+ delegate.startElement(uri, localName, qName, attributes);
+ } else {
+ handled = false;
+ }
}
if (!handled) {
- throw new SAXException("Unhandled element " + localName
- + " in namespace: " + uri);
+ if (uri == null || uri.length() == 0) {
+ throw new SAXException("Unhandled element " + localName
+ + " in namespace: " + uri);
+ } else {
+ log.warn("Unhandled element " + localName
+ + " in namespace: " + uri);
+ }
}
}
}
}
}
+ /**
+ * Handles objects created by "sub-parsers" that implement the ObjectSource interface.
+ * An example of object handled here are ExtensionAttachments.
+ * @param obj the Object to be handled.
+ */
+ protected void handleExternallyGeneratedObject(Object obj) {
+ if (areaStack.size() == 0 && obj instanceof ExtensionAttachment) {
+ ExtensionAttachment attachment = (ExtensionAttachment)obj;
+ if (this.currentPageViewport == null) {
+ this.treeModel.handleOffDocumentItem(
+ new OffDocumentExtensionAttachment(attachment));
+ } else {
+ this.currentPageViewport.addExtensionAttachment(attachment);
+ }
+ } else {
+ log.warn("Don't know how to handle externally generated object: " + obj);
+ }
+ }
+
/** @see org.xml.sax.helpers.DefaultHandler */
public void endElement(String uri, String localName, String qName) throws SAXException {
if (delegate != null) {
delegateStack.pop();
if (delegateStack.size() == 0) {
delegate.endDocument();
- delegate = null;
- domImplementation = null;
+ if (delegate instanceof ContentHandlerFactory.ObjectSource) {
+ Object obj = ((ContentHandlerFactory.ObjectSource)delegate).getObject();
+ handleExternallyGeneratedObject(obj);
+ }
+ delegate = null; //Sub-document is processed, return to normal processing
}
} else {
if ("".equals(uri)) {
import org.apache.commons.logging.LogFactory;
import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fo.pagination.SimplePageMaster;
/**
return this.simplePageMasterName;
}
+ /**
+ * Adds a new ExtensionAttachment instance to this page.
+ * @param attachment the ExtensionAttachment
+ */
+ public void addExtensionAttachment(ExtensionAttachment attachment) {
+ if (this.extensionAttachments == null) {
+ this.extensionAttachments = new java.util.ArrayList();
+ }
+ extensionAttachments.add(attachment);
+ }
+
/** @return the list of extension attachments for this page */
public List getExtensionAttachments() {
return this.extensionAttachments;
--- /dev/null
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fop.area.AreaTreeParser;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * ContentHandler (parser) for restoring PSExtension objects from XML.
+ */
+public class PSExtensionHandler extends DefaultHandler
+ implements ContentHandlerFactory.ObjectSource {
+
+ /** Logger instance */
+ protected static Log log = LogFactory.getLog(PSExtensionHandler.class);
+
+ private StringBuffer content = new StringBuffer();
+ private Attributes lastAttributes;
+
+ private PSSetupCode returnedObject;
+
+ /** @see org.xml.sax.helpers.DefaultHandler */
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ boolean handled = false;
+ if (PSSetupCode.CATEGORY.equals(uri)) {
+ lastAttributes = attributes;
+ handled = true;
+ if ("ps-setup-code".equals(localName)) {
+ //handled in endElement
+ } else {
+ handled = false;
+ }
+ }
+ if (!handled) {
+ if (PSSetupCode.CATEGORY.equals(uri)) {
+ throw new SAXException("Unhandled element " + localName
+ + " in namespace: " + uri);
+ } else {
+ log.warn("Unhandled element " + localName
+ + " in namespace: " + uri);
+ }
+ }
+ }
+
+ /** @see org.xml.sax.helpers.DefaultHandler */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (PSSetupCode.CATEGORY.equals(uri)) {
+ if ("ps-setup-code".equals(localName)) {
+ String name = lastAttributes.getValue("name");
+ this.returnedObject = new PSSetupCode(name, content.toString());
+ }
+ }
+ content.setLength(0); //Reset text buffer (see characters())
+ }
+
+ /** @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ content.append(ch, start, length);
+ }
+
+ /** @see org.apache.fop.area.AreaTreeParser.ObjectSource#getObject() */
+ public Object getObject() {
+ return returnedObject;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.fop.util.ContentHandlerFactory;
+import org.xml.sax.ContentHandler;
+
+/**
+ * Factory for the ContentHandler that handles serialized PSSetupCode instances.
+ */
+public class PSExtensionHandlerFactory implements ContentHandlerFactory {
+
+ private static final String[] NAMESPACES = new String[] {PSSetupCode.CATEGORY};
+
+ /** @see org.apache.fop.util.ContentHandlerFactory#getSupportedNamespaces() */
+ public String[] getSupportedNamespaces() {
+ return NAMESPACES;
+ }
+
+ /** @see org.apache.fop.util.ContentHandlerFactory#createContentHandler() */
+ public ContentHandler createContentHandler() {
+ return new PSExtensionHandler();
+ }
+
+}
/*
- * Copyright 2005 The Apache Software Foundation.
+ * Copyright 2005-2006 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
import java.io.Serializable;
import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.util.XMLizable;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
/**
* This is the pass-through value object for the PostScript extension.
*/
-public class PSSetupCode implements ExtensionAttachment, Serializable {
+public class PSSetupCode implements ExtensionAttachment, Serializable, XMLizable {
/** The category URI for this extension attachment. */
public static final String CATEGORY = "apache:fop:extensions:postscript";
return CATEGORY;
}
-
/** @see java.lang.Object#toString() */
public String toString() {
return "PSSetupCode(name=" + getName() + ")";
}
+
+ private static final String ATT_NAME = "name";
+ private static final String ELEMENT = "ps-setup-code";
+
+ /** @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ if (name != null && name.length() > 0) {
+ atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name);
+ }
+ handler.startElement(CATEGORY, ELEMENT, ELEMENT, atts);
+ if (content != null && content.length() > 0) {
+ char[] chars = content.toCharArray();
+ handler.characters(chars, 0, chars.length);
+ }
+ handler.endElement(CATEGORY, ELEMENT, ELEMENT);
+ }
+
}
import org.apache.fop.render.Renderer;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.XMLHandler;
+import org.apache.fop.util.XMLizable;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.area.Footnote;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.MainReference;
+import org.apache.fop.area.OffDocumentExtensionAttachment;
+import org.apache.fop.area.OffDocumentItem;
import org.apache.fop.area.PageViewport;
import org.apache.fop.area.RegionReference;
import org.apache.fop.area.RegionViewport;
import org.apache.fop.area.inline.SpaceArea;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontSetup;
import org.apache.fop.fonts.FontTriplet;
/** The OutputStream to write the generated XML to. */
protected OutputStream out;
+
+ /** A list of ExtensionAttachements received through processOffDocumentItem() */
+ protected List extensionAttachments;
/**
* Creates a new XML renderer.
+ (int) rect.getWidth() + " " + (int) rect.getHeight();
}
+ private void handleDocumentExtensionAttachments() {
+ if (extensionAttachments != null && extensionAttachments.size() > 0) {
+ handleExtensionAttachments(extensionAttachments);
+ extensionAttachments.clear();
+ }
+ }
+
+ /** @see org.apache.fop.render.AbstractRenderer#processOffDocumentItem() */
+ public void processOffDocumentItem(OffDocumentItem oDI) {
+ if (oDI instanceof OffDocumentExtensionAttachment) {
+ ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
+ if (extensionAttachments == null) {
+ extensionAttachments = new java.util.ArrayList();
+ }
+ extensionAttachments.add(attachment);
+ } else {
+ String warn = "Ignoring OffDocumentItem: " + oDI;
+ comment("WARNING: " + warn);
+ log.warn(warn);
+ }
+ }
+
/**
* @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
*/
addAttribute("nr", page.getPageNumberString());
startElement("pageViewport", atts);
startElement("page");
+
+ handlePageExtensionAttachments(page);
super.renderPage(page);
+
endElement("page");
endElement("pageViewport");
}
+ private void handleExtensionAttachments(List attachments) {
+ if (attachments != null && attachments.size() > 0) {
+ startElement("extension-attachments");
+ Iterator i = attachments.iterator();
+ while (i.hasNext()) {
+ ExtensionAttachment attachment = (ExtensionAttachment)i.next();
+ if (attachment instanceof XMLizable) {
+ try {
+ ((XMLizable)attachment).toSAX(this.handler);
+ } catch (SAXException e) {
+ log.error("Error while serializing Extension Attachment", e);
+ }
+ } else {
+ String warn = "Ignoring non-XMLizable ExtensionAttachment: " + attachment;
+ comment("WARNING: " + warn);
+ log.warn(warn);
+ }
+ }
+ endElement("extension-attachments");
+ }
+ }
+
+ private void handlePageExtensionAttachments(PageViewport page) {
+ handleExtensionAttachments(page.getExtensionAttachments());
+ }
+
/**
* @see org.apache.fop.render.Renderer#startPageSequence(LineArea)
*/
public void startPageSequence(LineArea seqTitle) {
+ handleDocumentExtensionAttachments();
if (startedSequence) {
endElement("pageSequence");
}
--- /dev/null
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import org.xml.sax.ContentHandler;
+
+/**
+ * Factory interface implemented by classes that can instantiate ContentHandler subclasses which
+ * parse a SAX stream into Java objects.
+ */
+public interface ContentHandlerFactory {
+
+ /**
+ * @return an array of supported namespaces.
+ */
+ String[] getSupportedNamespaces();
+
+ /**
+ * @return a new ContentHandler to handle a SAX stream
+ */
+ ContentHandler createContentHandler();
+
+ /**
+ * Interface that ContentHandler implementations that parse Java objects from XML can implement
+ * to return these objects.
+ */
+ public interface ObjectSource {
+
+ /**
+ * @return the object parsed from the SAX stream (call valid after parsing)
+ */
+ Object getObject();
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.util.Service;
+
+/**
+ * This class holds references to various XML handlers used by FOP. It also
+ * supports automatic discovery of additional XML handlers available through
+ * the class path.
+ */
+public class ContentHandlerFactoryRegistry {
+
+ /** the logger */
+ private static Log log = LogFactory.getLog(ContentHandlerFactoryRegistry.class);
+
+ private static ContentHandlerFactoryRegistry instance;
+
+ /** Map from namespace URIs to ContentHandlerFactories */
+ private Map factories = new java.util.HashMap();
+
+ /**
+ * @return a singleton instance of the ContentHandlerFactoryRegistry.
+ */
+ public static ContentHandlerFactoryRegistry getInstance() {
+ if (instance == null) {
+ instance = new ContentHandlerFactoryRegistry();
+ }
+ return instance;
+ }
+
+ /**
+ * Default constructor.
+ */
+ public ContentHandlerFactoryRegistry() {
+ discover();
+ }
+
+ /**
+ * Add an XML handler. The handler itself is inspected to find out what it supports.
+ * @param classname the fully qualified class name
+ */
+ public void addContentHandlerFactory(String classname) {
+ try {
+ ContentHandlerFactory factory
+ = (ContentHandlerFactory)Class.forName(classname).newInstance();
+ addContentHandlerFactory(factory);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Could not find "
+ + classname);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Could not instantiate "
+ + classname);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Could not access "
+ + classname);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(classname
+ + " is not an "
+ + ContentHandlerFactory.class.getName());
+ }
+ }
+
+ /**
+ * Add an ContentHandlerFactory. The instance is inspected to find out what it supports.
+ * @param factory the ContentHandlerFactory instance
+ */
+ public void addContentHandlerFactory(ContentHandlerFactory factory) {
+ String[] ns = factory.getSupportedNamespaces();
+ for (int i = 0; i < ns.length; i++) {
+ factories.put(ns[i], factory);
+ }
+ }
+
+ /**
+ * Retrieves a ContentHandlerFactory instance of a given namespace URI.
+ * @param namespaceURI the namespace to be handled.
+ * @return the ContentHandlerFactory or null, if no suitable instance is available.
+ */
+ public ContentHandlerFactory getFactory(String namespaceURI) {
+ ContentHandlerFactory factory = (ContentHandlerFactory)factories.get(namespaceURI);
+ return factory;
+ }
+
+ /**
+ * Discovers ContentHandlerFactory implementations through the classpath and dynamically
+ * registers them.
+ */
+ private void discover() {
+ // add mappings from available services
+ Iterator providers = Service.providers(ContentHandlerFactory.class);
+ if (providers != null) {
+ while (providers.hasNext()) {
+ String str = (String)providers.next();
+ try {
+ if (log.isDebugEnabled()) {
+ log.debug("Dynamically adding ContentHandlerFactory: " + str);
+ }
+ addContentHandlerFactory(str);
+ } catch (IllegalArgumentException e) {
+ log.error("Error while adding ContentHandlerFactory", e);
+ }
+
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2002-2004 The Apache Software Foundation.
+ *
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+/*
+ * Copied from Apache Excalibur:
+ * https://svn.apache.org/repos/asf/excalibur/trunk/components/xmlutil/
+ * src/java/org/apache/excalibur/xml/sax/XMLizable.java
+ */
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * This interface can be implemented by classes willing to provide an XML representation
+ * of their current state as SAX events.
+ */
+public interface XMLizable {
+
+ /**
+ * Generates SAX events representing the object's state.
+ * @param handler ContentHandler instance to send the SAX events to
+ * @throws SAXException if there's a problem generating the SAX events
+ */
+ void toSAX(ContentHandler handler) throws SAXException;
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2006 The Apache Software Foundation
+
+ 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.
+-->
+<!-- $Id$ -->
+<testcase>
+ <info>
+ <p>
+ This test checks the PostScript extension for custom setup code. The extension attachments need to show
+ up in the area tree XML so the AreaTreeParser can fully restore the area tree.
+ </p>
+ </info>
+ <fo>
+ <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ps="http://xmlgraphics.apache.org/fop/postscript">
+ <fo:layout-master-set>
+ <fo:simple-page-master master-name="normal" page-width="5in" page-height="5in">
+ <fo:region-body/>
+ <ps:ps-page-setup-code name="media-dict">%FOPTestPSPageSetupCode: MediaDict!</ps:ps-page-setup-code>
+ <ps:ps-page-setup-code name="bla">%FOPTestPSPageSetupCode: Blah blah!</ps:ps-page-setup-code>
+ </fo:simple-page-master>
+ </fo:layout-master-set>
+ <fo:declarations>
+ <ps:ps-setup-code>%FOPTestPSSetupCode: General setup code here!</ps:ps-setup-code>
+ <ps:ps-setup-code name="multi-line">
+%FOPTestPSSetupCode: Line 1
+%FOPTestPSSetupCode: Line 2
+ </ps:ps-setup-code>
+ </fo:declarations>
+ <fo:page-sequence master-reference="normal">
+ <fo:flow flow-name="xsl-region-body">
+ <fo:block>Text on page <fo:page-number/>.</fo:block>
+ <fo:block break-before="page">Text on page <fo:page-number/>.</fo:block>
+ </fo:flow>
+ </fo:page-sequence>
+ </fo:root>
+ </fo>
+ <checks>
+ <eval expected="2" xpath="count(/areaTree/extension-attachments/child::*)"/>
+ <eval expected="%FOPTestPSSetupCode: General setup code here!" xpath="/areaTree/extension-attachments/child::*[1]"/>
+ <true xpath="contains(/areaTree/extension-attachments/child::*[2], '%FOPTestPSSetupCode: Line 1')"/>
+ <eval expected="multi-line" xpath="/areaTree/extension-attachments/child::*[2]/@name"/>
+
+ <eval expected="2" xpath="count(/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*)"/>
+ <eval expected="media-dict" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[1]/@name"/>
+ <eval expected="%FOPTestPSPageSetupCode: MediaDict!" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[1]"/>
+ <eval expected="bla" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[2]/@name"/>
+ <eval expected="%FOPTestPSPageSetupCode: Blah blah!" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[2]"/>
+
+ <eval expected="2" xpath="count(/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*)"/>
+ <eval expected="media-dict" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[1]/@name"/>
+ <eval expected="%FOPTestPSPageSetupCode: MediaDict!" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[1]"/>
+ <eval expected="bla" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[2]/@name"/>
+ <eval expected="%FOPTestPSPageSetupCode: Blah blah!" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[2]"/>
+ </checks>
+</testcase>