From b073a605e44760bcb7add72968a3a2f78cf06d97 Mon Sep 17 00:00:00 2001 From: Vincent Hennebert Date: Mon, 24 Oct 2011 16:29:56 +0000 Subject: Removed the FO document pre-processing step that was adding ptr and creating a reduced FO tree. * Removed addPtr.xsl and reduceFOTree.xsl * Implemented Common Accessibility properties on objects that support them * Added a special implementation of FOEventHandler to build the structure tree * Added mockito library and its dependencies * Added mocks for some FO nodes and properties * Removed FOTreeUnitTester and converted it into a re-usable FODocumentParser class * Re-worked table FO test cases accordingly git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ImproveAccessibility@1188205 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop/fo/DelegatingFOEventHandlerTestCase.fo | 175 +++++++ .../fop/fo/DelegatingFOEventHandlerTestCase.java | 522 +++++++++++++++++++++ test/java/org/apache/fop/fo/FODocumentParser.java | 145 ++++++ test/java/org/apache/fop/fo/FONodeMocks.java | 88 ++++ test/java/org/apache/fop/fo/LoadingException.java | 34 ++ test/java/org/apache/fop/fo/extract-events.xsl | 56 +++ .../fop/fo/flow/table/AbstractTableTestCase.java | 28 +- .../table/CollapsedConditionalBorderTestCase.java | 7 +- .../fop/fo/flow/table/ErrorCheckTestCase.java | 11 +- .../fop/fo/flow/table/IllegalRowSpanTestCase.java | 4 - .../fop/fo/flow/table/RowGroupBuilderTestCase.java | 4 - .../table/TableColumnColumnNumberTestCase.java | 7 +- .../fop/fo/flow/table/TooManyColumnsTestCase.java | 4 - .../table/UnimplementedWarningNeutralizer.java | 38 ++ .../fop/fo/properties/AltTextHolderTestCase.java | 77 +++ .../CommonAccessibilityHolderTestCase.java | 127 +++++ .../fop/fo/properties/PropertyListMocks.java | 94 ++++ .../apache/fop/fo/properties/PropertyMocks.java | 80 ++++ .../org/apache/fop/fotreetest/FOTreeTestSuite.java | 19 +- .../apache/fop/fotreetest/FOTreeUnitTester.java | 96 ---- 20 files changed, 1475 insertions(+), 141 deletions(-) create mode 100644 test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo create mode 100644 test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java create mode 100644 test/java/org/apache/fop/fo/FODocumentParser.java create mode 100644 test/java/org/apache/fop/fo/FONodeMocks.java create mode 100644 test/java/org/apache/fop/fo/LoadingException.java create mode 100644 test/java/org/apache/fop/fo/extract-events.xsl create mode 100644 test/java/org/apache/fop/fo/flow/table/UnimplementedWarningNeutralizer.java create mode 100644 test/java/org/apache/fop/fo/properties/AltTextHolderTestCase.java create mode 100644 test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java create mode 100644 test/java/org/apache/fop/fo/properties/PropertyListMocks.java create mode 100644 test/java/org/apache/fop/fo/properties/PropertyMocks.java delete mode 100644 test/java/org/apache/fop/fotreetest/FOTreeUnitTester.java (limited to 'test/java') diff --git a/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo new file mode 100644 index 000000000..74afd6c16 --- /dev/null +++ b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + This is the page headerPage + + + + + + This is a link to the next page-sequence + (which starts on page and ends on + page ). + A Title Block + This block of text contains a footnote1A footnote with a link to the FOP + website + call. + + + + + + + Header 1.1 + + + Header 1.2 + + + + + + + Footer 1.1 + + + Footer 1.2 + + + + + + + Cell 1.1 + + + Cell 1.2 + + + + + Cell 2.1 + + + Cell 2.2 + + + + + + + This is an absolutely positioned + block-container. Nullam interdum mattis ipsum sit amet molestie. + + Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Integer vel lacinia diam. Etiam venenatis magna vel libero imperdiet + rhoncus. + + + + + + + This is the page headerPage + + + + Starting a new page-sequence. + The + logo. + + + + 1. + + + First item of a list + + + + + 2. + + + Second item of a list + + + + + 3. + + + Third item of a list + + + + + + + + + + + + + A block containing an inline + element. + A block containing a fancy + character. + A leader with special + content: . + Ending the page-sequence. + + + + diff --git a/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java new file mode 100644 index 000000000..faa88e38e --- /dev/null +++ b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java @@ -0,0 +1,522 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo; + +import static org.junit.Assert.assertArrayEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamSource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.FODocumentParser.FOEventHandlerFactory; +import org.apache.fop.fo.flow.BasicLink; +import org.apache.fop.fo.flow.Block; +import org.apache.fop.fo.flow.BlockContainer; +import org.apache.fop.fo.flow.Character; +import org.apache.fop.fo.flow.ExternalGraphic; +import org.apache.fop.fo.flow.Footnote; +import org.apache.fop.fo.flow.FootnoteBody; +import org.apache.fop.fo.flow.Inline; +import org.apache.fop.fo.flow.InstreamForeignObject; +import org.apache.fop.fo.flow.Leader; +import org.apache.fop.fo.flow.ListBlock; +import org.apache.fop.fo.flow.ListItem; +import org.apache.fop.fo.flow.ListItemBody; +import org.apache.fop.fo.flow.ListItemLabel; +import org.apache.fop.fo.flow.PageNumber; +import org.apache.fop.fo.flow.PageNumberCitation; +import org.apache.fop.fo.flow.PageNumberCitationLast; +import org.apache.fop.fo.flow.Wrapper; +import org.apache.fop.fo.flow.table.Table; +import org.apache.fop.fo.flow.table.TableBody; +import org.apache.fop.fo.flow.table.TableCell; +import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.flow.table.TableFooter; +import org.apache.fop.fo.flow.table.TableHeader; +import org.apache.fop.fo.flow.table.TableRow; +import org.apache.fop.fo.pagination.Flow; +import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fo.pagination.StaticContent; + +/** + * Tests that {@link DelegatingFOEventHandler} does forward every event to its delegate + * event handler. + */ +public class DelegatingFOEventHandlerTestCase { + + private InputStream document; + + private List expectedEvents; + + private List actualEvents; + + private FODocumentParser documentParser; + + private class DelegatingFOEventHandlerTester extends FOEventHandler { + + DelegatingFOEventHandlerTester(FOUserAgent foUserAgent) { + super(foUserAgent); + } + + private final StringBuilder eventBuilder = new StringBuilder(); + + @Override + public void startDocument() throws SAXException { + actualEvents.add("start root"); + } + + @Override + public void endDocument() throws SAXException { + actualEvents.add("end root"); + } + + @Override + public void startPageSequence(PageSequence pageSeq) { + startElement(pageSeq); + } + + @Override + public void endPageSequence(PageSequence pageSeq) { + endElement(pageSeq); + } + + @Override + public void startPageNumber(PageNumber pagenum) { + startElement(pagenum); + } + + @Override + public void endPageNumber(PageNumber pagenum) { + endElement(pagenum); + } + + @Override + public void startPageNumberCitation(PageNumberCitation pageCite) { + startElement(pageCite); + } + + @Override + public void endPageNumberCitation(PageNumberCitation pageCite) { + endElement(pageCite); + } + + @Override + public void startPageNumberCitationLast(PageNumberCitationLast pageLast) { + startElement(pageLast); + } + + @Override + public void endPageNumberCitationLast(PageNumberCitationLast pageLast) { + endElement(pageLast); + } + + @Override + public void startFlow(Flow fl) { + startElement(fl); + } + + @Override + public void endFlow(Flow fl) { + endElement(fl); + } + + @Override + public void startBlock(Block bl) { + startElement(bl); + } + + @Override + public void endBlock(Block bl) { + endElement(bl); + } + + @Override + public void startBlockContainer(BlockContainer blc) { + startElement(blc); + } + + @Override + public void endBlockContainer(BlockContainer blc) { + endElement(blc); + } + + @Override + public void startInline(Inline inl) { + startElement(inl); + } + + @Override + public void endInline(Inline inl) { + endElement(inl); + } + + @Override + public void startTable(Table tbl) { + startElement(tbl); + } + + @Override + public void endTable(Table tbl) { + endElement(tbl); + } + + @Override + public void startColumn(TableColumn tc) { + startElement(tc); + } + + @Override + public void endColumn(TableColumn tc) { + endElement(tc); + } + + @Override + public void startHeader(TableHeader header) { + startElement(header); + } + + @Override + public void endHeader(TableHeader header) { + endElement(header); + } + + @Override + public void startFooter(TableFooter footer) { + startElement(footer); + } + + @Override + public void endFooter(TableFooter footer) { + endElement(footer); + } + + @Override + public void startBody(TableBody body) { + startElement(body); + } + + @Override + public void endBody(TableBody body) { + endElement(body); + } + + @Override + public void startRow(TableRow tr) { + startElement(tr); + } + + @Override + public void endRow(TableRow tr) { + endElement(tr); + } + + @Override + public void startCell(TableCell tc) { + startElement(tc); + } + + @Override + public void endCell(TableCell tc) { + endElement(tc); + } + + @Override + public void startList(ListBlock lb) { + startElement(lb); + } + + @Override + public void endList(ListBlock lb) { + endElement(lb); + } + + @Override + public void startListItem(ListItem li) { + startElement(li); + } + + @Override + public void endListItem(ListItem li) { + endElement(li); + } + + @Override + public void startListLabel(ListItemLabel listItemLabel) { + startElement(listItemLabel); + } + + @Override + public void endListLabel(ListItemLabel listItemLabel) { + endElement(listItemLabel); + } + + @Override + public void startListBody(ListItemBody listItemBody) { + startElement(listItemBody); + } + + @Override + public void endListBody(ListItemBody listItemBody) { + endElement(listItemBody); + } + + @Override + public void startStatic(StaticContent staticContent) { + startElement(staticContent); + } + + @Override + public void endStatic(StaticContent statisContent) { + endElement(statisContent); + } + + @Override + public void startLink(BasicLink basicLink) { + startElement(basicLink); + } + + @Override + public void endLink(BasicLink basicLink) { + endElement(basicLink); + } + + @Override + public void image(ExternalGraphic eg) { + startElement(eg); + endElement(eg); + } + + @Override + public void startInstreamForeignObject(InstreamForeignObject ifo) { + startElement(ifo); + } + + @Override + public void endInstreamForeignObject(InstreamForeignObject ifo) { + endElement(ifo); + } + + @Override + public void startFootnote(Footnote footnote) { + startElement(footnote); + } + + @Override + public void endFootnote(Footnote footnote) { + endElement(footnote); + } + + @Override + public void startFootnoteBody(FootnoteBody body) { + startElement(body); + } + + @Override + public void endFootnoteBody(FootnoteBody body) { + endElement(body); + } + + @Override + public void startLeader(Leader l) { + startElement(l); + } + + @Override + public void endLeader(Leader l) { + endElement(l); + } + + @Override + public void startWrapper(Wrapper wrapper) { + startElement(wrapper); + } + + @Override + public void endWrapper(Wrapper wrapper) { + endElement(wrapper); + } + + @Override + public void character(Character c) { + startElement(c); + endElement(c); + } + + private void startElement(FObj node) { + addEvent("start ", node); + } + + private void endElement(FObj node) { + addEvent("end ", node); + } + + private void addEvent(String event, FObj node) { + eventBuilder.append(event); + eventBuilder.append(node.getLocalName()); + addID(node); + actualEvents.add(eventBuilder.toString()); + eventBuilder.setLength(0); + } + + private void addID(FObj node) { + String id = node.getId(); + if (id != null && id.length() > 0) { + eventBuilder.append(" id=\""); + eventBuilder.append(id); + eventBuilder.append("\""); + } + } + } + + @Before + public void setUp() throws IOException { + setUpEvents(); + loadDocument(); + createDocumentParser(); + } + + private void setUpEvents() throws IOException { + loadDocument(); + loadExpectedEvents(); + actualEvents = new ArrayList(expectedEvents.size()); + } + + private void loadDocument() { + Class clazz = getClass(); + String documentName = clazz.getSimpleName() + ".fo"; + document = clazz.getResourceAsStream(documentName); + } + + private void loadExpectedEvents() throws IOException { + expectedEvents = new ArrayList(); + InputStream xslt = getClass().getResourceAsStream("extract-events.xsl"); + try { + runXSLT(xslt); + } finally { + closeStream(xslt); + closeStream(document); + } + } + + private void runXSLT(InputStream xslt) { + Transformer transformer = createTransformer(xslt); + Source fo = new StreamSource(document); + Result result = createTransformOutputHandler(); + try { + transformer.transform(fo, result); + } catch (TransformerException e) { + throw new RuntimeException(e); + } + } + + private Transformer createTransformer(InputStream xslt) { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + try { + return transformerFactory.newTransformer(new StreamSource(xslt)); + } catch (TransformerConfigurationException e) { + throw new RuntimeException(e); + } + } + + private Result createTransformOutputHandler() { + return new SAXResult(new DefaultHandler() { + + private final StringBuilder event = new StringBuilder(); + + @Override + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { + event.setLength(0); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + event.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + expectedEvents.add(event.toString()); + } + + }); + } + + private void closeStream(InputStream stream) { + try { + stream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void createDocumentParser() { + documentParser = FODocumentParser.newInstance(new FOEventHandlerFactory() { + + public FOEventHandler newFOEventHandler(FOUserAgent foUserAgent) { + return new DelegatingFOEventHandler( + new DelegatingFOEventHandlerTester(foUserAgent)) { + }; + } + }); + } + + @Test + public void testFOEventHandler() throws Exception { + documentParser.parse(document); + assertArrayEquals(expectedEvents.toArray(), actualEvents.toArray()); + } + + @After + public void unloadDocument() throws IOException { + document.close(); + } + + /** + * Prints the given list to {@code System.out}, each element on a new line. For + * debugging purpose. + * + * @param list a list + */ + public void printList(List list) { + for (Object element : list) { + System.out.println(element); + } + } + +} diff --git a/test/java/org/apache/fop/fo/FODocumentParser.java b/test/java/org/apache/fop/fo/FODocumentParser.java new file mode 100644 index 000000000..4706866a4 --- /dev/null +++ b/test/java/org/apache/fop/fo/FODocumentParser.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo; + +import java.io.InputStream; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.Fop; +import org.apache.fop.apps.FopFactory; +import org.apache.fop.events.EventListener; + +/** + * Parse an FO document and run the corresponding FO events through a given + * {@link FOEventHandler} instance. That instance is created using the helper + * {@link FOEventHandlerFactory}. + * + *

An instance of this class may not be used in multiple threads concurrently.

+ * + *

An instance of this class may be used multiple times if the given + * {@link FOEventHandler} implementation can be used multiple times. + */ +public final class FODocumentParser { + + private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + + private static final FopFactory FOP_FACTORY = FopFactory.newInstance(); + + private final FOEventHandlerFactory foEventHandlerFactory; + + private Fop fop; + + private Transformer transformer; + + private EventListener eventListener; + + /** + * A factory to create custom instances of {@link FOEventHandler}. + */ + public static interface FOEventHandlerFactory { + + /** + * Creates a new {@code FOEventHandler} instance parameterized with the given FO user agent. + * + * @param foUserAgent an FO user agent + * @return a new {@code FOEventHandler} instance + */ + FOEventHandler newFOEventHandler(FOUserAgent foUserAgent); + } + + private FODocumentParser(FOEventHandlerFactory foeEventHandlerFactory) { + this.foEventHandlerFactory = foeEventHandlerFactory; + } + + /** + * Creates and returns a new FO document parser. The given factory will be used to + * customize the handler that will receive FO events, using the + * {@link FOUserAgent#setFOEventHandlerOverride(FOEventHandler)} method. + * + * @param foEventHandlerFactory the factory to be used to create {@code + * FOEventHandler} instances + * @return a new parser + */ + public static FODocumentParser newInstance(FOEventHandlerFactory foEventHandlerFactory) { + return new FODocumentParser(foEventHandlerFactory); + } + + /** + * Sets the event listener to be used if events occurs when parsing the document. + * + * @param eventListener an event listener + */ + public void setEventListener(EventListener eventListener) { + this.eventListener = eventListener; + } + + /** + * Runs FOP on the given document. + * + * @param document XSL-FO document to parse + * @throws FOPException if an error occurs when initializing FOP + * @throws LoadingException if an error occurs when parsing the document + */ + public void parse(InputStream document) throws FOPException, LoadingException { + FOUserAgent foUserAgent = createFOUserAgent(); + fop = FOP_FACTORY.newFop(foUserAgent); + createTransformer(); + runTransformer(document); + } + + private FOUserAgent createFOUserAgent() { + FOUserAgent userAgent = FOP_FACTORY.newFOUserAgent(); + FOEventHandler foEventHandler = foEventHandlerFactory.newFOEventHandler(userAgent); + userAgent.setFOEventHandlerOverride(foEventHandler); + if (eventListener != null) { + userAgent.getEventBroadcaster().addEventListener(eventListener); + } + return userAgent; + } + + private void createTransformer() { + try { + transformer = TRANSFORMER_FACTORY.newTransformer(); + } catch (TransformerConfigurationException e) { + throw new RuntimeException(e); + } + } + + private void runTransformer(InputStream input) throws LoadingException, FOPException { + Source source = new StreamSource(input); + Result result = new SAXResult(fop.getDefaultHandler()); + try { + transformer.transform(source, result); + } catch (TransformerException e) { + Throwable cause = e.getCause(); + throw new LoadingException(cause == null ? e : cause); + } + } +} diff --git a/test/java/org/apache/fop/fo/FONodeMocks.java b/test/java/org/apache/fop/fo/FONodeMocks.java new file mode 100644 index 000000000..1310d4a78 --- /dev/null +++ b/test/java/org/apache/fop/fo/FONodeMocks.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.FopFactory; + +/** + * A helper class for creating mocks of {@link FONode} and its descendants. + */ +public final class FONodeMocks { + + private FONodeMocks() { } + + /** + * Creates and returns a mock {@link FONode} configured with a mock + * {@link FOEventHandler}. The FO event handler returns a mock {@link FOUserAgent}, + * which in turn returns a mock {@link FopFactory}, which returns a mock + * {@link ImageManager}. + * + * @return a mock FO node + */ + public static FONode mockFONode() { + FONode mockFONode = mock(FONode.class); + mockGetFOEventHandler(mockFONode); + return mockFONode; + } + + private static void mockGetFOEventHandler(FONode mockFONode) { + FOEventHandler mockFOEventHandler = mock(FOEventHandler.class); + mockGetUserAgent(mockFOEventHandler); + when(mockFONode.getFOEventHandler()).thenReturn(mockFOEventHandler); + } + + private static void mockGetUserAgent(FOEventHandler mockFOEventHandler) { + FOUserAgent mockFOUserAgent = mock(FOUserAgent.class); + mockGetFactory(mockFOUserAgent); + when(mockFOEventHandler.getUserAgent()).thenReturn(mockFOUserAgent); + } + + private static void mockGetFactory(FOUserAgent mockFOUserAgent) { + FopFactory mockFopFactory = mock(FopFactory.class); + mockGetImageManager(mockFopFactory); + when(mockFOUserAgent.getFactory()).thenReturn(mockFopFactory); + } + + private static void mockGetImageManager(FopFactory mockFopFactory) { + try { + ImageManager mockImageManager = mock(ImageManager.class); + when(mockImageManager.getImageInfo(anyString(), any(ImageSessionContext.class))) + .thenReturn(null); + when(mockFopFactory.getImageManager()).thenReturn(mockImageManager); + } catch (ImageException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/test/java/org/apache/fop/fo/LoadingException.java b/test/java/org/apache/fop/fo/LoadingException.java new file mode 100644 index 000000000..a5d509209 --- /dev/null +++ b/test/java/org/apache/fop/fo/LoadingException.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo; + +/** + * This class specifies an exceptional condition that occurred while an XSL-FO document + * was being parsed. + */ +public class LoadingException extends Exception { + + private static final long serialVersionUID = 7529029475875542916L; + + LoadingException(Throwable cause) { + super(cause); + } + +} diff --git a/test/java/org/apache/fop/fo/extract-events.xsl b/test/java/org/apache/fop/fo/extract-events.xsl new file mode 100644 index 000000000..8edc0ee49 --- /dev/null +++ b/test/java/org/apache/fop/fo/extract-events.xsl @@ -0,0 +1,56 @@ + + + + + + + + start root + + + + end root + + + + + + + + + + + + + + + + + + + + start + + + + + + end + + + + + + + id=" + + " + + + + + diff --git a/test/java/org/apache/fop/fo/flow/table/AbstractTableTestCase.java b/test/java/org/apache/fop/fo/flow/table/AbstractTableTestCase.java index 90d89d702..01e20166a 100644 --- a/test/java/org/apache/fop/fo/flow/table/AbstractTableTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/AbstractTableTestCase.java @@ -19,33 +19,37 @@ package org.apache.fop.fo.flow.table; +import java.io.FileInputStream; import java.util.Iterator; import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.FODocumentParser; import org.apache.fop.fo.FOEventHandler; -import org.apache.fop.fotreetest.FOTreeUnitTester; +import org.apache.fop.fo.FODocumentParser.FOEventHandlerFactory; +import org.apache.fop.util.ConsoleEventListenerForTests; /** * Superclass for testcases related to tables, factoring the common stuff. */ -abstract class AbstractTableTestCase extends FOTreeUnitTester { +abstract class AbstractTableTestCase { - private FOTreeUnitTester.FOEventHandlerFactory tableHandlerFactory; + private FODocumentParser documentParser; private TableHandler tableHandler; - public AbstractTableTestCase() throws Exception { - super(); - tableHandlerFactory = new FOEventHandlerFactory() { - public FOEventHandler createFOEventHandler(FOUserAgent foUserAgent) { + protected void setUp(String filename) throws Exception { + createDocumentParser(); + documentParser.setEventListener(new ConsoleEventListenerForTests(filename)); + documentParser.parse(new FileInputStream("test/fotree/unittests/" + filename)); + } + + private void createDocumentParser() { + documentParser = FODocumentParser.newInstance(new FOEventHandlerFactory() { + public FOEventHandler newFOEventHandler(FOUserAgent foUserAgent) { tableHandler = new TableHandler(foUserAgent); return tableHandler; } - }; - } - - protected void setUp(String filename) throws Exception { - setUp(filename, tableHandlerFactory); + }); } protected TableHandler getTableHandler() { diff --git a/test/java/org/apache/fop/fo/flow/table/CollapsedConditionalBorderTestCase.java b/test/java/org/apache/fop/fo/flow/table/CollapsedConditionalBorderTestCase.java index 7501c1b9a..d046537e3 100644 --- a/test/java/org/apache/fop/fo/flow/table/CollapsedConditionalBorderTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/CollapsedConditionalBorderTestCase.java @@ -25,10 +25,11 @@ import java.awt.Color; import java.util.Iterator; import java.util.List; +import org.junit.Test; + import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode.FONodeIterator; import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; -import org.junit.Test; /** * A testcase for the resolution of collapsed borders in the FO tree, taking @@ -107,10 +108,6 @@ public class CollapsedConditionalBorderTestCase extends AbstractTableTestCase { {{border8pt, Color.black}, {border6pt, Color.blue}, {border8pt, Color.black}, {border6pt, Color.blue}, {border4pt, Color.black}, {border4pt, Color.black}, {border4pt, Color.red}, {border8pt, Color.black}, {border8pt, Color.black}, {border8pt, Color.black}, {border8pt, Color.black}, {border4pt, Color.blue}, {border4pt, Color.red}, {border6pt, Color.magenta}, {border6pt, Color.magenta}, {border6pt, Color.magenta}} }; - public CollapsedConditionalBorderTestCase() throws Exception { - super(); - } - private static GridUnit getGridUnit(TablePart part) { return (GridUnit) ((List) ((List) part.getRowGroups().get(0)).get(0)).get(0); } diff --git a/test/java/org/apache/fop/fo/flow/table/ErrorCheckTestCase.java b/test/java/org/apache/fop/fo/flow/table/ErrorCheckTestCase.java index a3a6b9e71..68c7d9f30 100644 --- a/test/java/org/apache/fop/fo/flow/table/ErrorCheckTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/ErrorCheckTestCase.java @@ -19,8 +19,10 @@ package org.apache.fop.fo.flow.table; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.apache.fop.fo.LoadingException; import org.apache.fop.fo.ValidationException; /** @@ -28,16 +30,13 @@ import org.apache.fop.fo.ValidationException; */ abstract class ErrorCheckTestCase extends AbstractTableTestCase { - public ErrorCheckTestCase() throws Exception { - super(); - } - protected void launchTest(String filename) throws Exception { try { setUp(filename); - fail(); - } catch (ValidationException e) { + fail("Expected ValidationException to be thrown"); + } catch (LoadingException e) { // TODO check location + assertTrue(e.getCause() instanceof ValidationException); } } diff --git a/test/java/org/apache/fop/fo/flow/table/IllegalRowSpanTestCase.java b/test/java/org/apache/fop/fo/flow/table/IllegalRowSpanTestCase.java index 5473f098a..4018f957c 100644 --- a/test/java/org/apache/fop/fo/flow/table/IllegalRowSpanTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/IllegalRowSpanTestCase.java @@ -27,10 +27,6 @@ import org.junit.Test; */ public class IllegalRowSpanTestCase extends ErrorCheckTestCase { - public IllegalRowSpanTestCase() throws Exception { - super(); - } - @Test public void testBody1() throws Exception { launchTest("table/illegal-row-span_body_1.fo"); diff --git a/test/java/org/apache/fop/fo/flow/table/RowGroupBuilderTestCase.java b/test/java/org/apache/fop/fo/flow/table/RowGroupBuilderTestCase.java index 48c25faca..1af3083ec 100644 --- a/test/java/org/apache/fop/fo/flow/table/RowGroupBuilderTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/RowGroupBuilderTestCase.java @@ -35,10 +35,6 @@ import org.junit.Test; */ public class RowGroupBuilderTestCase extends AbstractTableTestCase { - public RowGroupBuilderTestCase() throws Exception { - super(); - } - /** * Checks that the given table-body(header,footer) will return row groups as expected. * More precisely, checks that the number of row groups corresponds to the size of the diff --git a/test/java/org/apache/fop/fo/flow/table/TableColumnColumnNumberTestCase.java b/test/java/org/apache/fop/fo/flow/table/TableColumnColumnNumberTestCase.java index 0f24ae68c..c6a93a65c 100644 --- a/test/java/org/apache/fop/fo/flow/table/TableColumnColumnNumberTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/TableColumnColumnNumberTestCase.java @@ -23,9 +23,10 @@ import static org.junit.Assert.assertEquals; import java.util.Iterator; +import org.junit.Test; + import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.FObj; -import org.junit.Test; public class TableColumnColumnNumberTestCase extends AbstractTableTestCase { @@ -50,10 +51,6 @@ public class TableColumnColumnNumberTestCase extends AbstractTableTestCase { private TablePercentBaseContext percentBaseContext = new TablePercentBaseContext(); - public TableColumnColumnNumberTestCase() throws Exception { - super(); - } - private void checkColumn(Table t, int number, boolean isImplicit, int spans, int repeated, int width) { TableColumn c = t.getColumn(number - 1); // TODO a repeated column has a correct number only for its first occurrence diff --git a/test/java/org/apache/fop/fo/flow/table/TooManyColumnsTestCase.java b/test/java/org/apache/fop/fo/flow/table/TooManyColumnsTestCase.java index 665a88759..7a9af100e 100644 --- a/test/java/org/apache/fop/fo/flow/table/TooManyColumnsTestCase.java +++ b/test/java/org/apache/fop/fo/flow/table/TooManyColumnsTestCase.java @@ -23,10 +23,6 @@ import org.junit.Test; public class TooManyColumnsTestCase extends ErrorCheckTestCase { - public TooManyColumnsTestCase() throws Exception { - super(); - } - @Test public void testBody1() throws Exception { launchTest("table/too-many-columns_body_1.fo"); diff --git a/test/java/org/apache/fop/fo/flow/table/UnimplementedWarningNeutralizer.java b/test/java/org/apache/fop/fo/flow/table/UnimplementedWarningNeutralizer.java new file mode 100644 index 000000000..1a5e38291 --- /dev/null +++ b/test/java/org/apache/fop/fo/flow/table/UnimplementedWarningNeutralizer.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo.flow.table; + +/** + * This class aims at easing testing, by preventing the event notification system from + * getting in the way just to issue an Unimplemented Feature warning. + */ +public final class UnimplementedWarningNeutralizer { + + private UnimplementedWarningNeutralizer() { } + + /** + * Neutralizes Unimplemented Feature events from the {@link TableAndCaption} and + * {@link TableCaption} classes. + */ + public static void neutralizeUnimplementedWarning() { + TableAndCaption.notImplementedWarningGiven = true; + TableCaption.notImplementedWarningGiven = true; + } +} diff --git a/test/java/org/apache/fop/fo/properties/AltTextHolderTestCase.java b/test/java/org/apache/fop/fo/properties/AltTextHolderTestCase.java new file mode 100644 index 000000000..cd5d545ff --- /dev/null +++ b/test/java/org/apache/fop/fo/properties/AltTextHolderTestCase.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo.properties; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FONodeMocks; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.expr.PropertyException; +import org.apache.fop.fo.flow.AbstractGraphics; +import org.apache.fop.fo.flow.ExternalGraphic; +import org.apache.fop.fo.flow.InstreamForeignObject; + + +/** + * Tests that the fox:alt-text property is correctly set on objects that support it. + */ +public class AltTextHolderTestCase { + + private final String altText = "alternative text"; + + @Test + public void externalGraphicHasAltText() throws FOPException { + testAltTextGetter(new ExternalGraphic(mockFONode())); + } + + @Test + public void instreamForeignObjectHasAltText() throws FOPException { + testAltTextGetter(new InstreamForeignObject(mockFONode())); + } + + private FONode mockFONode() { + FONode mockFONode = FONodeMocks.mockFONode(); + FOUserAgent mockFOUserAgent = mockFONode.getFOEventHandler().getUserAgent(); + when(mockFOUserAgent.isAccessibilityEnabled()).thenReturn(true); + return mockFONode; + } + + private void testAltTextGetter(AbstractGraphics g) throws FOPException { + g.bind(mockPropertyList()); + assertEquals(altText, g.getAltText()); + } + + private PropertyList mockPropertyList() throws PropertyException { + PropertyList mockPropertyList = PropertyListMocks.mockPropertyList(); + Property mockAltText = mock(Property.class); + when(mockAltText.getString()).thenReturn(altText); + when(mockPropertyList.get(Constants.PR_X_ALT_TEXT)).thenReturn(mockAltText); + return mockPropertyList; + } + +} diff --git a/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java b/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java new file mode 100644 index 000000000..45f6a61ab --- /dev/null +++ b/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo.properties; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FONodeMocks; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.expr.PropertyException; + +/** + * This tests that all the FONodes that implement CommonAccessibilityHolder correctly configure + * the CommonAccessibility property. + */ +public class CommonAccessibilityHolderTestCase { + + private static final List> IMPLEMENTATIONS + = new ArrayList>(); + + private final String role = "role"; + + private final String sourceDocument = "source document"; + + static { + /* This triggers 'unimplemented feature' FO validation events so that the event system is + * not triggered when testing, avoiding extra convoluted dependency stubbing. */ +// UnimplementedWarningNeutralizer.neutralizeUnimplementedWarning(); + + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.BasicLink.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.Block.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.pagination.bookmarks.Bookmark.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.pagination.bookmarks.BookmarkTitle.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.ExternalGraphic.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.Footnote.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.FootnoteBody.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.InitialPropertySet.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.Inline.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.InstreamForeignObject.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.Leader.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.ListBlock.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.ListItem.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.ListItemBody.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.ListItemLabel.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.PageNumber.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.PageNumberCitation.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.PageNumberCitationLast.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.pagination.Root.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.Table.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableAndCaption.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableBody.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableCaption.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableCell.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableFooter.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableHeader.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.flow.table.TableRow.class); + IMPLEMENTATIONS.add(org.apache.fop.fo.pagination.Title.class); + } + + /** + * Bind should be overridden to correctly configure the CommonAccessibility property + * @throws Exception - + */ + @Test + public void bindMustSetRoleAndSourceDoc() throws Exception { + final PropertyList mockPList = mockPropertyList(); + final FONode parent = FONodeMocks.mockFONode(); + for (Class clazz : IMPLEMENTATIONS) { + Constructor constructor + = clazz.getConstructor(FONode.class); + CommonAccessibilityHolder sut = constructor.newInstance(parent); + ((FONode)sut).bind(mockPList); + String errorMessage = "Test failed for " + clazz + ": "; + assertEquals(errorMessage, role, sut.getCommonAccessibility().getRole()); + assertEquals(errorMessage, sourceDocument, + sut.getCommonAccessibility().getSourceDocument()); + } + } + + private PropertyList mockPropertyList() throws PropertyException { + final PropertyList mockPList = PropertyListMocks.mockPropertyList(); + PropertyListMocks.mockTableProperties(mockPList); + PropertyListMocks.mockCommonBorderPaddingBackgroundProps(mockPList); + mockRoleProperty(mockPList); + mockSourceDocProperty(mockPList); + return mockPList; + } + + private void mockRoleProperty(PropertyList mockPList) throws PropertyException { + final Property mockRoleProperty = mock(Property.class); + when(mockRoleProperty.getString()).thenReturn(role); + when(mockPList.get(Constants.PR_ROLE)).thenReturn(mockRoleProperty); + } + + private void mockSourceDocProperty(PropertyList mockPList) throws PropertyException { + final Property mockSourceDocProperty = mock(Property.class); + when(mockSourceDocProperty.getString()).thenReturn(sourceDocument); + when(mockPList.get(Constants.PR_SOURCE_DOCUMENT)).thenReturn(mockSourceDocProperty); + } + +} diff --git a/test/java/org/apache/fop/fo/properties/PropertyListMocks.java b/test/java/org/apache/fop/fo/properties/PropertyListMocks.java new file mode 100644 index 000000000..380f6e5a8 --- /dev/null +++ b/test/java/org/apache/fop/fo/properties/PropertyListMocks.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo.properties; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.expr.PropertyException; + +/** + * A helper class for mocking a property list. + */ +public final class PropertyListMocks { + + private PropertyListMocks() { } + + /** + * Creates and returns a mock property list returning a generic default for the + * {@link PropertyList#get(int)} method. + * + * @return a mock property list + */ + public static PropertyList mockPropertyList() { + try { + final PropertyList mockPList = mock(PropertyList.class); + final Property mockGenericProperty = PropertyMocks.mockGenericProperty(); + when(mockPList.get(anyInt())).thenReturn(mockGenericProperty); + return mockPList; + } catch (PropertyException e) { + throw new RuntimeException(e); + } + } + + /** + * Overrides with working mock properties the values returned by + * {@link PropertyList#get(int)} for {@link Constants#PR_COLUMN_NUMBER}, + * {@link Constants#PR_NUMBER_COLUMNS_SPANNED}, + * {@link Constants#PR_NUMBER_ROWS_SPANNED} and {@link Constants#PR_BORDER_COLLAPSE}. + * + * @param mockPList a mock property list + */ + public static void mockTableProperties(PropertyList mockPList) { + try { + final Property mockNumberProperty = PropertyMocks.mockNumberProperty(); + when(mockPList.get(Constants.PR_COLUMN_NUMBER)).thenReturn(mockNumberProperty); + when(mockPList.get(Constants.PR_NUMBER_COLUMNS_SPANNED)).thenReturn(mockNumberProperty); + when(mockPList.get(Constants.PR_NUMBER_ROWS_SPANNED)).thenReturn(mockNumberProperty); + + final Property borderCollapseProperty = mock(Property.class); + when(borderCollapseProperty.getEnum()).thenReturn(Constants.EN_SEPARATE); + when(mockPList.get(Constants.PR_BORDER_COLLAPSE)).thenReturn(borderCollapseProperty); + } catch (PropertyException e) { + throw new RuntimeException(e); + } + } + + /** + * Overrides with a working mock property the value returned by + * {@link PropertyList#getBorderPaddingBackgroundProps()}. + * + * @param mockPList a mock property list + */ + public static void mockCommonBorderPaddingBackgroundProps(PropertyList mockPList) { + try { + final CommonBorderPaddingBackground mockCommonBorderPaddingBackground + = mock(CommonBorderPaddingBackground.class); + when(mockPList.getBorderPaddingBackgroundProps()) + .thenReturn(mockCommonBorderPaddingBackground); + } catch (PropertyException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/test/java/org/apache/fop/fo/properties/PropertyMocks.java b/test/java/org/apache/fop/fo/properties/PropertyMocks.java new file mode 100644 index 000000000..40c923249 --- /dev/null +++ b/test/java/org/apache/fop/fo/properties/PropertyMocks.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.fo.properties; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.fop.datatypes.Numeric; +import org.apache.fop.datatypes.PercentBaseContext; +import org.apache.fop.fo.Constants; + +/** + * Helper class to create mocks of various kinds of properties. + */ +public final class PropertyMocks { + + private PropertyMocks() { } + + /** + * Creates and returns a generic mock property returning decent defaults for the + * {@link Property#getString()}, {@link Property#getEnum()} and + * {@link Property#getLengthRange()} methods. + * + * @return a mock all-purpose property + */ + public static Property mockGenericProperty() { + final Property mockGenericProperty = mock(Property.class); + when(mockGenericProperty.getString()).thenReturn("A non-empty string"); + when(mockGenericProperty.getEnum()).thenReturn(Constants.EN_SPACE); + LengthRangeProperty lengthRangeProperty = mockLengthRangeProperty(); + when(mockGenericProperty.getLengthRange()).thenReturn(lengthRangeProperty); + return mockGenericProperty; + } + + private static LengthRangeProperty mockLengthRangeProperty() { + final LengthRangeProperty mockLengthRangeProperty = mock(LengthRangeProperty.class); + final Property optimum = mockOptimumProperty(); + when(mockLengthRangeProperty.getOptimum(any(PercentBaseContext.class))) + .thenReturn(optimum); + return mockLengthRangeProperty; + } + + /** + * Creates and returns a mock property returning a decent default for the + * {@link Property#getNumeric()} method. + * + * @return a mock number property + */ + public static Property mockNumberProperty() { + final Property mockNumberProperty = mock(Property.class); + final Numeric mockNumeric = mock(Numeric.class); + when(mockNumberProperty.getNumeric()).thenReturn(mockNumeric); + return mockNumberProperty; + } + + private static Property mockOptimumProperty() { + final Property optimum = mock(Property.class); + when(optimum.isAuto()).thenReturn(true); + return optimum; + } + +} diff --git a/test/java/org/apache/fop/fotreetest/FOTreeTestSuite.java b/test/java/org/apache/fop/fotreetest/FOTreeTestSuite.java index 6e6f62b92..b43576901 100644 --- a/test/java/org/apache/fop/fotreetest/FOTreeTestSuite.java +++ b/test/java/org/apache/fop/fotreetest/FOTreeTestSuite.java @@ -19,20 +19,29 @@ package org.apache.fop.fotreetest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import org.apache.fop.fo.DelegatingFOEventHandlerTestCase; import org.apache.fop.fo.flow.table.CollapsedConditionalBorderTestCase; import org.apache.fop.fo.flow.table.IllegalRowSpanTestCase; import org.apache.fop.fo.flow.table.RowGroupBuilderTestCase; import org.apache.fop.fo.flow.table.TableColumnColumnNumberTestCase; import org.apache.fop.fo.flow.table.TooManyColumnsTestCase; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; /** * JUnit test suit for running layout engine test under JUnit control. */ @RunWith(Suite.class) -@Suite.SuiteClasses({ TooManyColumnsTestCase.class, IllegalRowSpanTestCase.class, - RowGroupBuilderTestCase.class, TableColumnColumnNumberTestCase.class, - CollapsedConditionalBorderTestCase.class, FOTreeTester.class }) +@Suite.SuiteClasses({ + TooManyColumnsTestCase.class, + IllegalRowSpanTestCase.class, + RowGroupBuilderTestCase.class, + TableColumnColumnNumberTestCase.class, + CollapsedConditionalBorderTestCase.class, + FOTreeTester.class, + org.apache.fop.fo.properties.CommonAccessibilityHolderTestCase.class, + DelegatingFOEventHandlerTestCase.class +}) public final class FOTreeTestSuite { } diff --git a/test/java/org/apache/fop/fotreetest/FOTreeUnitTester.java b/test/java/org/apache/fop/fotreetest/FOTreeUnitTester.java deleted file mode 100644 index 0906c266d..000000000 --- a/test/java/org/apache/fop/fotreetest/FOTreeUnitTester.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.fotreetest; - -import java.io.File; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import org.apache.fop.apps.FOUserAgent; -import org.apache.fop.apps.Fop; -import org.apache.fop.apps.FopFactory; -import org.apache.fop.fo.FOEventHandler; -import org.apache.fop.util.ConsoleEventListenerForTests; -import org.xml.sax.XMLReader; - - -/** - * Base class for unit-testing the FO tree building code. It performs the necessary setup - * to parse an FO file and register a proper {@link FOEventHandler}. That handler will be - * the entry point to test classes from the FObj hierarchy. - */ -public abstract class FOTreeUnitTester { - - private XMLReader foReader; - - private FopFactory fopFactory; - - /** - * Should be implemented by children testcases for properly setting up the custom - * FOEventHandler needed to test FObj classes. - */ - public abstract static class FOEventHandlerFactory { - - /** - * This method is called by FOTreeUnitTester when creating a {@link Fop} instance. - * That lets pass to the custom FOEventHandler the proper user agent that will be - * used by this instance. - * - * @param foUserAgent the user agent needed by the Fop instance that will be used - * to create the FO tree - * @return the appropriate FOEventHandler for performing the tests - */ - public abstract FOEventHandler createFOEventHandler(FOUserAgent foUserAgent); - } - - public FOTreeUnitTester() throws Exception { - // Stuff that needs to be set up only once and will be re-used for each test - SAXParserFactory spf = SAXParserFactory.newInstance(); - spf.setNamespaceAware(true); - spf.setValidating(false); - SAXParser parser; - parser = spf.newSAXParser(); - foReader = parser.getXMLReader(); - fopFactory = FopFactory.newInstance(); - } - - /** - * Launches FOP on the given FO file. - * - * @param filename path to the test FO file - * @param factory to create the appropriate FOEventHandler for performing tests - */ - public void setUp(String filename, FOEventHandlerFactory factory) throws Exception { - FOUserAgent ua = fopFactory.newFOUserAgent(); - ua.setFOEventHandlerOverride(factory.createFOEventHandler(ua)); - ua.getEventBroadcaster().addEventListener( - new ConsoleEventListenerForTests(filename)); - - Fop fop = fopFactory.newFop(ua); - - foReader.setContentHandler(fop.getDefaultHandler()); - foReader.setDTDHandler(fop.getDefaultHandler()); - foReader.setErrorHandler(fop.getDefaultHandler()); - foReader.setEntityResolver(fop.getDefaultHandler()); - - foReader.parse(new File("test/fotree/unittests/" + filename).toURI().toURL().toExternalForm()); - } -} -- cgit v1.2.3 From c6fb066a02573904f7ca404605f14c800adf80c5 Mon Sep 17 00:00:00 2001 From: Vincent Hennebert Date: Thu, 24 Nov 2011 17:15:28 +0000 Subject: Removed the DOM representation of the structure tree. The structure tree is now directly converted into corresponding PDF objects. When going the IF route, the structure tree is stored in the form of SAX events that will be re-played when it's time to stream them into the output. This may still change. * Extracted RFC3066 methods from XMLUtil into new LanguageTags class that can be re-used in a non-XML context. * Dropped support for accessibility in the old Area Tree XML. * Added support for the xml:lang property on fo:root, so that the global language can be set for the document without retrieving the language from the first page-sequence. * Renamed StructureTreeBuilder into more appropriate StructureTreeEventHandler (same for applicable sub-classes) * Renamed StructureTreeBuildingFOEventHandler into FO2StructureTreeConverter and added test case * Added test cases for classes in the StructureTreeEventHandler hierarchy git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ImproveAccessibility@1205935 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop-intermediate-format-ng.xsd | 15 +- .../DummyStructureTreeEventHandler.java | 52 ++ .../accessibility/FO2StructureTreeConverter.java | 823 ++++++++++++++++++++ .../apache/fop/accessibility/StructureTree.java | 102 --- .../StructureTree2SAXEventAdapter.java | 104 +++ .../fop/accessibility/StructureTreeBuilder.java | 95 --- .../StructureTreeBuildingFOEventHandler.java | 840 --------------------- .../accessibility/StructureTreeEventHandler.java | 56 ++ src/java/org/apache/fop/apps/FOUserAgent.java | 30 +- src/java/org/apache/fop/area/AreaTreeHandler.java | 9 + src/java/org/apache/fop/area/AreaTreeModel.java | 8 + src/java/org/apache/fop/area/AreaTreeParser.java | 56 +- src/java/org/apache/fop/area/RenderPagesModel.java | 6 + .../apache/fop/fo/DelegatingFOEventHandler.java | 11 + src/java/org/apache/fop/fo/FOElementMapping.java | 5 +- src/java/org/apache/fop/fo/FOEventHandler.java | 7 + src/java/org/apache/fop/fo/FOTreeBuilder.java | 8 +- src/java/org/apache/fop/fo/pagination/Root.java | 26 + src/java/org/apache/fop/pdf/PDFDocument.java | 19 - src/java/org/apache/fop/pdf/PDFProfile.java | 6 +- src/java/org/apache/fop/pdf/PDFRoot.java | 14 +- src/java/org/apache/fop/pdf/PDFStructElem.java | 4 +- .../org/apache/fop/render/AbstractRenderer.java | 7 +- src/java/org/apache/fop/render/Renderer.java | 7 + .../intermediate/AbstractIFDocumentHandler.java | 17 +- .../fop/render/intermediate/IFConstants.java | 1 + .../fop/render/intermediate/IFDocumentHandler.java | 16 +- .../apache/fop/render/intermediate/IFParser.java | 68 +- .../apache/fop/render/intermediate/IFRenderer.java | 14 +- .../fop/render/intermediate/IFSerializer.java | 48 +- .../fop/render/intermediate/IFSerializerMaker.java | 3 + .../intermediate/IFStructureTreeBuilder.java | 190 +++++ .../intermediate/util/IFDocumentHandlerProxy.java | 16 +- .../org/apache/fop/render/pdf/FOToPDFRoleMap.java | 35 +- .../apache/fop/render/pdf/PDFDocumentHandler.java | 59 +- .../fop/render/pdf/PDFDocumentHandlerMaker.java | 3 + .../fop/render/pdf/PDFLogicalStructureHandler.java | 72 +- .../fop/render/pdf/PDFStructureTreeBuilder.java | 93 +++ .../org/apache/fop/render/xml/XMLRenderer.java | 31 +- src/java/org/apache/fop/util/LanguageTags.java | 66 ++ src/java/org/apache/fop/util/XMLUtil.java | 36 - test/accessibility/config-painter.xconf | 23 - test/accessibility/config-renderer.xconf | 24 - test/accessibility/fop.xconf | 23 + .../pdf/background-image_jpg_repeat.pdf | Bin 12767 -> 12814 bytes .../pdf/background-image_jpg_single.pdf | Bin 12606 -> 12653 bytes .../pdf/background-image_png_repeat.pdf | Bin 35390 -> 35437 bytes .../pdf/background-image_png_single.pdf | Bin 58601 -> 58317 bytes .../pdf/background-image_svg_repeat.pdf | Bin 17431 -> 17104 bytes .../pdf/background-image_svg_single.pdf | Bin 9988 -> 9943 bytes test/accessibility/pdf/complete.pdf | Bin 96966 -> 96730 bytes test/accessibility/pdf/image_jpg.pdf | Bin 9031 -> 9078 bytes test/accessibility/pdf/image_png.pdf | Bin 58072 -> 57788 bytes test/accessibility/pdf/image_svg.pdf | Bin 14045 -> 14292 bytes test/accessibility/pdf/image_wmf.pdf | Bin 177205 -> 208454 bytes test/accessibility/pdf/leader.pdf | Bin 9334 -> 9379 bytes test/accessibility/pdf/links.pdf | Bin 9666 -> 9712 bytes test/accessibility/pdf/role.pdf | Bin 18924 -> 18976 bytes test/accessibility/pdf/role_non-standard.pdf | Bin 18938 -> 18990 bytes test/accessibility/pdf/text_1.pdf | Bin 7458 -> 7504 bytes test/accessibility/pdf/text_2.pdf | Bin 10296 -> 10346 bytes test/accessibility/pdf/text_font-embedding.pdf | Bin 19781 -> 19785 bytes test/java/org/apache/fop/StandardTestSuite.java | 3 +- .../FO2StructureTreeConverterTestCase.java | 202 +++++ .../apache/fop/accessibility/fo2StructureTree.xsl | 116 +++ .../fop/fo/DelegatingFOEventHandlerTestCase.fo | 175 ----- .../fop/fo/DelegatingFOEventHandlerTestCase.java | 19 +- test/java/org/apache/fop/fo/FODocumentParser.java | 20 +- test/java/org/apache/fop/fo/complete_document.fo | 176 +++++ test/java/org/apache/fop/fo/extract-events.xsl | 14 +- .../CommonAccessibilityHolderTestCase.java | 3 +- .../apache/fop/intermediate/IFParserTestCase.java | 17 +- .../IFStructureTreeBuilderTestCase.java | 169 +++++ .../intermediate/SAXEventRecorderTestCase.java | 131 ++++ .../org/apache/fop/util/LanguageTagsTestCase.java | 58 ++ test/java/org/apache/fop/util/XMLUtilTestCase.java | 50 -- 76 files changed, 2660 insertions(+), 1641 deletions(-) create mode 100644 src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java create mode 100644 src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java delete mode 100644 src/java/org/apache/fop/accessibility/StructureTree.java create mode 100644 src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java delete mode 100644 src/java/org/apache/fop/accessibility/StructureTreeBuilder.java delete mode 100644 src/java/org/apache/fop/accessibility/StructureTreeBuildingFOEventHandler.java create mode 100644 src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java create mode 100644 src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java create mode 100644 src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java create mode 100644 src/java/org/apache/fop/util/LanguageTags.java delete mode 100644 test/accessibility/config-painter.xconf delete mode 100644 test/accessibility/config-renderer.xconf create mode 100644 test/accessibility/fop.xconf create mode 100644 test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java create mode 100644 test/java/org/apache/fop/accessibility/fo2StructureTree.xsl delete mode 100644 test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo create mode 100644 test/java/org/apache/fop/fo/complete_document.fo create mode 100644 test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java create mode 100644 test/java/org/apache/fop/render/intermediate/SAXEventRecorderTestCase.java create mode 100644 test/java/org/apache/fop/util/LanguageTagsTestCase.java delete mode 100644 test/java/org/apache/fop/util/XMLUtilTestCase.java (limited to 'test/java') diff --git a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng.xsd b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng.xsd index b6fa00bd2..86dce1a3c 100644 --- a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng.xsd +++ b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng.xsd @@ -40,10 +40,17 @@ - - - - + + + + + + + + + + + diff --git a/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java b/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java new file mode 100644 index 000000000..80a011bab --- /dev/null +++ b/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.accessibility; + +import java.util.Locale; + +import org.xml.sax.Attributes; + +/** + * This implementation ignores all structure tree events. + */ +public final class DummyStructureTreeEventHandler implements StructureTreeEventHandler { + + /** The singleton instance of this class. */ + public static final StructureTreeEventHandler INSTANCE = new DummyStructureTreeEventHandler(); + + private DummyStructureTreeEventHandler() { } + + /** {@inheritDoc} */ + public void startPageSequence(Locale locale) { + } + + /** {@inheritDoc} */ + public void startNode(String name, Attributes attributes) { + } + + /** {@inheritDoc} */ + public void endNode(String name) { + } + + /** {@inheritDoc} */ + public void endPageSequence() { + } + +} diff --git a/src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java b/src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java new file mode 100644 index 000000000..e6d0193b9 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java @@ -0,0 +1,823 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.accessibility; + +import java.util.Locale; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.DelegatingFOEventHandler; +import org.apache.fop.fo.FOEventHandler; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.ExternalDocument; +import org.apache.fop.fo.extensions.InternalElementMapping; +import org.apache.fop.fo.flow.AbstractGraphics; +import org.apache.fop.fo.flow.BasicLink; +import org.apache.fop.fo.flow.Block; +import org.apache.fop.fo.flow.BlockContainer; +import org.apache.fop.fo.flow.Character; +import org.apache.fop.fo.flow.ExternalGraphic; +import org.apache.fop.fo.flow.Footnote; +import org.apache.fop.fo.flow.FootnoteBody; +import org.apache.fop.fo.flow.Inline; +import org.apache.fop.fo.flow.InstreamForeignObject; +import org.apache.fop.fo.flow.Leader; +import org.apache.fop.fo.flow.ListBlock; +import org.apache.fop.fo.flow.ListItem; +import org.apache.fop.fo.flow.ListItemBody; +import org.apache.fop.fo.flow.ListItemLabel; +import org.apache.fop.fo.flow.PageNumber; +import org.apache.fop.fo.flow.PageNumberCitation; +import org.apache.fop.fo.flow.PageNumberCitationLast; +import org.apache.fop.fo.flow.Wrapper; +import org.apache.fop.fo.flow.table.Table; +import org.apache.fop.fo.flow.table.TableBody; +import org.apache.fop.fo.flow.table.TableCell; +import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.flow.table.TableFooter; +import org.apache.fop.fo.flow.table.TableHeader; +import org.apache.fop.fo.flow.table.TableRow; +import org.apache.fop.fo.pagination.Flow; +import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fo.pagination.Root; +import org.apache.fop.fo.pagination.StaticContent; +import org.apache.fop.fo.properties.CommonAccessibilityHolder; +import org.apache.fop.util.XMLUtil; + +/** + * Allows to create the structure tree of an FO document, by converting FO + * events into appropriate structure tree events. + */ +public class FO2StructureTreeConverter extends DelegatingFOEventHandler { + + private int idCounter; + + /** Delegates to either {@link #foToStructureTreeEventAdapter} or {@link #eventSwallower}. */ + private FOEventHandler converter; + + private final FOEventHandler foToStructureTreeEventAdapter; + + /** The descendants of some elements like fo:leader must be ignored. */ + private final FOEventHandler eventSwallower; + + private final StructureTreeEventHandler structureTreeEventHandler; + + private final class FOToStructureTreeEventAdapter extends FOEventHandler { + + public FOToStructureTreeEventAdapter(FOUserAgent foUserAgent) { + super(foUserAgent); + } + + @Override + public void startDocument() throws SAXException { + } + + @Override + public void endDocument() throws SAXException { + } + + @Override + public void startPageSequence(PageSequence pageSeq) { + Locale locale = null; + if (pageSeq.getLanguage() != null) { + if (pageSeq.getCountry() != null) { + locale = new Locale(pageSeq.getLanguage(), pageSeq.getCountry()); + } else { + locale = new Locale(pageSeq.getLanguage()); + } + } + structureTreeEventHandler.startPageSequence(locale); + } + + @Override + public void endPageSequence(PageSequence pageSeq) { + structureTreeEventHandler.endPageSequence(); + } + + @Override + public void startPageNumber(PageNumber pagenum) { + startElementWithID(pagenum); + } + + @Override + public void endPageNumber(PageNumber pagenum) { + endElement(pagenum); + } + + @Override + public void startPageNumberCitation(PageNumberCitation pageCite) { + startElementWithID(pageCite); + } + + @Override + public void endPageNumberCitation(PageNumberCitation pageCite) { + endElement(pageCite); + } + + @Override + public void startPageNumberCitationLast(PageNumberCitationLast pageLast) { + startElementWithID(pageLast); + } + + @Override + public void endPageNumberCitationLast(PageNumberCitationLast pageLast) { + endElement(pageLast); + } + + @Override + public void startFlow(Flow fl) { + startElement(fl); + } + + @Override + public void endFlow(Flow fl) { + endElement(fl); + } + + @Override + public void startBlock(Block bl) { + startElementWithID(bl); + } + + @Override + public void endBlock(Block bl) { + endElement(bl); + } + + @Override + public void startBlockContainer(BlockContainer blc) { + startElement(blc); + } + + @Override + public void endBlockContainer(BlockContainer blc) { + endElement(blc); + } + + @Override + public void startInline(Inline inl) { + startElementWithID(inl); + } + + @Override + public void endInline(Inline inl) { + endElement(inl); + } + + @Override + public void startTable(Table tbl) { + startElementWithID(tbl); + } + + @Override + public void endTable(Table tbl) { + endElement(tbl); + } + + @Override + public void startHeader(TableHeader header) { + startElementWithID(header); + } + + @Override + public void endHeader(TableHeader header) { + endElement(header); + } + + @Override + public void startFooter(TableFooter footer) { + startElementWithID(footer); + } + + @Override + public void endFooter(TableFooter footer) { + endElement(footer); + } + + @Override + public void startBody(TableBody body) { + startElementWithID(body); + } + + @Override + public void endBody(TableBody body) { + endElement(body); + } + + @Override + public void startRow(TableRow tr) { + startElementWithID(tr); + } + + @Override + public void endRow(TableRow tr) { + endElement(tr); + } + + @Override + public void startCell(TableCell tc) { + AttributesImpl attributes = new AttributesImpl(); + int colSpan = tc.getNumberColumnsSpanned(); + if (colSpan > 1) { + addNoNamespaceAttribute(attributes, "number-columns-spanned", + Integer.toString(colSpan)); + } + startElementWithID(tc, attributes); + } + + @Override + public void endCell(TableCell tc) { + endElement(tc); + } + + @Override + public void startList(ListBlock lb) { + startElement(lb); + } + + @Override + public void endList(ListBlock lb) { + endElement(lb); + } + + @Override + public void startListItem(ListItem li) { + startElement(li); + } + + @Override + public void endListItem(ListItem li) { + endElement(li); + } + + @Override + public void startListLabel(ListItemLabel listItemLabel) { + startElement(listItemLabel); + } + + @Override + public void endListLabel(ListItemLabel listItemLabel) { + endElement(listItemLabel); + } + + @Override + public void startListBody(ListItemBody listItemBody) { + startElement(listItemBody); + } + + @Override + public void endListBody(ListItemBody listItemBody) { + endElement(listItemBody); + } + + @Override + public void startStatic(StaticContent staticContent) { + startElement(staticContent); + } + + @Override + public void endStatic(StaticContent statisContent) { + endElement(statisContent); + } + + @Override + public void startLink(BasicLink basicLink) { + startElementWithID(basicLink); + } + + @Override + public void endLink(BasicLink basicLink) { + endElement(basicLink); + } + + @Override + public void image(ExternalGraphic eg) { + startElementWithIDAndAltText(eg); + endElement(eg); + } + + @Override + public void startInstreamForeignObject(InstreamForeignObject ifo) { + startElementWithIDAndAltText(ifo); + } + + @Override + public void endInstreamForeignObject(InstreamForeignObject ifo) { + endElement(ifo); + } + + @Override + public void startFootnote(Footnote footnote) { + startElement(footnote); + } + + @Override + public void endFootnote(Footnote footnote) { + endElement(footnote); + } + + @Override + public void startFootnoteBody(FootnoteBody body) { + startElement(body); + } + + @Override + public void endFootnoteBody(FootnoteBody body) { + endElement(body); + } + + @Override + public void startWrapper(Wrapper wrapper) { + startElement(wrapper); + } + + @Override + public void endWrapper(Wrapper wrapper) { + endElement(wrapper); + } + + @Override + public void character(Character c) { + startElementWithID(c); + endElement(c); + } + + + private void startElement(FONode node) { + startElement(node, new AttributesImpl()); + } + + private void startElementWithID(FONode node) { + startElementWithID(node, new AttributesImpl()); + } + + private void startElementWithIDAndAltText(AbstractGraphics node) { + AttributesImpl attributes = new AttributesImpl(); + addAttribute(attributes, ExtensionElementMapping.URI, "alt-text", + ExtensionElementMapping.STANDARD_PREFIX, node.getAltText()); + startElementWithID(node, attributes); + } + + private void startElementWithID(FONode node, AttributesImpl attributes) { + String id = Integer.toHexString(idCounter++); + node.setPtr(id); + addAttribute(attributes, + InternalElementMapping.URI, "ptr", InternalElementMapping.STANDARD_PREFIX, id); + startElement(node, attributes); + } + + private void startElement(FONode node, AttributesImpl attributes) { + String localName = node.getLocalName(); + if (node instanceof CommonAccessibilityHolder) { + addRole((CommonAccessibilityHolder) node, attributes); + } + structureTreeEventHandler.startNode(localName, attributes); + } + + private void addNoNamespaceAttribute(AttributesImpl attributes, String name, String value) { + attributes.addAttribute("", name, name, XMLUtil.CDATA, value); + } + + private void addAttribute(AttributesImpl attributes, + String namespace, String localName, String prefix, String value) { + assert namespace.length() > 0 && prefix.length() > 0; + String qualifiedName = prefix + ":" + localName; + attributes.addAttribute(namespace, localName, qualifiedName, XMLUtil.CDATA, value); + } + + private void addRole(CommonAccessibilityHolder node, AttributesImpl attributes) { + String role = node.getCommonAccessibility().getRole(); + if (role != null) { + addNoNamespaceAttribute(attributes, "role", role); + } + } + + private void endElement(FONode node) { + String localName = node.getLocalName(); + structureTreeEventHandler.endNode(localName); + } + + } + + /** + * Creates a new instance. + * + * @param structureTreeEventHandler the object that will hold the structure tree + * @param delegate the FO event handler that must be wrapped by this instance + */ + public FO2StructureTreeConverter(StructureTreeEventHandler structureTreeEventHandler, + FOEventHandler delegate) { + super(delegate); + this.structureTreeEventHandler = structureTreeEventHandler; + this.foToStructureTreeEventAdapter = new FOToStructureTreeEventAdapter(foUserAgent); + this.converter = foToStructureTreeEventAdapter; + this.eventSwallower = new FOEventHandler(foUserAgent) { }; + } + + @Override + public void startDocument() throws SAXException { + converter.startDocument(); + super.startDocument(); + } + + @Override + public void endDocument() throws SAXException { + converter.endDocument(); + super.endDocument(); + } + + @Override + public void startRoot(Root root) { + converter.startRoot(root); + super.startRoot(root); + } + + @Override + public void endRoot(Root root) { + converter.endRoot(root); + super.endRoot(root); + } + + @Override + public void startPageSequence(PageSequence pageSeq) { + converter.startPageSequence(pageSeq); + super.startPageSequence(pageSeq); + } + + @Override + public void endPageSequence(PageSequence pageSeq) { + converter.endPageSequence(pageSeq); + super.endPageSequence(pageSeq); + } + + @Override + public void startPageNumber(PageNumber pagenum) { + converter.startPageNumber(pagenum); + super.startPageNumber(pagenum); + } + + @Override + public void endPageNumber(PageNumber pagenum) { + converter.endPageNumber(pagenum); + super.endPageNumber(pagenum); + } + + @Override + public void startPageNumberCitation(PageNumberCitation pageCite) { + converter.startPageNumberCitation(pageCite); + super.startPageNumberCitation(pageCite); + } + + @Override + public void endPageNumberCitation(PageNumberCitation pageCite) { + converter.endPageNumberCitation(pageCite); + super.endPageNumberCitation(pageCite); + } + + @Override + public void startPageNumberCitationLast(PageNumberCitationLast pageLast) { + converter.startPageNumberCitationLast(pageLast); + super.startPageNumberCitationLast(pageLast); + } + + @Override + public void endPageNumberCitationLast(PageNumberCitationLast pageLast) { + converter.endPageNumberCitationLast(pageLast); + super.endPageNumberCitationLast(pageLast); + } + + @Override + public void startFlow(Flow fl) { + converter.startFlow(fl); + super.startFlow(fl); + } + + @Override + public void endFlow(Flow fl) { + converter.endFlow(fl); + super.endFlow(fl); + } + + @Override + public void startBlock(Block bl) { + converter.startBlock(bl); + super.startBlock(bl); + } + + @Override + public void endBlock(Block bl) { + converter.endBlock(bl); + super.endBlock(bl); + } + + @Override + public void startBlockContainer(BlockContainer blc) { + converter.startBlockContainer(blc); + super.startBlockContainer(blc); + } + + @Override + public void endBlockContainer(BlockContainer blc) { + converter.endBlockContainer(blc); + super.endBlockContainer(blc); + } + + @Override + public void startInline(Inline inl) { + converter.startInline(inl); + super.startInline(inl); + } + + @Override + public void endInline(Inline inl) { + converter.endInline(inl); + super.endInline(inl); + } + + @Override + public void startTable(Table tbl) { + converter.startTable(tbl); + super.startTable(tbl); + } + + @Override + public void endTable(Table tbl) { + converter.endTable(tbl); + super.endTable(tbl); + } + + @Override + public void startColumn(TableColumn tc) { + converter.startColumn(tc); + super.startColumn(tc); + } + + @Override + public void endColumn(TableColumn tc) { + converter.endColumn(tc); + super.endColumn(tc); + } + + @Override + public void startHeader(TableHeader header) { + converter.startHeader(header); + super.startHeader(header); + } + + @Override + public void endHeader(TableHeader header) { + converter.endHeader(header); + super.endHeader(header); + } + + @Override + public void startFooter(TableFooter footer) { + converter.startFooter(footer); + super.startFooter(footer); + } + + @Override + public void endFooter(TableFooter footer) { + converter.endFooter(footer); + super.endFooter(footer); + } + + @Override + public void startBody(TableBody body) { + converter.startBody(body); + super.startBody(body); + } + + @Override + public void endBody(TableBody body) { + converter.endBody(body); + super.endBody(body); + } + + @Override + public void startRow(TableRow tr) { + converter.startRow(tr); + super.startRow(tr); + } + + @Override + public void endRow(TableRow tr) { + converter.endRow(tr); + super.endRow(tr); + } + + @Override + public void startCell(TableCell tc) { + converter.startCell(tc); + super.startCell(tc); + } + + @Override + public void endCell(TableCell tc) { + converter.endCell(tc); + super.endCell(tc); + } + + @Override + public void startList(ListBlock lb) { + converter.startList(lb); + super.startList(lb); + } + + @Override + public void endList(ListBlock lb) { + converter.endList(lb); + super.endList(lb); + } + + @Override + public void startListItem(ListItem li) { + converter.startListItem(li); + super.startListItem(li); + } + + @Override + public void endListItem(ListItem li) { + converter.endListItem(li); + super.endListItem(li); + } + + @Override + public void startListLabel(ListItemLabel listItemLabel) { + converter.startListLabel(listItemLabel); + super.startListLabel(listItemLabel); + } + + @Override + public void endListLabel(ListItemLabel listItemLabel) { + converter.endListLabel(listItemLabel); + super.endListLabel(listItemLabel); + } + + @Override + public void startListBody(ListItemBody listItemBody) { + converter.startListBody(listItemBody); + super.startListBody(listItemBody); + } + + @Override + public void endListBody(ListItemBody listItemBody) { + converter.endListBody(listItemBody); + super.endListBody(listItemBody); + } + + @Override + public void startStatic(StaticContent staticContent) { + converter.startStatic(staticContent); + super.startStatic(staticContent); + } + + @Override + public void endStatic(StaticContent statisContent) { + converter.endStatic(statisContent); + super.endStatic(statisContent); + } + + @Override + public void startMarkup() { + converter.startMarkup(); + super.startMarkup(); + } + + @Override + public void endMarkup() { + converter.endMarkup(); + super.endMarkup(); + } + + @Override + public void startLink(BasicLink basicLink) { + converter.startLink(basicLink); + super.startLink(basicLink); + } + + @Override + public void endLink(BasicLink basicLink) { + converter.endLink(basicLink); + super.endLink(basicLink); + } + + @Override + public void image(ExternalGraphic eg) { + converter.image(eg); + super.image(eg); + } + + @Override + public void pageRef() { + converter.pageRef(); + super.pageRef(); + } + + @Override + public void startInstreamForeignObject(InstreamForeignObject ifo) { + converter.startInstreamForeignObject(ifo); + super.startInstreamForeignObject(ifo); + } + + @Override + public void endInstreamForeignObject(InstreamForeignObject ifo) { + converter.endInstreamForeignObject(ifo); + super.endInstreamForeignObject(ifo); + } + + @Override + public void startFootnote(Footnote footnote) { + converter.startFootnote(footnote); + super.startFootnote(footnote); + } + + @Override + public void endFootnote(Footnote footnote) { + converter.endFootnote(footnote); + super.endFootnote(footnote); + } + + @Override + public void startFootnoteBody(FootnoteBody body) { + converter.startFootnoteBody(body); + super.startFootnoteBody(body); + } + + @Override + public void endFootnoteBody(FootnoteBody body) { + converter.endFootnoteBody(body); + super.endFootnoteBody(body); + } + + @Override + public void startLeader(Leader l) { + converter = eventSwallower; + converter.startLeader(l); + super.startLeader(l); + } + + @Override + public void endLeader(Leader l) { + converter.endLeader(l); + converter = foToStructureTreeEventAdapter; + super.endLeader(l); + } + + @Override + public void startWrapper(Wrapper wrapper) { + converter.startWrapper(wrapper); + super.startWrapper(wrapper); + } + + @Override + public void endWrapper(Wrapper wrapper) { + converter.endWrapper(wrapper); + super.endWrapper(wrapper); + } + + @Override + public void character(Character c) { + converter.character(c); + super.character(c); + } + + @Override + public void characters(char[] data, int start, int length) { + converter.characters(data, start, length); + super.characters(data, start, length); + } + + @Override + public void startExternalDocument(ExternalDocument document) { + converter.startExternalDocument(document); + super.startExternalDocument(document); + } + + @Override + public void endExternalDocument(ExternalDocument document) { + converter.endExternalDocument(document); + super.endExternalDocument(document); + } + +} diff --git a/src/java/org/apache/fop/accessibility/StructureTree.java b/src/java/org/apache/fop/accessibility/StructureTree.java deleted file mode 100644 index a8afd4ef5..000000000 --- a/src/java/org/apache/fop/accessibility/StructureTree.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.accessibility; - -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * A reduced version of the document's FO tree, containing only its logical - * structure. Used by accessible output formats. - */ -public final class StructureTree { - - private final List pageSequenceStructures = new ArrayList(); - - /** - * Package-private default constructor. - */ - public StructureTree() { } - - private static boolean flowOrStaticContentNodes(NodeList nodes) { - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - if (node.getNodeType() != Node.ELEMENT_NODE) { - return false; - } - String name = node.getLocalName(); - if (!(name.equals("flow") || name.equals("static-content"))) { - return false; - } - } - return true; - } - - void addPageSequenceStructure(NodeList structureTree) { - assert flowOrStaticContentNodes(structureTree); - pageSequenceStructures.add(structureTree); - } - - /** - * Returns the list of nodes that are the children of the given page sequence. - * - * @param index index of the page sequence, 0-based - * @return its children nodes - */ - public NodeList getPageSequence(int index) { - return (NodeList) pageSequenceStructures.get(index); - } - - /** - * Returns an XML-like representation of the structure trees. - *

- * Note: use only for debugging purpose, as this method - * performs non-trivial operations. - *

- * @return a string representation of this object - */ - public String toString() { - try { - Transformer t = TransformerFactory.newInstance().newTransformer(); - Writer str = new StringWriter(); - for (Iterator iter = pageSequenceStructures.iterator(); iter.hasNext();) { - NodeList nodes = (NodeList) iter.next(); - for (int i = 0, c = nodes.getLength(); i < c; i++) { - t.transform(new DOMSource(nodes.item(i)), new StreamResult(str)); - } - } - return str.toString(); - } catch (Exception e) { - return e.toString(); - } - } - -} diff --git a/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java b/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java new file mode 100644 index 000000000..240c384d6 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.accessibility; + +import java.util.Locale; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.fop.fo.FOElementMapping; +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.InternalElementMapping; +import org.apache.fop.render.intermediate.IFConstants; + +/** + * Converts structure tree events to SAX events. + */ +public final class StructureTree2SAXEventAdapter implements StructureTreeEventHandler { + + private final ContentHandler contentHandler; + + private StructureTree2SAXEventAdapter(ContentHandler currentContentHandler) { + this.contentHandler = currentContentHandler; + } + + /** + * Factory method that creates a new instance. + * @param contentHandler The handler that receives SAX events + */ + public static StructureTreeEventHandler newInstance(ContentHandler contentHandler) { + return new StructureTree2SAXEventAdapter(contentHandler); + } + + /** {@inheritDoc} */ + public void startPageSequence(Locale locale) { + try { + + contentHandler.startPrefixMapping( + InternalElementMapping.STANDARD_PREFIX, InternalElementMapping.URI); + contentHandler.startPrefixMapping( + ExtensionElementMapping.STANDARD_PREFIX, ExtensionElementMapping.URI); + contentHandler.startElement(IFConstants.NAMESPACE, + IFConstants.EL_STRUCTURE_TREE, IFConstants.EL_STRUCTURE_TREE, + new AttributesImpl()); + } catch (SAXException e) { + throw new RuntimeException(e); + } + } + + /** {@inheritDoc} */ + public void endPageSequence() { + try { + contentHandler.endElement(IFConstants.NAMESPACE, IFConstants.EL_STRUCTURE_TREE, + IFConstants.EL_STRUCTURE_TREE); + contentHandler.endPrefixMapping( + ExtensionElementMapping.STANDARD_PREFIX); + contentHandler.endPrefixMapping( + InternalElementMapping.STANDARD_PREFIX); + + } catch (SAXException e) { + throw new RuntimeException(e); + } + } + + /** {@inheritDoc} */ + public void startNode(String name, Attributes attributes) { + try { + contentHandler.startElement(FOElementMapping.URI, name, + FOElementMapping.STANDARD_PREFIX + ":" + name, + attributes); + } catch (SAXException e) { + throw new RuntimeException(e); + } + } + + /** {@inheritDoc} */ + public void endNode(String name) { + try { + contentHandler.endElement(FOElementMapping.URI, name, + FOElementMapping.STANDARD_PREFIX + ":" + name); + } catch (SAXException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java b/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java deleted file mode 100644 index 036502e99..000000000 --- a/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.accessibility; - -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.dom.DOMResult; -import javax.xml.transform.sax.SAXTransformerFactory; -import javax.xml.transform.sax.TransformerHandler; - -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; - -import org.apache.fop.util.DelegatingContentHandler; - -/** - * Helper class that re-builds a structure tree from what is stored in an - * intermediate XML file (IF XML or Area Tree XML). - */ -public final class StructureTreeBuilder { - - private final SAXTransformerFactory factory; - - private final StructureTree structureTree = new StructureTree(); - - /** - * Creates a new instance. - * - * @param factory a factory internally used to build the structures of page - * sequences - */ - public StructureTreeBuilder(SAXTransformerFactory factory) { - this.factory = factory; - } - - /** - * Returns the structure tree that will result from the parsing. - * - * @return the structure tree built by this object - */ - public StructureTree getStructureTree() { - return structureTree; - } - - /** - * Returns a ContenHandler for parsing the structure of a new page sequence. - * It is assumed that page sequences are being parsed in the document order. - * - * @return a handler for parsing the <structure-tree> or - * <structureTree> element and its descendants - * @throws SAXException if there is an error when creating the handler - */ - public ContentHandler getHandlerForNextPageSequence() throws SAXException { - TransformerHandler structureTreeBuilder; - try { - structureTreeBuilder = factory.newTransformerHandler(); - } catch (TransformerConfigurationException e) { - throw new SAXException(e); - } - final DOMResult domResult = new DOMResult(); - structureTreeBuilder.setResult(domResult); - return new DelegatingContentHandler(structureTreeBuilder) { - - public void characters(char[] ch, int start, int length) throws SAXException { - /* - * There's no text node in the structure tree. This is just - * whitespace => ignore - */ - } - - public void endDocument() throws SAXException { - super.endDocument(); - structureTree.addPageSequenceStructure(domResult.getNode().getFirstChild() - .getChildNodes()); - } - }; - } - -} diff --git a/src/java/org/apache/fop/accessibility/StructureTreeBuildingFOEventHandler.java b/src/java/org/apache/fop/accessibility/StructureTreeBuildingFOEventHandler.java deleted file mode 100644 index e3f946810..000000000 --- a/src/java/org/apache/fop/accessibility/StructureTreeBuildingFOEventHandler.java +++ /dev/null @@ -1,840 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.accessibility; - -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMResult; -import javax.xml.transform.sax.SAXTransformerFactory; -import javax.xml.transform.sax.TransformerHandler; - -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; - -import org.apache.fop.apps.FOUserAgent; -import org.apache.fop.fo.DelegatingFOEventHandler; -import org.apache.fop.fo.FOEventHandler; -import org.apache.fop.fo.FONode; -import org.apache.fop.fo.extensions.ExtensionElementMapping; -import org.apache.fop.fo.extensions.ExternalDocument; -import org.apache.fop.fo.extensions.InternalElementMapping; -import org.apache.fop.fo.flow.AbstractGraphics; -import org.apache.fop.fo.flow.BasicLink; -import org.apache.fop.fo.flow.Block; -import org.apache.fop.fo.flow.BlockContainer; -import org.apache.fop.fo.flow.Character; -import org.apache.fop.fo.flow.ExternalGraphic; -import org.apache.fop.fo.flow.Footnote; -import org.apache.fop.fo.flow.FootnoteBody; -import org.apache.fop.fo.flow.Inline; -import org.apache.fop.fo.flow.InstreamForeignObject; -import org.apache.fop.fo.flow.Leader; -import org.apache.fop.fo.flow.ListBlock; -import org.apache.fop.fo.flow.ListItem; -import org.apache.fop.fo.flow.ListItemBody; -import org.apache.fop.fo.flow.ListItemLabel; -import org.apache.fop.fo.flow.PageNumber; -import org.apache.fop.fo.flow.PageNumberCitation; -import org.apache.fop.fo.flow.PageNumberCitationLast; -import org.apache.fop.fo.flow.Wrapper; -import org.apache.fop.fo.flow.table.Table; -import org.apache.fop.fo.flow.table.TableBody; -import org.apache.fop.fo.flow.table.TableCell; -import org.apache.fop.fo.flow.table.TableColumn; -import org.apache.fop.fo.flow.table.TableFooter; -import org.apache.fop.fo.flow.table.TableHeader; -import org.apache.fop.fo.flow.table.TableRow; -import org.apache.fop.fo.pagination.Flow; -import org.apache.fop.fo.pagination.PageSequence; -import org.apache.fop.fo.pagination.StaticContent; -import org.apache.fop.fo.properties.CommonAccessibilityHolder; -import org.apache.fop.util.XMLUtil; - -/** - * A class that builds the document's structure tree. - */ -public class StructureTreeBuildingFOEventHandler extends DelegatingFOEventHandler { - - private int idCounter; - - private final StructureTree structureTree; - - private TransformerHandler structureTreeDOMBuilder; - - private DOMResult result; - - /** Delegates to either {@link #actualStructureTreeBuilder} or {@link #eventSwallower}. */ - private FOEventHandler structureTreeBuilder; - - private FOEventHandler actualStructureTreeBuilder; - - /** The descendants of some elements like fo:leader must be ignored. */ - private final FOEventHandler eventSwallower; - - private final class StructureTreeBuilder extends FOEventHandler { - - public StructureTreeBuilder(FOUserAgent foUserAgent) { - super(foUserAgent); - } - - @Override - public void startDocument() throws SAXException { - } - - @Override - public void endDocument() throws SAXException { - } - - @Override - public void startPageSequence(PageSequence pageSeq) { - SAXTransformerFactory transformerFactory = - (SAXTransformerFactory) TransformerFactory.newInstance(); - try { - structureTreeDOMBuilder = transformerFactory.newTransformerHandler(); - } catch (TransformerConfigurationException e) { - throw new RuntimeException(e); - } - result = new DOMResult(); - structureTreeDOMBuilder.setResult(result); - try { - structureTreeDOMBuilder.startDocument(); - } catch (SAXException e) { - throw new RuntimeException(e); - } - startElement(pageSeq); - } - - @Override - public void endPageSequence(PageSequence pageSeq) { - endElement(pageSeq); - try { - structureTreeDOMBuilder.endDocument(); - } catch (SAXException e) { - throw new RuntimeException(e); - } - structureTree.addPageSequenceStructure( - result.getNode().getFirstChild().getChildNodes()); - } - - @Override - public void startPageNumber(PageNumber pagenum) { - startElementWithID(pagenum); - } - - @Override - public void endPageNumber(PageNumber pagenum) { - endElement(pagenum); - } - - @Override - public void startPageNumberCitation(PageNumberCitation pageCite) { - startElementWithID(pageCite); - } - - @Override - public void endPageNumberCitation(PageNumberCitation pageCite) { - endElement(pageCite); - } - - @Override - public void startPageNumberCitationLast(PageNumberCitationLast pageLast) { - startElementWithID(pageLast); - } - - @Override - public void endPageNumberCitationLast(PageNumberCitationLast pageLast) { - endElement(pageLast); - } - - @Override - public void startFlow(Flow fl) { - startElement(fl); - } - - @Override - public void endFlow(Flow fl) { - endElement(fl); - } - - @Override - public void startBlock(Block bl) { - startElementWithID(bl); - } - - @Override - public void endBlock(Block bl) { - endElement(bl); - } - - @Override - public void startBlockContainer(BlockContainer blc) { - startElement(blc); - } - - @Override - public void endBlockContainer(BlockContainer blc) { - endElement(blc); - } - - @Override - public void startInline(Inline inl) { - startElementWithID(inl); - } - - @Override - public void endInline(Inline inl) { - endElement(inl); - } - - @Override - public void startTable(Table tbl) { - startElementWithID(tbl); - } - - @Override - public void endTable(Table tbl) { - endElement(tbl); - } - - @Override - public void startHeader(TableHeader header) { - startElementWithID(header); - } - - @Override - public void endHeader(TableHeader header) { - endElement(header); - } - - @Override - public void startFooter(TableFooter footer) { - startElementWithID(footer); - } - - @Override - public void endFooter(TableFooter footer) { - endElement(footer); - } - - @Override - public void startBody(TableBody body) { - startElementWithID(body); - } - - @Override - public void endBody(TableBody body) { - endElement(body); - } - - @Override - public void startRow(TableRow tr) { - startElementWithID(tr); - } - - @Override - public void endRow(TableRow tr) { - endElement(tr); - } - - @Override - public void startCell(TableCell tc) { - AttributesImpl attributes = new AttributesImpl(); - int colSpan = tc.getNumberColumnsSpanned(); - if (colSpan > 1) { - addNoNamespaceAttribute(attributes, "number-columns-spanned", - Integer.toString(colSpan)); - } - startElementWithID(tc, attributes); - } - - @Override - public void endCell(TableCell tc) { - endElement(tc); - } - - @Override - public void startList(ListBlock lb) { - startElement(lb); - } - - @Override - public void endList(ListBlock lb) { - endElement(lb); - } - - @Override - public void startListItem(ListItem li) { - startElement(li); - } - - @Override - public void endListItem(ListItem li) { - endElement(li); - } - - @Override - public void startListLabel(ListItemLabel listItemLabel) { - startElement(listItemLabel); - } - - @Override - public void endListLabel(ListItemLabel listItemLabel) { - endElement(listItemLabel); - } - - @Override - public void startListBody(ListItemBody listItemBody) { - startElement(listItemBody); - } - - @Override - public void endListBody(ListItemBody listItemBody) { - endElement(listItemBody); - } - - @Override - public void startStatic(StaticContent staticContent) { - startElement(staticContent); - } - - @Override - public void endStatic(StaticContent statisContent) { - endElement(statisContent); - } - - @Override - public void startLink(BasicLink basicLink) { - startElementWithID(basicLink); - } - - @Override - public void endLink(BasicLink basicLink) { - endElement(basicLink); - } - - @Override - public void image(ExternalGraphic eg) { - startElementWithIDAndAltText(eg); - endElement(eg); - } - - @Override - public void startInstreamForeignObject(InstreamForeignObject ifo) { - startElementWithIDAndAltText(ifo); - } - - @Override - public void endInstreamForeignObject(InstreamForeignObject ifo) { - endElement(ifo); - } - - @Override - public void startFootnote(Footnote footnote) { - startElement(footnote); - } - - @Override - public void endFootnote(Footnote footnote) { - endElement(footnote); - } - - @Override - public void startFootnoteBody(FootnoteBody body) { - startElement(body); - } - - @Override - public void endFootnoteBody(FootnoteBody body) { - endElement(body); - } - - @Override - public void startWrapper(Wrapper wrapper) { - startElement(wrapper); - } - - @Override - public void endWrapper(Wrapper wrapper) { - endElement(wrapper); - } - - @Override - public void character(Character c) { - startElementWithID(c); - endElement(c); - } - - private void startElement(FONode node) { - startElement(node, new AttributesImpl()); - } - - private void startElementWithID(FONode node) { - startElementWithID(node, new AttributesImpl()); - } - - private void startElementWithIDAndAltText(AbstractGraphics node) { - AttributesImpl attributes = new AttributesImpl(); - addAttribute(attributes, ExtensionElementMapping.URI, "alt-text", - ExtensionElementMapping.STANDARD_PREFIX, node.getAltText()); - startElementWithID(node, attributes); - } - - private void startElementWithID(FONode node, AttributesImpl attributes) { - String id = Integer.toHexString(idCounter++); - node.setPtr(id); - addAttribute(attributes, - InternalElementMapping.URI, "ptr", InternalElementMapping.STANDARD_PREFIX, id); - startElement(node, attributes); - } - - private void startElement(FONode node, AttributesImpl attributes) { - String localName = node.getLocalName(); - if (node instanceof CommonAccessibilityHolder) { - addRole((CommonAccessibilityHolder) node, attributes); - } - try { - structureTreeDOMBuilder.startElement(node.getNamespaceURI(), localName, - node.getNormalNamespacePrefix() + ":" + localName, - attributes); - } catch (SAXException e) { - throw new RuntimeException(e); - } - } - - private void addNoNamespaceAttribute(AttributesImpl attributes, String name, String value) { - attributes.addAttribute("", name, name, XMLUtil.CDATA, value); - } - - private void addAttribute(AttributesImpl attributes, - String namespace, String localName, String prefix, String value) { - assert namespace.length() > 0 && prefix.length() > 0; - String qualifiedName = prefix + ":" + localName; - attributes.addAttribute(namespace, localName, qualifiedName, XMLUtil.CDATA, value); - } - - private void addRole(CommonAccessibilityHolder node, AttributesImpl attributes) { - String role = node.getCommonAccessibility().getRole(); - if (role != null) { - addNoNamespaceAttribute(attributes, "role", role); - } - } - - private void endElement(FONode node) { - String localName = node.getLocalName(); - try { - structureTreeDOMBuilder.endElement(node.getNamespaceURI(), localName, - node.getNormalNamespacePrefix() + ":" + localName); - } catch (SAXException e) { - throw new RuntimeException(e); - } - } - - } - - /** - * Creates a new instance. - * - * @param structureTree the object that will hold the structure tree - * @param delegate the FO event handler that must be wrapped by this instance - */ - public StructureTreeBuildingFOEventHandler(StructureTree structureTree, - FOEventHandler delegate) { - super(delegate); - this.structureTree = structureTree; - this.actualStructureTreeBuilder = new StructureTreeBuilder(foUserAgent); - this.structureTreeBuilder = actualStructureTreeBuilder; - this.eventSwallower = new FOEventHandler(foUserAgent) { }; - } - - @Override - public void startDocument() throws SAXException { - structureTreeBuilder.startDocument(); - super.startDocument(); - } - - @Override - public void endDocument() throws SAXException { - structureTreeBuilder.endDocument(); - super.endDocument(); - } - - @Override - public void startPageSequence(PageSequence pageSeq) { - structureTreeBuilder.startPageSequence(pageSeq); - super.startPageSequence(pageSeq); - } - - @Override - public void endPageSequence(PageSequence pageSeq) { - structureTreeBuilder.endPageSequence(pageSeq); - super.endPageSequence(pageSeq); - } - - @Override - public void startPageNumber(PageNumber pagenum) { - structureTreeBuilder.startPageNumber(pagenum); - super.startPageNumber(pagenum); - } - - @Override - public void endPageNumber(PageNumber pagenum) { - structureTreeBuilder.endPageNumber(pagenum); - super.endPageNumber(pagenum); - } - - @Override - public void startPageNumberCitation(PageNumberCitation pageCite) { - structureTreeBuilder.startPageNumberCitation(pageCite); - super.startPageNumberCitation(pageCite); - } - - @Override - public void endPageNumberCitation(PageNumberCitation pageCite) { - structureTreeBuilder.endPageNumberCitation(pageCite); - super.endPageNumberCitation(pageCite); - } - - @Override - public void startPageNumberCitationLast(PageNumberCitationLast pageLast) { - structureTreeBuilder.startPageNumberCitationLast(pageLast); - super.startPageNumberCitationLast(pageLast); - } - - @Override - public void endPageNumberCitationLast(PageNumberCitationLast pageLast) { - structureTreeBuilder.endPageNumberCitationLast(pageLast); - super.endPageNumberCitationLast(pageLast); - } - - @Override - public void startFlow(Flow fl) { - structureTreeBuilder.startFlow(fl); - super.startFlow(fl); - } - - @Override - public void endFlow(Flow fl) { - structureTreeBuilder.endFlow(fl); - super.endFlow(fl); - } - - @Override - public void startBlock(Block bl) { - structureTreeBuilder.startBlock(bl); - super.startBlock(bl); - } - - @Override - public void endBlock(Block bl) { - structureTreeBuilder.endBlock(bl); - super.endBlock(bl); - } - - @Override - public void startBlockContainer(BlockContainer blc) { - structureTreeBuilder.startBlockContainer(blc); - super.startBlockContainer(blc); - } - - @Override - public void endBlockContainer(BlockContainer blc) { - structureTreeBuilder.endBlockContainer(blc); - super.endBlockContainer(blc); - } - - @Override - public void startInline(Inline inl) { - structureTreeBuilder.startInline(inl); - super.startInline(inl); - } - - @Override - public void endInline(Inline inl) { - structureTreeBuilder.endInline(inl); - super.endInline(inl); - } - - @Override - public void startTable(Table tbl) { - structureTreeBuilder.startTable(tbl); - super.startTable(tbl); - } - - @Override - public void endTable(Table tbl) { - structureTreeBuilder.endTable(tbl); - super.endTable(tbl); - } - - @Override - public void startColumn(TableColumn tc) { - structureTreeBuilder.startColumn(tc); - super.startColumn(tc); - } - - @Override - public void endColumn(TableColumn tc) { - structureTreeBuilder.endColumn(tc); - super.endColumn(tc); - } - - @Override - public void startHeader(TableHeader header) { - structureTreeBuilder.startHeader(header); - super.startHeader(header); - } - - @Override - public void endHeader(TableHeader header) { - structureTreeBuilder.endHeader(header); - super.endHeader(header); - } - - @Override - public void startFooter(TableFooter footer) { - structureTreeBuilder.startFooter(footer); - super.startFooter(footer); - } - - @Override - public void endFooter(TableFooter footer) { - structureTreeBuilder.endFooter(footer); - super.endFooter(footer); - } - - @Override - public void startBody(TableBody body) { - structureTreeBuilder.startBody(body); - super.startBody(body); - } - - @Override - public void endBody(TableBody body) { - structureTreeBuilder.endBody(body); - super.endBody(body); - } - - @Override - public void startRow(TableRow tr) { - structureTreeBuilder.startRow(tr); - super.startRow(tr); - } - - @Override - public void endRow(TableRow tr) { - structureTreeBuilder.endRow(tr); - super.endRow(tr); - } - - @Override - public void startCell(TableCell tc) { - structureTreeBuilder.startCell(tc); - super.startCell(tc); - } - - @Override - public void endCell(TableCell tc) { - structureTreeBuilder.endCell(tc); - super.endCell(tc); - } - - @Override - public void startList(ListBlock lb) { - structureTreeBuilder.startList(lb); - super.startList(lb); - } - - @Override - public void endList(ListBlock lb) { - structureTreeBuilder.endList(lb); - super.endList(lb); - } - - @Override - public void startListItem(ListItem li) { - structureTreeBuilder.startListItem(li); - super.startListItem(li); - } - - @Override - public void endListItem(ListItem li) { - structureTreeBuilder.endListItem(li); - super.endListItem(li); - } - - @Override - public void startListLabel(ListItemLabel listItemLabel) { - structureTreeBuilder.startListLabel(listItemLabel); - super.startListLabel(listItemLabel); - } - - @Override - public void endListLabel(ListItemLabel listItemLabel) { - structureTreeBuilder.endListLabel(listItemLabel); - super.endListLabel(listItemLabel); - } - - @Override - public void startListBody(ListItemBody listItemBody) { - structureTreeBuilder.startListBody(listItemBody); - super.startListBody(listItemBody); - } - - @Override - public void endListBody(ListItemBody listItemBody) { - structureTreeBuilder.endListBody(listItemBody); - super.endListBody(listItemBody); - } - - @Override - public void startStatic(StaticContent staticContent) { - structureTreeBuilder.startStatic(staticContent); - super.startStatic(staticContent); - } - - @Override - public void endStatic(StaticContent statisContent) { - structureTreeBuilder.endStatic(statisContent); - super.endStatic(statisContent); - } - - @Override - public void startMarkup() { - structureTreeBuilder.startMarkup(); - super.startMarkup(); - } - - @Override - public void endMarkup() { - structureTreeBuilder.endMarkup(); - super.endMarkup(); - } - - @Override - public void startLink(BasicLink basicLink) { - structureTreeBuilder.startLink(basicLink); - super.startLink(basicLink); - } - - @Override - public void endLink(BasicLink basicLink) { - structureTreeBuilder.endLink(basicLink); - super.endLink(basicLink); - } - - @Override - public void image(ExternalGraphic eg) { - structureTreeBuilder.image(eg); - super.image(eg); - } - - @Override - public void pageRef() { - structureTreeBuilder.pageRef(); - super.pageRef(); - } - - @Override - public void startInstreamForeignObject(InstreamForeignObject ifo) { - structureTreeBuilder.startInstreamForeignObject(ifo); - super.startInstreamForeignObject(ifo); - } - - @Override - public void endInstreamForeignObject(InstreamForeignObject ifo) { - structureTreeBuilder.endInstreamForeignObject(ifo); - super.endInstreamForeignObject(ifo); - } - - @Override - public void startFootnote(Footnote footnote) { - structureTreeBuilder.startFootnote(footnote); - super.startFootnote(footnote); - } - - @Override - public void endFootnote(Footnote footnote) { - structureTreeBuilder.endFootnote(footnote); - super.endFootnote(footnote); - } - - @Override - public void startFootnoteBody(FootnoteBody body) { - structureTreeBuilder.startFootnoteBody(body); - super.startFootnoteBody(body); - } - - @Override - public void endFootnoteBody(FootnoteBody body) { - structureTreeBuilder.endFootnoteBody(body); - super.endFootnoteBody(body); - } - - @Override - public void startLeader(Leader l) { - structureTreeBuilder = eventSwallower; - structureTreeBuilder.startLeader(l); - super.startLeader(l); - } - - @Override - public void endLeader(Leader l) { - structureTreeBuilder.endLeader(l); - structureTreeBuilder = actualStructureTreeBuilder; - super.endLeader(l); - } - - @Override - public void startWrapper(Wrapper wrapper) { - structureTreeBuilder.startWrapper(wrapper); - super.startWrapper(wrapper); - } - - @Override - public void endWrapper(Wrapper wrapper) { - structureTreeBuilder.endWrapper(wrapper); - super.endWrapper(wrapper); - } - - @Override - public void character(Character c) { - structureTreeBuilder.character(c); - super.character(c); - } - - @Override - public void characters(char[] data, int start, int length) { - structureTreeBuilder.characters(data, start, length); - super.characters(data, start, length); - } - - @Override - public void startExternalDocument(ExternalDocument document) { - structureTreeBuilder.startExternalDocument(document); - super.startExternalDocument(document); - } - - @Override - public void endExternalDocument(ExternalDocument document) { - structureTreeBuilder.endExternalDocument(document); - super.endExternalDocument(document); - } - -} diff --git a/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java b/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java new file mode 100644 index 000000000..d84d07870 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.accessibility; + +import java.util.Locale; + +import org.xml.sax.Attributes; + +/** + * Receive notifications relating to the structure tree of an FO document. + * A structure tree is a reduced version of the document's FO tree, containing only the logical + * structure that is used by accessible output formats. + */ +public interface StructureTreeEventHandler { + + /** + * Starts a page sequence structure tree node. + * @param locale The locale of the page sequence + */ + void startPageSequence(Locale locale); + + /** + * Starts a structure tree node. + * @param name The name of the structure tree node + * @param attributes Map of node properties + */ + void startNode(String name, Attributes attributes); + + /** + * Ends a structure tree node. + * @param name The name of the structure tree node + */ + void endNode(String name); + + /** + * Ends a page sequence structure tree node. + */ + void endPageSequence(); +} diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index 071066e04..d5cc695c8 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -39,7 +39,8 @@ import org.apache.xmlgraphics.util.UnitConv; import org.apache.fop.Version; import org.apache.fop.accessibility.Accessibility; -import org.apache.fop.accessibility.StructureTree; +import org.apache.fop.accessibility.DummyStructureTreeEventHandler; +import org.apache.fop.accessibility.StructureTreeEventHandler; import org.apache.fop.events.DefaultEventBroadcaster; import org.apache.fop.events.Event; import org.apache.fop.events.EventBroadcaster; @@ -101,8 +102,7 @@ public class FOUserAgent { private boolean locatorEnabled = true; // true by default (for error messages). private boolean conserveMemoryPolicy = false; private EventBroadcaster eventBroadcaster = new FOPEventBroadcaster(); - - private StructureTree structureTree; + private StructureTreeEventHandler structureTreeEventHandler = DummyStructureTreeEventHandler.INSTANCE; /** Producer: Metadata element for the system/software that produces * the document. (Some renderers can store this in the document.) @@ -173,6 +173,9 @@ public class FOUserAgent { * @param documentHandler the document handler instance to use */ public void setDocumentHandlerOverride(IFDocumentHandler documentHandler) { + if (isAccessibilityEnabled()) { + setStructureTreeEventHandler(documentHandler.getStructureTreeEventHandler()); + } this.documentHandlerOverride = documentHandler; } @@ -674,24 +677,23 @@ public class FOUserAgent { } /** - * Sets the document's structure tree, for use by accessible output formats. + * Sets the document's structure tree event handler, for use by accessible + * output formats. * - * @param structureTree a simplified version of the FO tree, retaining only - * its logical structure + * @param structureTreeEventHandler The structure tree event handler to set */ - public void setStructureTree(StructureTree structureTree) { - this.structureTree = structureTree; + public void setStructureTreeEventHandler(StructureTreeEventHandler structureTreeEventHandler) { + this.structureTreeEventHandler = structureTreeEventHandler; } /** - * Returns the document's structure tree, for use by accessible output - * formats. + * Returns the document's structure tree event handler, for use by + * accessible output formats. * - * @return a simplified version of the FO tree, retaining only its logical - * structure + * @return The structure tree event handler */ - public StructureTree getStructureTree() { - return this.structureTree; + public StructureTreeEventHandler getStructureTreeEventHandler() { + return this.structureTreeEventHandler; } } diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java index f45245bfe..6448e7c5d 100644 --- a/src/java/org/apache/fop/area/AreaTreeHandler.java +++ b/src/java/org/apache/fop/area/AreaTreeHandler.java @@ -22,6 +22,7 @@ package org.apache.fop.area; // Java import java.io.OutputStream; import java.util.List; +import java.util.Locale; import org.xml.sax.SAXException; @@ -182,6 +183,14 @@ public class AreaTreeHandler extends FOEventHandler { } } + @Override + public void startRoot(Root root) { + Locale locale = root.getLocale(); + if (locale != null) { + model.setDocumentLocale(locale); + } + } + /** * finish the previous pageSequence */ diff --git a/src/java/org/apache/fop/area/AreaTreeModel.java b/src/java/org/apache/fop/area/AreaTreeModel.java index 2a1f14ab5..941f6cea2 100644 --- a/src/java/org/apache/fop/area/AreaTreeModel.java +++ b/src/java/org/apache/fop/area/AreaTreeModel.java @@ -21,6 +21,7 @@ package org.apache.fop.area; // Java import java.util.List; +import java.util.Locale; import org.xml.sax.SAXException; @@ -123,4 +124,11 @@ public class AreaTreeModel { public PageViewport getPage(int seq, int count) { return pageSequenceList.get(seq - 1).getPage(count); } + + /** + * + * @param locale The locale of the document + */ + public void setDocumentLocale(Locale locale) { + } } diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index d2f52f991..545c9015d 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -58,8 +58,6 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.util.QName; import org.apache.fop.ResourceEventProducer; -import org.apache.fop.accessibility.AccessibilityEventProducer; -import org.apache.fop.accessibility.StructureTreeBuilder; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Trait.Background; import org.apache.fop.area.Trait.InternalLink; @@ -69,11 +67,11 @@ import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.InlineBlockParent; import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; -import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.area.inline.WordArea; import org.apache.fop.fo.ElementMappingRegistry; import org.apache.fop.fo.expr.PropertyException; @@ -86,7 +84,6 @@ import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactoryRegistry; import org.apache.fop.util.ConversionUtils; import org.apache.fop.util.DefaultErrorListener; -import org.apache.fop.util.DelegatingContentHandler; import org.apache.fop.util.XMLConstants; import org.apache.fop.util.XMLUtil; @@ -166,27 +163,8 @@ public class AreaTreeParser { private DOMImplementation domImplementation; private Locator locator; - - private StructureTreeBuilder structureTreeBuilder; - - private ContentHandler structureTreeBuilderWrapper; - private Attributes pageSequenceAttributes; - private final class StructureTreeBuilderWrapper extends DelegatingContentHandler { - - private StructureTreeBuilderWrapper() - throws SAXException { - super(structureTreeBuilder.getHandlerForNextPageSequence()); - } - - public void endDocument() throws SAXException { - super.endDocument(); - startAreaTreeElement("pageSequence", pageSequenceAttributes); - pageSequenceAttributes = null; - } - } - public Handler(AreaTreeModel treeModel, FOUserAgent userAgent, ElementMappingRegistry elementMappingRegistry) { this.treeModel = treeModel; @@ -223,11 +201,6 @@ public class AreaTreeParser { makers.put("bookmarkTree", new BookmarkTreeMaker()); makers.put("bookmark", new BookmarkMaker()); makers.put("destination", new DestinationMaker()); - - if (userAgent.isAccessibilityEnabled()) { - structureTreeBuilder = new StructureTreeBuilder(tFactory); - userAgent.setStructureTree(structureTreeBuilder.getStructureTree()); - } } private Area findAreaType(Class clazz) { @@ -308,32 +281,15 @@ public class AreaTreeParser { } else { boolean handled = true; if ("".equals(uri)) { - if (localName.equals("pageSequence") && userAgent.isAccessibilityEnabled()) { - structureTreeBuilderWrapper = new StructureTreeBuilderWrapper(); - pageSequenceAttributes = new AttributesImpl(attributes); - } else if (localName.equals("structureTree")) { - if (userAgent.isAccessibilityEnabled()) { - delegate = structureTreeBuilderWrapper; - } else { - /* Delegate to a handler that does nothing */ - delegate = new DefaultHandler(); - } + if (localName.equals("structureTree")) { + + /* The area tree parser no longer supports the structure tree. */ + delegate = new DefaultHandler(); + delegateStack.push(qName); delegate.startDocument(); delegate.startElement(uri, localName, qName, attributes); } else { - if (pageSequenceAttributes != null) { - /* - * This means that no structure-element tag was - * found in the XML, otherwise a - * StructureTreeBuilderWrapper object would have - * been created, which would have reset the - * pageSequenceAttributes field. - */ - AccessibilityEventProducer.Provider - .get(userAgent.getEventBroadcaster()) - .noStructureTreeInXML(this); - } handled = startAreaTreeElement(localName, attributes); } } else { diff --git a/src/java/org/apache/fop/area/RenderPagesModel.java b/src/java/org/apache/fop/area/RenderPagesModel.java index afec850f8..cef2552c8 100644 --- a/src/java/org/apache/fop/area/RenderPagesModel.java +++ b/src/java/org/apache/fop/area/RenderPagesModel.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.List; +import java.util.Locale; import org.xml.sax.SAXException; @@ -83,6 +84,11 @@ public class RenderPagesModel extends AreaTreeModel { } } + @Override + public void setDocumentLocale(Locale locale) { + renderer.setDocumentLocale(locale); + } + /** {@inheritDoc} */ @Override public void startPageSequence(PageSequence pageSequence) { diff --git a/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java b/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java index cb4ded17f..ed85bd1c9 100644 --- a/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java +++ b/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java @@ -50,6 +50,7 @@ import org.apache.fop.fo.flow.table.TableHeader; import org.apache.fop.fo.flow.table.TableRow; import org.apache.fop.fo.pagination.Flow; import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fo.pagination.Root; import org.apache.fop.fo.pagination.StaticContent; import org.apache.fop.fonts.FontInfo; @@ -90,6 +91,16 @@ public abstract class DelegatingFOEventHandler extends FOEventHandler { delegate.endDocument(); } + @Override + public void startRoot(Root root) { + delegate.startRoot(root); + } + + @Override + public void endRoot(Root root) { + delegate.endRoot(root); + } + @Override public void startPageSequence(PageSequence pageSeq) { delegate.startPageSequence(pageSeq); diff --git a/src/java/org/apache/fop/fo/FOElementMapping.java b/src/java/org/apache/fop/fo/FOElementMapping.java index 8069880c6..c96c3b933 100644 --- a/src/java/org/apache/fop/fo/FOElementMapping.java +++ b/src/java/org/apache/fop/fo/FOElementMapping.java @@ -32,6 +32,9 @@ public class FOElementMapping extends ElementMapping { /** The XSL-FO namespace URI */ public static final String URI = "http://www.w3.org/1999/XSL/Format"; + /** Standard prefix */ + public static final String STANDARD_PREFIX = "fo"; + /** * Basic constructor; inititializes the namespace URI for the fo: namespace */ @@ -141,7 +144,7 @@ public class FOElementMapping extends ElementMapping { /** {@inheritDoc} */ public String getStandardPrefix() { - return "fo"; + return STANDARD_PREFIX; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fo/FOEventHandler.java b/src/java/org/apache/fop/fo/FOEventHandler.java index 453edec35..1f3514dea 100644 --- a/src/java/org/apache/fop/fo/FOEventHandler.java +++ b/src/java/org/apache/fop/fo/FOEventHandler.java @@ -50,6 +50,7 @@ import org.apache.fop.fo.flow.table.TableHeader; import org.apache.fop.fo.flow.table.TableRow; import org.apache.fop.fo.pagination.Flow; import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fo.pagination.Root; import org.apache.fop.fo.pagination.StaticContent; import org.apache.fop.fonts.FontEventAdapter; import org.apache.fop.fonts.FontInfo; @@ -117,6 +118,12 @@ public abstract class FOEventHandler { public void endDocument() throws SAXException { } + public void startRoot(Root root) { + } + + public void endRoot(Root root) { + } + /** * * @param pageSeq PageSequence that is starting. diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index 890138e5c..b053692d6 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -33,8 +33,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.util.QName; -import org.apache.fop.accessibility.StructureTree; -import org.apache.fop.accessibility.StructureTreeBuildingFOEventHandler; +import org.apache.fop.accessibility.FO2StructureTreeConverter; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FormattingResults; @@ -107,9 +106,8 @@ public class FOTreeBuilder extends DefaultHandler { foEventHandler = foUserAgent.getRendererFactory().createFOEventHandler( foUserAgent, outputFormat, stream); if (userAgent.isAccessibilityEnabled()) { - StructureTree structureTree = new StructureTree(); - foEventHandler = new StructureTreeBuildingFOEventHandler(structureTree, foEventHandler); - userAgent.setStructureTree(structureTree); + foEventHandler = new FO2StructureTreeConverter( + foUserAgent.getStructureTreeEventHandler(), foEventHandler); } builderContext = new FOTreeBuilderContext(); builderContext.setPropertyListMaker(new PropertyListMaker() { diff --git a/src/java/org/apache/fop/fo/pagination/Root.java b/src/java/org/apache/fop/fo/pagination/Root.java index c670d6e42..6df6e9537 100644 --- a/src/java/org/apache/fop/fo/pagination/Root.java +++ b/src/java/org/apache/fop/fo/pagination/Root.java @@ -21,6 +21,7 @@ package org.apache.fop.fo.pagination; // java import java.util.List; +import java.util.Locale; import org.xml.sax.Locator; @@ -52,6 +53,7 @@ public class Root extends FObj implements CommonAccessibilityHolder { private BookmarkTree bookmarkTree = null; private List destinationList; private List pageSequences; + private Locale locale; // temporary until above list populated private boolean pageSequenceFound = false; @@ -88,6 +90,24 @@ public class Root extends FObj implements CommonAccessibilityHolder { super.bind(pList); commonAccessibility = CommonAccessibility.getInstance(pList); mediaUsage = pList.get(PR_MEDIA_USAGE).getEnum(); + String language = pList.get(PR_LANGUAGE).getString(); + String country = pList.get(PR_COUNTRY).getString(); + if (isLocalePropertySet(language)) { + if (isLocalePropertySet(country)) { + locale = new Locale(language, country); + } else { + locale = new Locale(language); + } + } + } + + private boolean isLocalePropertySet(String property) { + return property != null && !property.equals("none"); + } + + /** {@inheritDoc} */ + protected void startOfNode() throws FOPException { + foEventHandler.startRoot(this); } /** {@inheritDoc} */ @@ -96,6 +116,7 @@ public class Root extends FObj implements CommonAccessibilityHolder { missingChildElementError("(layout-master-set, declarations?, " + "bookmark-tree?, (page-sequence|fox:external-document)+)"); } + foEventHandler.endRoot(this); } /** @@ -343,4 +364,9 @@ public class Root extends FObj implements CommonAccessibilityHolder { return FO_ROOT; } + + public Locale getLocale() { + return locale; + } + } diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index cbca3ea8f..e9f42d4b2 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -353,25 +353,6 @@ public class PDFDocument { return this.root; } - /** - * Makes sure a Lang entry has been set on the document catalog, setting it - * to a default value if necessary. When accessibility is enabled the - * language must be specified for any text element in the document. - */ - public void enforceLanguageOnRoot() { - if (root.getLanguage() == null) { - String fallbackLanguage; - if (getProfile().getPDFAMode().isPDFA1LevelA()) { - //According to Annex B of ISO-19005-1:2005(E), section B.2 - fallbackLanguage = "x-unknown"; - } else { - //No language has been set on the first page-sequence, so fall back to "en". - fallbackLanguage = "en"; - } - root.setLanguage(fallbackLanguage); - } - } - /** * Get the {@link PDFInfo} object for this document. * diff --git a/src/java/org/apache/fop/pdf/PDFProfile.java b/src/java/org/apache/fop/pdf/PDFProfile.java index fb4575105..b4b43c422 100644 --- a/src/java/org/apache/fop/pdf/PDFProfile.java +++ b/src/java/org/apache/fop/pdf/PDFProfile.java @@ -133,8 +133,12 @@ public class PDFProfile { //---------=== Info and validation methods ===--------- + private String format(String pattern, Object[] args) { + return MessageFormat.format(pattern, args); + } + private String format(String pattern, Object arg) { - return MessageFormat.format(pattern, new Object[] {arg}); + return format(pattern, new Object[] {arg}); } /** Checks if encryption is allowed. */ diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index f71841005..177ca4b2d 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -21,6 +21,9 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.util.Locale; + +import org.apache.fop.util.LanguageTags; /** * Class representing a Root (/Catalog) object. @@ -69,6 +72,7 @@ public class PDFRoot extends PDFDictionary { setObjectNumber(objnum); put("Type", new PDFName("Catalog")); setRootPages(pages); + setLanguage("x-unknown"); } /** {@inheritDoc} */ @@ -254,10 +258,14 @@ public class PDFRoot extends PDFDictionary { * Sets the language identifier of the document. * @param lang the language identifier of the document. */ - public void setLanguage(String lang) { - if (lang == null) { - throw new NullPointerException("lang must not be null"); + public void setLanguage(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale must not be null"); } + setLanguage(LanguageTags.toLanguageTag(locale)); + } + + private void setLanguage(String lang) { put("Lang", lang); } diff --git a/src/java/org/apache/fop/pdf/PDFStructElem.java b/src/java/org/apache/fop/pdf/PDFStructElem.java index 4fb8cbcd5..6dfb0dc95 100644 --- a/src/java/org/apache/fop/pdf/PDFStructElem.java +++ b/src/java/org/apache/fop/pdf/PDFStructElem.java @@ -21,7 +21,7 @@ package org.apache.fop.pdf; import java.util.Locale; -import org.apache.fop.util.XMLUtil; +import org.apache.fop.util.LanguageTags; /** * Class representing a PDF Structure Element. @@ -145,7 +145,7 @@ public class PDFStructElem extends PDFDictionary { * @param language a value for the Lang entry */ public void setLanguage(Locale language) { - setLanguage(XMLUtil.toRFC3066(language)); + setLanguage(LanguageTags.toLanguageTag(language)); } /** diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 7ff236d16..ec14e597c 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Set; import org.w3c.dom.Document; @@ -61,11 +62,11 @@ import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.InlineBlockParent; import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; -import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.area.inline.WordArea; import org.apache.fop.fo.Constants; import org.apache.fop.fonts.FontInfo; @@ -152,6 +153,10 @@ public abstract class AbstractRenderer return false; } + /** {@inheritDoc} */ + public void setDocumentLocale(Locale locale) { + } + /** * {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/Renderer.java b/src/java/org/apache/fop/render/Renderer.java index 76f245251..599b8e260 100644 --- a/src/java/org/apache/fop/render/Renderer.java +++ b/src/java/org/apache/fop/render/Renderer.java @@ -22,6 +22,7 @@ package org.apache.fop.render; // Java import java.io.IOException; import java.io.OutputStream; +import java.util.Locale; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; @@ -115,6 +116,12 @@ public interface Renderer { */ boolean supportsOutOfOrder(); + /** + * + * @param locale Locale of the language + */ + void setDocumentLocale(Locale locale); + /** * Tells the renderer to process an item not explicitly placed on the * document (e.g., PDF bookmarks). Note - not all renderers will process diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java index b156b6c3a..ad4e6ad0e 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java @@ -19,6 +19,13 @@ package org.apache.fop.render.intermediate; +import java.util.Locale; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.accessibility.DummyStructureTreeEventHandler; +import org.apache.fop.accessibility.StructureTreeEventHandler; import org.apache.fop.apps.FOUserAgent; /** @@ -52,6 +59,11 @@ public abstract class AbstractIFDocumentHandler implements IFDocumentHandler { return getContext().getUserAgent(); } + /** {@inheritDoc} */ + public StructureTreeEventHandler getStructureTreeEventHandler() { + return DummyStructureTreeEventHandler.INSTANCE; + } + /** {@inheritDoc} */ public IFDocumentNavigationHandler getDocumentNavigationHandler() { return null; //By default, this is not supported @@ -65,6 +77,10 @@ public abstract class AbstractIFDocumentHandler implements IFDocumentHandler { } } + /** {@inheritDoc} */ + public void setDocumentLocale(Locale locale) { + } + /** {@inheritDoc} */ public void startDocumentHeader() throws IFException { //nop @@ -104,5 +120,4 @@ public abstract class AbstractIFDocumentHandler implements IFDocumentHandler { public void endPageTrailer() throws IFException { //nop } - } diff --git a/src/java/org/apache/fop/render/intermediate/IFConstants.java b/src/java/org/apache/fop/render/intermediate/IFConstants.java index 2b16b343d..dba39bec0 100644 --- a/src/java/org/apache/fop/render/intermediate/IFConstants.java +++ b/src/java/org/apache/fop/render/intermediate/IFConstants.java @@ -39,6 +39,7 @@ public interface IFConstants extends XMLConstants { String EL_HEADER = "header"; /** element name trailer */ String EL_TRAILER = "trailer"; + String EL_LOCALE = "locale"; /** element name page-sequence */ String EL_PAGE_SEQUENCE = "page-sequence"; /** element name page */ diff --git a/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java index af1451fe3..6cb8f2795 100644 --- a/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java +++ b/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java @@ -20,9 +20,11 @@ package org.apache.fop.render.intermediate; import java.awt.Dimension; +import java.util.Locale; import javax.xml.transform.Result; +import org.apache.fop.accessibility.StructureTreeEventHandler; import org.apache.fop.fonts.FontInfo; /** @@ -32,6 +34,7 @@ import org.apache.fop.fonts.FontInfo; *

*

  * startDocument()
+ *   [setDocumentLocale()]
  *   startDocumentHeader()
  *   [handleExtension()]*
  *   endDocumentHeader()
@@ -117,6 +120,11 @@ public interface IFDocumentHandler {
      */
     IFDocumentHandlerConfigurator getConfigurator();
 
+    /**
+     * @return the structure tree builder
+     */
+    StructureTreeEventHandler getStructureTreeEventHandler();
+
     /**
      * Returns a document navigation handler if this feature is supported.
      * @return the document navigation handler or null if not supported
@@ -151,6 +159,11 @@ public interface IFDocumentHandler {
      */
     void endDocument() throws IFException;
 
+    /**
+    * @param locale Locale of the document.
+    */
+    void setDocumentLocale(Locale locale);
+
     /**
      * Indicates the start of the document header. This method is called right after the
      * {@link #startDocument()} method. Extensions sent to this painter between
@@ -261,7 +274,4 @@ public interface IFDocumentHandler {
      * @throws IFException if an error occurs while handling this event
      */
     void handleExtensionObject(Object extension) throws IFException;
-
-    //TODO Prototype the following:
-    //ContentHandler handleExtension() throws Exception
 }
diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java
index 313cee685..8f0bb88ec 100644
--- a/src/java/org/apache/fop/render/intermediate/IFParser.java
+++ b/src/java/org/apache/fop/render/intermediate/IFParser.java
@@ -25,6 +25,7 @@ import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -48,7 +49,7 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.xmlgraphics.util.QName;
 
 import org.apache.fop.accessibility.AccessibilityEventProducer;
-import org.apache.fop.accessibility.StructureTreeBuilder;
+import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.fo.ElementMapping;
 import org.apache.fop.fo.ElementMappingRegistry;
@@ -62,7 +63,7 @@ import org.apache.fop.util.ContentHandlerFactory;
 import org.apache.fop.util.ContentHandlerFactoryRegistry;
 import org.apache.fop.util.DOMBuilderContentHandlerFactory;
 import org.apache.fop.util.DefaultErrorListener;
-import org.apache.fop.util.DelegatingContentHandler;
+import org.apache.fop.util.LanguageTags;
 import org.apache.fop.util.XMLUtil;
 
 /**
@@ -153,24 +154,40 @@ public class IFParser implements IFConstants {
 
         private ContentHandler navParser;
 
-        private StructureTreeBuilder structureTreeBuilder;
-
-        private ContentHandler structureTreeBuilderWrapper;
+        private ContentHandler structureTreeHandler;
 
         private Attributes pageSequenceAttributes;
 
-        private final class StructureTreeBuilderWrapper extends DelegatingContentHandler {
+        private final class StructureTreeHandler extends DefaultHandler {
 
-            private StructureTreeBuilderWrapper()
-                    throws SAXException {
-                super(structureTreeBuilder.getHandlerForNextPageSequence());
+            private final StructureTreeEventHandler structureTreeEventHandler;
+
+            private StructureTreeHandler(StructureTreeEventHandler structureTreeEventHandler,
+                    Locale pageSequenceLanguage) throws SAXException {
+                this.structureTreeEventHandler = structureTreeEventHandler;
+                structureTreeEventHandler.startPageSequence(pageSequenceLanguage);
             }
 
             public void endDocument() throws SAXException {
-                super.endDocument();
                 startIFElement(EL_PAGE_SEQUENCE, pageSequenceAttributes);
                 pageSequenceAttributes = null;
             }
+
+            @Override
+            public void startElement(String uri, String localName, String qName,
+                    Attributes attributes) throws SAXException {
+                if (!"structure-tree".equals(localName)) {
+                    structureTreeEventHandler.startNode(localName, attributes);
+                }
+            }
+
+            @Override
+            public void endElement(String uri, String localName, String arqNameg2)
+                    throws SAXException {
+                if (!"structure-tree".equals(localName)) {
+                    structureTreeEventHandler.endNode(localName);
+                }
+            }
         }
 
         public Handler(IFDocumentHandler documentHandler, FOUserAgent userAgent,
@@ -180,6 +197,7 @@ public class IFParser implements IFConstants {
             this.elementMappingRegistry = elementMappingRegistry;
             elementHandlers.put(EL_DOCUMENT, new DocumentHandler());
             elementHandlers.put(EL_HEADER, new DocumentHeaderHandler());
+            elementHandlers.put(EL_LOCALE, new LocaleHandler());
             elementHandlers.put(EL_TRAILER, new DocumentTrailerHandler());
             elementHandlers.put(EL_PAGE_SEQUENCE, new PageSequenceHandler());
             elementHandlers.put(EL_PAGE, new PageHandler());
@@ -197,11 +215,6 @@ public class IFParser implements IFConstants {
             elementHandlers.put(EL_LINE, new LineHandler());
             elementHandlers.put(EL_BORDER_RECT, new BorderRectHandler());
             elementHandlers.put(EL_IMAGE, new ImageHandler());
-
-            if (userAgent.isAccessibilityEnabled()) {
-                structureTreeBuilder = new StructureTreeBuilder(tFactory);
-                userAgent.setStructureTree(structureTreeBuilder.getStructureTree());
-            }
         }
 
         private void establishForeignAttributes(Map foreignAttributes) {
@@ -231,10 +244,13 @@ public class IFParser implements IFConstants {
                 if (NAMESPACE.equals(uri)) {
                     if (localName.equals(EL_PAGE_SEQUENCE) && userAgent.isAccessibilityEnabled()) {
                         pageSequenceAttributes = new AttributesImpl(attributes);
-                        structureTreeBuilderWrapper = new StructureTreeBuilderWrapper();
+                        Locale language = getLanguage(attributes);
+                        structureTreeHandler = new StructureTreeHandler(
+                                userAgent.getStructureTreeEventHandler(), language);
+
                     } else if (localName.equals(EL_STRUCTURE_TREE)) {
                         if (userAgent.isAccessibilityEnabled()) {
-                            delegate = structureTreeBuilderWrapper;
+                            delegate = structureTreeHandler;
                         } else {
                             /* Delegate to a handler that does nothing */
                             delegate = new DefaultHandler();
@@ -299,6 +315,11 @@ public class IFParser implements IFConstants {
             }
         }
 
+        private static Locale getLanguage(Attributes attributes) {
+            String xmllang = attributes.getValue(XML_NAMESPACE, "lang");
+            return (xmllang == null) ? null : LanguageTags.toLocale(xmllang);
+        }
+
         private boolean startIFElement(String localName, Attributes attributes)
                 throws SAXException {
             lastAttributes = new AttributesImpl(attributes);
@@ -413,6 +434,12 @@ public class IFParser implements IFConstants {
 
         }
 
+        private class LocaleHandler extends AbstractElementHandler {
+            public void startElement(Attributes attributes) throws IFException {
+                documentHandler.setDocumentLocale(getLanguage(attributes));
+            }
+        }
+
         private class DocumentTrailerHandler extends AbstractElementHandler {
 
             public void startElement(Attributes attributes) throws IFException {
@@ -429,10 +456,9 @@ public class IFParser implements IFConstants {
 
             public void startElement(Attributes attributes) throws IFException {
                 String id = attributes.getValue("id");
-                String xmllang = attributes.getValue(XML_NAMESPACE, "lang");
-                if (xmllang != null) {
-                    documentHandler.getContext().setLanguage(
-                            XMLUtil.convertRFC3066ToLocale(xmllang));
+                Locale language = getLanguage(attributes);
+                if (language != null) {
+                    documentHandler.getContext().setLanguage(language);
                 }
                 Map foreignAttributes = getForeignAttributes(lastAttributes);
                 establishForeignAttributes(foreignAttributes);
diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java
index 24a2e8a75..d217646f6 100644
--- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java
+++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java
@@ -51,6 +51,7 @@ import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
 
 import org.apache.fop.Version;
 import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.MimeConstants;
 import org.apache.fop.area.Area;
 import org.apache.fop.area.AreaTreeObject;
@@ -70,10 +71,10 @@ import org.apache.fop.area.inline.ForeignObject;
 import org.apache.fop.area.inline.Image;
 import org.apache.fop.area.inline.InlineArea;
 import org.apache.fop.area.inline.InlineParent;
+import org.apache.fop.area.inline.InlineViewport;
 import org.apache.fop.area.inline.Leader;
 import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
-import org.apache.fop.area.inline.InlineViewport;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.datatypes.URISpecification;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
@@ -227,7 +228,11 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
      */
     protected IFDocumentHandler createDefaultDocumentHandler() {
         IFSerializer serializer = new IFSerializer();
-        serializer.setContext(new IFContext(getUserAgent()));
+        FOUserAgent userAgent = getUserAgent();
+        serializer.setContext(new IFContext(userAgent));
+        if (userAgent.isAccessibilityEnabled()) {
+            userAgent.setStructureTreeEventHandler(serializer.getStructureTreeEventHandler());
+        }
         return serializer;
     }
 
@@ -294,6 +299,11 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         log.debug("Rendering finished.");
     }
 
+    @Override
+    public void setDocumentLocale(Locale locale) {
+        documentHandler.setDocumentLocale(locale);
+    }
+
     /** {@inheritDoc} */
     public void processOffDocumentItem(OffDocumentItem odi) {
         if (odi instanceof DestinationData) {
diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java
index 471a982ca..a4431b972 100644
--- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java
+++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java
@@ -31,16 +31,13 @@ import java.util.Locale;
 import java.util.Map;
 
 import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
 
 import org.apache.xmlgraphics.util.QName;
 import org.apache.xmlgraphics.util.XMLizable;
 
-import org.apache.fop.accessibility.StructureTree;
+import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.render.PrintRendererConfigurator;
 import org.apache.fop.render.RenderingContext;
@@ -54,9 +51,11 @@ import org.apache.fop.traits.BorderProps;
 import org.apache.fop.traits.RuleStyle;
 import org.apache.fop.util.ColorUtil;
 import org.apache.fop.util.DOM2SAX;
+import org.apache.fop.util.LanguageTags;
 import org.apache.fop.util.XMLConstants;
 import org.apache.fop.util.XMLUtil;
 
+
 /**
  * IFPainter implementation that serializes the intermediate format to XML.
  */
@@ -71,11 +70,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
 
     private String currentID = "";
 
-    /**
-     * Default constructor.
-     */
-    public IFSerializer() {
-    }
+    private IFStructureTreeBuilder structureTreeBuilder;
 
     /** {@inheritDoc} */
     @Override
@@ -150,6 +145,14 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
         }
     }
 
+    @Override
+    public StructureTreeEventHandler getStructureTreeEventHandler() {
+        if (structureTreeBuilder == null) {
+            structureTreeBuilder = new IFStructureTreeBuilder();
+        }
+        return structureTreeBuilder;
+    }
+
     /** {@inheritDoc} */
     @Override
     public void startDocument() throws IFException {
@@ -166,6 +169,19 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
         }
     }
 
+    @Override
+    public void setDocumentLocale(Locale locale) {
+        AttributesImpl atts  = new AttributesImpl();
+        atts.addAttribute(XML_NAMESPACE, "lang", "xml:lang", XMLUtil.CDATA,
+                LanguageTags.toLanguageTag(locale));
+        try {
+            handler.startElement(EL_LOCALE, atts);
+            handler.endElement(EL_LOCALE);
+        } catch (SAXException e) {
+            throw new RuntimeException("Unable to create the " + EL_LOCALE + " element.", e);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public void startDocumentHeader() throws IFException {
@@ -227,20 +243,14 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
             Locale lang = getContext().getLanguage();
             if (lang != null) {
                 atts.addAttribute(XML_NAMESPACE, "lang", "xml:lang", XMLUtil.CDATA,
-                        XMLUtil.toRFC3066(lang));
+                        LanguageTags.toLanguageTag(lang));
             }
             XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve");
             addForeignAttributes(atts);
             handler.startElement(EL_PAGE_SEQUENCE, atts);
             if (this.getUserAgent().isAccessibilityEnabled()) {
-                StructureTree structureTree = getUserAgent().getStructureTree();
-                handler.startElement(EL_STRUCTURE_TREE); // add structure tree
-                NodeList nodes = structureTree.getPageSequence(pageSequenceIndex++);
-                for (int i = 0, n = nodes.getLength(); i < n; i++) {
-                    Node node = nodes.item(i);
-                    new DOM2SAX(handler).writeFragment(node);
-                }
-                handler.endElement(EL_STRUCTURE_TREE);
+                assert (structureTreeBuilder != null);
+                structureTreeBuilder.replayEventsForPageSequence(handler, pageSequenceIndex++);
             }
         } catch (SAXException e) {
             throw new IFException("SAX error in startPageSequence()", e);
@@ -250,6 +260,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
     /** {@inheritDoc} */
     public void endPageSequence() throws IFException {
         try {
+
             handler.endElement(EL_PAGE_SEQUENCE);
         } catch (SAXException e) {
             throw new IFException("SAX error in endPageSequence()", e);
@@ -806,5 +817,4 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
             throw new IFException("SAX error serializing object", e);
         }
     }
-
 }
diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializerMaker.java b/src/java/org/apache/fop/render/intermediate/IFSerializerMaker.java
index 699fd0549..c0060ab8e 100644
--- a/src/java/org/apache/fop/render/intermediate/IFSerializerMaker.java
+++ b/src/java/org/apache/fop/render/intermediate/IFSerializerMaker.java
@@ -31,6 +31,9 @@ public class IFSerializerMaker extends AbstractIFDocumentHandlerMaker {
     public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) {
         IFSerializer handler = new IFSerializer();
         handler.setContext(new IFContext(ua));
+        if (ua.isAccessibilityEnabled()) {
+            ua.setStructureTreeEventHandler(handler.getStructureTreeEventHandler());
+        }
         return handler;
     }
 
diff --git a/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java b/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java
new file mode 100644
index 000000000..66457681f
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.intermediate;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.fop.accessibility.StructureTree2SAXEventAdapter;
+import org.apache.fop.accessibility.StructureTreeEventHandler;
+
+/**
+ * Saves structure tree events as SAX events in order to replay them when it's
+ * time to stream the structure tree to the output.
+ */
+final class IFStructureTreeBuilder implements StructureTreeEventHandler {
+
+    private StructureTreeEventHandler delegate;
+
+    private final List pageSequenceEventRecorders = new ArrayList();
+
+    /**
+     * Replay SAX events for a page sequence.
+     * @param handler The handler that receives SAX events
+     * @param pageSequenceIndex The index of the page sequence
+     * @throws SAXException
+     */
+    public void replayEventsForPageSequence(ContentHandler handler,
+            int pageSequenceIndex) throws SAXException {
+        pageSequenceEventRecorders.get(pageSequenceIndex).replay(handler);
+    }
+
+    /** {@inheritDoc} */
+    public void startPageSequence(Locale locale) {
+        SAXEventRecorder eventRecorder = new SAXEventRecorder();
+        pageSequenceEventRecorders.add(eventRecorder);
+        delegate = StructureTree2SAXEventAdapter.newInstance(eventRecorder);
+        delegate.startPageSequence(locale);
+    }
+
+    /** {@inheritDoc} */
+    public void endPageSequence() {
+         delegate.endPageSequence();
+    }
+
+    /** {@inheritDoc} */
+    public void startNode(String name, Attributes attributes) {
+        delegate.startNode(name, attributes);
+    }
+
+    /** {@inheritDoc} */
+    public void endNode(String name) {
+        delegate.endNode(name);
+    }
+
+    /** A SAX handler that records events to replay them later. */
+    static class SAXEventRecorder extends DefaultHandler {
+
+        private final List events = new ArrayList();
+
+        private abstract static class Event {
+            abstract void replay(ContentHandler handler) throws SAXException;
+        }
+
+        private abstract static class Element extends SAXEventRecorder.Event {
+
+            protected final String uri;
+            protected final String localName;
+            protected final String qName;
+
+            private Element(String uri, String localName, String qName) {
+                this.uri = uri;
+                this.localName = localName;
+                this.qName = qName;
+            }
+        }
+
+        private static final class StartElement extends SAXEventRecorder.Element {
+
+            private final Attributes attributes;
+
+            private StartElement(String uri, String localName, String qName,
+                    Attributes attributes) {
+                super(uri, localName, qName);
+                this.attributes = attributes;
+            }
+
+            @Override
+            void replay(ContentHandler handler) throws SAXException {
+                handler.startElement(uri, localName, qName, attributes);
+            }
+        }
+
+        private static final class EndElement extends SAXEventRecorder.Element {
+
+            private EndElement(String uri, String localName, String qName) {
+                super(uri, localName, qName);
+            }
+
+            @Override
+            void replay(ContentHandler handler) throws SAXException {
+                handler.endElement(uri, localName, qName);
+            }
+        }
+
+        private static final class StartPrefixMapping extends SAXEventRecorder.Event {
+
+            protected final String prefix;
+            protected final String uri;
+
+            private StartPrefixMapping(String prefix, String uri) {
+                this.prefix = prefix;
+                this.uri = uri;
+            }
+
+            @Override
+            void replay(ContentHandler handler) throws SAXException {
+                handler.startPrefixMapping(prefix, uri);
+            }
+        }
+
+        private static final class EndPrefixMapping extends SAXEventRecorder.Event {
+
+            protected final String prefix;
+
+            private EndPrefixMapping(String prefix) {
+                this.prefix = prefix;
+            }
+
+            @Override
+            void replay(ContentHandler handler) throws SAXException {
+                handler.endPrefixMapping(prefix);
+            }
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName,
+                Attributes attributes) throws SAXException {
+            events.add(new StartElement(uri, localName, qName, attributes));
+        };
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            events.add(new EndElement(uri, localName, qName));
+        };
+
+        @Override
+        public void startPrefixMapping(String prefix, String uri) throws SAXException {
+            events.add(new StartPrefixMapping(prefix, uri));
+        };
+
+        @Override
+        public void endPrefixMapping(String prefix) throws SAXException {
+            events.add(new EndPrefixMapping(prefix));
+        };
+
+        /**
+         * Replays the recorded events.
+         *
+         * @param handler {@code ContentHandler} to replay events on
+         */
+        public void replay(ContentHandler handler) throws SAXException {
+            for (SAXEventRecorder.Event e : events) {
+                e.replay(handler);
+            }
+        }
+    }
+}
diff --git a/src/java/org/apache/fop/render/intermediate/util/IFDocumentHandlerProxy.java b/src/java/org/apache/fop/render/intermediate/util/IFDocumentHandlerProxy.java
index 3d89e812e..64c876fb1 100644
--- a/src/java/org/apache/fop/render/intermediate/util/IFDocumentHandlerProxy.java
+++ b/src/java/org/apache/fop/render/intermediate/util/IFDocumentHandlerProxy.java
@@ -20,9 +20,12 @@
 package org.apache.fop.render.intermediate.util;
 
 import java.awt.Dimension;
+import java.util.Locale;
 
 import javax.xml.transform.Result;
 
+import org.apache.fop.accessibility.DummyStructureTreeEventHandler;
+import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.render.intermediate.IFContext;
 import org.apache.fop.render.intermediate.IFDocumentHandler;
@@ -93,6 +96,11 @@ public class IFDocumentHandlerProxy implements IFDocumentHandler {
         return this.delegate.getDocumentNavigationHandler();
     }
 
+    /** {@inheritDoc} */
+    public StructureTreeEventHandler getStructureTreeEventHandler() {
+        return DummyStructureTreeEventHandler.INSTANCE;
+    }
+
     /** {@inheritDoc} */
     public void setResult(Result result) throws IFException {
         this.delegate.setResult(result);
@@ -103,6 +111,12 @@ public class IFDocumentHandlerProxy implements IFDocumentHandler {
         this.delegate.startDocument();
     }
 
+    /** {@inheritDoc} */
+    public void setDocumentLocale(Locale locale) {
+         this.delegate.setDocumentLocale(locale);
+
+    }
+
     /** {@inheritDoc} */
     public void startDocumentHeader() throws IFException {
         this.delegate.startDocumentHeader();
@@ -184,4 +198,4 @@ public class IFDocumentHandlerProxy implements IFDocumentHandler {
         this.delegate.handleExtensionObject(extension);
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java
index 26595bca1..c773b1339 100644
--- a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java
+++ b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java
@@ -22,8 +22,6 @@ package org.apache.fop.render.pdf;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.w3c.dom.Node;
-
 import org.apache.fop.events.EventBroadcaster;
 import org.apache.fop.pdf.PDFName;
 import org.apache.fop.pdf.PDFObject;
@@ -37,9 +35,9 @@ final class FOToPDFRoleMap {
     /**
      * Standard structure types defined by the PDF Reference, Fourth Edition (PDF 1.5).
      */
-    private static final Map STANDARD_STRUCTURE_TYPES = new HashMap();
+    private static final Map STANDARD_STRUCTURE_TYPES = new HashMap();
 
-    private static final Map DEFAULT_MAPPINGS = new java.util.HashMap();
+    private static final Map DEFAULT_MAPPINGS = new java.util.HashMap();
 
     private static final PDFName THEAD;
     private static final PDFName NON_STRUCT;
@@ -172,7 +170,7 @@ final class FOToPDFRoleMap {
      * @return the structure type or null if no match could be found
      */
     public static PDFName mapFormattingObject(String fo, PDFObject parent) {
-        Mapper mapper = (Mapper)DEFAULT_MAPPINGS.get(fo);
+        Mapper mapper = (Mapper) DEFAULT_MAPPINGS.get(fo);
         if (mapper != null) {
             return mapper.getStructureType(parent);
         } else {
@@ -180,27 +178,32 @@ final class FOToPDFRoleMap {
         }
     }
 
-    public static PDFName mapFormattingObject(Node fo, PDFObject parent,
-            EventBroadcaster eventBroadcaster) {
+    /**
+     * Maps a Formatting Object to a PDFName representing the associated structure type.
+     * @param fo the formatting object's local name
+     * @param role the value of the formatting object's role property
+     * @param parent the parent of the structure element to be mapped
+     * @param eventBroadcaster the event broadcaster
+     * @return the structure type or null if no match could be found
+     */
+    public static PDFName mapFormattingObject(String fo, String role,
+            PDFObject parent, EventBroadcaster eventBroadcaster) {
         PDFName type = null;
-        Node role = fo.getAttributes().getNamedItemNS(null, "role");
         if (role == null) {
-            type = mapFormattingObject(fo.getLocalName(), parent);
+            type = mapFormattingObject(fo, parent);
         } else {
-            String customType = role.getNodeValue();
-            type = (PDFName) STANDARD_STRUCTURE_TYPES.get(customType);
+            type = (PDFName) STANDARD_STRUCTURE_TYPES.get(role);
             if (type == null) {
-                String foName = fo.getLocalName();
-                type = mapFormattingObject(foName, parent);
+                type = mapFormattingObject(fo, parent);
                 PDFEventProducer.Provider.get(eventBroadcaster).nonStandardStructureType(fo,
-                        foName, customType, type.toString().substring(1));
+                        fo, role, type.toString().substring(1));
             }
         }
         assert type != null;
         return type;
     }
 
-    private static interface Mapper {
+    private interface Mapper {
         PDFName getStructureType(PDFObject parent);
     }
 
@@ -222,7 +225,7 @@ final class FOToPDFRoleMap {
 
         public PDFName getStructureType(PDFObject parent) {
             PDFStructElem grandParent = (PDFStructElem)
-                ((PDFStructElem)parent).getParentStructElem();
+                ((PDFStructElem) parent).getParentStructElem();
             //TODO What to do with cells from table-footer? Currently they are mapped on TD.
             PDFName type;
             if (THEAD.equals(grandParent.getStructureType())) {
diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java
index c1d959dd1..5be8c3001 100644
--- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java
+++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java
@@ -25,15 +25,16 @@ import java.awt.geom.AffineTransform;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 
-import org.w3c.dom.NodeList;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.xmlgraphics.xmp.Metadata;
 
+import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.apps.MimeConstants;
 import org.apache.fop.fo.extensions.xmp.XMPMetadata;
 import org.apache.fop.pdf.PDFAnnotList;
@@ -45,28 +46,26 @@ import org.apache.fop.render.extensions.prepress.PageBoundaries;
 import org.apache.fop.render.extensions.prepress.PageScale;
 import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
 import org.apache.fop.render.intermediate.IFContext;
-import org.apache.fop.render.intermediate.IFDocumentHandler;
 import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
 import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
 import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.render.intermediate.IFPainter;
 import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment;
-import org.apache.fop.util.XMLUtil;
 
 /**
- * {@link IFDocumentHandler} implementation that produces PDF.
+ * {@link org.apache.fop.render.intermediate.IFDocumentHandler} implementation that produces PDF.
  */
 public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
 
     /** logging instance */
     private static Log log = LogFactory.getLog(PDFDocumentHandler.class);
 
-    private int pageSequenceIndex;
-
     private boolean accessEnabled;
 
     private PDFLogicalStructureHandler logicalStructureHandler;
 
+    private PDFStructureTreeBuilder structureTreeBuilder;
+
     /** the PDF Document being created */
     protected PDFDocument pdfDoc;
 
@@ -92,8 +91,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
     protected PageReference currentPageRef;
 
     /** Used for bookmarks/outlines. */
-    protected Map pageReferences
-        = new java.util.HashMap();
+    protected Map pageReferences = new HashMap();
 
     private final PDFDocumentNavigationHandler documentNavigationHandler
             = new PDFDocumentNavigationHandler(this);
@@ -145,15 +143,23 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
             this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream);
             this.accessEnabled = getUserAgent().isAccessibilityEnabled();
             if (accessEnabled) {
-                pdfDoc.getRoot().makeTagged();
-                logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc,
-                        getUserAgent().getEventBroadcaster());
+                setupAccessibility();
             }
         } catch (IOException e) {
             throw new IFException("I/O error in startDocument()", e);
         }
     }
 
+    private void setupAccessibility() {
+        pdfDoc.getRoot().makeTagged();
+        logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc);
+        // TODO this is ugly. All the necessary information should be available
+        // at creation time in order to enforce immutability
+        structureTreeBuilder.setPdfFactory(pdfDoc.getFactory());
+        structureTreeBuilder.setLogicalStructureHandler(logicalStructureHandler);
+        structureTreeBuilder.setEventBroadcaster(getUserAgent().getEventBroadcaster());
+    }
+
     /** {@inheritDoc} */
     public void endDocumentHeader() throws IFException {
         pdfUtil.generateDefaultXMPMetadata();
@@ -178,18 +184,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
 
     /** {@inheritDoc} */
     public void startPageSequence(String id) throws IFException {
-        //TODO page sequence title
-
-        if (this.pdfDoc.getRoot().getLanguage() == null
-                && getContext().getLanguage() != null) {
-            //No document-level language set, so we use the first page-sequence's language
-            this.pdfDoc.getRoot().setLanguage(XMLUtil.toRFC3066(getContext().getLanguage()));
-        }
-
-        if (accessEnabled) {
-            NodeList nodes = getUserAgent().getStructureTree().getPageSequence(pageSequenceIndex++);
-            logicalStructureHandler.processStructureTree(nodes, getContext().getLanguage());
-        }
+        //nop
     }
 
     /** {@inheritDoc} */
@@ -289,9 +284,9 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
     /** {@inheritDoc} */
     public void handleExtensionObject(Object extension) throws IFException {
         if (extension instanceof XMPMetadata) {
-            pdfUtil.renderXMPMetadata((XMPMetadata)extension);
+            pdfUtil.renderXMPMetadata((XMPMetadata) extension);
         } else if (extension instanceof Metadata) {
-            XMPMetadata wrapper = new XMPMetadata(((Metadata)extension));
+            XMPMetadata wrapper = new XMPMetadata(((Metadata) extension));
             pdfUtil.renderXMPMetadata(wrapper);
         } else if (extension instanceof PDFEmbeddedFileExtensionAttachment) {
             PDFEmbeddedFileExtensionAttachment embeddedFile
@@ -307,6 +302,11 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
         }
     }
 
+    /** {@inheritDoc} */
+    public void setDocumentLocale(Locale locale) {
+        pdfDoc.getRoot().setLanguage(locale);
+    }
+
     PageReference getPageReference(int pageIndex) {
         return this.pageReferences.get(Integer.valueOf(pageIndex));
     }
@@ -332,4 +332,11 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
         }
     }
 
+    @Override
+    public StructureTreeEventHandler getStructureTreeEventHandler() {
+        if (structureTreeBuilder == null) {
+            structureTreeBuilder = new PDFStructureTreeBuilder();
+        }
+        return structureTreeBuilder;
+    }
 }
diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java
index f4ba03d5b..1186db3ae 100644
--- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java
+++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java
@@ -36,6 +36,9 @@ public class PDFDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker {
     public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) {
         PDFDocumentHandler handler = new PDFDocumentHandler();
         handler.setContext(new IFContext(ua));
+        if (ua.isAccessibilityEnabled()) {
+            ua.setStructureTreeEventHandler(handler.getStructureTreeEventHandler());
+        }
         return handler;
     }
 
diff --git a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
index ce86f4849..d49ef4a3e 100644
--- a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
+++ b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
@@ -23,12 +23,6 @@ import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import org.apache.fop.events.EventBroadcaster;
-import org.apache.fop.fo.extensions.ExtensionElementMapping;
-import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.pdf.PDFArray;
 import org.apache.fop.pdf.PDFDictionary;
 import org.apache.fop.pdf.PDFDocument;
@@ -53,12 +47,10 @@ class PDFLogicalStructureHandler {
 
     private final PDFDocument pdfDoc;
 
-    private final EventBroadcaster eventBroadcaster;
-
     /**
      * Map of references to the corresponding structure elements.
      */
-    private final Map structTreeMap = new HashMap();
+    private final Map structTreeMap = new HashMap();
 
     private final PDFParentTree parentTree = new PDFParentTree();
 
@@ -108,23 +100,16 @@ class PDFLogicalStructureHandler {
      *
      * @param pdfDoc a document
      */
-    PDFLogicalStructureHandler(PDFDocument pdfDoc, EventBroadcaster eventBroadcaster) {
+    PDFLogicalStructureHandler(PDFDocument pdfDoc) {
         this.pdfDoc = pdfDoc;
-        this.eventBroadcaster = eventBroadcaster;
         PDFStructTreeRoot structTreeRoot = pdfDoc.getFactory().makeStructTreeRoot(parentTree);
         rootStructureElement = pdfDoc.getFactory().makeStructureElement(
                 FOToPDFRoleMap.mapFormattingObject("root", structTreeRoot), structTreeRoot);
         structTreeRoot.addKid(rootStructureElement);
     }
 
-    /**
-     * Converts the given structure tree into PDF.
-     *
-     * @param structureTree the structure tree of the current page sequence
-     * @param language language set on the page sequence
-     */
-    void processStructureTree(NodeList structureTree, Locale language) {
-        pdfDoc.enforceLanguageOnRoot();
+
+    PDFStructElem createPageSequence(Locale language) {
         PDFStructElem structElemPart = pdfDoc.getFactory().makeStructureElement(
                 FOToPDFRoleMap.mapFormattingObject("page-sequence", rootStructureElement),
                 rootStructureElement);
@@ -132,50 +117,7 @@ class PDFLogicalStructureHandler {
         if (language != null) {
             structElemPart.setLanguage(language);
         }
-
-        for (int i = 0, n = structureTree.getLength(); i < n; i++) {
-            Node node = structureTree.item(i);
-            assert node.getLocalName().equals("flow")
-                    || node.getLocalName().equals("static-content");
-            PDFStructElem structElemSect = pdfDoc.getFactory().makeStructureElement(
-                    FOToPDFRoleMap.mapFormattingObject(node.getLocalName(), structElemPart),
-                    structElemPart);
-            structElemPart.addKid(structElemSect);
-            NodeList childNodes = node.getChildNodes();
-            for (int j = 0, m = childNodes.getLength(); j < m; j++) {
-                processNode(childNodes.item(j), structElemSect, true);
-            }
-        }
-    }
-
-    private void processNode(Node node, PDFStructElem parent, boolean addKid) {
-        PDFStructElem structElem = pdfDoc.getFactory().makeStructureElement(
-                FOToPDFRoleMap.mapFormattingObject(node, parent, eventBroadcaster), parent);
-        // TODO necessary? If a page-sequence is empty (e.g., contains a single
-        // empty fo:block), should the block still be added to the structure
-        // tree? This is not being done for descendant empty elements...
-        if (addKid) {
-            parent.addKid(structElem);
-        }
-        String nodeName = node.getLocalName();
-        if (nodeName.equals("external-graphic") || nodeName.equals("instream-foreign-object")) {
-            Node altTextNode = node.getAttributes().getNamedItemNS(
-                    ExtensionElementMapping.URI, "alt-text");
-            if (altTextNode != null) {
-                structElem.put("Alt", altTextNode.getNodeValue());
-            } else {
-                structElem.put("Alt", "No alternate text specified");
-            }
-        }
-        Node attr = node.getAttributes().getNamedItemNS(InternalElementMapping.URI, "ptr");
-        if (attr != null) {
-            String ptr = attr.getNodeValue();
-            structTreeMap.put(ptr, structElem);
-        }
-        NodeList nodes = node.getChildNodes();
-        for (int i = 0, n = nodes.getLength(); i < n; i++) {
-            processNode(nodes.item(i), structElem, false);
-        }
+        return structElemPart;
     }
 
     private int getNextParentTreeKey() {
@@ -301,4 +243,8 @@ class PDFLogicalStructureHandler {
         parent.addKid(contentItem);
     }
 
+    void addStructurePointer(String ptr, PDFStructElem structElem) {
+        structTreeMap.put(ptr, structElem);
+    }
+
 }
diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
new file mode 100644
index 000000000..8ec10b209
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.pdf;
+
+import java.util.LinkedList;
+import java.util.Locale;
+
+import org.xml.sax.Attributes;
+
+import org.apache.fop.accessibility.StructureTreeEventHandler;
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.fo.extensions.ExtensionElementMapping;
+import org.apache.fop.fo.extensions.InternalElementMapping;
+import org.apache.fop.pdf.PDFFactory;
+import org.apache.fop.pdf.PDFStructElem;
+
+class PDFStructureTreeBuilder implements StructureTreeEventHandler {
+
+    private PDFFactory pdfFactory;
+
+    private PDFLogicalStructureHandler logicalStructureHandler;
+
+    private EventBroadcaster eventBroadcaster;
+
+    private LinkedList ancestors = new LinkedList();
+
+    void setPdfFactory(PDFFactory pdfFactory) {
+        this.pdfFactory = pdfFactory;
+    }
+
+    void setLogicalStructureHandler(PDFLogicalStructureHandler logicalStructureHandler) {
+        this.logicalStructureHandler = logicalStructureHandler;
+    }
+
+    void setEventBroadcaster(EventBroadcaster eventBroadcaster) {
+        this.eventBroadcaster = eventBroadcaster;
+    }
+
+    public void startPageSequence(Locale locale) {
+        ancestors = new LinkedList();
+        ancestors.add(logicalStructureHandler.createPageSequence(locale));
+    }
+
+    public void endPageSequence() {
+    }
+
+    public void startNode(String name, Attributes attributes) {
+        PDFStructElem parent = ancestors.getFirst();
+        String role = attributes.getValue("role");
+        PDFStructElem created = pdfFactory.makeStructureElement(
+                FOToPDFRoleMap.mapFormattingObject(name, role, parent,
+                        eventBroadcaster), parent);
+        if (ancestors.size() <= 2) { // TODO remove
+            parent.addKid(created);
+        }
+        String ptr = attributes.getValue(InternalElementMapping.URI, "ptr");
+        if (ptr != null) {
+            logicalStructureHandler.addStructurePointer(ptr, created);
+        }
+
+        if (name.equals("external-graphic") || name.equals("instream-foreign-object")) {
+            String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text");
+            if (altTextNode != null) {
+                created.put("Alt", altTextNode);
+            } else {
+                created.put("Alt", "No alternate text specified");
+            }
+        }
+        ancestors.addFirst(created);
+    }
+
+    public void endNode(String name) {
+        ancestors.removeFirst();
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java
index 21d28042d..2a62d2cea 100644
--- a/src/java/org/apache/fop/render/xml/XMLRenderer.java
+++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java
@@ -35,9 +35,6 @@ import javax.xml.transform.sax.TransformerHandler;
 import javax.xml.transform.stream.StreamResult;
 
 import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
 import org.xml.sax.SAXException;
 
 import org.apache.xmlgraphics.util.QName;
@@ -75,23 +72,20 @@ import org.apache.fop.area.inline.Image;
 import org.apache.fop.area.inline.InlineArea;
 import org.apache.fop.area.inline.InlineBlockParent;
 import org.apache.fop.area.inline.InlineParent;
+import org.apache.fop.area.inline.InlineViewport;
 import org.apache.fop.area.inline.Leader;
 import org.apache.fop.area.inline.Space;
 import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
-import org.apache.fop.area.inline.InlineViewport;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
-import org.apache.fop.fo.extensions.ExtensionElementMapping;
-import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.fonts.FontTriplet;
 import org.apache.fop.render.Renderer;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.render.XMLHandler;
 import org.apache.fop.util.ColorUtil;
-import org.apache.fop.util.DOM2SAX;
 
 /**
  * Renderer that renders areas to XML for debugging purposes.
@@ -461,29 +455,6 @@ public class XMLRenderer extends AbstractXMLRenderer {
         }
         transferForeignObjects(pageSequence);
         startElement("pageSequence", atts);
-        if (this.getUserAgent().isAccessibilityEnabled()) {
-            String structureTreeElement = "structureTree";
-            startElement(structureTreeElement);
-            try {
-                this.handler.startPrefixMapping("foi", InternalElementMapping.URI);
-                this.handler.startPrefixMapping("fox", ExtensionElementMapping.URI);
-                NodeList nodes = getUserAgent().getStructureTree().getPageSequence(
-                        pageSequenceIndex++);
-                for (int i = 0, n = nodes.getLength(); i < n; i++) {
-                    Node node = nodes.item(i);
-                    try {
-                        new DOM2SAX(handler).writeFragment(node);
-                    } catch (SAXException e) {
-                        handleSAXException(e);
-                    }
-                }
-                this.handler.endPrefixMapping("fox");
-                this.handler.endPrefixMapping("foi");
-            } catch (SAXException se) {
-                handleSAXException(se);
-            }
-            endElement(structureTreeElement);
-        }
         handleExtensionAttachments(pageSequence.getExtensionAttachments());
         LineArea seqTitle = pageSequence.getTitle();
         if (seqTitle != null) {
diff --git a/src/java/org/apache/fop/util/LanguageTags.java b/src/java/org/apache/fop/util/LanguageTags.java
new file mode 100644
index 000000000..070fa15db
--- /dev/null
+++ b/src/java/org/apache/fop/util/LanguageTags.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.Locale;
+
+/**
+ * Provides utility methods for manipulating language tags compliant with the
+ * RFC 3066 specification available at http://www.ietf.org/rfc/rfc3066.txt. A
+ * typical language tag is a 2-letter language code sometimes followed by a country
+ * code. For example: en, en-US.
+ */
+public final class LanguageTags {
+
+    private LanguageTags() {
+    }
+
+    /**
+     * Converts the given locale to an RFC 3066 compliant language tag.
+     *
+     * @param locale a locale
+     * @return the corresponding language tag
+     * @throws NullPointerException if the specified locale is null
+     */
+    public static String toLanguageTag(Locale locale) {
+        StringBuffer sb = new StringBuffer(5);
+        sb.append(locale.getLanguage());
+        String country = locale.getCountry();
+        if (country.length() > 0) {
+            sb.append('-');
+            sb.append(country);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Converts an RFC 3066 compliant language tag to a locale.
+     *
+     * @throws NullPointerException if the specified language tag is null
+     */
+    public static Locale toLocale(String languageTag) {
+        String[] parts = languageTag.split("-");
+        if (parts.length == 1) {
+            return new Locale(parts[0]);
+        } else {
+            return new Locale(parts[0], parts[1]);
+        }
+    }
+}
diff --git a/src/java/org/apache/fop/util/XMLUtil.java b/src/java/org/apache/fop/util/XMLUtil.java
index 0a55ce573..d4397c2c7 100644
--- a/src/java/org/apache/fop/util/XMLUtil.java
+++ b/src/java/org/apache/fop/util/XMLUtil.java
@@ -21,7 +21,6 @@ package org.apache.fop.util;
 
 import java.awt.Rectangle;
 import java.awt.geom.Rectangle2D;
-import java.util.Locale;
 
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
@@ -174,39 +173,4 @@ public final class XMLUtil implements XMLConstants {
         atts.addAttribute("", localName, localName, XMLUtil.CDATA, value);
     }
 
-    /**
-     * Converts a {@link Locale} instance to an RFC 3066 compliant language identifier.
-     * @param language the language
-     * @return the formatted language identifier
-     */
-    public static String toRFC3066(Locale language) {
-        if (language == null || language.getLanguage().length() == 0) {
-            return null;
-        }
-        StringBuffer sb = new StringBuffer();
-        sb.append(language.getLanguage());
-        if (language.getCountry().length() > 0) {
-            sb.append('-');
-            sb.append(language.getCountry());
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Converts an RFC 3066 compliant language identifier to a {@link Locale} instance.
-     * @param lang the language string
-     * @return the converted locale instance
-     */
-    public static Locale convertRFC3066ToLocale(String lang) {
-        if (lang == null || lang.length() == 0) {
-            return null;
-        }
-        String[] parts = lang.split("-");
-        if (parts.length == 1) {
-            return new Locale(parts[0]);
-        } else {
-            return new Locale(parts[0], parts[1]);
-        }
-    }
-
 }
diff --git a/test/accessibility/config-painter.xconf b/test/accessibility/config-painter.xconf
deleted file mode 100644
index 8c5dc2bd5..000000000
--- a/test/accessibility/config-painter.xconf
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-  true
-  144
-  false
-  ../resources/fonts/
-  
-    
-      
-        null
-      
-      
-        flate
-        ascii-85
-      
-      
-        
-          
-        
-      
-    
-  
-
diff --git a/test/accessibility/config-renderer.xconf b/test/accessibility/config-renderer.xconf
deleted file mode 100644
index 4b55e393f..000000000
--- a/test/accessibility/config-renderer.xconf
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-  true
-  true
-  144
-  false
-  ../resources/fonts/
-  
-    
-      
-        null
-      
-      
-        flate
-        ascii-85
-      
-      
-        
-          
-        
-      
-    
-  
-
diff --git a/test/accessibility/fop.xconf b/test/accessibility/fop.xconf
new file mode 100644
index 000000000..8c5dc2bd5
--- /dev/null
+++ b/test/accessibility/fop.xconf
@@ -0,0 +1,23 @@
+
+
+  true
+  144
+  false
+  ../resources/fonts/
+  
+    
+      
+        null
+      
+      
+        flate
+        ascii-85
+      
+      
+        
+          
+        
+      
+    
+  
+
diff --git a/test/accessibility/pdf/background-image_jpg_repeat.pdf b/test/accessibility/pdf/background-image_jpg_repeat.pdf
index c7c27f9d1..97b304c53 100644
Binary files a/test/accessibility/pdf/background-image_jpg_repeat.pdf and b/test/accessibility/pdf/background-image_jpg_repeat.pdf differ
diff --git a/test/accessibility/pdf/background-image_jpg_single.pdf b/test/accessibility/pdf/background-image_jpg_single.pdf
index 080f0b876..e1d7f9340 100644
Binary files a/test/accessibility/pdf/background-image_jpg_single.pdf and b/test/accessibility/pdf/background-image_jpg_single.pdf differ
diff --git a/test/accessibility/pdf/background-image_png_repeat.pdf b/test/accessibility/pdf/background-image_png_repeat.pdf
index a10e1357e..9f5e7ff12 100644
Binary files a/test/accessibility/pdf/background-image_png_repeat.pdf and b/test/accessibility/pdf/background-image_png_repeat.pdf differ
diff --git a/test/accessibility/pdf/background-image_png_single.pdf b/test/accessibility/pdf/background-image_png_single.pdf
index 1e9ff00b4..81156e18d 100644
Binary files a/test/accessibility/pdf/background-image_png_single.pdf and b/test/accessibility/pdf/background-image_png_single.pdf differ
diff --git a/test/accessibility/pdf/background-image_svg_repeat.pdf b/test/accessibility/pdf/background-image_svg_repeat.pdf
index c641fbbe5..0e438958d 100644
Binary files a/test/accessibility/pdf/background-image_svg_repeat.pdf and b/test/accessibility/pdf/background-image_svg_repeat.pdf differ
diff --git a/test/accessibility/pdf/background-image_svg_single.pdf b/test/accessibility/pdf/background-image_svg_single.pdf
index 09a9ad895..a46b6df38 100644
Binary files a/test/accessibility/pdf/background-image_svg_single.pdf and b/test/accessibility/pdf/background-image_svg_single.pdf differ
diff --git a/test/accessibility/pdf/complete.pdf b/test/accessibility/pdf/complete.pdf
index caf7a41f8..a42bb2a97 100644
Binary files a/test/accessibility/pdf/complete.pdf and b/test/accessibility/pdf/complete.pdf differ
diff --git a/test/accessibility/pdf/image_jpg.pdf b/test/accessibility/pdf/image_jpg.pdf
index 1336f99eb..6b70c268d 100644
Binary files a/test/accessibility/pdf/image_jpg.pdf and b/test/accessibility/pdf/image_jpg.pdf differ
diff --git a/test/accessibility/pdf/image_png.pdf b/test/accessibility/pdf/image_png.pdf
index b22c157b0..d63db6708 100644
Binary files a/test/accessibility/pdf/image_png.pdf and b/test/accessibility/pdf/image_png.pdf differ
diff --git a/test/accessibility/pdf/image_svg.pdf b/test/accessibility/pdf/image_svg.pdf
index 1f5393c43..94720f564 100644
Binary files a/test/accessibility/pdf/image_svg.pdf and b/test/accessibility/pdf/image_svg.pdf differ
diff --git a/test/accessibility/pdf/image_wmf.pdf b/test/accessibility/pdf/image_wmf.pdf
index 599b4c5ff..4b6eb0827 100644
Binary files a/test/accessibility/pdf/image_wmf.pdf and b/test/accessibility/pdf/image_wmf.pdf differ
diff --git a/test/accessibility/pdf/leader.pdf b/test/accessibility/pdf/leader.pdf
index cf77a5c96..4a6c25253 100644
Binary files a/test/accessibility/pdf/leader.pdf and b/test/accessibility/pdf/leader.pdf differ
diff --git a/test/accessibility/pdf/links.pdf b/test/accessibility/pdf/links.pdf
index ee37c72d6..b8d4e3be3 100644
Binary files a/test/accessibility/pdf/links.pdf and b/test/accessibility/pdf/links.pdf differ
diff --git a/test/accessibility/pdf/role.pdf b/test/accessibility/pdf/role.pdf
index 1eaaf1a9d..ec5d5f536 100644
Binary files a/test/accessibility/pdf/role.pdf and b/test/accessibility/pdf/role.pdf differ
diff --git a/test/accessibility/pdf/role_non-standard.pdf b/test/accessibility/pdf/role_non-standard.pdf
index f8b1c9d2c..d90693066 100644
Binary files a/test/accessibility/pdf/role_non-standard.pdf and b/test/accessibility/pdf/role_non-standard.pdf differ
diff --git a/test/accessibility/pdf/text_1.pdf b/test/accessibility/pdf/text_1.pdf
index 2ad8a12f2..0b1e1ee9f 100644
Binary files a/test/accessibility/pdf/text_1.pdf and b/test/accessibility/pdf/text_1.pdf differ
diff --git a/test/accessibility/pdf/text_2.pdf b/test/accessibility/pdf/text_2.pdf
index 21ad6d0d8..3330cc902 100644
Binary files a/test/accessibility/pdf/text_2.pdf and b/test/accessibility/pdf/text_2.pdf differ
diff --git a/test/accessibility/pdf/text_font-embedding.pdf b/test/accessibility/pdf/text_font-embedding.pdf
index d5c61c040..a268dbb63 100644
Binary files a/test/accessibility/pdf/text_font-embedding.pdf and b/test/accessibility/pdf/text_font-embedding.pdf differ
diff --git a/test/java/org/apache/fop/StandardTestSuite.java b/test/java/org/apache/fop/StandardTestSuite.java
index 1dacd9614..840b04917 100644
--- a/test/java/org/apache/fop/StandardTestSuite.java
+++ b/test/java/org/apache/fop/StandardTestSuite.java
@@ -68,7 +68,8 @@ import org.apache.fop.traits.MinOptMaxTest;
     CharactersetEncoderTest.class,
     org.apache.fop.render.afp.AFPTestSuite.class,
     PSTestSuite.class,
-    MinOptMaxTest.class
+    MinOptMaxTest.class,
+    org.apache.fop.render.intermediate.IFStructureTreeBuilderTestCase.class
 })
 public class StandardTestSuite {
 }
diff --git a/test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java b/test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java
new file mode 100644
index 000000000..f8719edd6
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamSource;
+
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.Difference;
+import org.custommonkey.xmlunit.DifferenceConstants;
+import org.custommonkey.xmlunit.DifferenceListener;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fo.FODocumentParser;
+import org.apache.fop.fo.FODocumentParser.FOEventHandlerFactory;
+import org.apache.fop.fo.FOEventHandler;
+import org.apache.fop.fo.LoadingException;
+import org.apache.fop.fotreetest.DummyFOEventHandler;
+
+public class FO2StructureTreeConverterTestCase {
+
+    private static class IgnoringPtrDifferenceListener implements DifferenceListener {
+
+        public int differenceFound(Difference difference) {
+            switch (difference.getId()) {
+            case DifferenceConstants.ELEMENT_NUM_ATTRIBUTES_ID:
+                return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
+            case DifferenceConstants.ATTR_NAME_NOT_FOUND_ID:
+                String additionalAttribute = difference.getTestNodeDetail().getValue();
+                if (additionalAttribute.equals("ptr")) {
+                    return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
+                } else {
+                    return RETURN_ACCEPT_DIFFERENCE;
+                }
+            default:
+                return RETURN_ACCEPT_DIFFERENCE;
+            }
+        }
+
+        public void skippedComparison(Node control, Node test) {
+            throw new UnsupportedOperationException("Not implemented");
+        }
+    }
+
+    private static final String STRUCTURE_TREE_SEQUENCE_NAME = "structure-tree-sequence";
+
+    @Test
+    public void testConverter() throws Exception {
+        DOMResult expectedStructureTree = loadExpectedStructureTree();
+        DOMResult actualStructureTree = buildActualStructureTree();
+        final Diff diff = createDiff(expectedStructureTree, actualStructureTree);
+        assertTrue(diff.toString(), diff.similar());
+    }
+
+    private static DOMResult loadExpectedStructureTree() {
+        DOMResult expectedStructureTree = new DOMResult();
+        runXSLT(getXsltInputStream(), getFoInputStream(), expectedStructureTree);
+        return expectedStructureTree;
+    }
+
+    private static InputStream getXsltInputStream() {
+        return FO2StructureTreeConverterTestCase.class.getResourceAsStream("foToIfStructureTree.xsl");
+    }
+
+    private static InputStream getFoInputStream() {
+        return FO2StructureTreeConverterTestCase.class.getResourceAsStream(
+                "/org/apache/fop/fo/complete_document.fo");
+    }
+
+    private static void runXSLT(InputStream xslt, InputStream doc, Result result) {
+        Source fo = new StreamSource(doc);
+        try {
+            Transformer transformer = TransformerFactory.newInstance()
+                    .newTransformer(new StreamSource(xslt));
+            transformer.transform(fo, result);
+        } catch (TransformerConfigurationException e) {
+            throw new RuntimeException(e);
+        } catch (TransformerException e) {
+            throw new RuntimeException(e);
+        } finally {
+            closeStream(xslt);
+            closeStream(doc);
+        }
+    }
+
+    private static void closeStream(InputStream stream) {
+        try {
+            stream.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static DOMResult buildActualStructureTree() throws Exception {
+        DOMResult actualStructureTree = new DOMResult();
+        createStructureTreeFromDocument(getFoInputStream(), actualStructureTree);
+        return actualStructureTree;
+    }
+
+    private static void createStructureTreeFromDocument(InputStream foInputStream,
+            DOMResult domResult) throws Exception {
+        TransformerHandler tHandler = createTransformerHandler(domResult);
+        startStructureTreeSequence(tHandler);
+        StructureTreeEventHandler structureTreeEventHandler
+                = StructureTree2SAXEventAdapter.newInstance(tHandler);
+        FODocumentParser documentParser = createDocumentParser(structureTreeEventHandler);
+        FOUserAgent userAgent = createFOUserAgent(documentParser);
+        parseDocument(foInputStream, documentParser, userAgent);
+        endStructureTreeSequence(tHandler);
+    }
+
+    private static TransformerHandler createTransformerHandler(DOMResult domResult)
+            throws TransformerConfigurationException, TransformerFactoryConfigurationError {
+        SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+        TransformerHandler transformerHandler = factory.newTransformerHandler();
+        transformerHandler.setResult(domResult);
+        return transformerHandler;
+    }
+
+    private static void startStructureTreeSequence(TransformerHandler tHandler) throws SAXException {
+        tHandler.startDocument();
+        tHandler.startElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME,
+                new AttributesImpl());
+    }
+
+    private static FODocumentParser createDocumentParser(
+            final StructureTreeEventHandler structureTreeEventHandler) {
+        return FODocumentParser.newInstance(new FOEventHandlerFactory() {
+            public FOEventHandler newFOEventHandler(FOUserAgent foUserAgent) {
+                return new FO2StructureTreeConverter(structureTreeEventHandler,
+                        new DummyFOEventHandler(foUserAgent));
+            }
+        });
+    }
+
+    private static FOUserAgent createFOUserAgent(FODocumentParser documentParser) {
+        FOUserAgent userAgent = documentParser.createFOUserAgent();
+        userAgent.setAccessibility(true);
+        return userAgent;
+    }
+
+    private static void parseDocument(InputStream foInputStream, FODocumentParser documentParser,
+            FOUserAgent userAgent) throws FOPException, LoadingException {
+        try {
+            documentParser.parse(foInputStream, userAgent);
+        } finally {
+            closeStream(foInputStream);
+        }
+    }
+
+    private static void endStructureTreeSequence(TransformerHandler tHandler) throws SAXException {
+        tHandler.endElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME);
+        tHandler.endDocument();
+    }
+
+    private static Diff createDiff(DOMResult expected, DOMResult actual) {
+        Diff diff = new Diff(getDocument(expected), getDocument(actual));
+        diff.overrideDifferenceListener(new IgnoringPtrDifferenceListener());
+        return diff;
+    }
+
+    private static Document getDocument(DOMResult result) {
+        return (Document) result.getNode();
+    }
+}
diff --git a/test/java/org/apache/fop/accessibility/fo2StructureTree.xsl b/test/java/org/apache/fop/accessibility/fo2StructureTree.xsl
new file mode 100644
index 000000000..5c9c5615c
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/fo2StructureTree.xsl
@@ -0,0 +1,116 @@
+
+
+
+
+
+  
+
+  
+    
+      
+    
+  
+
+
+  
+  
+    
+      
+    
+  
+
+  
+  
+    
+        
+    
+  
+
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+    
+  
+
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+
+  
+  
+
+
+  
+  
+    
+  
+
+  
+
+
+  
+  
+
+
diff --git a/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo
deleted file mode 100644
index 74afd6c16..000000000
--- a/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.fo
+++ /dev/null
@@ -1,175 +0,0 @@
-
-
-
-
-
-  
-    
-      
-      
-    
-  
-
-  
-    
-      This is the page headerPage 
-    
-    
-      
-    
-    
-      This is a link to the next page-sequence 
-        (which starts on page  and ends on 
-        page ).
-      A Title Block
-      This block of text contains a footnote1A footnote with a link to the  FOP 
-                  website 
-        call.
-      
-        
-        
-        
-          
-            
-              Header 1.1
-            
-            
-              Header 1.2
-            
-          
-        
-        
-          
-            
-              Footer 1.1
-            
-            
-              Footer 1.2
-            
-          
-        
-        
-          
-            
-              Cell 1.1
-            
-            
-              Cell 1.2
-            
-          
-          
-            
-              Cell 2.1
-            
-            
-              Cell 2.2
-            
-          
-        
-      
-      
-        
-          This is an absolutely positioned 
-            block-container. Nullam interdum mattis ipsum sit amet molestie.
-        
-        Lorem ipsum dolor sit amet, consectetur adipiscing 
-          elit. Integer vel lacinia diam. Etiam venenatis magna vel libero imperdiet 
-          rhoncus.
-      
-    
-  
-
-  
-    
-      This is the page headerPage 
-        
-    
-    
-      Starting a new page-sequence.
-      The  
-        logo.
-      
-        
-          
-            1.
-          
-          
-            First item of a list
-          
-        
-        
-          
-            2.
-          
-          
-            Second item of a list
-          
-        
-        
-          
-            3.
-          
-          
-            Third item of a list
-          
-        
-      
-      
-          
-            
-              
-              
-              
-            
-          
-      
-      A block containing an inline 
-        element.
-      A block containing a fancy  
-        character.
-      A leader with special 
-        content: .
-      Ending the page-sequence.
-    
-  
-
-
diff --git a/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java
index faa88e38e..313379e02 100644
--- a/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java
+++ b/test/java/org/apache/fop/fo/DelegatingFOEventHandlerTestCase.java
@@ -71,6 +71,7 @@ import org.apache.fop.fo.flow.table.TableHeader;
 import org.apache.fop.fo.flow.table.TableRow;
 import org.apache.fop.fo.pagination.Flow;
 import org.apache.fop.fo.pagination.PageSequence;
+import org.apache.fop.fo.pagination.Root;
 import org.apache.fop.fo.pagination.StaticContent;
 
 /**
@@ -97,12 +98,22 @@ public class DelegatingFOEventHandlerTestCase {
 
         @Override
         public void startDocument() throws SAXException {
-            actualEvents.add("start root");
+            actualEvents.add("start document");
         }
 
         @Override
         public void endDocument() throws SAXException {
-            actualEvents.add("end   root");
+            actualEvents.add("end   document");
+        }
+
+        @Override
+        public void startRoot(Root root) {
+            startElement(root);
+        }
+
+        @Override
+        public void endRoot(Root root) {
+            endElement(root);
         }
 
         @Override
@@ -417,9 +428,7 @@ public class DelegatingFOEventHandlerTestCase {
     }
 
     private void loadDocument() {
-        Class clazz = getClass();
-        String documentName = clazz.getSimpleName() + ".fo";
-        document = clazz.getResourceAsStream(documentName);
+        document = getClass().getResourceAsStream("complete_document.fo");
     }
 
     private void loadExpectedEvents() throws IOException {
diff --git a/test/java/org/apache/fop/fo/FODocumentParser.java b/test/java/org/apache/fop/fo/FODocumentParser.java
index 4706866a4..a7574e49d 100644
--- a/test/java/org/apache/fop/fo/FODocumentParser.java
+++ b/test/java/org/apache/fop/fo/FODocumentParser.java
@@ -108,13 +108,29 @@ public final class FODocumentParser {
      * @throws LoadingException if an error occurs when parsing the document
      */
     public void parse(InputStream document) throws FOPException, LoadingException {
-        FOUserAgent foUserAgent = createFOUserAgent();
+        parse(document, createFOUserAgent());
+    }
+
+    /**
+     * Runs FOP on the given document with the supplied {@link FOUserAgent}.
+     *
+     * @param document XSL-FO document to parse
+     * @param foUserAgent The user agent
+     * @throws FOPException if an error occurs when initializing FOP
+     * @throws LoadingException if an error occurs when parsing the document
+     */
+    public void parse(InputStream document, FOUserAgent foUserAgent)
+            throws FOPException, LoadingException {
         fop = FOP_FACTORY.newFop(foUserAgent);
         createTransformer();
         runTransformer(document);
     }
 
-    private FOUserAgent createFOUserAgent() {
+    /**
+     * Creates a new {@link FOUserAgent}.
+     * @return It
+     */
+    public FOUserAgent createFOUserAgent() {
         FOUserAgent userAgent = FOP_FACTORY.newFOUserAgent();
         FOEventHandler foEventHandler = foEventHandlerFactory.newFOEventHandler(userAgent);
         userAgent.setFOEventHandlerOverride(foEventHandler);
diff --git a/test/java/org/apache/fop/fo/complete_document.fo b/test/java/org/apache/fop/fo/complete_document.fo
new file mode 100644
index 000000000..5a34e9e9a
--- /dev/null
+++ b/test/java/org/apache/fop/fo/complete_document.fo
@@ -0,0 +1,176 @@
+
+
+
+
+
+  
+    
+      
+      
+    
+  
+
+  
+    
+      This is the page headerPage 
+    
+    
+      
+    
+    
+      This is a link to the next page-sequence 
+        (which starts on page  and ends on 
+        page ).
+      A Title Block
+      This block of text contains a footnote1A footnote with a link to the  FOP 
+                  website 
+        call.
+      
+        
+        
+        
+          
+            
+              Header 1.1
+            
+            
+              Header 1.2
+            
+          
+        
+        
+          
+            
+              Footer 1.1
+            
+            
+              Footer 1.2
+            
+          
+        
+        
+          
+            
+              Cell 1.1
+            
+            
+              Cell 1.2
+            
+          
+          
+            
+              Cell 2.1
+            
+            
+              Cell 2.2
+            
+          
+        
+      
+      
+        
+          This is an absolutely positioned 
+            block-container. Nullam interdum mattis ipsum sit amet molestie.
+        
+        Lorem ipsum dolor sit amet, consectetur adipiscing 
+          elit. Integer vel lacinia diam. Etiam venenatis magna vel libero imperdiet 
+          rhoncus.
+      
+    
+  
+
+  
+    
+      This is the page headerPage 
+        
+    
+    
+      Starting a new page-sequence.
+      The  
+        logo.
+      
+        
+          
+            1.
+          
+          
+            First item of a list
+          
+        
+        
+          
+            2.
+          
+          
+            Second item of a list
+          
+        
+        
+          
+            3.
+          
+          
+            Third item of a list
+          
+        
+      
+      
+          
+            
+              
+              
+              
+            
+          
+      
+      A block containing an inline 
+        element.
+      A block containing a fancy  
+        character.
+      A leader with special 
+        content: .
+      Ending the page-sequence.
+    
+  
+
+
diff --git a/test/java/org/apache/fop/fo/extract-events.xsl b/test/java/org/apache/fop/fo/extract-events.xsl
index 8edc0ee49..6cf42c984 100644
--- a/test/java/org/apache/fop/fo/extract-events.xsl
+++ b/test/java/org/apache/fop/fo/extract-events.xsl
@@ -6,16 +6,22 @@
 
   
 
-  
+  
     
-      start root
+      start document
     
-    
+    
     
-      end   root
+      end   document
     
   
 
+  
+    start root
+    
+    end   root
+  
+
   
     
       
diff --git a/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java b/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java
index 45f6a61ab..352a39713 100644
--- a/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java
+++ b/test/java/org/apache/fop/fo/properties/CommonAccessibilityHolderTestCase.java
@@ -34,6 +34,7 @@ import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FONodeMocks;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.expr.PropertyException;
+import org.apache.fop.fo.flow.table.UnimplementedWarningNeutralizer;
 
 /**
  * This tests that all the FONodes that implement CommonAccessibilityHolder correctly configure
@@ -51,7 +52,7 @@ public class CommonAccessibilityHolderTestCase {
     static {
         /* This triggers 'unimplemented feature' FO validation events so that the event system is
          * not triggered when testing, avoiding extra convoluted dependency stubbing. */
-//        UnimplementedWarningNeutralizer.neutralizeUnimplementedWarning();
+        UnimplementedWarningNeutralizer.neutralizeUnimplementedWarning();
 
         IMPLEMENTATIONS.add(org.apache.fop.fo.flow.BasicLink.class);
         IMPLEMENTATIONS.add(org.apache.fop.fo.flow.Block.class);
diff --git a/test/java/org/apache/fop/intermediate/IFParserTestCase.java b/test/java/org/apache/fop/intermediate/IFParserTestCase.java
index 52d322e3e..6ac20c556 100644
--- a/test/java/org/apache/fop/intermediate/IFParserTestCase.java
+++ b/test/java/org/apache/fop/intermediate/IFParserTestCase.java
@@ -48,6 +48,9 @@ import org.apache.fop.render.intermediate.IFSerializer;
 @RunWith(Parameterized.class)
 public class IFParserTestCase extends AbstractIFTestCase {
 
+    /** Set this to true to get the correspondence between test number and test file. */
+    private static final boolean DEBUG = false;
+
     /**
      * Gets the parameters for this test
      *
@@ -56,7 +59,19 @@ public class IFParserTestCase extends AbstractIFTestCase {
      */
     @Parameters
     public static Collection getParameters() throws IOException {
-        return LayoutEngineTestUtils.getLayoutTestFiles();
+        Collection testFiles = LayoutEngineTestUtils.getLayoutTestFiles();
+        if (DEBUG) {
+            printFiles(testFiles);
+        }
+        return testFiles;
+    }
+
+    private static void printFiles(Collection files) {
+        int index = 0;
+        for (File[] file : files) {
+            assert file.length == 1;
+            System.out.println(String.format("%3d %s", index++, file[0]));
+        }
     }
 
     /**
diff --git a/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java b/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java
new file mode 100644
index 000000000..d7db7db7f
--- /dev/null
+++ b/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.intermediate;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InOrder;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.fo.FOElementMapping;
+import org.apache.fop.fo.extensions.ExtensionElementMapping;
+import org.apache.fop.fo.extensions.InternalElementMapping;
+import org.apache.fop.util.XMLUtil;
+
+public class IFStructureTreeBuilderTestCase {
+
+    private IFStructureTreeBuilder sut;
+
+    @Before
+    public void setUp() {
+        sut = new IFStructureTreeBuilder();
+    }
+
+    @Test
+    public void startAndEndPageSequence() throws SAXException {
+        final ContentHandler handler = mock(ContentHandler.class);
+
+        try {
+            sut.replayEventsForPageSequence(handler, 0);
+            fail("No page sequences created");
+        } catch (IndexOutOfBoundsException e) {
+            // Expected
+        }
+
+        sut.startPageSequence(null);
+        sut.endPageSequence();
+
+        sut.replayEventsForPageSequence(handler, 0);
+
+        InOrder inOrder = inOrder(handler);
+
+        inOrder.verify(handler).startPrefixMapping(
+                InternalElementMapping.STANDARD_PREFIX, InternalElementMapping.URI);
+        inOrder.verify(handler).startPrefixMapping(
+                ExtensionElementMapping.STANDARD_PREFIX, ExtensionElementMapping.URI);
+        inOrder.verify(handler).startElement(eq(IFConstants.NAMESPACE),
+                eq(IFConstants.EL_STRUCTURE_TREE),
+                eq(IFConstants.EL_STRUCTURE_TREE),
+                any(Attributes.class));
+        inOrder.verify(handler).endElement(eq(IFConstants.NAMESPACE),
+                eq(IFConstants.EL_STRUCTURE_TREE),
+                eq(IFConstants.EL_STRUCTURE_TREE));
+        inOrder.verify(handler).endPrefixMapping(ExtensionElementMapping.STANDARD_PREFIX);
+        inOrder.verify(handler).endPrefixMapping(InternalElementMapping.STANDARD_PREFIX);
+    }
+
+    @Test
+    public void startNode() throws Exception {
+        final String[] attributes = {"ptr", "1"};
+        final String nodeName = "block";
+        final ContentHandler handler = mock(ContentHandler.class);
+
+        sut.startPageSequence(null);
+        sut.startNode(nodeName, createSimpleAttributes(attributes));
+        sut.endPageSequence();
+
+        sut.replayEventsForPageSequence(handler, 0);
+
+        verify(handler).startElement(eq(FOElementMapping.URI), eq(nodeName),
+                eq(FOElementMapping.STANDARD_PREFIX + ":" + nodeName),
+                AttributesMatcher.match(createSimpleAttributes(attributes)));
+    }
+
+    @Test
+    public void endNode() throws Exception {
+        final String nodeName = "block";
+        final ContentHandler handler = mock(ContentHandler.class);
+
+        sut.startPageSequence(null);
+        sut.endNode(nodeName);
+        sut.endPageSequence();
+
+        sut.replayEventsForPageSequence(handler, 0);
+
+        verify(handler).endElement(eq(FOElementMapping.URI), eq(nodeName),
+                eq(FOElementMapping.STANDARD_PREFIX + ":" + nodeName));
+    }
+
+    private static Attributes createSimpleAttributes(String... attributes) {
+        assert (attributes.length % 2 == 0);
+        final AttributesImpl atts = new AttributesImpl();
+        for (int i = 0; i < attributes.length; i += 2) {
+            String key = attributes[i];
+            String value = attributes[i + 1];
+            atts.addAttribute("", key, key, XMLUtil.CDATA, value);
+        }
+        return atts;
+    }
+
+    private static final class AttributesMatcher extends ArgumentMatcher {
+
+        private final Attributes expected;
+
+        private AttributesMatcher(Attributes expected) {
+            this.expected = expected;
+        }
+
+        public static Attributes match(Attributes expected) {
+            return argThat(new AttributesMatcher(expected));
+        }
+
+        public boolean matches(Object attributes) {
+            return attributesEqual(expected, (Attributes) attributes);
+        }
+
+        private static boolean attributesEqual(Attributes attributes1, Attributes attributes2) {
+            if (attributes1.getLength() != attributes2.getLength()) {
+                return false;
+            }
+            for (int i = 0; i < attributes1.getLength(); i++) {
+                if (attributes1.getLocalName(i) != attributes2.getLocalName(i)) {
+                    return false;
+                }
+                if (attributes1.getQName(i) != attributes2.getQName(i)) {
+                    return false;
+                }
+                if (attributes1.getType(i) != attributes2.getType(i)) {
+                    return false;
+                }
+                if (attributes1.getURI(i) != attributes2.getURI(i)) {
+                    return false;
+                }
+                if (attributes1.getValue(i) != attributes2.getValue(i)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/test/java/org/apache/fop/render/intermediate/SAXEventRecorderTestCase.java b/test/java/org/apache/fop/render/intermediate/SAXEventRecorderTestCase.java
new file mode 100644
index 000000000..c5aad66d0
--- /dev/null
+++ b/test/java/org/apache/fop/render/intermediate/SAXEventRecorderTestCase.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.intermediate;
+
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.render.intermediate.IFStructureTreeBuilder.SAXEventRecorder;
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Tests {@link SAXEventRecorder}.
+ */
+public class SAXEventRecorderTestCase {
+
+    private static final String URI = "http://www.example.com/";
+
+    private SAXEventRecorder sut;
+
+    @Before
+    public void setUp() {
+        sut = new SAXEventRecorder();
+    }
+
+    @Test
+    public void testStartEvent() throws SAXException {
+        final String localName = "element";
+        final String qName = "prefix:" + localName;
+        final Attributes attributes = new AttributesImpl();
+
+        sut.startElement(URI, localName, qName, attributes);
+        ContentHandler handler = mock(ContentHandler.class);
+        sut.replay(handler);
+        verify(handler).startElement(URI, localName, qName, attributes);
+    }
+
+    @Test
+    public void testEndEvent() throws SAXException {
+        final String localName = "element";
+        final String qName = "prefix:" + localName;
+        sut.endElement(URI, localName, qName);
+        ContentHandler handler = mock(ContentHandler.class);
+        sut.replay(handler);
+        verify(handler).endElement(URI, localName, qName);
+    }
+
+    @Test
+    public void testStartPrefixMapping() throws SAXException {
+        final String prefix = "prefix";
+
+        sut.startPrefixMapping(URI, prefix);
+        ContentHandler handler = mock(ContentHandler.class);
+        sut.replay(handler);
+        verify(handler).startPrefixMapping(URI, prefix);
+    }
+
+    @Test
+    public void testEndPrefixMapping() throws SAXException {
+        final String prefix = "prefix";
+
+        sut.endPrefixMapping(prefix);
+        ContentHandler handler = mock(ContentHandler.class);
+        sut.replay(handler);
+        verify(handler).endPrefixMapping(prefix);
+    }
+
+    @Test
+    public void completeTest() throws SAXException {
+        final String localName1 = "element";
+        final String qName1 = "prefix:" + localName1;
+        final Attributes attributes1 = createAttributes(URI, localName1, qName1, "value-1");
+        final String localName2 = "element2";
+        final String qName2 = "prefix:" + localName2;
+        final Attributes attributes2 = createAttributes(URI, localName2, qName2, "value-2");
+        final ContentHandler handler = mock(ContentHandler.class);
+        final String extensionUrl = "http://www.example.com/extension";
+        final String extensionPrefix = "ext";
+
+        sut.startPrefixMapping(extensionPrefix, extensionUrl);
+        sut.startElement(URI, localName1, qName1, attributes1);
+        sut.startElement(URI, localName2, qName2, attributes2);
+        sut.endElement(URI, localName2, qName2);
+        sut.endElement(URI, localName1, qName1);
+        sut.endPrefixMapping(extensionPrefix);
+
+        sut.replay(handler);
+
+        InOrder inOrder = inOrder(handler);
+        inOrder.verify(handler).startPrefixMapping(extensionPrefix, extensionUrl);
+        inOrder.verify(handler).startElement(URI, localName1, qName1, attributes1);
+        inOrder.verify(handler).startElement(URI, localName2, qName2, attributes2);
+        inOrder.verify(handler).endElement(URI, localName2, qName2);
+        inOrder.verify(handler).endElement(URI, localName1, qName1);
+        inOrder.verify(handler).endPrefixMapping(extensionPrefix);
+    }
+
+    private static Attributes createAttributes(String uri, String localName,
+            String qName, String value) {
+        final AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(uri, localName, qName, XMLUtil.CDATA, value);
+        return atts;
+    }
+
+}
diff --git a/test/java/org/apache/fop/util/LanguageTagsTestCase.java b/test/java/org/apache/fop/util/LanguageTagsTestCase.java
new file mode 100644
index 000000000..f7383c720
--- /dev/null
+++ b/test/java/org/apache/fop/util/LanguageTagsTestCase.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 static org.junit.Assert.assertEquals;
+
+import java.util.Locale;
+
+import org.junit.Test;
+
+/**
+ * Tests {@link LanguageTags}.
+ */
+public class LanguageTagsTestCase {
+
+    @Test(expected = NullPointerException.class)
+    public void toLanguageTagRejectsNull() {
+        LanguageTags.toLanguageTag(null);
+    }
+
+    @Test
+    public void testToLanguageTag() throws Exception {
+        assertEquals("", LanguageTags.toLanguageTag(new Locale("")));
+        assertEquals("en", LanguageTags.toLanguageTag(new Locale("en")));
+        assertEquals("en-US", LanguageTags.toLanguageTag(new Locale("en", "US")));
+        assertEquals("en-US", LanguageTags.toLanguageTag(new Locale("EN", "us")));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void toLocaleRejectsNull() {
+        LanguageTags.toLocale(null);
+    }
+
+    @Test
+    public void testRFC3066ToLocale() throws Exception {
+        assertEquals(new Locale(""), LanguageTags.toLocale(""));
+        assertEquals(new Locale("en"), LanguageTags.toLocale("en"));
+        assertEquals(new Locale("en", "US"), LanguageTags.toLocale("en-US"));
+        assertEquals(new Locale("en", "US"), LanguageTags.toLocale("EN-us"));
+    }
+}
diff --git a/test/java/org/apache/fop/util/XMLUtilTestCase.java b/test/java/org/apache/fop/util/XMLUtilTestCase.java
deleted file mode 100644
index 4c1b999e1..000000000
--- a/test/java/org/apache/fop/util/XMLUtilTestCase.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import java.util.Locale;
-
-import org.junit.Test;
-
-/**
- * Tests {@link XMLUtil}.
- */
-public class XMLUtilTestCase {
-
-    @Test
-    public void testLocaleToRFC3066() throws Exception {
-        assertNull(XMLUtil.toRFC3066(null));
-        assertEquals("en", XMLUtil.toRFC3066(new Locale("en")));
-        assertEquals("en-US", XMLUtil.toRFC3066(new Locale("en", "US")));
-        assertEquals("en-US", XMLUtil.toRFC3066(new Locale("EN", "us")));
-    }
-
-    @Test
-    public void testRFC3066ToLocale() throws Exception {
-        assertNull(XMLUtil.convertRFC3066ToLocale(null));
-        assertNull(XMLUtil.convertRFC3066ToLocale(""));
-        assertEquals(new Locale("en"), XMLUtil.convertRFC3066ToLocale("en"));
-        assertEquals(new Locale("en", "US"), XMLUtil.convertRFC3066ToLocale("en-US"));
-        assertEquals(new Locale("en", "US"), XMLUtil.convertRFC3066ToLocale("EN-us"));
-    }
-}
-- 
cgit v1.2.3


From 160d78ce1c348b96e9807f59f3d20bb2226e75c0 Mon Sep 17 00:00:00 2001
From: Peter Hancock 
Date: Fri, 27 Jan 2012 15:36:05 +0000
Subject: Associate structure tree elements directly to render content

* Defer the binding of PCData to struct elems using a placeholder mechanism.
* Translate text nodes to marked-content sequences in IF structure tree.
* Replace ptr with structure tree element.
* Re-order table footers so they appear at the end of the structure tree.


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ImproveAccessibility@1236718 13f79535-47bb-0310-9956-ffa450edef68
---
 build.xml                                          |   1 +
 .../DummyStructureTreeEventHandler.java            |  23 +-
 .../accessibility/FO2StructureTreeConverter.java   | 823 ---------------------
 .../StructureTree2SAXEventAdapter.java             |  35 +-
 .../fop/accessibility/StructureTreeElement.java    |  31 +
 .../accessibility/StructureTreeEventHandler.java   |  31 +-
 .../fo/FO2StructureTreeConverter.java              | 491 ++++++++++++
 .../fop/accessibility/fo/FOEventRecorder.java      | 508 +++++++++++++
 .../fo/StructureTreeEventTrigger.java              | 408 ++++++++++
 src/java/org/apache/fop/area/AreaTreeParser.java   |  11 -
 src/java/org/apache/fop/area/Trait.java            |   6 +-
 .../apache/fop/fo/DelegatingFOEventHandler.java    |   4 +-
 src/java/org/apache/fop/fo/FOEventHandler.java     |  10 +-
 src/java/org/apache/fop/fo/FONode.java             |   3 +-
 src/java/org/apache/fop/fo/FOText.java             |  33 +-
 src/java/org/apache/fop/fo/FOTreeBuilder.java      |   2 +-
 .../fop/fo/extensions/InternalElementMapping.java  |   9 +-
 .../org/apache/fop/fo/flow/AbstractGraphics.java   |  15 +-
 .../fop/fo/flow/AbstractPageNumberCitation.java    |  15 +-
 src/java/org/apache/fop/fo/flow/BasicLink.java     |  15 +-
 src/java/org/apache/fop/fo/flow/Block.java         |  14 +-
 src/java/org/apache/fop/fo/flow/Character.java     |  15 +-
 src/java/org/apache/fop/fo/flow/Inline.java        |  14 +-
 src/java/org/apache/fop/fo/flow/PageNumber.java    |  15 +-
 .../org/apache/fop/fo/flow/table/TableFObj.java    |  14 +-
 .../fo/properties/StructurePointerPropertySet.java |  34 -
 .../fo/properties/StructureTreeElementHolder.java  |  38 +
 .../apache/fop/layoutmgr/BlockLayoutManager.java   |   1 -
 src/java/org/apache/fop/layoutmgr/TraitSetter.java |  14 +-
 .../inline/AbstractGraphicsLayoutManager.java      |   2 +-
 .../AbstractPageNumberCitationLayoutManager.java   |   2 +-
 .../layoutmgr/inline/BasicLinkLayoutManager.java   |   2 +-
 .../layoutmgr/inline/CharacterLayoutManager.java   |   2 +-
 .../layoutmgr/inline/PageNumberLayoutManager.java  |   8 +-
 .../fop/layoutmgr/inline/TextLayoutManager.java    |  18 +-
 src/java/org/apache/fop/pdf/PDFStructElem.java     | 102 ++-
 .../apache/fop/render/intermediate/IFContext.java  |  33 +-
 .../apache/fop/render/intermediate/IFParser.java   |  56 +-
 .../apache/fop/render/intermediate/IFRenderer.java |  21 +-
 .../fop/render/intermediate/IFSerializer.java      |  29 +-
 .../intermediate/IFStructureTreeBuilder.java       | 118 ++-
 .../intermediate/extensions/AbstractAction.java    |  14 +-
 .../extensions/DocumentNavigationHandler.java      |  24 +-
 .../render/pdf/PDFDocumentNavigationHandler.java   |   8 +-
 .../fop/render/pdf/PDFLogicalStructureHandler.java |  84 +--
 src/java/org/apache/fop/render/pdf/PDFPainter.java |  21 +-
 .../fop/render/pdf/PDFStructureTreeBuilder.java    |  71 +-
 src/java/org/apache/fop/render/rtf/RTFHandler.java |  12 +-
 test/accessibility/background-image_jpg_repeat.fo  |   4 +-
 test/accessibility/background-image_jpg_single.fo  |   4 +-
 test/accessibility/background-image_png_repeat.fo  |   4 +-
 test/accessibility/background-image_png_single.fo  |   4 +-
 test/accessibility/background-image_svg_repeat.fo  |   4 +-
 test/accessibility/background-image_svg_single.fo  |   4 +-
 test/accessibility/complete.fo                     |   6 +-
 test/accessibility/image_jpg.fo                    |   4 +-
 test/accessibility/image_png.fo                    |   4 +-
 test/accessibility/image_svg.fo                    |   4 +-
 test/accessibility/image_wmf.fo                    |   4 +-
 test/accessibility/leader.fo                       |   4 +-
 test/accessibility/links.fo                        |   4 +-
 .../pdf/background-image_jpg_repeat.pdf            | Bin 12814 -> 12806 bytes
 .../pdf/background-image_jpg_single.pdf            | Bin 12653 -> 12645 bytes
 .../pdf/background-image_png_repeat.pdf            | Bin 35437 -> 35429 bytes
 .../pdf/background-image_png_single.pdf            | Bin 58317 -> 58309 bytes
 .../pdf/background-image_svg_repeat.pdf            | Bin 17104 -> 17096 bytes
 .../pdf/background-image_svg_single.pdf            | Bin 9943 -> 9935 bytes
 test/accessibility/pdf/complete.pdf                | Bin 96730 -> 96722 bytes
 test/accessibility/pdf/image_jpg.pdf               | Bin 9078 -> 9070 bytes
 test/accessibility/pdf/image_png.pdf               | Bin 57788 -> 57780 bytes
 test/accessibility/pdf/image_svg.pdf               | Bin 14292 -> 14284 bytes
 test/accessibility/pdf/image_wmf.pdf               | Bin 208454 -> 304960 bytes
 test/accessibility/pdf/leader.pdf                  | Bin 9379 -> 9371 bytes
 test/accessibility/pdf/links.pdf                   | Bin 9712 -> 9704 bytes
 test/accessibility/pdf/role.pdf                    | Bin 18976 -> 18968 bytes
 test/accessibility/pdf/role_non-standard.pdf       | Bin 18990 -> 18982 bytes
 test/accessibility/pdf/text_1.pdf                  | Bin 7504 -> 7496 bytes
 test/accessibility/pdf/text_2.pdf                  | Bin 10346 -> 10338 bytes
 test/accessibility/pdf/text_font-embedding.pdf     | Bin 19785 -> 19777 bytes
 test/accessibility/role.fo                         |   4 +-
 test/accessibility/role_non-standard.fo            |   4 +-
 test/accessibility/text_1.fo                       |   4 +-
 test/accessibility/text_2.fo                       |   4 +-
 test/accessibility/text_font-embedding.fo          |   4 +-
 .../FO2StructureTreeConverterTestCase.java         | 202 -----
 .../apache/fop/accessibility/fo/DOMResultUtil.java |  54 ++
 .../fo/FO2StructureTreeConverterTestCase.java      | 223 ++++++
 .../fop/accessibility/fo/fo2StructureTree.xsl      | 135 ++++
 .../apache/fop/accessibility/fo/table-footers.fo   | 195 +++++
 .../fo/wrapCompleteDocumentInTableFooter.xsl       |  66 ++
 .../apache/fop/accessibility/fo2StructureTree.xsl  | 116 ---
 .../IFStructureTreeBuilderTestCase.java            |   2 +-
 92 files changed, 2725 insertions(+), 1602 deletions(-)
 delete mode 100644 src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java
 create mode 100644 src/java/org/apache/fop/accessibility/StructureTreeElement.java
 create mode 100644 src/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverter.java
 create mode 100644 src/java/org/apache/fop/accessibility/fo/FOEventRecorder.java
 create mode 100644 src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java
 delete mode 100644 src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java
 create mode 100644 src/java/org/apache/fop/fo/properties/StructureTreeElementHolder.java
 delete mode 100644 test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java
 create mode 100644 test/java/org/apache/fop/accessibility/fo/DOMResultUtil.java
 create mode 100644 test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java
 create mode 100644 test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl
 create mode 100644 test/java/org/apache/fop/accessibility/fo/table-footers.fo
 create mode 100644 test/java/org/apache/fop/accessibility/fo/wrapCompleteDocumentInTableFooter.xsl
 delete mode 100644 test/java/org/apache/fop/accessibility/fo2StructureTree.xsl

(limited to 'test/java')

diff --git a/build.xml b/build.xml
index dda6493ae..8bc66cd95 100644
--- a/build.xml
+++ b/build.xml
@@ -581,6 +581,7 @@ list of possible build targets.
 
     
       
+      
       
       
       
diff --git a/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java b/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java
index 80a011bab..07daf2f73 100644
--- a/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java
+++ b/src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java
@@ -33,20 +33,31 @@ public final class DummyStructureTreeEventHandler implements StructureTreeEventH
 
     private DummyStructureTreeEventHandler() { }
 
-    /** {@inheritDoc} */
     public void startPageSequence(Locale locale) {
     }
 
-    /** {@inheritDoc} */
-    public void startNode(String name, Attributes attributes) {
+    public void endPageSequence() {
+    }
+
+    public StructureTreeElement startNode(String name, Attributes attributes) {
+        return null;
     }
 
-    /** {@inheritDoc} */
     public void endNode(String name) {
     }
 
-    /** {@inheritDoc} */
-    public void endPageSequence() {
+    public StructureTreeElement startImageNode(String name, Attributes attributes) {
+        return null;
+    }
+
+    public void endImageNode(String name) {
+    }
+
+    public StructureTreeElement startReferencedNode(String name, Attributes attributes) {
+        return null;
+    }
+
+    public void endReferencedNode(String name) {
     }
 
 }
diff --git a/src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java b/src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java
deleted file mode 100644
index e6d0193b9..000000000
--- a/src/java/org/apache/fop/accessibility/FO2StructureTreeConverter.java
+++ /dev/null
@@ -1,823 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.accessibility;
-
-import java.util.Locale;
-
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-import org.apache.fop.apps.FOUserAgent;
-import org.apache.fop.fo.DelegatingFOEventHandler;
-import org.apache.fop.fo.FOEventHandler;
-import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.extensions.ExtensionElementMapping;
-import org.apache.fop.fo.extensions.ExternalDocument;
-import org.apache.fop.fo.extensions.InternalElementMapping;
-import org.apache.fop.fo.flow.AbstractGraphics;
-import org.apache.fop.fo.flow.BasicLink;
-import org.apache.fop.fo.flow.Block;
-import org.apache.fop.fo.flow.BlockContainer;
-import org.apache.fop.fo.flow.Character;
-import org.apache.fop.fo.flow.ExternalGraphic;
-import org.apache.fop.fo.flow.Footnote;
-import org.apache.fop.fo.flow.FootnoteBody;
-import org.apache.fop.fo.flow.Inline;
-import org.apache.fop.fo.flow.InstreamForeignObject;
-import org.apache.fop.fo.flow.Leader;
-import org.apache.fop.fo.flow.ListBlock;
-import org.apache.fop.fo.flow.ListItem;
-import org.apache.fop.fo.flow.ListItemBody;
-import org.apache.fop.fo.flow.ListItemLabel;
-import org.apache.fop.fo.flow.PageNumber;
-import org.apache.fop.fo.flow.PageNumberCitation;
-import org.apache.fop.fo.flow.PageNumberCitationLast;
-import org.apache.fop.fo.flow.Wrapper;
-import org.apache.fop.fo.flow.table.Table;
-import org.apache.fop.fo.flow.table.TableBody;
-import org.apache.fop.fo.flow.table.TableCell;
-import org.apache.fop.fo.flow.table.TableColumn;
-import org.apache.fop.fo.flow.table.TableFooter;
-import org.apache.fop.fo.flow.table.TableHeader;
-import org.apache.fop.fo.flow.table.TableRow;
-import org.apache.fop.fo.pagination.Flow;
-import org.apache.fop.fo.pagination.PageSequence;
-import org.apache.fop.fo.pagination.Root;
-import org.apache.fop.fo.pagination.StaticContent;
-import org.apache.fop.fo.properties.CommonAccessibilityHolder;
-import org.apache.fop.util.XMLUtil;
-
-/**
- * Allows to create the structure tree of an FO document, by converting FO
- * events into appropriate structure tree events.
- */
-public class FO2StructureTreeConverter extends DelegatingFOEventHandler {
-
-    private int idCounter;
-
-    /** Delegates to either {@link #foToStructureTreeEventAdapter} or {@link #eventSwallower}. */
-    private FOEventHandler converter;
-
-    private final FOEventHandler foToStructureTreeEventAdapter;
-
-    /** The descendants of some elements like fo:leader must be ignored. */
-    private final FOEventHandler eventSwallower;
-
-    private final StructureTreeEventHandler structureTreeEventHandler;
-
-    private final class FOToStructureTreeEventAdapter extends FOEventHandler {
-
-        public FOToStructureTreeEventAdapter(FOUserAgent foUserAgent) {
-            super(foUserAgent);
-        }
-
-        @Override
-        public void startDocument() throws SAXException {
-        }
-
-        @Override
-        public void endDocument() throws SAXException {
-        }
-
-        @Override
-        public void startPageSequence(PageSequence pageSeq) {
-            Locale locale = null;
-            if (pageSeq.getLanguage() != null) {
-                if (pageSeq.getCountry() != null) {
-                    locale = new Locale(pageSeq.getLanguage(), pageSeq.getCountry());
-                } else {
-                    locale = new Locale(pageSeq.getLanguage());
-                }
-            }
-            structureTreeEventHandler.startPageSequence(locale);
-        }
-
-        @Override
-        public void endPageSequence(PageSequence pageSeq) {
-            structureTreeEventHandler.endPageSequence();
-        }
-
-        @Override
-        public void startPageNumber(PageNumber pagenum) {
-            startElementWithID(pagenum);
-        }
-
-        @Override
-        public void endPageNumber(PageNumber pagenum) {
-            endElement(pagenum);
-        }
-
-        @Override
-        public void startPageNumberCitation(PageNumberCitation pageCite) {
-            startElementWithID(pageCite);
-        }
-
-        @Override
-        public void endPageNumberCitation(PageNumberCitation pageCite) {
-            endElement(pageCite);
-        }
-
-        @Override
-        public void startPageNumberCitationLast(PageNumberCitationLast pageLast) {
-            startElementWithID(pageLast);
-        }
-
-        @Override
-        public void endPageNumberCitationLast(PageNumberCitationLast pageLast) {
-            endElement(pageLast);
-        }
-
-        @Override
-        public void startFlow(Flow fl) {
-            startElement(fl);
-        }
-
-        @Override
-        public void endFlow(Flow fl) {
-            endElement(fl);
-        }
-
-        @Override
-        public void startBlock(Block bl) {
-            startElementWithID(bl);
-        }
-
-        @Override
-        public void endBlock(Block bl) {
-            endElement(bl);
-        }
-
-        @Override
-        public void startBlockContainer(BlockContainer blc) {
-            startElement(blc);
-        }
-
-        @Override
-        public void endBlockContainer(BlockContainer blc) {
-            endElement(blc);
-        }
-
-        @Override
-        public void startInline(Inline inl) {
-            startElementWithID(inl);
-        }
-
-        @Override
-        public void endInline(Inline inl) {
-            endElement(inl);
-        }
-
-        @Override
-        public void startTable(Table tbl) {
-            startElementWithID(tbl);
-        }
-
-        @Override
-        public void endTable(Table tbl) {
-            endElement(tbl);
-        }
-
-        @Override
-        public void startHeader(TableHeader header) {
-            startElementWithID(header);
-        }
-
-        @Override
-        public void endHeader(TableHeader header) {
-            endElement(header);
-        }
-
-        @Override
-        public void startFooter(TableFooter footer) {
-            startElementWithID(footer);
-        }
-
-        @Override
-        public void endFooter(TableFooter footer) {
-            endElement(footer);
-        }
-
-        @Override
-        public void startBody(TableBody body) {
-            startElementWithID(body);
-        }
-
-        @Override
-        public void endBody(TableBody body) {
-            endElement(body);
-        }
-
-        @Override
-        public void startRow(TableRow tr) {
-            startElementWithID(tr);
-        }
-
-        @Override
-        public void endRow(TableRow tr) {
-            endElement(tr);
-        }
-
-        @Override
-        public void startCell(TableCell tc) {
-            AttributesImpl attributes = new AttributesImpl();
-            int colSpan = tc.getNumberColumnsSpanned();
-            if (colSpan > 1) {
-                addNoNamespaceAttribute(attributes, "number-columns-spanned",
-                        Integer.toString(colSpan));
-            }
-            startElementWithID(tc, attributes);
-        }
-
-        @Override
-        public void endCell(TableCell tc) {
-            endElement(tc);
-        }
-
-        @Override
-        public void startList(ListBlock lb) {
-            startElement(lb);
-        }
-
-        @Override
-        public void endList(ListBlock lb) {
-            endElement(lb);
-        }
-
-        @Override
-        public void startListItem(ListItem li) {
-            startElement(li);
-        }
-
-        @Override
-        public void endListItem(ListItem li) {
-            endElement(li);
-        }
-
-        @Override
-        public void startListLabel(ListItemLabel listItemLabel) {
-            startElement(listItemLabel);
-        }
-
-        @Override
-        public void endListLabel(ListItemLabel listItemLabel) {
-            endElement(listItemLabel);
-        }
-
-        @Override
-        public void startListBody(ListItemBody listItemBody) {
-            startElement(listItemBody);
-        }
-
-        @Override
-        public void endListBody(ListItemBody listItemBody) {
-            endElement(listItemBody);
-        }
-
-        @Override
-        public void startStatic(StaticContent staticContent) {
-            startElement(staticContent);
-        }
-
-        @Override
-        public void endStatic(StaticContent statisContent) {
-            endElement(statisContent);
-        }
-
-        @Override
-        public void startLink(BasicLink basicLink) {
-            startElementWithID(basicLink);
-        }
-
-        @Override
-        public void endLink(BasicLink basicLink) {
-            endElement(basicLink);
-        }
-
-        @Override
-        public void image(ExternalGraphic eg) {
-            startElementWithIDAndAltText(eg);
-            endElement(eg);
-        }
-
-        @Override
-        public void startInstreamForeignObject(InstreamForeignObject ifo) {
-            startElementWithIDAndAltText(ifo);
-        }
-
-        @Override
-        public void endInstreamForeignObject(InstreamForeignObject ifo) {
-            endElement(ifo);
-        }
-
-        @Override
-        public void startFootnote(Footnote footnote) {
-            startElement(footnote);
-        }
-
-        @Override
-        public void endFootnote(Footnote footnote) {
-            endElement(footnote);
-        }
-
-        @Override
-        public void startFootnoteBody(FootnoteBody body) {
-            startElement(body);
-        }
-
-        @Override
-        public void endFootnoteBody(FootnoteBody body) {
-            endElement(body);
-        }
-
-        @Override
-        public void startWrapper(Wrapper wrapper) {
-            startElement(wrapper);
-        }
-
-        @Override
-        public void endWrapper(Wrapper wrapper) {
-            endElement(wrapper);
-        }
-
-        @Override
-        public void character(Character c) {
-            startElementWithID(c);
-            endElement(c);
-        }
-
-
-        private void startElement(FONode node) {
-            startElement(node, new AttributesImpl());
-        }
-
-        private void startElementWithID(FONode node) {
-            startElementWithID(node, new AttributesImpl());
-        }
-
-        private void startElementWithIDAndAltText(AbstractGraphics node) {
-            AttributesImpl attributes = new AttributesImpl();
-            addAttribute(attributes, ExtensionElementMapping.URI, "alt-text",
-                    ExtensionElementMapping.STANDARD_PREFIX, node.getAltText());
-            startElementWithID(node, attributes);
-        }
-
-        private void startElementWithID(FONode node, AttributesImpl attributes) {
-            String id = Integer.toHexString(idCounter++);
-            node.setPtr(id);
-            addAttribute(attributes,
-                    InternalElementMapping.URI, "ptr", InternalElementMapping.STANDARD_PREFIX, id);
-            startElement(node, attributes);
-        }
-
-        private void startElement(FONode node, AttributesImpl attributes) {
-            String localName = node.getLocalName();
-            if (node instanceof CommonAccessibilityHolder) {
-                addRole((CommonAccessibilityHolder) node, attributes);
-            }
-            structureTreeEventHandler.startNode(localName, attributes);
-        }
-
-        private void addNoNamespaceAttribute(AttributesImpl attributes, String name, String value) {
-            attributes.addAttribute("", name, name, XMLUtil.CDATA, value);
-        }
-
-        private void addAttribute(AttributesImpl attributes,
-                String namespace, String localName, String prefix, String value) {
-            assert namespace.length() > 0 && prefix.length() > 0;
-            String qualifiedName = prefix + ":" + localName;
-            attributes.addAttribute(namespace, localName, qualifiedName, XMLUtil.CDATA, value);
-        }
-
-        private void addRole(CommonAccessibilityHolder node, AttributesImpl attributes) {
-            String role = node.getCommonAccessibility().getRole();
-            if (role != null) {
-                addNoNamespaceAttribute(attributes, "role", role);
-            }
-        }
-
-        private void endElement(FONode node) {
-            String localName = node.getLocalName();
-            structureTreeEventHandler.endNode(localName);
-        }
-
-    }
-
-    /**
-     * Creates a new instance.
-     *
-     * @param structureTreeEventHandler the object that will hold the structure tree
-     * @param delegate the FO event handler that must be wrapped by this instance
-     */
-    public FO2StructureTreeConverter(StructureTreeEventHandler structureTreeEventHandler,
-            FOEventHandler delegate) {
-        super(delegate);
-        this.structureTreeEventHandler = structureTreeEventHandler;
-        this.foToStructureTreeEventAdapter = new FOToStructureTreeEventAdapter(foUserAgent);
-        this.converter = foToStructureTreeEventAdapter;
-        this.eventSwallower = new FOEventHandler(foUserAgent) { };
-    }
-
-    @Override
-    public void startDocument() throws SAXException {
-        converter.startDocument();
-        super.startDocument();
-    }
-
-    @Override
-    public void endDocument() throws SAXException {
-        converter.endDocument();
-        super.endDocument();
-    }
-
-    @Override
-    public void startRoot(Root root) {
-       converter.startRoot(root);
-       super.startRoot(root);
-    }
-
-    @Override
-    public void endRoot(Root root) {
-        converter.endRoot(root);
-        super.endRoot(root);
-    }
-
-    @Override
-    public void startPageSequence(PageSequence pageSeq) {
-        converter.startPageSequence(pageSeq);
-        super.startPageSequence(pageSeq);
-    }
-
-    @Override
-    public void endPageSequence(PageSequence pageSeq) {
-        converter.endPageSequence(pageSeq);
-        super.endPageSequence(pageSeq);
-    }
-
-    @Override
-    public void startPageNumber(PageNumber pagenum) {
-        converter.startPageNumber(pagenum);
-        super.startPageNumber(pagenum);
-    }
-
-    @Override
-    public void endPageNumber(PageNumber pagenum) {
-        converter.endPageNumber(pagenum);
-        super.endPageNumber(pagenum);
-    }
-
-    @Override
-    public void startPageNumberCitation(PageNumberCitation pageCite) {
-        converter.startPageNumberCitation(pageCite);
-        super.startPageNumberCitation(pageCite);
-    }
-
-    @Override
-    public void endPageNumberCitation(PageNumberCitation pageCite) {
-        converter.endPageNumberCitation(pageCite);
-        super.endPageNumberCitation(pageCite);
-    }
-
-    @Override
-    public void startPageNumberCitationLast(PageNumberCitationLast pageLast) {
-        converter.startPageNumberCitationLast(pageLast);
-        super.startPageNumberCitationLast(pageLast);
-    }
-
-    @Override
-    public void endPageNumberCitationLast(PageNumberCitationLast pageLast) {
-        converter.endPageNumberCitationLast(pageLast);
-        super.endPageNumberCitationLast(pageLast);
-    }
-
-    @Override
-    public void startFlow(Flow fl) {
-        converter.startFlow(fl);
-        super.startFlow(fl);
-    }
-
-    @Override
-    public void endFlow(Flow fl) {
-        converter.endFlow(fl);
-        super.endFlow(fl);
-    }
-
-    @Override
-    public void startBlock(Block bl) {
-        converter.startBlock(bl);
-        super.startBlock(bl);
-    }
-
-    @Override
-    public void endBlock(Block bl) {
-        converter.endBlock(bl);
-        super.endBlock(bl);
-    }
-
-    @Override
-    public void startBlockContainer(BlockContainer blc) {
-        converter.startBlockContainer(blc);
-        super.startBlockContainer(blc);
-    }
-
-    @Override
-    public void endBlockContainer(BlockContainer blc) {
-        converter.endBlockContainer(blc);
-        super.endBlockContainer(blc);
-    }
-
-    @Override
-    public void startInline(Inline inl) {
-        converter.startInline(inl);
-        super.startInline(inl);
-    }
-
-    @Override
-    public void endInline(Inline inl) {
-        converter.endInline(inl);
-        super.endInline(inl);
-    }
-
-    @Override
-    public void startTable(Table tbl) {
-        converter.startTable(tbl);
-        super.startTable(tbl);
-    }
-
-    @Override
-    public void endTable(Table tbl) {
-        converter.endTable(tbl);
-        super.endTable(tbl);
-    }
-
-    @Override
-    public void startColumn(TableColumn tc) {
-        converter.startColumn(tc);
-        super.startColumn(tc);
-    }
-
-    @Override
-    public void endColumn(TableColumn tc) {
-        converter.endColumn(tc);
-        super.endColumn(tc);
-    }
-
-    @Override
-    public void startHeader(TableHeader header) {
-        converter.startHeader(header);
-        super.startHeader(header);
-    }
-
-    @Override
-    public void endHeader(TableHeader header) {
-        converter.endHeader(header);
-        super.endHeader(header);
-    }
-
-    @Override
-    public void startFooter(TableFooter footer) {
-        converter.startFooter(footer);
-        super.startFooter(footer);
-    }
-
-    @Override
-    public void endFooter(TableFooter footer) {
-        converter.endFooter(footer);
-        super.endFooter(footer);
-    }
-
-    @Override
-    public void startBody(TableBody body) {
-        converter.startBody(body);
-        super.startBody(body);
-    }
-
-    @Override
-    public void endBody(TableBody body) {
-        converter.endBody(body);
-        super.endBody(body);
-    }
-
-    @Override
-    public void startRow(TableRow tr) {
-        converter.startRow(tr);
-        super.startRow(tr);
-    }
-
-    @Override
-    public void endRow(TableRow tr) {
-        converter.endRow(tr);
-        super.endRow(tr);
-    }
-
-    @Override
-    public void startCell(TableCell tc) {
-        converter.startCell(tc);
-        super.startCell(tc);
-    }
-
-    @Override
-    public void endCell(TableCell tc) {
-        converter.endCell(tc);
-        super.endCell(tc);
-    }
-
-    @Override
-    public void startList(ListBlock lb) {
-        converter.startList(lb);
-        super.startList(lb);
-    }
-
-    @Override
-    public void endList(ListBlock lb) {
-        converter.endList(lb);
-        super.endList(lb);
-    }
-
-    @Override
-    public void startListItem(ListItem li) {
-        converter.startListItem(li);
-        super.startListItem(li);
-    }
-
-    @Override
-    public void endListItem(ListItem li) {
-        converter.endListItem(li);
-        super.endListItem(li);
-    }
-
-    @Override
-    public void startListLabel(ListItemLabel listItemLabel) {
-        converter.startListLabel(listItemLabel);
-        super.startListLabel(listItemLabel);
-    }
-
-    @Override
-    public void endListLabel(ListItemLabel listItemLabel) {
-        converter.endListLabel(listItemLabel);
-        super.endListLabel(listItemLabel);
-    }
-
-    @Override
-    public void startListBody(ListItemBody listItemBody) {
-        converter.startListBody(listItemBody);
-        super.startListBody(listItemBody);
-    }
-
-    @Override
-    public void endListBody(ListItemBody listItemBody) {
-        converter.endListBody(listItemBody);
-        super.endListBody(listItemBody);
-    }
-
-    @Override
-    public void startStatic(StaticContent staticContent) {
-        converter.startStatic(staticContent);
-        super.startStatic(staticContent);
-    }
-
-    @Override
-    public void endStatic(StaticContent statisContent) {
-        converter.endStatic(statisContent);
-        super.endStatic(statisContent);
-    }
-
-    @Override
-    public void startMarkup() {
-        converter.startMarkup();
-        super.startMarkup();
-    }
-
-    @Override
-    public void endMarkup() {
-        converter.endMarkup();
-        super.endMarkup();
-    }
-
-    @Override
-    public void startLink(BasicLink basicLink) {
-        converter.startLink(basicLink);
-        super.startLink(basicLink);
-    }
-
-    @Override
-    public void endLink(BasicLink basicLink) {
-        converter.endLink(basicLink);
-        super.endLink(basicLink);
-    }
-
-    @Override
-    public void image(ExternalGraphic eg) {
-        converter.image(eg);
-        super.image(eg);
-    }
-
-    @Override
-    public void pageRef() {
-        converter.pageRef();
-        super.pageRef();
-    }
-
-    @Override
-    public void startInstreamForeignObject(InstreamForeignObject ifo) {
-        converter.startInstreamForeignObject(ifo);
-        super.startInstreamForeignObject(ifo);
-    }
-
-    @Override
-    public void endInstreamForeignObject(InstreamForeignObject ifo) {
-        converter.endInstreamForeignObject(ifo);
-        super.endInstreamForeignObject(ifo);
-    }
-
-    @Override
-    public void startFootnote(Footnote footnote) {
-        converter.startFootnote(footnote);
-        super.startFootnote(footnote);
-    }
-
-    @Override
-    public void endFootnote(Footnote footnote) {
-        converter.endFootnote(footnote);
-        super.endFootnote(footnote);
-    }
-
-    @Override
-    public void startFootnoteBody(FootnoteBody body) {
-        converter.startFootnoteBody(body);
-        super.startFootnoteBody(body);
-    }
-
-    @Override
-    public void endFootnoteBody(FootnoteBody body) {
-        converter.endFootnoteBody(body);
-        super.endFootnoteBody(body);
-    }
-
-    @Override
-    public void startLeader(Leader l) {
-        converter = eventSwallower;
-        converter.startLeader(l);
-        super.startLeader(l);
-    }
-
-    @Override
-    public void endLeader(Leader l) {
-        converter.endLeader(l);
-        converter = foToStructureTreeEventAdapter;
-        super.endLeader(l);
-    }
-
-    @Override
-    public void startWrapper(Wrapper wrapper) {
-        converter.startWrapper(wrapper);
-        super.startWrapper(wrapper);
-    }
-
-    @Override
-    public void endWrapper(Wrapper wrapper) {
-        converter.endWrapper(wrapper);
-        super.endWrapper(wrapper);
-    }
-
-    @Override
-    public void character(Character c) {
-        converter.character(c);
-        super.character(c);
-    }
-
-    @Override
-    public void characters(char[] data, int start, int length) {
-        converter.characters(data, start, length);
-        super.characters(data, start, length);
-    }
-
-    @Override
-    public void startExternalDocument(ExternalDocument document) {
-        converter.startExternalDocument(document);
-        super.startExternalDocument(document);
-    }
-
-    @Override
-    public void endExternalDocument(ExternalDocument document) {
-        converter.endExternalDocument(document);
-        super.endExternalDocument(document);
-    }
-
-}
diff --git a/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java b/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java
index 240c384d6..2cd92a5ce 100644
--- a/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java
+++ b/src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java
@@ -50,7 +50,6 @@ public final class StructureTree2SAXEventAdapter implements StructureTreeEventHa
         return new StructureTree2SAXEventAdapter(contentHandler);
     }
 
-    /** {@inheritDoc} */
     public void startPageSequence(Locale locale) {
         try {
 
@@ -66,7 +65,6 @@ public final class StructureTree2SAXEventAdapter implements StructureTreeEventHa
         }
     }
 
-    /** {@inheritDoc} */
     public void endPageSequence() {
         try {
             contentHandler.endElement(IFConstants.NAMESPACE, IFConstants.EL_STRUCTURE_TREE,
@@ -81,18 +79,23 @@ public final class StructureTree2SAXEventAdapter implements StructureTreeEventHa
         }
     }
 
-    /** {@inheritDoc} */
-    public void startNode(String name, Attributes attributes) {
+    public StructureTreeElement startNode(String name, Attributes attributes) {
         try {
-            contentHandler.startElement(FOElementMapping.URI, name,
-                    FOElementMapping.STANDARD_PREFIX + ":" + name,
-                    attributes);
+            if (name.equals("#PCDATA")) {
+                name = "marked-content";
+                contentHandler.startElement(IFConstants.NAMESPACE, name,
+                        name, attributes);
+            } else {
+                contentHandler.startElement(FOElementMapping.URI, name,
+                        FOElementMapping.STANDARD_PREFIX + ":" + name,
+                        attributes);
+            }
+            return null;
         } catch (SAXException e) {
             throw new RuntimeException(e);
         }
     }
 
-    /** {@inheritDoc} */
     public void endNode(String name) {
         try {
             contentHandler.endElement(FOElementMapping.URI, name,
@@ -101,4 +104,20 @@ public final class StructureTree2SAXEventAdapter implements StructureTreeEventHa
             throw new RuntimeException(e);
         }
     }
+
+    public StructureTreeElement startImageNode(String name, Attributes attributes) {
+        return startNode(name, attributes);
+    }
+
+    public void endImageNode(String name) {
+        endNode(name);
+    }
+
+    public StructureTreeElement startReferencedNode(String name, Attributes attributes) {
+        return startNode(name, attributes);
+    }
+
+    public void endReferencedNode(String name) {
+        endNode(name);
+    }
 }
diff --git a/src/java/org/apache/fop/accessibility/StructureTreeElement.java b/src/java/org/apache/fop/accessibility/StructureTreeElement.java
new file mode 100644
index 000000000..3a691c42c
--- /dev/null
+++ b/src/java/org/apache/fop/accessibility/StructureTreeElement.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility;
+
+/**
+ * An object that represents the structure of the document in the output format.
+ * In PDF, an implementation of this interface will typically result into the
+ * creation of a structure element dictionary (a dictionary of type StructElem).
+ */
+public interface StructureTreeElement {
+}
diff --git a/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java b/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java
index d84d07870..4b94d61f1 100644
--- a/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java
+++ b/src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java
@@ -32,23 +32,46 @@ public interface StructureTreeEventHandler {
 
     /**
      * Starts a page sequence structure tree node.
+     *
      * @param locale The locale of the page sequence
      */
     void startPageSequence(Locale locale);
 
     /**
      * Starts a structure tree node.
-     * @param name The name of the structure tree node
-     * @param attributes Map of node properties
+     *
+     * @param name the name of the structure tree node
+     * @param attributes the node properties
+     * @return the corresponding structure tree element
      */
-    void startNode(String name, Attributes attributes);
+    StructureTreeElement startNode(String name, Attributes attributes);
 
     /**
      * Ends a structure tree node.
-     * @param name The name of the structure tree node
+     *
+     * @param name the name of the structure tree node
      */
     void endNode(String name);
 
+    /**
+     * Starts an image node.
+     *
+     * @param name the name of the structure tree node
+     * @param attributes the node properties
+     * @return the corresponding structure tree element
+     */
+    StructureTreeElement startImageNode(String name, Attributes attributes);
+
+    /**
+     * Starts a node that can be referenced by other nodes. This is usually a
+     * node that can have Marked Content References as children.
+     *
+     * @param name the name of the structure tree node
+     * @param attributes the node properties
+     * @return the corresponding structure tree element
+     */
+    StructureTreeElement startReferencedNode(String name, Attributes attributes);
+
     /**
      * Ends a page sequence structure tree node.
      */
diff --git a/src/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverter.java b/src/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverter.java
new file mode 100644
index 000000000..47c227e9a
--- /dev/null
+++ b/src/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverter.java
@@ -0,0 +1,491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility.fo;
+
+import java.util.Stack;
+
+import org.xml.sax.SAXException;
+
+import org.apache.fop.accessibility.StructureTreeEventHandler;
+import org.apache.fop.fo.DelegatingFOEventHandler;
+import org.apache.fop.fo.FOEventHandler;
+import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.extensions.ExternalDocument;
+import org.apache.fop.fo.flow.BasicLink;
+import org.apache.fop.fo.flow.Block;
+import org.apache.fop.fo.flow.BlockContainer;
+import org.apache.fop.fo.flow.Character;
+import org.apache.fop.fo.flow.ExternalGraphic;
+import org.apache.fop.fo.flow.Footnote;
+import org.apache.fop.fo.flow.FootnoteBody;
+import org.apache.fop.fo.flow.Inline;
+import org.apache.fop.fo.flow.InstreamForeignObject;
+import org.apache.fop.fo.flow.Leader;
+import org.apache.fop.fo.flow.ListBlock;
+import org.apache.fop.fo.flow.ListItem;
+import org.apache.fop.fo.flow.ListItemBody;
+import org.apache.fop.fo.flow.ListItemLabel;
+import org.apache.fop.fo.flow.PageNumber;
+import org.apache.fop.fo.flow.PageNumberCitation;
+import org.apache.fop.fo.flow.PageNumberCitationLast;
+import org.apache.fop.fo.flow.Wrapper;
+import org.apache.fop.fo.flow.table.Table;
+import org.apache.fop.fo.flow.table.TableBody;
+import org.apache.fop.fo.flow.table.TableCell;
+import org.apache.fop.fo.flow.table.TableColumn;
+import org.apache.fop.fo.flow.table.TableFooter;
+import org.apache.fop.fo.flow.table.TableHeader;
+import org.apache.fop.fo.flow.table.TableRow;
+import org.apache.fop.fo.pagination.Flow;
+import org.apache.fop.fo.pagination.PageSequence;
+import org.apache.fop.fo.pagination.Root;
+import org.apache.fop.fo.pagination.StaticContent;
+
+/**
+ * Allows to create the structure tree of an FO document, by converting FO
+ * events into appropriate structure tree events.
+ */
+public class FO2StructureTreeConverter extends DelegatingFOEventHandler {
+
+    /** The top of the {@link converters} stack. */
+    private FOEventHandler converter;
+
+    private final Stack converters = new Stack();
+
+    private final Stack tableFooterRecorders = new Stack();
+
+    private final FOEventHandler structureTreeEventTrigger;
+
+    /** The descendants of some elements like fo:leader must be ignored. */
+    private final FOEventHandler eventSwallower = new FOEventHandler() {
+    };
+
+    /**
+     * Creates a new instance.
+     *
+     * @param structureTreeEventHandler the object that will hold the structure tree
+     * @param delegate the FO event handler that must be wrapped by this instance
+     */
+    public FO2StructureTreeConverter(StructureTreeEventHandler structureTreeEventHandler,
+            FOEventHandler delegate) {
+        super(delegate);
+        this.structureTreeEventTrigger = new StructureTreeEventTrigger(structureTreeEventHandler);
+        this.converter = structureTreeEventTrigger;
+    }
+
+    @Override
+    public void startDocument() throws SAXException {
+        converter.startDocument();
+        super.startDocument();
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+        converter.endDocument();
+        super.endDocument();
+    }
+
+    @Override
+    public void startRoot(Root root) {
+       converter.startRoot(root);
+       super.startRoot(root);
+    }
+
+    @Override
+    public void endRoot(Root root) {
+        converter.endRoot(root);
+        super.endRoot(root);
+    }
+
+    @Override
+    public void startPageSequence(PageSequence pageSeq) {
+        converter.startPageSequence(pageSeq);
+        super.startPageSequence(pageSeq);
+    }
+
+    @Override
+    public void endPageSequence(PageSequence pageSeq) {
+        converter.endPageSequence(pageSeq);
+        super.endPageSequence(pageSeq);
+    }
+
+    @Override
+    public void startPageNumber(PageNumber pagenum) {
+        converter.startPageNumber(pagenum);
+        super.startPageNumber(pagenum);
+    }
+
+    @Override
+    public void endPageNumber(PageNumber pagenum) {
+        converter.endPageNumber(pagenum);
+        super.endPageNumber(pagenum);
+    }
+
+    @Override
+    public void startPageNumberCitation(PageNumberCitation pageCite) {
+        converter.startPageNumberCitation(pageCite);
+        super.startPageNumberCitation(pageCite);
+    }
+
+    @Override
+    public void endPageNumberCitation(PageNumberCitation pageCite) {
+        converter.endPageNumberCitation(pageCite);
+        super.endPageNumberCitation(pageCite);
+    }
+
+    @Override
+    public void startPageNumberCitationLast(PageNumberCitationLast pageLast) {
+        converter.startPageNumberCitationLast(pageLast);
+        super.startPageNumberCitationLast(pageLast);
+    }
+
+    @Override
+    public void endPageNumberCitationLast(PageNumberCitationLast pageLast) {
+        converter.endPageNumberCitationLast(pageLast);
+        super.endPageNumberCitationLast(pageLast);
+    }
+
+    @Override
+    public void startFlow(Flow fl) {
+        converter.startFlow(fl);
+        super.startFlow(fl);
+    }
+
+    @Override
+    public void endFlow(Flow fl) {
+        converter.endFlow(fl);
+        super.endFlow(fl);
+    }
+
+    @Override
+    public void startBlock(Block bl) {
+        converter.startBlock(bl);
+        super.startBlock(bl);
+    }
+
+    @Override
+    public void endBlock(Block bl) {
+        converter.endBlock(bl);
+        super.endBlock(bl);
+    }
+
+    @Override
+    public void startBlockContainer(BlockContainer blc) {
+        converter.startBlockContainer(blc);
+        super.startBlockContainer(blc);
+    }
+
+    @Override
+    public void endBlockContainer(BlockContainer blc) {
+        converter.endBlockContainer(blc);
+        super.endBlockContainer(blc);
+    }
+
+    @Override
+    public void startInline(Inline inl) {
+        converter.startInline(inl);
+        super.startInline(inl);
+    }
+
+    @Override
+    public void endInline(Inline inl) {
+        converter.endInline(inl);
+        super.endInline(inl);
+    }
+
+    @Override
+    public void startTable(Table tbl) {
+        converter.startTable(tbl);
+        tableFooterRecorders.push(null);
+        super.startTable(tbl);
+    }
+
+    @Override
+    public void endTable(Table tbl) {
+        FOEventRecorder tableFooterRecorder = tableFooterRecorders.pop();
+        if (tableFooterRecorder != null) {
+            tableFooterRecorder.replay(converter);
+        }
+        converter.endTable(tbl);
+        super.endTable(tbl);
+    }
+
+    @Override
+    public void startColumn(TableColumn tc) {
+        converter.startColumn(tc);
+        super.startColumn(tc);
+    }
+
+    @Override
+    public void endColumn(TableColumn tc) {
+        converter.endColumn(tc);
+        super.endColumn(tc);
+    }
+
+    @Override
+    public void startHeader(TableHeader header) {
+        converter.startHeader(header);
+        super.startHeader(header);
+    }
+
+    @Override
+    public void endHeader(TableHeader header) {
+        converter.endHeader(header);
+        super.endHeader(header);
+    }
+
+    @Override
+    public void startFooter(TableFooter footer) {
+        converters.push(converter);
+        converter = new FOEventRecorder();
+        converter.startFooter(footer);
+        super.startFooter(footer);
+    }
+
+    @Override
+    public void endFooter(TableFooter footer) {
+        converter.endFooter(footer);
+        /* Replace the dummy table footer with the real one. */
+        tableFooterRecorders.pop();
+        tableFooterRecorders.push((FOEventRecorder) converter);
+        converter = converters.pop();
+        super.endFooter(footer);
+    }
+
+    @Override
+    public void startBody(TableBody body) {
+        converter.startBody(body);
+        super.startBody(body);
+    }
+
+    @Override
+    public void endBody(TableBody body) {
+        converter.endBody(body);
+        super.endBody(body);
+    }
+
+    @Override
+    public void startRow(TableRow tr) {
+        converter.startRow(tr);
+        super.startRow(tr);
+    }
+
+    @Override
+    public void endRow(TableRow tr) {
+        converter.endRow(tr);
+        super.endRow(tr);
+    }
+
+    @Override
+    public void startCell(TableCell tc) {
+        converter.startCell(tc);
+        super.startCell(tc);
+    }
+
+    @Override
+    public void endCell(TableCell tc) {
+        converter.endCell(tc);
+        super.endCell(tc);
+    }
+
+    @Override
+    public void startList(ListBlock lb) {
+        converter.startList(lb);
+        super.startList(lb);
+    }
+
+    @Override
+    public void endList(ListBlock lb) {
+        converter.endList(lb);
+        super.endList(lb);
+    }
+
+    @Override
+    public void startListItem(ListItem li) {
+        converter.startListItem(li);
+        super.startListItem(li);
+    }
+
+    @Override
+    public void endListItem(ListItem li) {
+        converter.endListItem(li);
+        super.endListItem(li);
+    }
+
+    @Override
+    public void startListLabel(ListItemLabel listItemLabel) {
+        converter.startListLabel(listItemLabel);
+        super.startListLabel(listItemLabel);
+    }
+
+    @Override
+    public void endListLabel(ListItemLabel listItemLabel) {
+        converter.endListLabel(listItemLabel);
+        super.endListLabel(listItemLabel);
+    }
+
+    @Override
+    public void startListBody(ListItemBody listItemBody) {
+        converter.startListBody(listItemBody);
+        super.startListBody(listItemBody);
+    }
+
+    @Override
+    public void endListBody(ListItemBody listItemBody) {
+        converter.endListBody(listItemBody);
+        super.endListBody(listItemBody);
+    }
+
+    @Override
+    public void startStatic(StaticContent staticContent) {
+        converter.startStatic(staticContent);
+        super.startStatic(staticContent);
+    }
+
+    @Override
+    public void endStatic(StaticContent statisContent) {
+        converter.endStatic(statisContent);
+        super.endStatic(statisContent);
+    }
+
+    @Override
+    public void startMarkup() {
+        converter.startMarkup();
+        super.startMarkup();
+    }
+
+    @Override
+    public void endMarkup() {
+        converter.endMarkup();
+        super.endMarkup();
+    }
+
+    @Override
+    public void startLink(BasicLink basicLink) {
+        converter.startLink(basicLink);
+        super.startLink(basicLink);
+    }
+
+    @Override
+    public void endLink(BasicLink basicLink) {
+        converter.endLink(basicLink);
+        super.endLink(basicLink);
+    }
+
+    @Override
+    public void image(ExternalGraphic eg) {
+        converter.image(eg);
+        super.image(eg);
+    }
+
+    @Override
+    public void pageRef() {
+        converter.pageRef();
+        super.pageRef();
+    }
+
+    @Override
+    public void startInstreamForeignObject(InstreamForeignObject ifo) {
+        converter.startInstreamForeignObject(ifo);
+        super.startInstreamForeignObject(ifo);
+    }
+
+    @Override
+    public void endInstreamForeignObject(InstreamForeignObject ifo) {
+        converter.endInstreamForeignObject(ifo);
+        super.endInstreamForeignObject(ifo);
+    }
+
+    @Override
+    public void startFootnote(Footnote footnote) {
+        converter.startFootnote(footnote);
+        super.startFootnote(footnote);
+    }
+
+    @Override
+    public void endFootnote(Footnote footnote) {
+        converter.endFootnote(footnote);
+        super.endFootnote(footnote);
+    }
+
+    @Override
+    public void startFootnoteBody(FootnoteBody body) {
+        converter.startFootnoteBody(body);
+        super.startFootnoteBody(body);
+    }
+
+    @Override
+    public void endFootnoteBody(FootnoteBody body) {
+        converter.endFootnoteBody(body);
+        super.endFootnoteBody(body);
+    }
+
+    @Override
+    public void startLeader(Leader l) {
+        converters.push(converter);
+        converter = eventSwallower;
+        converter.startLeader(l);
+        super.startLeader(l);
+    }
+
+    @Override
+    public void endLeader(Leader l) {
+        converter.endLeader(l);
+        converter = converters.pop();
+        super.endLeader(l);
+    }
+
+    @Override
+    public void startWrapper(Wrapper wrapper) {
+        converter.startWrapper(wrapper);
+        super.startWrapper(wrapper);
+    }
+
+    @Override
+    public void endWrapper(Wrapper wrapper) {
+        converter.endWrapper(wrapper);
+        super.endWrapper(wrapper);
+    }
+
+    @Override
+    public void character(Character c) {
+        converter.character(c);
+        super.character(c);
+    }
+
+    @Override
+    public void characters(FOText foText) {
+        converter.characters(foText);
+        super.characters(foText);
+    }
+
+    @Override
+    public void startExternalDocument(ExternalDocument document) {
+        converter.startExternalDocument(document);
+        super.startExternalDocument(document);
+    }
+
+    @Override
+    public void endExternalDocument(ExternalDocument document) {
+        converter.endExternalDocument(document);
+        super.endExternalDocument(document);
+    }
+
+}
diff --git a/src/java/org/apache/fop/accessibility/fo/FOEventRecorder.java b/src/java/org/apache/fop/accessibility/fo/FOEventRecorder.java
new file mode 100644
index 000000000..b2b18046d
--- /dev/null
+++ b/src/java/org/apache/fop/accessibility/fo/FOEventRecorder.java
@@ -0,0 +1,508 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility.fo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.fo.FOEventHandler;
+import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.flow.BasicLink;
+import org.apache.fop.fo.flow.Block;
+import org.apache.fop.fo.flow.BlockContainer;
+import org.apache.fop.fo.flow.Character;
+import org.apache.fop.fo.flow.ExternalGraphic;
+import org.apache.fop.fo.flow.Footnote;
+import org.apache.fop.fo.flow.FootnoteBody;
+import org.apache.fop.fo.flow.Inline;
+import org.apache.fop.fo.flow.InstreamForeignObject;
+import org.apache.fop.fo.flow.Leader;
+import org.apache.fop.fo.flow.ListBlock;
+import org.apache.fop.fo.flow.ListItem;
+import org.apache.fop.fo.flow.ListItemBody;
+import org.apache.fop.fo.flow.ListItemLabel;
+import org.apache.fop.fo.flow.PageNumber;
+import org.apache.fop.fo.flow.PageNumberCitation;
+import org.apache.fop.fo.flow.PageNumberCitationLast;
+import org.apache.fop.fo.flow.Wrapper;
+import org.apache.fop.fo.flow.table.Table;
+import org.apache.fop.fo.flow.table.TableBody;
+import org.apache.fop.fo.flow.table.TableCell;
+import org.apache.fop.fo.flow.table.TableColumn;
+import org.apache.fop.fo.flow.table.TableFooter;
+import org.apache.fop.fo.flow.table.TableHeader;
+import org.apache.fop.fo.flow.table.TableRow;
+
+final class FOEventRecorder extends FOEventHandler {
+
+    private interface Event {
+        void replay(FOEventHandler target);
+    }
+
+    private final List events = new ArrayList();
+
+    public void replay(FOEventHandler target) {
+        for (Event event : events) {
+            event.replay(target);
+        }
+    }
+
+    @Override
+    public void startPageNumber(final PageNumber pagenum) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startPageNumber(pagenum);
+            }
+        });
+    }
+
+    @Override
+    public void endPageNumber(final PageNumber pagenum) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endPageNumber(pagenum);
+            }
+        });
+    }
+
+    @Override
+    public void startPageNumberCitation(final PageNumberCitation pageCite) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startPageNumberCitation(pageCite);
+            }
+        });
+    }
+
+    @Override
+    public void endPageNumberCitation(final PageNumberCitation pageCite) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endPageNumberCitation(pageCite);
+            }
+        });
+    }
+
+    @Override
+    public void startPageNumberCitationLast(final PageNumberCitationLast pageLast) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startPageNumberCitationLast(pageLast);
+            }
+        });
+    }
+
+    @Override
+    public void endPageNumberCitationLast(final PageNumberCitationLast pageLast) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endPageNumberCitationLast(pageLast);
+            }
+        });
+    }
+
+    @Override
+    public void startBlock(final Block bl) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startBlock(bl);
+            }
+        });
+    }
+
+    @Override
+    public void endBlock(final Block bl) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endBlock(bl);
+            }
+        });
+    }
+
+    @Override
+    public void startBlockContainer(final BlockContainer blc) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startBlockContainer(blc);
+            }
+        });
+    }
+
+    @Override
+    public void endBlockContainer(final BlockContainer blc) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endBlockContainer(blc);
+            }
+        });
+    }
+
+    @Override
+    public void startInline(final Inline inl) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startInline(inl);
+            }
+        });
+    }
+
+    @Override
+    public void endInline(final Inline inl) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endInline(inl);
+            }
+        });
+    }
+
+    @Override
+    public void startTable(final Table tbl) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startTable(tbl);
+            }
+        });
+    }
+
+    @Override
+    public void endTable(final Table tbl) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endTable(tbl);
+            }
+        });
+    }
+
+    @Override
+    public void startColumn(final TableColumn tc) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startColumn(tc);
+            }
+        });
+    }
+
+    @Override
+    public void endColumn(final TableColumn tc) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endColumn(tc);
+            }
+        });
+    }
+
+    @Override
+    public void startHeader(final TableHeader header) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startHeader(header);
+            }
+        });
+    }
+
+    @Override
+    public void endHeader(final TableHeader header) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endHeader(header);
+            }
+        });
+    }
+
+    @Override
+    public void startFooter(final TableFooter footer) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startFooter(footer);
+            }
+        });
+    }
+
+    @Override
+    public void endFooter(final TableFooter footer) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endFooter(footer);
+            }
+        });
+    }
+
+    @Override
+    public void startBody(final TableBody body) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startBody(body);
+            }
+        });
+    }
+
+    @Override
+    public void endBody(final TableBody body) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endBody(body);
+            }
+        });
+    }
+
+    @Override
+    public void startRow(final TableRow tr) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startRow(tr);
+            }
+        });
+    }
+
+    @Override
+    public void endRow(final TableRow tr) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endRow(tr);
+            }
+        });
+    }
+
+    @Override
+    public void startCell(final TableCell tc) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startCell(tc);
+            }
+        });
+    }
+
+    @Override
+    public void endCell(final TableCell tc) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endCell(tc);
+            }
+        });
+    }
+
+    @Override
+    public void startList(final ListBlock lb) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startList(lb);
+            }
+        });
+    }
+
+    @Override
+    public void endList(final ListBlock lb) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endList(lb);
+            }
+        });
+    }
+
+    @Override
+    public void startListItem(final ListItem li) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startListItem(li);
+            }
+        });
+    }
+
+    @Override
+    public void endListItem(final ListItem li) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endListItem(li);
+            }
+        });
+    }
+
+    @Override
+    public void startListLabel(final ListItemLabel listItemLabel) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startListLabel(listItemLabel);
+            }
+        });
+    }
+
+    @Override
+    public void endListLabel(final ListItemLabel listItemLabel) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endListLabel(listItemLabel);
+            }
+        });
+    }
+
+    @Override
+    public void startListBody(final ListItemBody listItemBody) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startListBody(listItemBody);
+            }
+        });
+    }
+
+    @Override
+    public void endListBody(final ListItemBody listItemBody) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endListBody(listItemBody);
+            }
+        });
+    }
+
+    @Override
+    public void startLink(final BasicLink basicLink) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startLink(basicLink);
+            }
+        });
+    }
+
+    @Override
+    public void endLink(final BasicLink basicLink) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endLink(basicLink);
+            }
+        });
+    }
+
+    @Override
+    public void image(final ExternalGraphic eg) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.image(eg);
+            }
+        });
+    }
+
+    @Override
+    public void startInstreamForeignObject(final InstreamForeignObject ifo) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startInstreamForeignObject(ifo);
+            }
+        });
+    }
+
+    @Override
+    public void endInstreamForeignObject(final InstreamForeignObject ifo) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endInstreamForeignObject(ifo);
+            }
+        });
+    }
+
+    @Override
+    public void startFootnote(final Footnote footnote) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startFootnote(footnote);
+            }
+        });
+    }
+
+    @Override
+    public void endFootnote(final Footnote footnote) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endFootnote(footnote);
+            }
+        });
+    }
+
+    @Override
+    public void startFootnoteBody(final FootnoteBody body) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startFootnoteBody(body);
+            }
+        });
+    }
+
+    @Override
+    public void endFootnoteBody(final FootnoteBody body) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endFootnoteBody(body);
+            }
+        });
+    }
+
+    @Override
+    public void startLeader(final Leader l) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startLeader(l);
+            }
+        });
+    }
+
+    @Override
+    public void endLeader(final Leader l) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endLeader(l);
+            }
+        });
+    }
+
+    @Override
+    public void startWrapper(final Wrapper wrapper) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.startWrapper(wrapper);
+            }
+        });
+    }
+
+    @Override
+    public void endWrapper(final Wrapper wrapper) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.endWrapper(wrapper);
+            }
+        });
+    }
+
+    @Override
+    public void character(final Character c) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.character(c);
+            }
+        });
+    }
+
+    @Override
+    public void characters(final FOText foText) {
+        events.add(new Event() {
+            public void replay(FOEventHandler target) {
+                target.characters(foText);
+            }
+        });
+    }
+
+}
diff --git a/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java b/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java
new file mode 100644
index 000000000..b5cd0a5a4
--- /dev/null
+++ b/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java
@@ -0,0 +1,408 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility.fo;
+
+import java.util.Locale;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.accessibility.StructureTreeElement;
+import org.apache.fop.accessibility.StructureTreeEventHandler;
+import org.apache.fop.fo.FOEventHandler;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.extensions.ExtensionElementMapping;
+import org.apache.fop.fo.flow.AbstractGraphics;
+import org.apache.fop.fo.flow.BasicLink;
+import org.apache.fop.fo.flow.Block;
+import org.apache.fop.fo.flow.BlockContainer;
+import org.apache.fop.fo.flow.Character;
+import org.apache.fop.fo.flow.ExternalGraphic;
+import org.apache.fop.fo.flow.Footnote;
+import org.apache.fop.fo.flow.FootnoteBody;
+import org.apache.fop.fo.flow.Inline;
+import org.apache.fop.fo.flow.InstreamForeignObject;
+import org.apache.fop.fo.flow.ListBlock;
+import org.apache.fop.fo.flow.ListItem;
+import org.apache.fop.fo.flow.ListItemBody;
+import org.apache.fop.fo.flow.ListItemLabel;
+import org.apache.fop.fo.flow.PageNumber;
+import org.apache.fop.fo.flow.PageNumberCitation;
+import org.apache.fop.fo.flow.PageNumberCitationLast;
+import org.apache.fop.fo.flow.Wrapper;
+import org.apache.fop.fo.flow.table.Table;
+import org.apache.fop.fo.flow.table.TableBody;
+import org.apache.fop.fo.flow.table.TableCell;
+import org.apache.fop.fo.flow.table.TableFooter;
+import org.apache.fop.fo.flow.table.TableHeader;
+import org.apache.fop.fo.flow.table.TableRow;
+import org.apache.fop.fo.pagination.Flow;
+import org.apache.fop.fo.pagination.PageSequence;
+import org.apache.fop.fo.pagination.StaticContent;
+import org.apache.fop.fo.properties.CommonAccessibilityHolder;
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * A bridge between {@link FOEventHandler} and {@link StructureTreeEventHandler}.
+ */
+class StructureTreeEventTrigger extends FOEventHandler {
+
+    private StructureTreeEventHandler structureTreeEventHandler;
+
+    public StructureTreeEventTrigger(StructureTreeEventHandler structureTreeEventHandler) {
+        this.structureTreeEventHandler = structureTreeEventHandler;
+    }
+
+    @Override
+    public void startDocument() throws SAXException {
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+    }
+
+    @Override
+    public void startPageSequence(PageSequence pageSeq) {
+        Locale locale = null;
+        if (pageSeq.getLanguage() != null) {
+            if (pageSeq.getCountry() != null) {
+                locale = new Locale(pageSeq.getLanguage(), pageSeq.getCountry());
+            } else {
+                locale = new Locale(pageSeq.getLanguage());
+            }
+        }
+        structureTreeEventHandler.startPageSequence(locale);
+    }
+
+    @Override
+    public void endPageSequence(PageSequence pageSeq) {
+        structureTreeEventHandler.endPageSequence();
+    }
+
+    @Override
+    public void startPageNumber(PageNumber pagenum) {
+        startElementWithID(pagenum);
+    }
+
+    @Override
+    public void endPageNumber(PageNumber pagenum) {
+        endElement(pagenum);
+    }
+
+    @Override
+    public void startPageNumberCitation(PageNumberCitation pageCite) {
+        startElementWithID(pageCite);
+    }
+
+    @Override
+    public void endPageNumberCitation(PageNumberCitation pageCite) {
+        endElement(pageCite);
+    }
+
+    @Override
+    public void startPageNumberCitationLast(PageNumberCitationLast pageLast) {
+        startElementWithID(pageLast);
+    }
+
+    @Override
+    public void endPageNumberCitationLast(PageNumberCitationLast pageLast) {
+        endElement(pageLast);
+    }
+
+    @Override
+    public void startFlow(Flow fl) {
+        startElement(fl);
+    }
+
+    @Override
+    public void endFlow(Flow fl) {
+        endElement(fl);
+    }
+
+    @Override
+    public void startBlock(Block bl) {
+        startElement(bl);
+    }
+
+    @Override
+    public void endBlock(Block bl) {
+        endElement(bl);
+    }
+
+    @Override
+    public void startBlockContainer(BlockContainer blc) {
+        startElement(blc);
+    }
+
+    @Override
+    public void endBlockContainer(BlockContainer blc) {
+        endElement(blc);
+    }
+
+    @Override
+    public void startInline(Inline inl) {
+        startElement(inl);
+    }
+
+    @Override
+    public void endInline(Inline inl) {
+        endElement(inl);
+    }
+
+    @Override
+    public void startTable(Table tbl) {
+        startElement(tbl);
+    }
+
+    @Override
+    public void endTable(Table tbl) {
+        endElement(tbl);
+    }
+
+    @Override
+    public void startHeader(TableHeader header) {
+        startElement(header);
+    }
+
+    @Override
+    public void endHeader(TableHeader header) {
+        endElement(header);
+    }
+
+    @Override
+    public void startFooter(TableFooter footer) {
+        startElement(footer);
+    }
+
+    @Override
+    public void endFooter(TableFooter footer) {
+        endElement(footer);
+    }
+
+    @Override
+    public void startBody(TableBody body) {
+        startElement(body);
+    }
+
+    @Override
+    public void endBody(TableBody body) {
+        endElement(body);
+    }
+
+    @Override
+    public void startRow(TableRow tr) {
+        startElement(tr);
+    }
+
+    @Override
+    public void endRow(TableRow tr) {
+        endElement(tr);
+    }
+
+    @Override
+    public void startCell(TableCell tc) {
+        AttributesImpl attributes = new AttributesImpl();
+        int colSpan = tc.getNumberColumnsSpanned();
+        if (colSpan > 1) {
+            addNoNamespaceAttribute(attributes, "number-columns-spanned",
+                    Integer.toString(colSpan));
+        }
+        startElement(tc, attributes);
+    }
+
+    @Override
+    public void endCell(TableCell tc) {
+        endElement(tc);
+    }
+
+    @Override
+    public void startList(ListBlock lb) {
+        startElement(lb);
+    }
+
+    @Override
+    public void endList(ListBlock lb) {
+        endElement(lb);
+    }
+
+    @Override
+    public void startListItem(ListItem li) {
+        startElement(li);
+    }
+
+    @Override
+    public void endListItem(ListItem li) {
+        endElement(li);
+    }
+
+    @Override
+    public void startListLabel(ListItemLabel listItemLabel) {
+        startElement(listItemLabel);
+    }
+
+    @Override
+    public void endListLabel(ListItemLabel listItemLabel) {
+        endElement(listItemLabel);
+    }
+
+    @Override
+    public void startListBody(ListItemBody listItemBody) {
+        startElement(listItemBody);
+    }
+
+    @Override
+    public void endListBody(ListItemBody listItemBody) {
+        endElement(listItemBody);
+    }
+
+    @Override
+    public void startStatic(StaticContent staticContent) {
+        startElement(staticContent);
+    }
+
+    @Override
+    public void endStatic(StaticContent statisContent) {
+        endElement(statisContent);
+    }
+
+    @Override
+    public void startLink(BasicLink basicLink) {
+        startElementWithID(basicLink);
+    }
+
+    @Override
+    public void endLink(BasicLink basicLink) {
+        endElement(basicLink);
+    }
+
+    @Override
+    public void image(ExternalGraphic eg) {
+        startElementWithIDAndAltText(eg);
+        endElement(eg);
+    }
+
+    @Override
+    public void startInstreamForeignObject(InstreamForeignObject ifo) {
+        startElementWithIDAndAltText(ifo);
+    }
+
+    @Override
+    public void endInstreamForeignObject(InstreamForeignObject ifo) {
+        endElement(ifo);
+    }
+
+    @Override
+    public void startFootnote(Footnote footnote) {
+        startElement(footnote);
+    }
+
+    @Override
+    public void endFootnote(Footnote footnote) {
+        endElement(footnote);
+    }
+
+    @Override
+    public void startFootnoteBody(FootnoteBody body) {
+        startElement(body);
+    }
+
+    @Override
+    public void endFootnoteBody(FootnoteBody body) {
+        endElement(body);
+    }
+
+    @Override
+    public void startWrapper(Wrapper wrapper) {
+        startElement(wrapper);
+    }
+
+    @Override
+    public void endWrapper(Wrapper wrapper) {
+        endElement(wrapper);
+    }
+
+    @Override
+    public void character(Character c) {
+        startElementWithID(c);
+        endElement(c);
+    }
+
+    @Override
+    public void characters(FOText foText) {
+        startElementWithID(foText);
+        endElement(foText);
+    }
+
+
+    private void startElement(FONode node) {
+        startElement(node, new AttributesImpl());
+    }
+
+    private void startElementWithID(FONode node) {
+        AttributesImpl attributes = new AttributesImpl();
+        String localName = node.getLocalName();
+        if (node instanceof CommonAccessibilityHolder) {
+            addRole((CommonAccessibilityHolder) node, attributes);
+        }
+        node.setStructureTreeElement(structureTreeEventHandler.startReferencedNode(localName, attributes));
+    }
+
+    private void startElementWithIDAndAltText(AbstractGraphics node) {
+        AttributesImpl attributes = new AttributesImpl();
+        String localName = node.getLocalName();
+        addRole(node, attributes);
+        addAttribute(attributes, ExtensionElementMapping.URI, "alt-text",
+                ExtensionElementMapping.STANDARD_PREFIX, node.getAltText());
+        node.setStructureTreeElement(structureTreeEventHandler.startImageNode(localName, attributes));
+    }
+
+    private StructureTreeElement startElement(FONode node, AttributesImpl attributes) {
+        String localName = node.getLocalName();
+        if (node instanceof CommonAccessibilityHolder) {
+            addRole((CommonAccessibilityHolder) node, attributes);
+        }
+        return structureTreeEventHandler.startNode(localName, attributes);
+    }
+
+    private void addNoNamespaceAttribute(AttributesImpl attributes, String name, String value) {
+        attributes.addAttribute("", name, name, XMLUtil.CDATA, value);
+    }
+
+    private void addAttribute(AttributesImpl attributes,
+            String namespace, String localName, String prefix, String value) {
+        assert namespace.length() > 0 && prefix.length() > 0;
+        String qualifiedName = prefix + ":" + localName;
+        attributes.addAttribute(namespace, localName, qualifiedName, XMLUtil.CDATA, value);
+    }
+
+    private void addRole(CommonAccessibilityHolder node, AttributesImpl attributes) {
+        String role = node.getCommonAccessibility().getRole();
+        if (role != null) {
+            addNoNamespaceAttribute(attributes, "role", role);
+        }
+    }
+
+    private void endElement(FONode node) {
+        String localName = node.getLocalName();
+        structureTreeEventHandler.endNode(localName);
+    }
+
+}
diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java
index 545c9015d..29a363495 100644
--- a/src/java/org/apache/fop/area/AreaTreeParser.java
+++ b/src/java/org/apache/fop/area/AreaTreeParser.java
@@ -728,7 +728,6 @@ public class AreaTreeParser {
                 setTraits(attributes, ip, SUBSET_BOX);
                 setTraits(attributes, ip, SUBSET_COLOR);
                 setTraits(attributes, ip, SUBSET_LINK);
-                setPtr(ip, attributes);
                 Area parent = (Area)areaStack.peek();
                 parent.addChildArea(ip);
                 areaStack.push(ip);
@@ -777,7 +776,6 @@ public class AreaTreeParser {
                         "tlsadjust", 0));
                 text.setTextWordSpaceAdjust(XMLUtil.getAttributeAsInt(attributes,
                         "twsadjust", 0));
-                setPtr(text, attributes);
                 Area parent = (Area)areaStack.peek();
                 parent.addChildArea(text);
                 areaStack.push(text);
@@ -870,7 +868,6 @@ public class AreaTreeParser {
                 viewport.setContentPosition(XMLUtil.getAttributeAsRectangle2D(attributes, "pos"));
                 viewport.setClip(XMLUtil.getAttributeAsBoolean(attributes, "clip", false));
                 viewport.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
-                setPtr(viewport, attributes);
                 Area parent = (Area)areaStack.peek();
                 parent.addChildArea(viewport);
                 areaStack.push(viewport);
@@ -889,7 +886,6 @@ public class AreaTreeParser {
                 transferForeignObjects(attributes, image);
                 setAreaAttributes(attributes, image);
                 setTraits(attributes, image, SUBSET_COMMON);
-                setPtr(image, attributes);
                 getCurrentViewport().setContent(image);
             }
         }
@@ -1174,13 +1170,6 @@ public class AreaTreeParser {
             }
         }
 
-        private void setPtr(Area area, Attributes attributes) {
-            String ptr = attributes.getValue("ptr");
-            if (ptr != null) {
-                area.addTrait(Trait.PTR, ptr);
-            }
-        }
-
         /** {@inheritDoc} */
         public void characters(char[] ch, int start, int length) throws SAXException {
             if (delegate != null) {
diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java
index 042b65337..d9194559d 100644
--- a/src/java/org/apache/fop/area/Trait.java
+++ b/src/java/org/apache/fop/area/Trait.java
@@ -153,8 +153,8 @@ public final class Trait implements Serializable {
     /** Trait for color of linethrough decorations when rendering inline parent. */
     public static final Integer LINETHROUGH_COLOR = 36;
 
-    /** The ptr trait. Used for accessibility   */
-    public static final Integer PTR = 37;
+    /** For navigation in the document structure. */
+    public static final Integer STRUCTURE_TREE_ELEMENT = 37;
 
     /** Maximum value used by trait keys */
     public static final int MAX_TRAIT_KEY = 37;
@@ -186,7 +186,7 @@ public final class Trait implements Serializable {
     static {
         // Create a hashmap mapping trait code to name for external representation
         //put(ID_LINK, new TraitInfo("id-link", String.class));
-        put(PTR,            new TraitInfo("ptr", String.class));
+        put(STRUCTURE_TREE_ELEMENT, new TraitInfo("structure-tree-element", String.class));
         put(INTERNAL_LINK,  new TraitInfo("internal-link", InternalLink.class));
         put(EXTERNAL_LINK,  new TraitInfo("external-link", ExternalLink.class));
         put(FONT,           new TraitInfo("font", FontTriplet.class));
diff --git a/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java b/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java
index ed85bd1c9..e64106a80 100644
--- a/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java
+++ b/src/java/org/apache/fop/fo/DelegatingFOEventHandler.java
@@ -387,8 +387,8 @@ public abstract class DelegatingFOEventHandler extends FOEventHandler {
     }
 
     @Override
-    public void characters(char[] data, int start, int length) {
-        delegate.characters(data, start, length);
+    public void characters(FOText foText) {
+        delegate.characters(foText);
     }
 
     @Override
diff --git a/src/java/org/apache/fop/fo/FOEventHandler.java b/src/java/org/apache/fop/fo/FOEventHandler.java
index 1f3514dea..bed1f3677 100644
--- a/src/java/org/apache/fop/fo/FOEventHandler.java
+++ b/src/java/org/apache/fop/fo/FOEventHandler.java
@@ -88,6 +88,10 @@ public abstract class FOEventHandler {
         this.fontInfo.setEventListener(new FontEventAdapter(foUserAgent.getEventBroadcaster()));
     }
 
+    /** Constructor for sub-classes that do not need an {@link FOUserAgent} instance. */
+    protected FOEventHandler() {
+    }
+
     /**
      * Returns the User Agent object associated with this FOEventHandler.
      * @return the User Agent object
@@ -532,11 +536,9 @@ public abstract class FOEventHandler {
 
     /**
      * Process character data.
-     * @param data Array of characters to process.
-     * @param start Offset for characters to process.
-     * @param length Portion of array to process.
+     * @param foText text to process
      */
-    public void characters(char[] data, int start, int length) {
+    public void characters(FOText foText) {
     }
 
     /**
diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java
index 4946f6d5c..622ff86d4 100644
--- a/src/java/org/apache/fop/fo/FONode.java
+++ b/src/java/org/apache/fop/fo/FONode.java
@@ -32,6 +32,7 @@ import org.apache.commons.logging.LogFactory;
 
 import org.apache.xmlgraphics.util.QName;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
@@ -912,7 +913,7 @@ public abstract class FONode implements Cloneable {
 
     }
 
-    public void setPtr(String ptr) {
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/src/java/org/apache/fop/fo/FOText.java b/src/java/org/apache/fop/fo/FOText.java
index 5db11f731..3858cdea9 100644
--- a/src/java/org/apache/fop/fo/FOText.java
+++ b/src/java/org/apache/fop/fo/FOText.java
@@ -25,6 +25,7 @@ import java.util.NoSuchElementException;
 
 import org.xml.sax.Locator;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.flow.Block;
@@ -79,6 +80,8 @@ public class FOText extends FONode implements CharSequence {
     /** Holds the text decoration values. May be null */
     private CommonTextDecoration textDecoration;
 
+    private StructureTreeElement structureTreeElement;
+
     private static final int IS_WORD_CHAR_FALSE = 0;
     private static final int IS_WORD_CHAR_TRUE = 1;
     private static final int IS_WORD_CHAR_MAYBE = 2;
@@ -115,25 +118,14 @@ public class FOText extends FONode implements CharSequence {
     /**
      * Return the array of characters for this instance.
      *
-     * @return  a char array containing the text
+     * @return  a char sequence containing the text
      */
-    public char[] getCharArray() {
-
+    public CharSequence getCharSequence() {
         if (this.charBuffer == null) {
             return null;
         }
-
-        if (this.charBuffer.hasArray()) {
-            return this.charBuffer.array();
-        }
-
-        // only if the buffer implementation has
-        // no accessible backing array, return a new one
-        char[] ca = new char[this.charBuffer.limit()];
         this.charBuffer.rewind();
-        this.charBuffer.get(ca);
-        return ca;
-
+        return this.charBuffer.asReadOnlyBuffer().subSequence(0, this.charBuffer.limit());
     }
 
     /** {@inheritDoc} */
@@ -176,8 +168,7 @@ public class FOText extends FONode implements CharSequence {
     /** {@inheritDoc} */
     protected void endOfNode() throws FOPException {
         super.endOfNode();
-        getFOEventHandler().characters(
-                this.getCharArray(), 0, this.charBuffer.limit());
+        getFOEventHandler().characters(this);
     }
 
     /** {@inheritDoc} */
@@ -670,4 +661,14 @@ public class FOText extends FONode implements CharSequence {
             this.charBuffer.rewind();
         }
     }
+
+    @Override
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
+    }
+
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
+    }
+
 }
diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java
index b053692d6..ece4e5448 100644
--- a/src/java/org/apache/fop/fo/FOTreeBuilder.java
+++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java
@@ -33,7 +33,7 @@ import org.apache.commons.logging.LogFactory;
 
 import org.apache.xmlgraphics.util.QName;
 
-import org.apache.fop.accessibility.FO2StructureTreeConverter;
+import org.apache.fop.accessibility.fo.FO2StructureTreeConverter;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.FormattingResults;
diff --git a/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java
index e939841e1..687952d25 100644
--- a/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java
+++ b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java
@@ -37,11 +37,18 @@ public class InternalElementMapping extends ElementMapping {
     /** The standard XML prefix for elements and attributes in this namespace. */
     public static final String STANDARD_PREFIX = "foi";
 
+    /** The "struct-id" attribute, to identify a structure tree element. */
+    public static final String STRUCT_ID = "struct-id";
+
+    /** The "struct-ref" attribute, to refer to a structure tree element. */
+    public static final String STRUCT_REF = "struct-ref";
+
     private static final Set PROPERTY_ATTRIBUTES = new java.util.HashSet();
 
     static {
         //These are FOP's extension properties for accessibility
-        PROPERTY_ATTRIBUTES.add("ptr");
+        PROPERTY_ATTRIBUTES.add(STRUCT_ID);
+        PROPERTY_ATTRIBUTES.add(STRUCT_REF);
     }
 
     /**
diff --git a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java
index 67a33d088..2b8fa8a0b 100644
--- a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java
+++ b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java
@@ -19,6 +19,7 @@
 
 package org.apache.fop.fo.flow;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.FONode;
@@ -31,7 +32,7 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.LengthRangeProperty;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
+import org.apache.fop.fo.properties.StructureTreeElementHolder;
 
 /**
  * Common base class for the 
@@ -40,7 +41,7 @@ import org.apache.fop.fo.properties.StructurePointerPropertySet;
  * fo:external-graphic flow formatting objects.
  */
 public abstract class AbstractGraphics extends FObj
-        implements GraphicsProperties, StructurePointerPropertySet, CommonAccessibilityHolder {
+        implements GraphicsProperties, StructureTreeElementHolder, CommonAccessibilityHolder {
 
     // The value of properties relevant for fo:instream-foreign-object
     // and external-graphics.
@@ -66,7 +67,7 @@ public abstract class AbstractGraphics extends FObj
     private int textAlign;
     private Length width;
     private String altText;
-    private String ptr;   // used for accessibility
+    private StructureTreeElement structureTreeElement;
     // Unused but valid items, commented out for performance:
     //     private CommonAccessibility commonAccessibility;
     //     private CommonAural commonAural;
@@ -226,13 +227,13 @@ public abstract class AbstractGraphics extends FObj
     }
 
     @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
     }
 
     public String getAltText() {
diff --git a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
index bbb632b29..108896d91 100644
--- a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
+++ b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
@@ -24,6 +24,7 @@ import java.awt.Color;
 import org.xml.sax.Attributes;
 import org.xml.sax.Locator;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.Constants;
@@ -37,7 +38,7 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.CommonFont;
 import org.apache.fop.fo.properties.CommonTextDecoration;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
+import org.apache.fop.fo.properties.StructureTreeElementHolder;
 
 /**
  * Common base class for the 
@@ -46,7 +47,7 @@ import org.apache.fop.fo.properties.StructurePointerPropertySet;
  * fo:page-number-citation-last objects.
  */
 public abstract class AbstractPageNumberCitation extends FObj
-        implements StructurePointerPropertySet, CommonAccessibilityHolder {
+        implements StructureTreeElementHolder, CommonAccessibilityHolder {
 
     // The value of properties relevant for fo:page-number-citation(-last).
     private CommonAccessibility commonAccessibility;
@@ -56,7 +57,7 @@ public abstract class AbstractPageNumberCitation extends FObj
     private int alignmentBaseline;
     private Length baselineShift;
     private int dominantBaseline;
-    private String ptr;  // used for accessibility
+    private StructureTreeElement structureTreeElement;
     // private ToBeImplementedProperty letterSpacing;
     private SpaceProperty lineHeight;
     private String refId;
@@ -151,13 +152,13 @@ public abstract class AbstractPageNumberCitation extends FObj
     }
 
     @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
     }
 
     /** @return the "alignment-adjust" property */
diff --git a/src/java/org/apache/fop/fo/flow/BasicLink.java b/src/java/org/apache/fop/fo/flow/BasicLink.java
index 7bff2d521..0d6d5d9b4 100644
--- a/src/java/org/apache/fop/fo/flow/BasicLink.java
+++ b/src/java/org/apache/fop/fo/flow/BasicLink.java
@@ -21,12 +21,13 @@ package org.apache.fop.fo.flow;
 
 import org.xml.sax.Locator;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
+import org.apache.fop.fo.properties.StructureTreeElementHolder;
 
 /**
  * Class modelling the 
@@ -36,14 +37,14 @@ import org.apache.fop.fo.properties.StructurePointerPropertySet;
  * and whether that link is external (uses a URI) or internal (an id
  * reference).
  */
-public class BasicLink extends InlineLevel implements StructurePointerPropertySet {
+public class BasicLink extends InlineLevel implements StructureTreeElementHolder {
 
     // The value of properties relevant for fo:basic-link.
     private Length alignmentAdjust;
     private int alignmentBaseline;
     private Length baselineShift;
     private int dominantBaseline;
-    private String ptr;
+    private StructureTreeElement structureTreeElement;
     // private ToBeImplementedProperty destinationPlacementOffset;
     private String externalDestination;
     // private ToBeImplementedProperty indicateDestination;
@@ -143,13 +144,13 @@ public class BasicLink extends InlineLevel implements StructurePointerPropertySe
     }
 
     @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
     }
 
     /**
diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java
index b1705288a..4e5b6f15f 100644
--- a/src/java/org/apache/fop/fo/flow/Block.java
+++ b/src/java/org/apache/fop/fo/flow/Block.java
@@ -42,13 +42,12 @@ import org.apache.fop.fo.properties.CommonMarginBlock;
 import org.apache.fop.fo.properties.CommonRelativePosition;
 import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
 
  /**
   * Class modelling the 
   * fo:block object.
   */
-public class Block extends FObjMixed implements BreakPropertySet, StructurePointerPropertySet,
+public class Block extends FObjMixed implements BreakPropertySet,
         CommonAccessibilityHolder {
 
     // used for FO validation
@@ -77,7 +76,6 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
     private int lineHeightShiftAdjustment;
     private int lineStackingStrategy;
     private Numeric orphans;
-    private String ptr;  //used for accessibility
     private int whiteSpaceTreatment;
     private int span;
     private int textAlign;
@@ -183,16 +181,6 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint
         return breakAfter;
     }
 
-    @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
-    }
-
-    /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
-    }
-
     /** @return the "break-before" property. */
     public int getBreakBefore() {
         return breakBefore;
diff --git a/src/java/org/apache/fop/fo/flow/Character.java b/src/java/org/apache/fop/fo/flow/Character.java
index a33bea53d..c4de9fb72 100644
--- a/src/java/org/apache/fop/fo/flow/Character.java
+++ b/src/java/org/apache/fop/fo/flow/Character.java
@@ -24,6 +24,7 @@ import java.util.NoSuchElementException;
 
 import org.xml.sax.Locator;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.CharIterator;
@@ -38,13 +39,13 @@ import org.apache.fop.fo.properties.CommonTextDecoration;
 import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.Property;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
+import org.apache.fop.fo.properties.StructureTreeElementHolder;
 
 /**
  * Class modelling the 
  * fo:character object.
  */
-public class Character extends FObj implements StructurePointerPropertySet {
+public class Character extends FObj implements StructureTreeElementHolder {
     // The value of properties relevant for fo:character.
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
     private CommonFont commonFont;
@@ -63,7 +64,7 @@ public class Character extends FObj implements StructurePointerPropertySet {
     private CommonTextDecoration textDecoration;
     // private ToBeImplementedProperty textShadow;
     private Property wordSpacing;
-    private String ptr;  // used for accessibility
+    private StructureTreeElement structureTreeElement;
     // Unused but valid items, commented out for performance:
     //     private CommonAural commonAural;
     //     private CommonMarginInline commonMarginInline;
@@ -210,13 +211,13 @@ public class Character extends FObj implements StructurePointerPropertySet {
     }
 
     @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
     }
 
     /** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/fo/flow/Inline.java b/src/java/org/apache/fop/fo/flow/Inline.java
index e458184f4..debf6bbf6 100644
--- a/src/java/org/apache/fop/fo/flow/Inline.java
+++ b/src/java/org/apache/fop/fo/flow/Inline.java
@@ -26,19 +26,17 @@ import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
 
 /**
  * Class modelling the 
  * fo:inline formatting object.
  */
-public class Inline extends InlineLevel implements StructurePointerPropertySet {
+public class Inline extends InlineLevel {
     // The value of properties relevant for fo:inline.
     // See also superclass InlineLevel
     private Length alignmentAdjust;
     private int alignmentBaseline;
     private Length baselineShift;
-    private String ptr;  // used for accessibility
     private int dominantBaseline;
     // Unused but valid items, commented out for performance:
     //     private CommonRelativePosition commonRelativePosition;
@@ -147,16 +145,6 @@ public class Inline extends InlineLevel implements StructurePointerPropertySet {
         return dominantBaseline;
     }
 
-    @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
-    }
-
-    /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
-    }
-
     /** {@inheritDoc} */
     public String getLocalName() {
         return "inline";
diff --git a/src/java/org/apache/fop/fo/flow/PageNumber.java b/src/java/org/apache/fop/fo/flow/PageNumber.java
index c0373aa7f..0e5ce5071 100644
--- a/src/java/org/apache/fop/fo/flow/PageNumber.java
+++ b/src/java/org/apache/fop/fo/flow/PageNumber.java
@@ -23,6 +23,7 @@ import java.awt.Color;
 
 import org.xml.sax.Locator;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.Constants;
@@ -36,14 +37,14 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.CommonFont;
 import org.apache.fop.fo.properties.CommonTextDecoration;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
+import org.apache.fop.fo.properties.StructureTreeElementHolder;
 
 /**
  * Class modelling the 
  * fo:page-number object.
  */
 public class PageNumber extends FObj
-        implements StructurePointerPropertySet, CommonAccessibilityHolder {
+        implements StructureTreeElementHolder, CommonAccessibilityHolder {
     // The value of properties relevant for fo:page-number.
     private CommonAccessibility commonAccessibility;
     private CommonBorderPaddingBackground commonBorderPaddingBackground;
@@ -52,7 +53,7 @@ public class PageNumber extends FObj
     private int alignmentBaseline;
     private Length baselineShift;
     private int dominantBaseline;
-    private String ptr; // used for accessibility
+    private StructureTreeElement structureTreeElement;
     // private ToBeImplementedProperty letterSpacing;
     private SpaceProperty lineHeight;
     /** Holds the text decoration values. May be null */
@@ -176,13 +177,13 @@ public class PageNumber extends FObj
     }
 
     @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
     }
 
     /** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/fo/flow/table/TableFObj.java b/src/java/org/apache/fop/fo/flow/table/TableFObj.java
index 0da7b9458..2506c26b4 100644
--- a/src/java/org/apache/fop/fo/flow/table/TableFObj.java
+++ b/src/java/org/apache/fop/fo/flow/table/TableFObj.java
@@ -36,19 +36,17 @@ import org.apache.fop.fo.properties.EnumProperty;
 import org.apache.fop.fo.properties.NumberProperty;
 import org.apache.fop.fo.properties.Property;
 import org.apache.fop.fo.properties.PropertyMaker;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
 import org.apache.fop.layoutmgr.table.CollapsingBorderModel;
 
 /**
  * Common base class for table-related FOs
  */
-public abstract class TableFObj extends FObj implements StructurePointerPropertySet {
+public abstract class TableFObj extends FObj {
 
     private Numeric borderAfterPrecedence;
     private Numeric borderBeforePrecedence;
     private Numeric borderEndPrecedence;
     private Numeric borderStartPrecedence;
-    private String ptr;
 
     ConditionalBorder borderBefore;             // CSOK: VisibilityModifier
     ConditionalBorder borderAfter;              // CSOK: VisibilityModifier
@@ -240,16 +238,6 @@ public abstract class TableFObj extends FObj implements StructurePointerProperty
         }
     }
 
-    @Override
-    public void setPtr(String ptr) {
-        this.ptr = ptr;
-    }
-
-    /** {@inheritDoc} */
-    public String getPtr() {
-        return ptr;
-    }
-
     /**
      * Prepares the borders of this element if the collapsing-border model is in use.
      * Conflict resolution with parent elements is done where applicable.
diff --git a/src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java b/src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java
deleted file mode 100644
index 5cce2822e..000000000
--- a/src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.fo.properties;
-
-/**
- * Defines property access methods for internal structure pointer extension properties.
- */
-public interface StructurePointerPropertySet {
-
-    /**
-     * Returns the value of the "foi:ptr" property, the internal structure pointer used
-     * for tagged PDF and other formats that support a structure tree in addition to paged content.
-     * @return the "foi:ptr" property
-     */
-    String getPtr();
-
-}
diff --git a/src/java/org/apache/fop/fo/properties/StructureTreeElementHolder.java b/src/java/org/apache/fop/fo/properties/StructureTreeElementHolder.java
new file mode 100644
index 000000000..6fbb608de
--- /dev/null
+++ b/src/java/org/apache/fop/fo/properties/StructureTreeElementHolder.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.fo.properties;
+
+import org.apache.fop.accessibility.StructureTreeElement;
+
+/**
+ * Implementations of this interface can return the element in the document's
+ * structure tree that they resulted into. Used for tagged PDF and other formats
+ * that support a structure tree in addition to paged content.
+ */
+public interface StructureTreeElementHolder {
+
+    /**
+     * Returns the element in the document's structure tree that corresponds to this instance.
+     *
+     * @return a structure tree element
+     */
+    StructureTreeElement getStructureTreeElement();
+
+}
diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
index b748c2152..ed2ea4ee9 100644
--- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
@@ -329,7 +329,6 @@ public class BlockLayoutManager extends BlockStackingLayoutManager
 
         addMarkersToPage(false, isFirst(firstPos), isLast(lastPos));
 
-        TraitSetter.addPtr(curBlockArea, getBlockFO().getPtr());  // used for accessibility
         TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(),
                 effSpaceBefore, effSpaceAfter);
         flush();
diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java
index c0e451577..da548c23c 100644
--- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java
+++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java
@@ -22,6 +22,7 @@ package org.apache.fop.layoutmgr;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.area.Area;
 import org.apache.fop.area.Trait;
 import org.apache.fop.datatypes.LengthBase;
@@ -29,9 +30,9 @@ import org.apache.fop.datatypes.PercentBaseContext;
 import org.apache.fop.datatypes.SimplePercentBaseContext;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
+import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
 import org.apache.fop.fo.properties.CommonMarginBlock;
 import org.apache.fop.fo.properties.CommonTextDecoration;
-import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.traits.BorderProps;
 import org.apache.fop.traits.MinOptMax;
@@ -591,13 +592,14 @@ public final class TraitSetter {
     }
 
     /**
-     * Adds the ptr trait to the area.
+     * Sets the structure tree element associated to the given area.
+     *
      * @param area the area to set the traits on
-     * @param ptr string
+     * @param structureTreeElement the element the area is associated to in the document structure
      */
-    public static void addPtr(Area area, String ptr) {
-        if (ptr != null && ptr.length() > 0) {
-            area.addTrait(Trait.PTR, ptr);
+    public static void addStructureTreeElement(Area area, StructureTreeElement structureTreeElement) {
+        if (structureTreeElement != null) {
+            area.addTrait(Trait.STRUCTURE_TREE_ELEMENT, structureTreeElement);
         }
     }
 
diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java
index 218497a53..8c797a48c 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java
@@ -85,7 +85,7 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage
         transferForeignAttributes(viewportArea);
 
         InlineViewport vp = new InlineViewport(viewportArea);
-        TraitSetter.addPtr(vp, fobj.getPtr());  // used for accessibility
+        TraitSetter.addStructureTreeElement(vp, fobj.getStructureTreeElement());
         TraitSetter.setProducerID(vp, fobj.getId());
         vp.setIPD(imageLayout.getViewportSize().width);
         vp.setBPD(imageLayout.getViewportSize().height);
diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java
index e090fbae6..8c769924a 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java
@@ -136,7 +136,7 @@ public abstract class AbstractPageNumberCitationLayoutManager extends LeafNodeLa
         text.setBaselineOffset(font.getAscender());
         TraitSetter.addFontTraits(text, font);
         text.addTrait(Trait.COLOR, fobj.getColor());
-        TraitSetter.addPtr(text, fobj.getPtr());   // used for accessibility
+        TraitSetter.addStructureTreeElement(text, fobj.getStructureTreeElement());
         TraitSetter.addTextDecoration(text, fobj.getTextDecoration());
     }
 
diff --git a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
index 40c9a324e..1390c04d8 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
@@ -59,7 +59,7 @@ public class BasicLinkLayoutManager extends InlineLayoutManager {
     private void setupBasicLinkArea(InlineArea area) {
         BasicLink fobj = (BasicLink) this.fobj;
         // internal destinations take precedence:
-        TraitSetter.addPtr(area, fobj.getPtr()); // used for accessibility
+        TraitSetter.addStructureTreeElement(area, fobj.getStructureTreeElement());
         if (fobj.hasInternalDestination()) {
             String idref = fobj.getInternalDestination();
             PageSequenceLayoutManager pslm = getPSLM();
diff --git a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
index 2178b2e77..4877ff9bd 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
@@ -87,7 +87,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager {
         }
         TraitSetter.setProducerID(text, node.getId());
         TraitSetter.addTextDecoration(text, node.getTextDecoration());
-        TraitSetter.addPtr(text, node.getPtr()); // used for accessibility
+        TraitSetter.addStructureTreeElement(text, node.getStructureTreeElement());
         return text;
     }
 
diff --git a/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
index d8cfe6cda..4b7289b37 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
@@ -19,13 +19,13 @@
 
 package org.apache.fop.layoutmgr.inline;
 
+import org.apache.fop.area.Trait;
+import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.fo.flow.PageNumber;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.fonts.FontTriplet;
-import org.apache.fop.area.inline.InlineArea;
-import org.apache.fop.area.inline.TextArea;
-import org.apache.fop.area.Trait;
 import org.apache.fop.layoutmgr.LayoutContext;
 import org.apache.fop.layoutmgr.TraitSetter;
 import org.apache.fop.traits.MinOptMax;
@@ -85,7 +85,7 @@ public class PageNumberLayoutManager extends LeafNodeLayoutManager {
         text.setBaselineOffset(font.getAscender());
         TraitSetter.addFontTraits(text, font);
         text.addTrait(Trait.COLOR, fobj.getColor());
-        TraitSetter.addPtr(text, fobj.getPtr()); // used for accessibility
+        TraitSetter.addStructureTreeElement(text, fobj.getStructureTreeElement());
         TraitSetter.addTextDecoration(text, fobj.getTextDecoration());
 
         return text;
diff --git a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
index 81240dec0..8767fe296 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
@@ -31,8 +31,6 @@ import org.apache.fop.area.Trait;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FOText;
-import org.apache.fop.fo.FObj;
-import org.apache.fop.fo.properties.StructurePointerPropertySet;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontSelector;
 import org.apache.fop.layoutmgr.InlineKnuthSequence;
@@ -438,7 +436,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
             setText();
             TraitSetter.addFontTraits(textArea, font);
             textArea.addTrait(Trait.COLOR, foText.getColor());
-            TraitSetter.addPtr(textArea, getPtr()); // used for accessibility
+            TraitSetter.addStructureTreeElement(textArea, foText.getStructureTreeElement());
             TraitSetter.addTextDecoration(textArea, foText.getTextDecoration());
             TraitSetter.addFontTraits(textArea, font);
             return textArea;
@@ -577,20 +575,6 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         }
     }
 
-    /**
-     * used for accessibility
-     * @return ptr of fobj
-     */
-    private String getPtr() {
-        FObj fobj = parentLayoutManager.getFObj();
-        if (fobj instanceof StructurePointerPropertySet) {
-            return (((StructurePointerPropertySet) fobj).getPtr());
-        } else {
-            //No structure pointer applicable
-            return null;
-        }
-    }
-
     private AreaInfo getAreaInfo(int index) {
         return (AreaInfo) areaInfos.get(index);
     }
diff --git a/src/java/org/apache/fop/pdf/PDFStructElem.java b/src/java/org/apache/fop/pdf/PDFStructElem.java
index 6dfb0dc95..44622638d 100644
--- a/src/java/org/apache/fop/pdf/PDFStructElem.java
+++ b/src/java/org/apache/fop/pdf/PDFStructElem.java
@@ -19,17 +19,28 @@
 
 package org.apache.fop.pdf;
 
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.util.LanguageTags;
 
 /**
  * Class representing a PDF Structure Element.
  */
-public class PDFStructElem extends PDFDictionary {
+public class PDFStructElem extends PDFDictionary implements StructureTreeElement {
 
     private PDFStructElem parentElement;
 
+    /**
+     * Elements to be added to the kids array.
+     */
+    protected List kids;
+
     /**
      * Creates a new structure element.
      *
@@ -57,20 +68,11 @@ public class PDFStructElem extends PDFDictionary {
 
     /** {@inheritDoc} */
     public void setParent(PDFObject parent) {
-        if (parent != null) {
+        if (parent != null && parent.hasObjectNumber()) {
            put("P", new PDFReference(parent));
         }
     }
 
-    /**
-     * Returns the kids of this structure element.
-     *
-     * @return the value of the K entry
-     */
-    private PDFArray getKids() {
-        return (PDFArray) get("K");
-    }
-
     /**
      * Add a kid to this structure element. This element will then add itself to
      * its parent structure element if it has not already, and so will the
@@ -79,24 +81,10 @@ public class PDFStructElem extends PDFDictionary {
      * @param kid element to be added
      */
     public void addKid(PDFObject kid) {
-        PDFArray kids = getKids();
         if (kids == null) {
-            kids = new PDFArray();
-            put("K", kids);
+            kids = new ArrayList();
         }
         kids.add(kid);
-        joinHierarchy();
-    }
-
-    private boolean containsKid(PDFObject kid) {
-        PDFArray kids = getKids();
-        return kids != null && kids.contains(kid);
-    }
-
-    private void joinHierarchy() {
-        if (parentElement != null && !parentElement.containsKid(this)) {
-            parentElement.addKid(this);
-        }
     }
 
     /**
@@ -109,7 +97,6 @@ public class PDFStructElem extends PDFDictionary {
      */
     public void setMCIDKid(int mcid) {
         put("K", mcid);
-        joinHierarchy();
     }
 
     /**
@@ -127,7 +114,7 @@ public class PDFStructElem extends PDFDictionary {
      * @return the value of the S entry
      */
     public PDFName getStructureType() {
-        return (PDFName)get("S");
+        return (PDFName) get("S");
     }
 
     /**
@@ -154,6 +141,63 @@ public class PDFStructElem extends PDFDictionary {
      * @return the value of the Lang entry (null if no language was specified)
      */
     public String getLanguage() {
-        return (String)get("Lang");
+        return (String) get("Lang");
+    }
+
+    @Override
+    protected void writeDictionary(OutputStream out, Writer writer) throws IOException {
+        attachKids();
+        super.writeDictionary(out, writer);
+    }
+
+    /**
+     * Attaches all valid kids to the kids array.
+     *
+     * @return true iff 1+ kids were added to the kids array
+     */
+    protected boolean attachKids() {
+        List validKids = new ArrayList();
+        if (kids != null) {
+            for (PDFObject kid : kids) {
+                if (kid instanceof Placeholder)  {
+                    if (((Placeholder) kid).attachKids()) {
+                        validKids.add(kid);
+                    }
+                } else {
+                    validKids.add(kid);
+                }
+            }
+        }
+        boolean kidsAttached = !validKids.isEmpty();
+        if (kidsAttached) {
+            PDFArray array = new PDFArray();
+            for (PDFObject ob : validKids) {
+                array.add(ob);
+            }
+            put("K", array);
+        }
+        return kidsAttached;
     }
+
+    public static class Placeholder extends PDFStructElem {
+
+        @Override
+        public void outputInline(OutputStream out, Writer writer) throws IOException {
+            if (kids != null) {
+                assert kids.size() > 0;
+                for (int i = 0; i < kids.size(); i++) {
+                    if (i > 0) {
+                        writer.write(' ');
+                    }
+                    Object obj = kids.get(i);
+                    formatObject(obj, out, writer);
+                }
+            }
+        }
+
+        public Placeholder(PDFObject parent, String name) {
+            super(parent, new PDFName(name));
+        }
+    }
+
 }
diff --git a/src/java/org/apache/fop/render/intermediate/IFContext.java b/src/java/org/apache/fop/render/intermediate/IFContext.java
index b8be97253..c59a02ba8 100644
--- a/src/java/org/apache/fop/render/intermediate/IFContext.java
+++ b/src/java/org/apache/fop/render/intermediate/IFContext.java
@@ -25,6 +25,7 @@ import java.util.Map;
 
 import org.apache.xmlgraphics.util.QName;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOUserAgent;
 
 /**
@@ -46,7 +47,7 @@ public class IFContext {
 
     private Locale language;
 
-    private String structurePointer;
+    private StructureTreeElement structureTreeElement;
 
     private String id = "";
 
@@ -132,29 +133,31 @@ public class IFContext {
     }
 
     /**
-     * Sets the structure pointer for the following painted marks. This method is used when
-     * accessibility features are enabled.
-     * @param ptr the structure pointer
+     * Sets the structure tree element to which the subsequently painted marks
+     * will correspond. This method is used when accessibility features are
+     * enabled.
+     *
+     * @param structureTreeElement the structure tree element
      */
-    public void setStructurePointer(String ptr) {
-        this.structurePointer = ptr;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /**
-     * Resets the current structure pointer.
-     * @see #setStructurePointer(String)
+     * Resets the current structure tree element.
+     * @see #setStructureTreeElement(String)
      */
-    public void resetStructurePointer() {
-        setStructurePointer(null);
+    public void resetStructureTreeElement() {
+        setStructureTreeElement(null);
     }
 
     /**
-     * Returns the current structure pointer.
-     * @return the structure pointer (or null if no pointer is active)
-     * @see #setStructurePointer(String)
+     * Returns the current structure tree element.
+     * @return the structure tree element (or null if no element is active)
+     * @see #setStructureTreeElement(String)
      */
-    public String getStructurePointer() {
-        return this.structurePointer;
+    public StructureTreeElement getStructureTreeElement() {
+        return this.structureTreeElement;
     }
 
     /**
diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java
index 8f0bb88ec..1a384c6e9 100644
--- a/src/java/org/apache/fop/render/intermediate/IFParser.java
+++ b/src/java/org/apache/fop/render/intermediate/IFParser.java
@@ -49,11 +49,13 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.xmlgraphics.util.QName;
 
 import org.apache.fop.accessibility.AccessibilityEventProducer;
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.fo.ElementMapping;
 import org.apache.fop.fo.ElementMappingRegistry;
 import org.apache.fop.fo.expr.PropertyException;
+import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.render.intermediate.extensions.DocumentNavigationExtensionConstants;
 import org.apache.fop.render.intermediate.extensions.DocumentNavigationHandler;
 import org.apache.fop.traits.BorderProps;
@@ -158,6 +160,9 @@ public class IFParser implements IFConstants {
 
         private Attributes pageSequenceAttributes;
 
+        private Map structureTreeElements =
+                new HashMap();
+
         private final class StructureTreeHandler extends DefaultHandler {
 
             private final StructureTreeEventHandler structureTreeEventHandler;
@@ -177,7 +182,23 @@ public class IFParser implements IFConstants {
             public void startElement(String uri, String localName, String qName,
                     Attributes attributes) throws SAXException {
                 if (!"structure-tree".equals(localName)) {
-                    structureTreeEventHandler.startNode(localName, attributes);
+                    if (localName.equals("marked-content")) {
+                        localName = "#PCDATA";
+                    }
+                    String structID = attributes.getValue(InternalElementMapping.URI,
+                            InternalElementMapping.STRUCT_ID);
+                    if (structID == null) {
+                        structureTreeEventHandler.startNode(localName, attributes);
+                    } else if (localName.equals("external-graphic")
+                            || localName.equals("instream-foreign-object")) {
+                        StructureTreeElement structureTreeElement =
+                            structureTreeEventHandler.startImageNode(localName, attributes);
+                        structureTreeElements.put(structID, structureTreeElement);
+                    } else {
+                        StructureTreeElement structureTreeElement =
+                            structureTreeEventHandler.startReferencedNode(localName, attributes);
+                        structureTreeElements.put(structID, structureTreeElement);
+                    }
                 }
             }
 
@@ -225,14 +246,6 @@ public class IFParser implements IFConstants {
             documentHandler.getContext().resetForeignAttributes();
         }
 
-        private void establishStructurePointer(String ptr) {
-            documentHandler.getContext().setStructurePointer(ptr);
-        }
-
-        private void resetStructurePointer() {
-            documentHandler.getContext().resetStructurePointer();
-        }
-
         /** {@inheritDoc} */
         public void startElement(String uri, String localName, String qName, Attributes attributes)
                     throws SAXException {
@@ -276,7 +289,7 @@ public class IFParser implements IFConstants {
                 } else if (DocumentNavigationExtensionConstants.NAMESPACE.equals(uri)) {
                     if (this.navParser == null) {
                         this.navParser = new DocumentNavigationHandler(
-                                this.documentHandler.getDocumentNavigationHandler());
+                                this.documentHandler.getDocumentNavigationHandler(), structureTreeElements);
                     }
                     delegate = this.navParser;
                     delegateDepth++;
@@ -604,9 +617,9 @@ public class IFParser implements IFConstants {
                 s = lastAttributes.getValue("word-spacing");
                 int wordSpacing = (s != null ? Integer.parseInt(s) : 0);
                 int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx");
-                setStructurePointer(lastAttributes);
+                establishStructureTreeElement(lastAttributes);
                 painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString());
-                resetStructurePointer();
+                resetStructureTreeElement();
             }
 
             public boolean ignoreCharacters() {
@@ -701,7 +714,7 @@ public class IFParser implements IFConstants {
                 int height = Integer.parseInt(lastAttributes.getValue("height"));
                 Map foreignAttributes = getForeignAttributes(lastAttributes);
                 establishForeignAttributes(foreignAttributes);
-                setStructurePointer(lastAttributes);
+                establishStructureTreeElement(lastAttributes);
                 if (foreignObject != null) {
                     painter.drawImage(foreignObject,
                             new Rectangle(x, y, width, height));
@@ -715,7 +728,7 @@ public class IFParser implements IFConstants {
                     painter.drawImage(uri, new Rectangle(x, y, width, height));
                 }
                 resetForeignAttributes();
-                resetStructurePointer();
+                resetStructureTreeElement();
                 inForeignObject = false;
             }
 
@@ -769,13 +782,20 @@ public class IFParser implements IFConstants {
             return foreignAttributes;
         }
 
-        private void setStructurePointer(Attributes attributes) {
-            String ptr = attributes.getValue("ptr");
-            if (ptr != null && ptr.length() > 0) {
-                establishStructurePointer(ptr);
+        private void establishStructureTreeElement(Attributes attributes) {
+            String structRef = attributes.getValue(InternalElementMapping.URI,
+                    InternalElementMapping.STRUCT_REF);
+            if (structRef != null && structRef.length() > 0) {
+                assert structureTreeElements.containsKey(structRef);
+                StructureTreeElement structureTreeElement = structureTreeElements.get(structRef);
+                documentHandler.getContext().setStructureTreeElement(structureTreeElement);
             }
         }
 
+        private void resetStructureTreeElement() {
+            documentHandler.getContext().resetStructureTreeElement();
+        }
+
         /** {@inheritDoc} */
         public void characters(char[] ch, int start, int length) throws SAXException {
             if (delegate != null) {
diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java
index d217646f6..a2d8a0bea 100644
--- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java
+++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java
@@ -50,6 +50,7 @@ import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
 import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
 
 import org.apache.fop.Version;
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.MimeConstants;
@@ -629,12 +630,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         documentHandler.getContext().resetForeignAttributes();
     }
 
-    private void establishStructurePointer(String ptr) {
-        documentHandler.getContext().setStructurePointer(ptr);
+    private void establishStructureTreeElement(StructureTreeElement structureTreeElement) {
+        documentHandler.getContext().setStructureTreeElement(structureTreeElement);
     }
 
     private void resetStructurePointer() {
-        documentHandler.getContext().resetStructurePointer();
+        documentHandler.getContext().resetStructureTreeElement();
     }
 
     /** {@inheritDoc} */
@@ -851,8 +852,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
 
     /** {@inheritDoc} */
     public void renderInlineViewport(InlineViewport viewport) {
-        String ptr = (String) viewport.getTrait(Trait.PTR);
-        establishStructurePointer(ptr);
+        StructureTreeElement structElem =
+                (StructureTreeElement) viewport.getTrait(Trait.STRUCTURE_TREE_ELEMENT);
+        establishStructureTreeElement(structElem);
         pushdID(viewport);
         Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD());
         viewportDimensionStack.push(dim);
@@ -912,7 +914,6 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
         // stuff we only need if a link must be created:
         Rectangle ipRect = null;
         AbstractAction action = null;
-        String ptr = (String) ip.getTrait(Trait.PTR); // used for accessibility
         // make sure the rect is determined *before* calling super!
         int ipp = currentIPPosition;
         int bpp = currentBPPosition + ip.getOffset();
@@ -956,7 +957,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
 
         // warn if link trait found but not allowed, else create link
         if (linkTraitFound) {
-            action.setStructurePointer(ptr);  // used for accessibility
+            StructureTreeElement structElem =
+                    (StructureTreeElement) ip.getTrait(Trait.STRUCTURE_TREE_ELEMENT);
+            action.setStructureTreeElement(structElem);
             Link link = new Link(action, ipRect);
             this.deferredLinks.add(link);
         }
@@ -1009,8 +1012,8 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
 
         String fontName = getInternalFontNameForArea(text);
         int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue();
-        String ptr = (String)text.getTrait(Trait.PTR); // used for accessibility
-        establishStructurePointer(ptr);
+        StructureTreeElement structElem = (StructureTreeElement) text.getTrait(Trait.STRUCTURE_TREE_ELEMENT);
+        establishStructureTreeElement(structElem);
 
         // This assumes that *all* CIDFonts use a /ToUnicode mapping
         Typeface tf = getTypeface(fontName);
diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java
index a4431b972..c6bf9af9e 100644
--- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java
+++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java
@@ -38,9 +38,11 @@ import org.apache.xmlgraphics.util.QName;
 import org.apache.xmlgraphics.util.XMLizable;
 
 import org.apache.fop.accessibility.StructureTreeEventHandler;
+import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.render.PrintRendererConfigurator;
 import org.apache.fop.render.RenderingContext;
+import org.apache.fop.render.intermediate.IFStructureTreeBuilder.IFStructureTreeElement;
 import org.apache.fop.render.intermediate.extensions.AbstractAction;
 import org.apache.fop.render.intermediate.extensions.Bookmark;
 import org.apache.fop.render.intermediate.extensions.BookmarkTree;
@@ -163,6 +165,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
             handler.startPrefixMapping(XLINK_PREFIX, XLINK_NAMESPACE);
             handler.startPrefixMapping(DocumentNavigationExtensionConstants.PREFIX,
                     DocumentNavigationExtensionConstants.NAMESPACE);
+            handler.startPrefixMapping(InternalElementMapping.STANDARD_PREFIX, InternalElementMapping.URI);
             handler.startElement(EL_DOCUMENT);
         } catch (SAXException e) {
             throw new IFException("SAX error in startDocument()", e);
@@ -439,7 +442,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
             addAttribute(atts, "width", Integer.toString(rect.width));
             addAttribute(atts, "height", Integer.toString(rect.height));
             addForeignAttributes(atts);
-            addStructurePointerAttribute(atts);
+            addStructureReference(atts);
             handler.element(EL_IMAGE, atts);
         } catch (SAXException e) {
             throw new IFException("SAX error in startGroup()", e);
@@ -467,7 +470,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
             addAttribute(atts, "width", Integer.toString(rect.width));
             addAttribute(atts, "height", Integer.toString(rect.height));
             addForeignAttributes(atts);
-            addStructurePointerAttribute(atts);
+            addStructureReference(atts);
             handler.startElement(EL_IMAGE, atts);
             new DOM2SAX(handler).writeDocument(doc, true);
             handler.endElement(EL_IMAGE);
@@ -582,7 +585,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
             if (dx != null) {
                 addAttribute(atts, "dx", IFUtil.toString(dx));
             }
-            addStructurePointerAttribute(atts);
+            addStructureReference(atts);
             handler.startElement(EL_TEXT, atts);
             char[] chars = text.toCharArray();
             handler.characters(chars, 0, chars.length);
@@ -682,13 +685,22 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
         XMLUtil.addAttribute(atts, localName, value);
     }
 
-    private void addStructurePointerAttribute(AttributesImpl atts) {
-        String ptr = getContext().getStructurePointer();
-        if (ptr != null) {
-            addAttribute(atts, "ptr", ptr);
+    private void addStructureReference(AttributesImpl atts) {
+        IFStructureTreeElement structureTreeElement =
+                (IFStructureTreeElement) getContext().getStructureTreeElement();
+        if (structureTreeElement != null) {
+            addStructRefAttribute(atts, structureTreeElement.id);
         }
     }
 
+    private void addStructRefAttribute(AttributesImpl atts, String id) {
+        atts.addAttribute(InternalElementMapping.URI,
+                InternalElementMapping.STRUCT_REF,
+                InternalElementMapping.STANDARD_PREFIX + ":" + InternalElementMapping.STRUCT_REF,
+                XMLConstants.CDATA,
+                id);
+    }
+
     private void addID() throws SAXException {
         String id = getContext().getID();
         if (!currentID.equals(id)) {
@@ -773,7 +785,8 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
         atts.addAttribute(null, "rect", "rect",
                 XMLConstants.CDATA, IFUtil.toString(link.getTargetRect()));
         if (getUserAgent().isAccessibilityEnabled()) {
-            addAttribute(atts, "ptr", link.getAction().getStructurePointer());
+            addStructRefAttribute(atts,
+                    ((IFStructureTreeElement) link.getAction().getStructureTreeElement()).id);
         }
         try {
             handler.startElement(DocumentNavigationExtensionConstants.LINK, atts);
diff --git a/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java b/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java
index 66457681f..b78ae35bc 100644
--- a/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java
+++ b/src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java
@@ -26,10 +26,14 @@ import java.util.Locale;
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
 import org.xml.sax.helpers.DefaultHandler;
 
 import org.apache.fop.accessibility.StructureTree2SAXEventAdapter;
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.accessibility.StructureTreeEventHandler;
+import org.apache.fop.fo.extensions.InternalElementMapping;
+import org.apache.fop.util.XMLUtil;
 
 /**
  * Saves structure tree events as SAX events in order to replay them when it's
@@ -37,42 +41,17 @@ import org.apache.fop.accessibility.StructureTreeEventHandler;
  */
 final class IFStructureTreeBuilder implements StructureTreeEventHandler {
 
-    private StructureTreeEventHandler delegate;
-
-    private final List pageSequenceEventRecorders = new ArrayList();
-
-    /**
-     * Replay SAX events for a page sequence.
-     * @param handler The handler that receives SAX events
-     * @param pageSequenceIndex The index of the page sequence
-     * @throws SAXException
-     */
-    public void replayEventsForPageSequence(ContentHandler handler,
-            int pageSequenceIndex) throws SAXException {
-        pageSequenceEventRecorders.get(pageSequenceIndex).replay(handler);
-    }
-
-    /** {@inheritDoc} */
-    public void startPageSequence(Locale locale) {
-        SAXEventRecorder eventRecorder = new SAXEventRecorder();
-        pageSequenceEventRecorders.add(eventRecorder);
-        delegate = StructureTree2SAXEventAdapter.newInstance(eventRecorder);
-        delegate.startPageSequence(locale);
-    }
+    static final class IFStructureTreeElement implements StructureTreeElement {
 
-    /** {@inheritDoc} */
-    public void endPageSequence() {
-         delegate.endPageSequence();
-    }
+        final String id;
 
-    /** {@inheritDoc} */
-    public void startNode(String name, Attributes attributes) {
-        delegate.startNode(name, attributes);
-    }
+        IFStructureTreeElement() {
+            this.id = null;
+        }
 
-    /** {@inheritDoc} */
-    public void endNode(String name) {
-        delegate.endNode(name);
+        IFStructureTreeElement(String id) {
+            this.id = id;
+        }
     }
 
     /** A SAX handler that records events to replay them later. */
@@ -159,22 +138,22 @@ final class IFStructureTreeBuilder implements StructureTreeEventHandler {
         public void startElement(String uri, String localName, String qName,
                 Attributes attributes) throws SAXException {
             events.add(new StartElement(uri, localName, qName, attributes));
-        };
+        }
 
         @Override
         public void endElement(String uri, String localName, String qName) throws SAXException {
             events.add(new EndElement(uri, localName, qName));
-        };
+        }
 
         @Override
         public void startPrefixMapping(String prefix, String uri) throws SAXException {
             events.add(new StartPrefixMapping(prefix, uri));
-        };
+        }
 
         @Override
         public void endPrefixMapping(String prefix) throws SAXException {
             events.add(new EndPrefixMapping(prefix));
-        };
+        }
 
         /**
          * Replays the recorded events.
@@ -187,4 +166,69 @@ final class IFStructureTreeBuilder implements StructureTreeEventHandler {
             }
         }
     }
+
+    private StructureTreeEventHandler delegate;
+
+    private final List pageSequenceEventRecorders = new ArrayList();
+
+    private int idCounter;
+
+    /**
+     * Replay SAX events for a page sequence.
+     * @param handler The handler that receives SAX events
+     * @param pageSequenceIndex The index of the page sequence
+     * @throws SAXException
+     */
+    public void replayEventsForPageSequence(ContentHandler handler,
+            int pageSequenceIndex) throws SAXException {
+        pageSequenceEventRecorders.get(pageSequenceIndex).replay(handler);
+    }
+
+    public void startPageSequence(Locale locale) {
+        SAXEventRecorder eventRecorder = new SAXEventRecorder();
+        pageSequenceEventRecorders.add(eventRecorder);
+        delegate = StructureTree2SAXEventAdapter.newInstance(eventRecorder);
+        delegate.startPageSequence(locale);
+    }
+
+    public void endPageSequence() {
+         delegate.endPageSequence();
+    }
+
+    public StructureTreeElement startNode(String name, Attributes attributes) {
+        delegate.startNode(name, attributes);
+        return new IFStructureTreeElement();
+    }
+
+    public void endNode(String name) {
+        delegate.endNode(name);
+    }
+
+    public StructureTreeElement startImageNode(String name, Attributes attributes) {
+        String id = getNextID();
+        AttributesImpl atts = addIDAttribute(attributes, id);
+        delegate.startImageNode(name, atts);
+        return new IFStructureTreeElement(id);
+    }
+
+    public StructureTreeElement startReferencedNode(String name, Attributes attributes) {
+        String id = getNextID();
+        AttributesImpl atts = addIDAttribute(attributes, id);
+        delegate.startReferencedNode(name, atts);
+        return new IFStructureTreeElement(id);
+    }
+
+    private String getNextID() {
+        return Integer.toHexString(idCounter++);
+    }
+
+    private AttributesImpl addIDAttribute(Attributes attributes, String id) {
+        AttributesImpl atts = new AttributesImpl(attributes);
+        atts.addAttribute(InternalElementMapping.URI,
+                InternalElementMapping.STRUCT_ID,
+                InternalElementMapping.STANDARD_PREFIX + ":" + InternalElementMapping.STRUCT_ID,
+                XMLUtil.CDATA,
+                id);
+        return atts;
+    }
 }
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java
index 340b2e068..a2595d320 100644
--- a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java
+++ b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java
@@ -21,13 +21,15 @@ package org.apache.fop.render.intermediate.extensions;
 
 import org.apache.xmlgraphics.util.XMLizable;
 
+import org.apache.fop.accessibility.StructureTreeElement;
+
 /**
  * Abstract base class for document actions, like "go-to" actions with absolute page coordinates.
  */
 public abstract class AbstractAction implements XMLizable {
 
     private String id;
-    private String structurePointer;
+    private StructureTreeElement structureTreeElement;
 
     /**
      * Sets an ID to make the action referencable.
@@ -47,18 +49,18 @@ public abstract class AbstractAction implements XMLizable {
 
     /**
      * Sets the structure element corresponding to this action.
-     * @param structurePointer a reference to the structure element
+     * @param structureTreeElement a reference to the structure element
      */
-    public void setStructurePointer(String structurePointer) {
-        this.structurePointer = structurePointer;
+    public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
+        this.structureTreeElement = structureTreeElement;
     }
 
     /**
      * Returns the structure element corresponding to this action.
      * @return the reference to the structure element
      */
-    public String getStructurePointer() {
-        return structurePointer;
+    public StructureTreeElement getStructureTreeElement() {
+        return structureTreeElement;
     }
 
     /**
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java
index 1e613d7eb..693497b73 100644
--- a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java
+++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java
@@ -21,6 +21,7 @@ package org.apache.fop.render.intermediate.extensions;
 
 import java.awt.Point;
 import java.awt.Rectangle;
+import java.util.Map;
 import java.util.Stack;
 
 import org.xml.sax.Attributes;
@@ -30,6 +31,8 @@ import org.xml.sax.helpers.DefaultHandler;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.fop.accessibility.StructureTreeElement;
+import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
 import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.util.XMLUtil;
@@ -48,14 +51,20 @@ public class DocumentNavigationHandler extends DefaultHandler
 
     private IFDocumentNavigationHandler navHandler;
 
-    private String structurePointer;
+    private StructureTreeElement structureTreeElement;
+
+    private Map structureTreeElements;
 
     /**
      * Main constructor.
      * @param navHandler the navigation handler that will receive the events
+     * @param structureTreeElements the elements representing the structure of the document
      */
-    public DocumentNavigationHandler(IFDocumentNavigationHandler navHandler) {
+    public DocumentNavigationHandler(IFDocumentNavigationHandler navHandler,
+            Map structureTreeElements) {
         this.navHandler = navHandler;
+        assert structureTreeElements != null;
+        this.structureTreeElements = structureTreeElements;
     }
 
     /** {@inheritDoc} */
@@ -98,7 +107,8 @@ public class DocumentNavigationHandler extends DefaultHandler
                     throw new SAXException(localName + " must be the root element!");
                 }
                 Rectangle targetRect = XMLUtil.getAttributeAsRectangle(attributes, "rect");
-                structurePointer = attributes.getValue("ptr");
+                structureTreeElement = structureTreeElements.get(
+                        attributes.getValue(InternalElementMapping.URI, InternalElementMapping.STRUCT_REF));
                 Link link = new Link(null, targetRect);
                 objectStack.push(link);
             } else if (GOTO_XY.getLocalName().equals(localName)) {
@@ -121,8 +131,8 @@ public class DocumentNavigationHandler extends DefaultHandler
                     }
                     action = new GoToXYAction(id, pageIndex, location);
                 }
-                if (structurePointer != null) {
-                    action.setStructurePointer(structurePointer);
+                if (structureTreeElement != null) {
+                    action.setStructureTreeElement(structureTreeElement);
                 }
                 objectStack.push(action);
             } else if (GOTO_URI.getLocalName().equals(localName)) {
@@ -134,8 +144,8 @@ public class DocumentNavigationHandler extends DefaultHandler
                 if (id != null) {
                     action.setID(id);
                 }
-                if (structurePointer != null) {
-                    action.setStructurePointer(structurePointer);
+                if (structureTreeElement != null) {
+                    action.setStructureTreeElement(structureTreeElement);
                 }
                 objectStack.push(action);
             } else {
diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java
index c8fa481d4..2236778b5 100644
--- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java
+++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java
@@ -31,6 +31,7 @@ import org.apache.fop.pdf.PDFFactory;
 import org.apache.fop.pdf.PDFGoTo;
 import org.apache.fop.pdf.PDFLink;
 import org.apache.fop.pdf.PDFOutline;
+import org.apache.fop.pdf.PDFStructElem;
 import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
 import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.render.intermediate.extensions.AbstractAction;
@@ -111,10 +112,9 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler
         PDFLink pdfLink = getPDFDoc().getFactory().makeLink(
                 targetRect2D, pdfAction);
         if (pdfLink != null) {
-            String ptr = link.getAction().getStructurePointer();
-            if (documentHandler.getUserAgent().isAccessibilityEnabled()
-                    && ptr != null && ptr.length() > 0) {
-                documentHandler.getLogicalStructureHandler().addLinkContentItem(pdfLink, ptr);
+            PDFStructElem structure = (PDFStructElem) link.getAction().getStructureTreeElement();
+            if (documentHandler.getUserAgent().isAccessibilityEnabled() && structure != null) {
+                documentHandler.getLogicalStructureHandler().addLinkContentItem(pdfLink, structure);
             }
             documentHandler.currentPage.addAnnotation(pdfLink);
         }
diff --git a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
index d49ef4a3e..88a6e9c22 100644
--- a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
+++ b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
@@ -19,9 +19,7 @@
 
 package org.apache.fop.render.pdf;
 
-import java.util.HashMap;
 import java.util.Locale;
-import java.util.Map;
 
 import org.apache.fop.pdf.PDFArray;
 import org.apache.fop.pdf.PDFDictionary;
@@ -47,11 +45,6 @@ class PDFLogicalStructureHandler {
 
     private final PDFDocument pdfDoc;
 
-    /**
-     * Map of references to the corresponding structure elements.
-     */
-    private final Map structTreeMap = new HashMap();
-
     private final PDFParentTree parentTree = new PDFParentTree();
 
     private int parentTreeKey;
@@ -151,100 +144,79 @@ class PDFLogicalStructureHandler {
         parentTree.getNums().put(currentPage.getStructParents(), pageParentTreeArray);
     }
 
-    private MarkedContentInfo addToParentTree(String structurePointer) {
-        PDFStructElem parent = (PDFStructElem) structTreeMap.get(structurePointer);
-        if (parent == null) {
-            return ARTIFACT;
-        } else {
-            pageParentTreeArray.add(parent);
-            String type = parent.getStructureType().toString();
-            int mcid = pageParentTreeArray.length() - 1;
-            return new MarkedContentInfo(type, mcid, parent);
-        }
+    private MarkedContentInfo addToParentTree(PDFStructElem structureTreeElement) {
+        PDFStructElem parent = (structureTreeElement instanceof PDFStructElem.Placeholder)
+                ? structureTreeElement.getParentStructElem()
+                : structureTreeElement;
+        pageParentTreeArray.add(parent);
+        String type = parent.getStructureType().toString();
+        int mcid = pageParentTreeArray.length() - 1;
+        return new MarkedContentInfo(type, mcid, structureTreeElement);
     }
 
     /**
      * Adds a content item corresponding to text into the structure tree, if
      * there is a structure element associated to it.
      *
-     * @param structurePointer reference to the parent structure element of the
-     * piece of text
+     * @param structElem the parent structure element of the piece of text
      * @return the necessary information for bracketing the content as a
      * marked-content sequence. If there is no element in the structure tree
      * associated to that content, returns an instance whose
      * {@link MarkedContentInfo#tag} value is null. The content
      * must then be treated as an artifact.
      */
-    MarkedContentInfo addTextContentItem(String structurePointer) {
-        MarkedContentInfo mci = addToParentTree(structurePointer);
-        if (mci != ARTIFACT) {
+    MarkedContentInfo addTextContentItem(PDFStructElem structElem) {
+        if (structElem == null) {
+            return ARTIFACT;
+        } else {
+            MarkedContentInfo mci = addToParentTree(structElem);
             PDFDictionary contentItem = new PDFDictionary();
             contentItem.put("Type", MCR);
             contentItem.put("Pg", this.currentPage);
             contentItem.put("MCID", mci.mcid);
             mci.parent.addKid(contentItem);
+            return mci;
         }
-        return mci;
     }
 
     /**
      * Adds a content item corresponding to an image into the structure tree, if
      * there is a structure element associated to it.
      *
-     * @param structurePointer reference to the parent structure element of the
-     * image
+     * @param structElem the parent structure element of the image
      * @return the necessary information for bracketing the content as a
      * marked-content sequence. If there is no element in the structure tree
      * associated to that image, returns an instance whose
-     * {@link MarkedContentInfo#tag} value is null. The image
-     * must then be treated as an artifact.
+     * {@link MarkedContentInfo#tag} value is null. The image must
+     * then be treated as an artifact.
      */
-    MarkedContentInfo addImageContentItem(String structurePointer) {
-        MarkedContentInfo mci = addToParentTree(structurePointer);
-        if (mci != ARTIFACT) {
+    MarkedContentInfo addImageContentItem(PDFStructElem structElem) {
+        if (structElem == null) {
+            return ARTIFACT;
+        } else {
+            MarkedContentInfo mci = addToParentTree(structElem);
             mci.parent.setMCIDKid(mci.mcid);
             mci.parent.setPage(this.currentPage);
+            return mci;
         }
-        return mci;
     }
 
-    // While the PDF spec allows images to be referred as PDF objects, this
-    // makes the Acrobat Pro checker complain that the image is not accessible.
-    // Its alt-text is still read aloud though. Using marked-content sequences
-    // like for text works.
-//    MarkedContentInfo addImageObject(String parentReference) {
-//        MarkedContentInfo mci = addToParentTree(parentReference);
-//        if (mci != ARTIFACT) {
-//            PDFDictionary contentItem = new PDFDictionary();
-//            contentItem.put("Type", OBJR);
-//            contentItem.put("Pg", this.currentPage);
-//            contentItem.put("Obj", null);
-//            mci.parent.addKid(contentItem);
-//        }
-//        return mci;
-//    }
-
     /**
      * Adds a content item corresponding to the given link into the structure
      * tree.
      *
      * @param link a link
-     * @param structurePointer reference to the corresponding parent structure element
+     * @param structureTreeElement its parent structure element
      */
-    void addLinkContentItem(PDFLink link, String structurePointer) {
+    void addLinkContentItem(PDFLink link, PDFStructElem structureTreeElement) {
         int structParent = getNextParentTreeKey();
         link.setStructParent(structParent);
         PDFDictionary contentItem = new PDFDictionary();
         contentItem.put("Type", OBJR);
         contentItem.put("Pg", this.currentPage);
         contentItem.put("Obj", link);
-        PDFStructElem parent = (PDFStructElem) structTreeMap.get(structurePointer);
-        parentTree.getNums().put(structParent, parent);
-        parent.addKid(contentItem);
-    }
-
-    void addStructurePointer(String ptr, PDFStructElem structElem) {
-        structTreeMap.put(ptr, structElem);
+        parentTree.getNums().put(structParent, structureTreeElement);
+        structureTreeElement.addKid(contentItem);
     }
 
 }
diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java
index 11af216a2..f2fbfd014 100644
--- a/src/java/org/apache/fop/render/pdf/PDFPainter.java
+++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java
@@ -37,6 +37,7 @@ import org.apache.fop.fonts.SingleByteFont;
 import org.apache.fop.fonts.Typeface;
 import org.apache.fop.pdf.PDFDocument;
 import org.apache.fop.pdf.PDFNumber;
+import org.apache.fop.pdf.PDFStructElem;
 import org.apache.fop.pdf.PDFTextUtil;
 import org.apache.fop.pdf.PDFXObject;
 import org.apache.fop.render.RenderingContext;
@@ -133,24 +134,24 @@ public class PDFPainter extends AbstractIFPainter {
         PDFXObject xobject = getPDFDoc().getXObject(uri);
         if (xobject != null) {
             if (accessEnabled) {
-                String ptr = getContext().getStructurePointer();
-                prepareImageMCID(ptr);
+                PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
+                prepareImageMCID(structElem);
                 placeImageAccess(rect, xobject);
             } else {
                 placeImage(rect, xobject);
             }
         } else {
             if (accessEnabled) {
-                String ptr = getContext().getStructurePointer();
-                prepareImageMCID(ptr);
+                PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
+                prepareImageMCID(structElem);
             }
             drawImageUsingURI(uri, rect);
             flushPDFDoc();
         }
     }
 
-    private void prepareImageMCID(String ptr) {
-        imageMCI = logicalStructureHandler.addImageContentItem(ptr);
+    private void prepareImageMCID(PDFStructElem structElem) {
+        imageMCI = logicalStructureHandler.addImageContentItem(structElem);
     }
 
     /** {@inheritDoc} */
@@ -194,8 +195,8 @@ public class PDFPainter extends AbstractIFPainter {
     /** {@inheritDoc} */
     public void drawImage(Document doc, Rectangle rect) throws IFException {
         if (accessEnabled) {
-            String ptr = getContext().getStructurePointer();
-            prepareImageMCID(ptr);
+            PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
+            prepareImageMCID(structElem);
         }
         drawImageUsingDocument(doc, rect);
         flushPDFDoc();
@@ -294,8 +295,8 @@ public class PDFPainter extends AbstractIFPainter {
             String text)
             throws IFException {
         if (accessEnabled) {
-            String ptr = getContext().getStructurePointer();
-            MarkedContentInfo mci = logicalStructureHandler.addTextContentItem(ptr);
+            PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
+            MarkedContentInfo mci = logicalStructureHandler.addTextContentItem(structElem);
             if (generator.getTextUtil().isInTextObject()) {
                 generator.separateTextElements(mci.tag, mci.mcid);
             }
diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
index 8ec10b209..3b5b00c33 100644
--- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
+++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
@@ -24,10 +24,10 @@ import java.util.Locale;
 
 import org.xml.sax.Attributes;
 
+import org.apache.fop.accessibility.StructureTreeElement;
 import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.events.EventBroadcaster;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
-import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.pdf.PDFFactory;
 import org.apache.fop.pdf.PDFStructElem;
 
@@ -61,33 +61,64 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler {
     public void endPageSequence() {
     }
 
-    public void startNode(String name, Attributes attributes) {
+    public StructureTreeElement startNode(String name, Attributes attributes) {
         PDFStructElem parent = ancestors.getFirst();
         String role = attributes.getValue("role");
-        PDFStructElem created = pdfFactory.makeStructureElement(
-                FOToPDFRoleMap.mapFormattingObject(name, role, parent,
-                        eventBroadcaster), parent);
-        if (ancestors.size() <= 2) { // TODO remove
-            parent.addKid(created);
-        }
-        String ptr = attributes.getValue(InternalElementMapping.URI, "ptr");
-        if (ptr != null) {
-            logicalStructureHandler.addStructurePointer(ptr, created);
+        PDFStructElem created;
+        created = pdfFactory.makeStructureElement(
+                FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent);
+        parent.addKid(created);
+        ancestors.addFirst(created);
+        return created;
+    }
+
+    public void endNode(String name) {
+        removeFirstAncestor();
+    }
+
+    private void removeFirstAncestor() {
+        ancestors.removeFirst();
+    }
+
+    public StructureTreeElement startImageNode(String name, Attributes attributes) {
+        PDFStructElem parent = ancestors.getFirst();
+        String role = attributes.getValue("role");
+        PDFStructElem created;
+        created = pdfFactory.makeStructureElement(
+                FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent);
+        parent.addKid(created);
+        String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text");
+        if (altTextNode != null) {
+            created.put("Alt", altTextNode);
+        } else {
+            created.put("Alt", "No alternate text specified");
         }
+        ancestors.addFirst(created);
+        return created;
+    }
+
+    public void endImageNode(String name) {
+        removeFirstAncestor();
+    }
 
-        if (name.equals("external-graphic") || name.equals("instream-foreign-object")) {
-            String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text");
-            if (altTextNode != null) {
-                created.put("Alt", altTextNode);
-            } else {
-                created.put("Alt", "No alternate text specified");
-            }
+    public StructureTreeElement startReferencedNode(String name, Attributes attributes) {
+        PDFStructElem parent = ancestors.getFirst();
+        String role = attributes.getValue("role");
+        PDFStructElem created;
+        if ("#PCDATA".equals(name)) {
+            created = new PDFStructElem.Placeholder(parent, name);
+        } else {
+            created = pdfFactory.makeStructureElement(
+                    FOToPDFRoleMap.mapFormattingObject(name, role, parent,
+                            eventBroadcaster), parent);
         }
+        parent.addKid(created);
         ancestors.addFirst(created);
+        return created;
     }
 
-    public void endNode(String name) {
-        ancestors.removeFirst();
+    public void endReferencedNode(String name) {
+        removeFirstAncestor();
     }
 
 }
diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java
index 8878b5d24..a3e3db461 100644
--- a/src/java/org/apache/fop/render/rtf/RTFHandler.java
+++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java
@@ -117,13 +117,13 @@ import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfFootnote;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfHyperLink;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfList;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfListItem;
+import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfListItem.RtfListItemLabel;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfPage;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfSection;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTable;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTableCell;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTableRow;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTextrun;
-import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfListItem.RtfListItemLabel;
 import org.apache.fop.render.rtf.rtflib.tools.BuilderContext;
 import org.apache.fop.render.rtf.rtflib.tools.PercentContext;
 import org.apache.fop.render.rtf.rtflib.tools.TableContext;
@@ -1362,11 +1362,9 @@ public class RTFHandler extends FOEventHandler {
 
     /**
      * @param text FOText object
-     * @param data Array of characters to process.
-     * @param start Offset for characters to process.
-     * @param length Portion of array to process.
+     * @param characters CharSequence of the characters to process.
      */
-    public void text(FOText text, char[] data, int start, int length) {
+    public void text(FOText text, CharSequence characters) {
         if (bDefer) {
             return;
         }
@@ -1381,7 +1379,7 @@ public class RTFHandler extends FOEventHandler {
                 = TextAttributesConverter.convertCharacterAttributes(text);
 
             textrun.pushInlineAttributes(rtfAttr);
-            textrun.addString(new String(data, start, length - start));
+            textrun.addString(characters.toString());
             textrun.popInlineAttributes();
         } catch (IOException ioe) {
             handleIOTrouble(ioe);
@@ -1558,7 +1556,7 @@ public class RTFHandler extends FOEventHandler {
         } else if (foNode instanceof FOText) {
             if (bStart) {
                 FOText text = (FOText) foNode;
-                text(text, text.getCharArray(), 0, text.length());
+                text(text, text.getCharSequence());
             }
         } else if (foNode instanceof Character) {
             if (bStart) {
diff --git a/test/accessibility/background-image_jpg_repeat.fo b/test/accessibility/background-image_jpg_repeat.fo
index 2c8f7f66d..727162e49 100644
--- a/test/accessibility/background-image_jpg_repeat.fo
+++ b/test/accessibility/background-image_jpg_repeat.fo
@@ -16,14 +16,14 @@
   limitations under the License.
 -->
 
-
+
   
     
       
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/background-image_jpg_single.fo b/test/accessibility/background-image_jpg_single.fo
index 2250c24e2..335353e03 100644
--- a/test/accessibility/background-image_jpg_single.fo
+++ b/test/accessibility/background-image_jpg_single.fo
@@ -16,7 +16,7 @@
   limitations under the License.
 -->
 
-
+
   
     
@@ -25,7 +25,7 @@
         background-position-vertical="50%"/>
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/background-image_png_repeat.fo b/test/accessibility/background-image_png_repeat.fo
index 7506c0f2b..5e4a8ba0d 100644
--- a/test/accessibility/background-image_png_repeat.fo
+++ b/test/accessibility/background-image_png_repeat.fo
@@ -16,14 +16,14 @@
   limitations under the License.
 -->
 
-
+
   
     
       
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/background-image_png_single.fo b/test/accessibility/background-image_png_single.fo
index 1f60e9411..90067ec53 100644
--- a/test/accessibility/background-image_png_single.fo
+++ b/test/accessibility/background-image_png_single.fo
@@ -16,7 +16,7 @@
   limitations under the License.
 -->
 
-
+
   
     
@@ -25,7 +25,7 @@
         background-position-vertical="50%"/>
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/background-image_svg_repeat.fo b/test/accessibility/background-image_svg_repeat.fo
index 6e71409d0..02520b6cf 100644
--- a/test/accessibility/background-image_svg_repeat.fo
+++ b/test/accessibility/background-image_svg_repeat.fo
@@ -16,14 +16,14 @@
   limitations under the License.
 -->
 
-
+
   
     
       
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/background-image_svg_single.fo b/test/accessibility/background-image_svg_single.fo
index 9dc67c86c..3029f32ec 100644
--- a/test/accessibility/background-image_svg_single.fo
+++ b/test/accessibility/background-image_svg_single.fo
@@ -16,7 +16,7 @@
   limitations under the License.
 -->
 
-
+
   
     
@@ -25,7 +25,7 @@
         background-position-vertical="50%"/>
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/complete.fo b/test/accessibility/complete.fo
index 81df372b5..75684750c 100644
--- a/test/accessibility/complete.fo
+++ b/test/accessibility/complete.fo
@@ -17,7 +17,7 @@
 -->
 
 
+  xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB">
   
     
@@ -25,7 +25,7 @@
       
     
   
-  
+  
     
       This is the page headerPage 
         
@@ -117,7 +117,7 @@
       And now we are going to see how a second page sequence is handled.
     
   
-  
+  
     
       This is the page headerPage 
         
diff --git a/test/accessibility/image_jpg.fo b/test/accessibility/image_jpg.fo
index be0e24bee..5fe36f61a 100644
--- a/test/accessibility/image_jpg.fo
+++ b/test/accessibility/image_jpg.fo
@@ -17,14 +17,14 @@
 -->
 
 
+  xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB">
   
     
       
     
   
-  
+  
     
       This document contains an image in the JPEG format: 
 
 
+  xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB">
   
     
       
     
   
-  
+  
     
       This document contains an image in the PNG format: 
 
 
+  xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB">
   
     
       
     
   
-  
+  
     
       This document contains an image in the SVG format: 
 
 
+  xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB">
   
     
       
     
   
-  
+  
     
       This document contains an image in the WMF format: 
 
-
+
   
     
       
     
   
-  
+  
     
       This is a text followed by a leader with leader-pattern=​"use-content", the 
diff --git a/test/accessibility/links.fo b/test/accessibility/links.fo
index 66f0f66f4..36250e332 100644
--- a/test/accessibility/links.fo
+++ b/test/accessibility/links.fo
@@ -17,14 +17,14 @@
 -->
 
 
+  xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB">
   
     
       
     
   
-  
+  
     
       This is a link to the next 
diff --git a/test/accessibility/pdf/background-image_jpg_repeat.pdf b/test/accessibility/pdf/background-image_jpg_repeat.pdf
index 97b304c53..221761757 100644
Binary files a/test/accessibility/pdf/background-image_jpg_repeat.pdf and b/test/accessibility/pdf/background-image_jpg_repeat.pdf differ
diff --git a/test/accessibility/pdf/background-image_jpg_single.pdf b/test/accessibility/pdf/background-image_jpg_single.pdf
index e1d7f9340..232afdd05 100644
Binary files a/test/accessibility/pdf/background-image_jpg_single.pdf and b/test/accessibility/pdf/background-image_jpg_single.pdf differ
diff --git a/test/accessibility/pdf/background-image_png_repeat.pdf b/test/accessibility/pdf/background-image_png_repeat.pdf
index 9f5e7ff12..52da4020a 100644
Binary files a/test/accessibility/pdf/background-image_png_repeat.pdf and b/test/accessibility/pdf/background-image_png_repeat.pdf differ
diff --git a/test/accessibility/pdf/background-image_png_single.pdf b/test/accessibility/pdf/background-image_png_single.pdf
index 81156e18d..356e4885c 100644
Binary files a/test/accessibility/pdf/background-image_png_single.pdf and b/test/accessibility/pdf/background-image_png_single.pdf differ
diff --git a/test/accessibility/pdf/background-image_svg_repeat.pdf b/test/accessibility/pdf/background-image_svg_repeat.pdf
index 0e438958d..0bce2404d 100644
Binary files a/test/accessibility/pdf/background-image_svg_repeat.pdf and b/test/accessibility/pdf/background-image_svg_repeat.pdf differ
diff --git a/test/accessibility/pdf/background-image_svg_single.pdf b/test/accessibility/pdf/background-image_svg_single.pdf
index a46b6df38..3e4afcdcf 100644
Binary files a/test/accessibility/pdf/background-image_svg_single.pdf and b/test/accessibility/pdf/background-image_svg_single.pdf differ
diff --git a/test/accessibility/pdf/complete.pdf b/test/accessibility/pdf/complete.pdf
index a42bb2a97..cffb9e2cb 100644
Binary files a/test/accessibility/pdf/complete.pdf and b/test/accessibility/pdf/complete.pdf differ
diff --git a/test/accessibility/pdf/image_jpg.pdf b/test/accessibility/pdf/image_jpg.pdf
index 6b70c268d..cb004bbd2 100644
Binary files a/test/accessibility/pdf/image_jpg.pdf and b/test/accessibility/pdf/image_jpg.pdf differ
diff --git a/test/accessibility/pdf/image_png.pdf b/test/accessibility/pdf/image_png.pdf
index d63db6708..c3289374f 100644
Binary files a/test/accessibility/pdf/image_png.pdf and b/test/accessibility/pdf/image_png.pdf differ
diff --git a/test/accessibility/pdf/image_svg.pdf b/test/accessibility/pdf/image_svg.pdf
index 94720f564..c3fce5b0e 100644
Binary files a/test/accessibility/pdf/image_svg.pdf and b/test/accessibility/pdf/image_svg.pdf differ
diff --git a/test/accessibility/pdf/image_wmf.pdf b/test/accessibility/pdf/image_wmf.pdf
index 4b6eb0827..b9ec8c55a 100644
Binary files a/test/accessibility/pdf/image_wmf.pdf and b/test/accessibility/pdf/image_wmf.pdf differ
diff --git a/test/accessibility/pdf/leader.pdf b/test/accessibility/pdf/leader.pdf
index 4a6c25253..c7432e751 100644
Binary files a/test/accessibility/pdf/leader.pdf and b/test/accessibility/pdf/leader.pdf differ
diff --git a/test/accessibility/pdf/links.pdf b/test/accessibility/pdf/links.pdf
index b8d4e3be3..91d7c2592 100644
Binary files a/test/accessibility/pdf/links.pdf and b/test/accessibility/pdf/links.pdf differ
diff --git a/test/accessibility/pdf/role.pdf b/test/accessibility/pdf/role.pdf
index ec5d5f536..acb435027 100644
Binary files a/test/accessibility/pdf/role.pdf and b/test/accessibility/pdf/role.pdf differ
diff --git a/test/accessibility/pdf/role_non-standard.pdf b/test/accessibility/pdf/role_non-standard.pdf
index d90693066..fcf614ed1 100644
Binary files a/test/accessibility/pdf/role_non-standard.pdf and b/test/accessibility/pdf/role_non-standard.pdf differ
diff --git a/test/accessibility/pdf/text_1.pdf b/test/accessibility/pdf/text_1.pdf
index 0b1e1ee9f..596419c9d 100644
Binary files a/test/accessibility/pdf/text_1.pdf and b/test/accessibility/pdf/text_1.pdf differ
diff --git a/test/accessibility/pdf/text_2.pdf b/test/accessibility/pdf/text_2.pdf
index 3330cc902..19fff21a4 100644
Binary files a/test/accessibility/pdf/text_2.pdf and b/test/accessibility/pdf/text_2.pdf differ
diff --git a/test/accessibility/pdf/text_font-embedding.pdf b/test/accessibility/pdf/text_font-embedding.pdf
index a268dbb63..0288449d5 100644
Binary files a/test/accessibility/pdf/text_font-embedding.pdf and b/test/accessibility/pdf/text_font-embedding.pdf differ
diff --git a/test/accessibility/role.fo b/test/accessibility/role.fo
index e284cde9c..ced8a4d44 100644
--- a/test/accessibility/role.fo
+++ b/test/accessibility/role.fo
@@ -16,14 +16,14 @@
   limitations under the License.
 -->
 
-
+
   
     
       
     
   
-  
+  
     
       
 
-
+
   
     
       
     
   
-  
+  
     
       
 
-
+
   
     
       
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/text_2.fo b/test/accessibility/text_2.fo
index 5eff059b6..f5693110e 100644
--- a/test/accessibility/text_2.fo
+++ b/test/accessibility/text_2.fo
@@ -16,14 +16,14 @@
   limitations under the License.
 -->
 
-
+
   
     
       
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/accessibility/text_font-embedding.fo b/test/accessibility/text_font-embedding.fo
index 1d3abe027..10c1c99d9 100644
--- a/test/accessibility/text_font-embedding.fo
+++ b/test/accessibility/text_font-embedding.fo
@@ -16,14 +16,14 @@
   limitations under the License.
 -->
 
-
+
   
     
       
     
   
-  
+  
     
       Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
         formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
diff --git a/test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java b/test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java
deleted file mode 100644
index f8719edd6..000000000
--- a/test/java/org/apache/fop/accessibility/FO2StructureTreeConverterTestCase.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.accessibility;
-
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.dom.DOMResult;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamSource;
-
-import org.custommonkey.xmlunit.Diff;
-import org.custommonkey.xmlunit.Difference;
-import org.custommonkey.xmlunit.DifferenceConstants;
-import org.custommonkey.xmlunit.DifferenceListener;
-import org.junit.Test;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-import org.apache.fop.apps.FOPException;
-import org.apache.fop.apps.FOUserAgent;
-import org.apache.fop.fo.FODocumentParser;
-import org.apache.fop.fo.FODocumentParser.FOEventHandlerFactory;
-import org.apache.fop.fo.FOEventHandler;
-import org.apache.fop.fo.LoadingException;
-import org.apache.fop.fotreetest.DummyFOEventHandler;
-
-public class FO2StructureTreeConverterTestCase {
-
-    private static class IgnoringPtrDifferenceListener implements DifferenceListener {
-
-        public int differenceFound(Difference difference) {
-            switch (difference.getId()) {
-            case DifferenceConstants.ELEMENT_NUM_ATTRIBUTES_ID:
-                return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
-            case DifferenceConstants.ATTR_NAME_NOT_FOUND_ID:
-                String additionalAttribute = difference.getTestNodeDetail().getValue();
-                if (additionalAttribute.equals("ptr")) {
-                    return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
-                } else {
-                    return RETURN_ACCEPT_DIFFERENCE;
-                }
-            default:
-                return RETURN_ACCEPT_DIFFERENCE;
-            }
-        }
-
-        public void skippedComparison(Node control, Node test) {
-            throw new UnsupportedOperationException("Not implemented");
-        }
-    }
-
-    private static final String STRUCTURE_TREE_SEQUENCE_NAME = "structure-tree-sequence";
-
-    @Test
-    public void testConverter() throws Exception {
-        DOMResult expectedStructureTree = loadExpectedStructureTree();
-        DOMResult actualStructureTree = buildActualStructureTree();
-        final Diff diff = createDiff(expectedStructureTree, actualStructureTree);
-        assertTrue(diff.toString(), diff.similar());
-    }
-
-    private static DOMResult loadExpectedStructureTree() {
-        DOMResult expectedStructureTree = new DOMResult();
-        runXSLT(getXsltInputStream(), getFoInputStream(), expectedStructureTree);
-        return expectedStructureTree;
-    }
-
-    private static InputStream getXsltInputStream() {
-        return FO2StructureTreeConverterTestCase.class.getResourceAsStream("foToIfStructureTree.xsl");
-    }
-
-    private static InputStream getFoInputStream() {
-        return FO2StructureTreeConverterTestCase.class.getResourceAsStream(
-                "/org/apache/fop/fo/complete_document.fo");
-    }
-
-    private static void runXSLT(InputStream xslt, InputStream doc, Result result) {
-        Source fo = new StreamSource(doc);
-        try {
-            Transformer transformer = TransformerFactory.newInstance()
-                    .newTransformer(new StreamSource(xslt));
-            transformer.transform(fo, result);
-        } catch (TransformerConfigurationException e) {
-            throw new RuntimeException(e);
-        } catch (TransformerException e) {
-            throw new RuntimeException(e);
-        } finally {
-            closeStream(xslt);
-            closeStream(doc);
-        }
-    }
-
-    private static void closeStream(InputStream stream) {
-        try {
-            stream.close();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static DOMResult buildActualStructureTree() throws Exception {
-        DOMResult actualStructureTree = new DOMResult();
-        createStructureTreeFromDocument(getFoInputStream(), actualStructureTree);
-        return actualStructureTree;
-    }
-
-    private static void createStructureTreeFromDocument(InputStream foInputStream,
-            DOMResult domResult) throws Exception {
-        TransformerHandler tHandler = createTransformerHandler(domResult);
-        startStructureTreeSequence(tHandler);
-        StructureTreeEventHandler structureTreeEventHandler
-                = StructureTree2SAXEventAdapter.newInstance(tHandler);
-        FODocumentParser documentParser = createDocumentParser(structureTreeEventHandler);
-        FOUserAgent userAgent = createFOUserAgent(documentParser);
-        parseDocument(foInputStream, documentParser, userAgent);
-        endStructureTreeSequence(tHandler);
-    }
-
-    private static TransformerHandler createTransformerHandler(DOMResult domResult)
-            throws TransformerConfigurationException, TransformerFactoryConfigurationError {
-        SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
-        TransformerHandler transformerHandler = factory.newTransformerHandler();
-        transformerHandler.setResult(domResult);
-        return transformerHandler;
-    }
-
-    private static void startStructureTreeSequence(TransformerHandler tHandler) throws SAXException {
-        tHandler.startDocument();
-        tHandler.startElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME,
-                new AttributesImpl());
-    }
-
-    private static FODocumentParser createDocumentParser(
-            final StructureTreeEventHandler structureTreeEventHandler) {
-        return FODocumentParser.newInstance(new FOEventHandlerFactory() {
-            public FOEventHandler newFOEventHandler(FOUserAgent foUserAgent) {
-                return new FO2StructureTreeConverter(structureTreeEventHandler,
-                        new DummyFOEventHandler(foUserAgent));
-            }
-        });
-    }
-
-    private static FOUserAgent createFOUserAgent(FODocumentParser documentParser) {
-        FOUserAgent userAgent = documentParser.createFOUserAgent();
-        userAgent.setAccessibility(true);
-        return userAgent;
-    }
-
-    private static void parseDocument(InputStream foInputStream, FODocumentParser documentParser,
-            FOUserAgent userAgent) throws FOPException, LoadingException {
-        try {
-            documentParser.parse(foInputStream, userAgent);
-        } finally {
-            closeStream(foInputStream);
-        }
-    }
-
-    private static void endStructureTreeSequence(TransformerHandler tHandler) throws SAXException {
-        tHandler.endElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME);
-        tHandler.endDocument();
-    }
-
-    private static Diff createDiff(DOMResult expected, DOMResult actual) {
-        Diff diff = new Diff(getDocument(expected), getDocument(actual));
-        diff.overrideDifferenceListener(new IgnoringPtrDifferenceListener());
-        return diff;
-    }
-
-    private static Document getDocument(DOMResult result) {
-        return (Document) result.getNode();
-    }
-}
diff --git a/test/java/org/apache/fop/accessibility/fo/DOMResultUtil.java b/test/java/org/apache/fop/accessibility/fo/DOMResultUtil.java
new file mode 100644
index 000000000..5b4e264f2
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/fo/DOMResultUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility.fo;
+
+import java.io.File;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/**
+ * Utility class to stream an instance of {@link DOMResult} into a file. May be
+ * useful for debugging.
+ */
+final class DOMResultUtil {
+
+    private DOMResultUtil() {
+    }
+
+    /**
+     * Streams the given result into a file of the given name.
+     *
+     * @param result the result of a transformation
+     * @param filename name of the file into which to stream the result
+     * @throws TransformerException if a problem occurs when streaming
+     */
+    public static void streamToFile(DOMResult result, String filename) throws TransformerException {
+        DOMSource source = new DOMSource(result.getNode());
+        TransformerFactory tFactory = TransformerFactory.newInstance();
+        Transformer transformer = tFactory.newTransformer();
+        transformer.transform(source, new StreamResult(new File(filename)));
+    }
+
+}
diff --git a/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java b/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java
new file mode 100644
index 000000000..9c53bdde3
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.accessibility.fo;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.custommonkey.xmlunit.Diff;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.accessibility.StructureTree2SAXEventAdapter;
+import org.apache.fop.accessibility.StructureTreeEventHandler;
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fo.FODocumentParser;
+import org.apache.fop.fo.FODocumentParser.FOEventHandlerFactory;
+import org.apache.fop.fo.FOEventHandler;
+import org.apache.fop.fo.LoadingException;
+import org.apache.fop.fotreetest.DummyFOEventHandler;
+
+public class FO2StructureTreeConverterTestCase {
+
+    private interface FOLoader {
+
+        InputStream getFoInputStream();
+    }
+
+    private static final String STRUCTURE_TREE_SEQUENCE_NAME = "structure-tree-sequence";
+
+    private FOLoader foLoader;
+
+    @Test
+    public void testCompleteDocument() throws Exception {
+        foLoader = new FOLoader() {
+            public InputStream getFoInputStream() {
+                return getResource("/org/apache/fop/fo/complete_document.fo");
+            }
+        };
+        testConverter();
+    }
+
+    @Test
+    public void testTableFooters() throws Exception {
+        foLoader = new FOLoader() {
+            public InputStream getFoInputStream() {
+                return getResource("table-footers.fo");
+            }
+        };
+        testConverter();
+    }
+
+    @Test
+    public void testCompleteContentWrappedInTableFooter() throws Exception {
+        Source xslt = new StreamSource(getResource("wrapCompleteDocumentInTableFooter.xsl"));
+        Transformer transformer = createTransformer(xslt);
+        InputStream originalFO = getResource("/org/apache/fop/fo/complete_document.fo");
+        ByteArrayOutputStream transformedFoOutput = new ByteArrayOutputStream();
+        transformer.transform(new StreamSource(originalFO), new StreamResult(transformedFoOutput));
+        final byte[] transformedFoOutputBytes = transformedFoOutput.toByteArray();
+        foLoader = new FOLoader() {
+            public InputStream getFoInputStream() {
+                return new ByteArrayInputStream(transformedFoOutputBytes);
+            }
+        };
+        testConverter();
+    }
+
+    private Transformer createTransformer(Source xslt) throws TransformerFactoryConfigurationError,
+            TransformerConfigurationException {
+        TransformerFactory transformerFactory = TransformerFactory.newInstance();
+        return transformerFactory.newTransformer(xslt);
+    }
+
+    private static InputStream getResource(String name) {
+        return FO2StructureTreeConverterTestCase.class.getResourceAsStream(name);
+    }
+
+    private void testConverter() throws Exception {
+        DOMResult expectedStructureTree = loadExpectedStructureTree();
+        DOMResult actualStructureTree = buildActualStructureTree();
+        final Diff diff = createDiff(expectedStructureTree, actualStructureTree);
+        assertTrue(diff.toString(), diff.identical());
+    }
+
+    private DOMResult loadExpectedStructureTree() {
+        DOMResult expectedStructureTree = new DOMResult();
+        InputStream xslt = getResource("fo2StructureTree.xsl");
+        runXSLT(xslt, foLoader.getFoInputStream(), expectedStructureTree);
+        return expectedStructureTree;
+    }
+
+    private static void runXSLT(InputStream xslt, InputStream doc, Result result) {
+        Source fo = new StreamSource(doc);
+        try {
+            Transformer transformer = TransformerFactory.newInstance()
+                    .newTransformer(new StreamSource(xslt));
+            transformer.transform(fo, result);
+        } catch (TransformerConfigurationException e) {
+            throw new RuntimeException(e);
+        } catch (TransformerException e) {
+            throw new RuntimeException(e);
+        } finally {
+            closeStream(xslt);
+            closeStream(doc);
+        }
+    }
+
+    private static void closeStream(InputStream stream) {
+        try {
+            stream.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private DOMResult buildActualStructureTree() throws Exception {
+        DOMResult actualStructureTree = new DOMResult();
+        createStructureTreeFromDocument(foLoader.getFoInputStream(), actualStructureTree);
+        return actualStructureTree;
+    }
+
+    private static void createStructureTreeFromDocument(InputStream foInputStream,
+            Result result) throws Exception {
+        TransformerHandler tHandler = createTransformerHandler(result);
+        startStructureTreeSequence(tHandler);
+        StructureTreeEventHandler structureTreeEventHandler
+                = StructureTree2SAXEventAdapter.newInstance(tHandler);
+        FODocumentParser documentParser = createDocumentParser(structureTreeEventHandler);
+        FOUserAgent userAgent = createFOUserAgent(documentParser);
+        parseDocument(foInputStream, documentParser, userAgent);
+        endStructureTreeSequence(tHandler);
+    }
+
+    private static TransformerHandler createTransformerHandler(Result domResult)
+            throws TransformerConfigurationException, TransformerFactoryConfigurationError {
+        SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+        TransformerHandler transformerHandler = factory.newTransformerHandler();
+        transformerHandler.setResult(domResult);
+        return transformerHandler;
+    }
+
+    private static void startStructureTreeSequence(TransformerHandler tHandler) throws SAXException {
+        tHandler.startDocument();
+        tHandler.startElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME,
+                new AttributesImpl());
+    }
+
+    private static FODocumentParser createDocumentParser(
+            final StructureTreeEventHandler structureTreeEventHandler) {
+        return FODocumentParser.newInstance(new FOEventHandlerFactory() {
+            public FOEventHandler newFOEventHandler(FOUserAgent foUserAgent) {
+                return new FO2StructureTreeConverter(structureTreeEventHandler,
+                        new DummyFOEventHandler(foUserAgent));
+            }
+        });
+    }
+
+    private static FOUserAgent createFOUserAgent(FODocumentParser documentParser) {
+        FOUserAgent userAgent = documentParser.createFOUserAgent();
+        userAgent.setAccessibility(true);
+        return userAgent;
+    }
+
+    private static void parseDocument(InputStream foInputStream, FODocumentParser documentParser,
+            FOUserAgent userAgent) throws FOPException, LoadingException {
+        try {
+            documentParser.parse(foInputStream, userAgent);
+        } finally {
+            closeStream(foInputStream);
+        }
+    }
+
+    private static void endStructureTreeSequence(TransformerHandler tHandler) throws SAXException {
+        tHandler.endElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME);
+        tHandler.endDocument();
+    }
+
+    private static Diff createDiff(DOMResult expected, DOMResult actual) {
+        Diff diff = new Diff(getDocument(expected), getDocument(actual));
+        return diff;
+    }
+
+    private static Document getDocument(DOMResult result) {
+        return (Document) result.getNode();
+    }
+}
diff --git a/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl b/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl
new file mode 100644
index 000000000..ce326f3b1
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl
@@ -0,0 +1,135 @@
+
+
+
+
+
+  
+
+  
+    
+      
+    
+  
+
+
+  
+  
+    
+      
+    
+  
+
+  
+  
+    
+        
+    
+  
+
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+    
+  
+
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+    
+      
+      
+      
+    
+  
+
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+  
+  
+    
+  
+
+
+  
+  
+
+
+  
+  
+    
+  
+
+  
+
+
+  
+  
+
+  
+  
+    
+  
+
+
diff --git a/test/java/org/apache/fop/accessibility/fo/table-footers.fo b/test/java/org/apache/fop/accessibility/fo/table-footers.fo
new file mode 100644
index 000000000..6dcb9b68d
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/fo/table-footers.fo
@@ -0,0 +1,195 @@
+
+
+  
+    
+      
+    
+  
+  
+    
+      
+        
+          
+            Start Outer Header
+            
+              
+                
+                  Inner Header 1.1
+                
+                
+                  Inner Header 1.2
+                
+              
+              
+                
+                  Inner Footer 1.1
+                
+                
+                  Inner Footer 1.2
+                
+              
+              
+                
+                  
+                    Inner Body 1.1
+                  
+                  
+                    Inner Body 1.2
+                  
+                
+                
+                  
+                    Inner Body 2.1
+                  
+                  
+                    Inner Body 2.2
+                  
+                
+              
+            
+            End Outer Header
+          
+        
+        
+          
+            Start Outer Footer
+            
+              
+                
+                  Inner Header 1.1
+                
+                
+                  Inner Header 1.2
+                
+              
+              
+                
+                  Start Inner Footer 1.1
+                  
+                    
+                      
+                        Inner Inner Header 1.1
+                      
+                      
+                        Inner Inner Header 1.2
+                      
+                    
+                    
+                      
+                        Inner Inner Footer 1.1
+                      
+                      
+                        Inner Inner Footer 1.2
+                      
+                    
+                    
+                      
+                        
+                          Inner Inner Body 1.1
+                        
+                        
+                          Inner Inner Body 1.2
+                        
+                      
+                      
+                        
+                          Inner Inner Body 2.1
+                        
+                        
+                          Inner Inner Body 2.2
+                        
+                      
+                    
+                  
+                  End Inner Footer 1.1
+                
+                
+                  Inner Footer 1.2
+                
+              
+              
+                
+                  
+                    Inner Body 1.1
+                  
+                  
+                    Inner Body 1.2
+                  
+                
+                
+                  
+                    Inner Body 2.1
+                  
+                  
+                    Inner Body 2.2
+                  
+                
+              
+            
+            End Outer Footer
+          
+        
+        
+          
+            
+              Outer Body Cell 1
+            
+          
+          
+            
+              Start Outer Body Cell 2
+              
+                
+                  
+                    Inner Footer 1.1
+                  
+                  
+                    Inner Footer 1.2
+                  
+                
+                
+                  
+                    Inner Header 1.1
+                  
+                  
+                    Inner Header 1.2
+                  
+                
+                
+                  
+                    
+                      Inner Body 1.1
+                    
+                    
+                      Inner Body 1.2
+                    
+                  
+                  
+                    
+                      Inner Body 2.1
+                    
+                    
+                      Inner Body 2.2
+                    
+                  
+                
+              
+              End Outer Body Cell 2
+            
+          
+        
+      
+    
+  
+
diff --git a/test/java/org/apache/fop/accessibility/fo/wrapCompleteDocumentInTableFooter.xsl b/test/java/org/apache/fop/accessibility/fo/wrapCompleteDocumentInTableFooter.xsl
new file mode 100644
index 000000000..9608b2fb9
--- /dev/null
+++ b/test/java/org/apache/fop/accessibility/fo/wrapCompleteDocumentInTableFooter.xsl
@@ -0,0 +1,66 @@
+
+
+
+
+
+  
+    
+      
+    
+  
+
+
+  
+    
+      
+        
+          
+        
+      
+      
+    
+  
+
+  
+    
+      
+    
+  
+
+  
+    
+      
+      
+        
+          
+            
+            
+          
+        
+        
+          
+            The content below is in the table footer.
+          
+        
+      
+    
+  
+
+
diff --git a/test/java/org/apache/fop/accessibility/fo2StructureTree.xsl b/test/java/org/apache/fop/accessibility/fo2StructureTree.xsl
deleted file mode 100644
index 5c9c5615c..000000000
--- a/test/java/org/apache/fop/accessibility/fo2StructureTree.xsl
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-  
-
-  
-    
-      
-    
-  
-
-
-  
-  
-    
-      
-    
-  
-
-  
-  
-    
-        
-    
-  
-
-
-  
-  
-    
-  
-
-  
-  
-    
-  
-
-  
-  
-    
-  
-
-  
-    
-  
-
-  
-    
-  
-
-  
-  
-    
-  
-
-  
-    
-  
-
-  
-  
-    
-  
-
-  
-  
-    
-  
-
-  
-  
-    
-  
-
-  
-  
-    
-  
-
-
-  
-  
-
-
-  
-  
-    
-  
-
-  
-
-
-  
-  
-
-
diff --git a/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java b/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java
index d7db7db7f..65c6b25a6 100644
--- a/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java
+++ b/test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java
@@ -85,7 +85,7 @@ public class IFStructureTreeBuilderTestCase {
 
     @Test
     public void startNode() throws Exception {
-        final String[] attributes = {"ptr", "1"};
+        final String[] attributes = {"struct-id", "1"};
         final String nodeName = "block";
         final ContentHandler handler = mock(ContentHandler.class);
 
-- 
cgit v1.2.3