blob: 060316bceafb4173e285d2df8b5886910624a02f [file] [log] [blame]
/*
* 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.HashMap;
import java.util.Map;
import java.util.Stack;
import org.xml.sax.SAXException;
import org.apache.fop.accessibility.Accessibility;
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.AbstractRetrieveMarker;
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.RetrieveMarker;
import org.apache.fop.fo.flow.RetrieveTableMarker;
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.CommonAccessibility;
import org.apache.fop.fo.properties.CommonAccessibilityHolder;
/**
* 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. */
protected FOEventHandler converter;
private Stack<FOEventHandler> converters = new Stack<FOEventHandler>();
private final StructureTreeEventTrigger structureTreeEventTrigger;
/** The descendants of some elements like fo:leader must be ignored. */
private final FOEventHandler eventSwallower = new FOEventHandler() {
};
private final Map<AbstractRetrieveMarker, State> states = new HashMap<AbstractRetrieveMarker, State>();
private static final class State {
private final FOEventHandler converter;
private final Stack<FOEventHandler> converters;
@SuppressWarnings("unchecked")
State(FO2StructureTreeConverter o) {
this.converter = o.converter;
this.converters = (Stack<FOEventHandler>) o.converters.clone();
}
}
private Event root = new Event((Event) null);
private Event currentNode = root;
private void startContent(Event event, boolean hasContent) {
if (getUserAgent().isKeepEmptyTags()) {
event.run();
} else {
Event node = new Event(currentNode);
event.hasContent = hasContent;
node.add(event);
currentNode.add(node);
currentNode = node;
}
}
private void content(Event event, boolean hasContent) {
if (getUserAgent().isKeepEmptyTags()) {
event.run();
} else {
currentNode.add(event);
event.hasContent = hasContent;
}
}
private void endContent(Event event) {
if (getUserAgent().isKeepEmptyTags()) {
event.run();
} else {
currentNode.add(event);
currentNode = currentNode.parent;
if (currentNode == root) {
root.run();
}
}
}
/**
* 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(final PageNumber pagenum) {
startContent(new Event(this) {
public void run() {
eventHandler.startPageNumber(pagenum);
}
}, true);
super.startPageNumber(pagenum);
}
@Override
public void endPageNumber(final PageNumber pagenum) {
endContent(new Event(this) {
public void run() {
eventHandler.endPageNumber(pagenum);
}
});
super.endPageNumber(pagenum);
}
@Override
public void startPageNumberCitation(final PageNumberCitation pageCite) {
startContent(new Event(this) {
public void run() {
eventHandler.startPageNumberCitation(pageCite);
}
}, true);
super.startPageNumberCitation(pageCite);
}
@Override
public void endPageNumberCitation(final PageNumberCitation pageCite) {
endContent(new Event(this) {
public void run() {
eventHandler.endPageNumberCitation(pageCite);
}
});
super.endPageNumberCitation(pageCite);
}
@Override
public void startPageNumberCitationLast(final PageNumberCitationLast pageLast) {
startContent(new Event(this) {
public void run() {
eventHandler.startPageNumberCitationLast(pageLast);
}
}, true);
super.startPageNumberCitationLast(pageLast);
}
@Override
public void endPageNumberCitationLast(final PageNumberCitationLast pageLast) {
endContent(new Event(this) {
public void run() {
eventHandler.endPageNumberCitationLast(pageLast);
}
});
super.endPageNumberCitationLast(pageLast);
}
@Override
public void startStatic(final StaticContent staticContent) {
handleStartArtifact(staticContent);
startContent(new Event(this) {
public void run() {
eventHandler.startStatic(staticContent);
}
}, true);
super.startStatic(staticContent);
}
@Override
public void endStatic(final StaticContent staticContent) {
endContent(new Event(this) {
public void run() {
eventHandler.endStatic(staticContent);
}
});
handleEndArtifact(staticContent);
super.endStatic(staticContent);
}
@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(final Block bl) {
startContent(new Event(this) {
public void run() {
eventHandler.startBlock(bl);
}
}, false);
super.startBlock(bl);
}
@Override
public void endBlock(final Block bl) {
endContent(new Event(this) {
public void run() {
eventHandler.endBlock(bl);
}
});
super.endBlock(bl);
}
@Override
public void startBlockContainer(final BlockContainer blc) {
startContent(new Event(this) {
public void run() {
eventHandler.startBlockContainer(blc);
}
}, false);
super.startBlockContainer(blc);
}
@Override
public void endBlockContainer(final BlockContainer blc) {
endContent(new Event(this) {
public void run() {
eventHandler.endBlockContainer(blc);
}
});
super.endBlockContainer(blc);
}
@Override
public void startInline(final Inline inl) {
startContent(new Event(this) {
public void run() {
eventHandler.startInline(inl);
}
}, true);
super.startInline(inl);
}
@Override
public void endInline(final Inline inl) {
endContent(new Event(this) {
public void run() {
eventHandler.endInline(inl);
}
});
super.endInline(inl);
}
@Override
public void startTable(final Table tbl) {
startContent(new Event(this) {
public void run() {
eventHandler.startTable(tbl);
}
}, true);
super.startTable(tbl);
}
@Override
public void endTable(final Table tbl) {
endContent(new Event(this) {
public void run() {
eventHandler.endTable(tbl);
}
});
super.endTable(tbl);
}
@Override
public void startColumn(final TableColumn tc) {
startContent(new Event(this) {
public void run() {
eventHandler.startColumn(tc);
}
}, true);
super.startColumn(tc);
}
@Override
public void endColumn(final TableColumn tc) {
endContent(new Event(this) {
public void run() {
eventHandler.endColumn(tc);
}
});
super.endColumn(tc);
}
@Override
public void startHeader(final TableHeader header) {
handleStartArtifact(header);
startContent(new Event(this) {
public void run() {
eventHandler.startHeader(header);
}
}, false);
super.startHeader(header);
}
@Override
public void endHeader(final TableHeader header) {
endContent(new Event(this) {
public void run() {
eventHandler.endHeader(header);
}
});
handleEndArtifact(header);
super.endHeader(header);
}
@Override
public void startFooter(final TableFooter footer) {
handleStartArtifact(footer);
startContent(new Event(this) {
public void run() {
eventHandler.startFooter(footer);
}
}, false);
super.startFooter(footer);
}
@Override
public void endFooter(final TableFooter footer) {
endContent(new Event(this) {
public void run() {
eventHandler.endFooter(footer);
}
});
handleEndArtifact(footer);
super.endFooter(footer);
}
@Override
public void startBody(final TableBody body) {
startContent(new Event(this) {
public void run() {
eventHandler.startBody(body);
}
}, true);
super.startBody(body);
}
@Override
public void endBody(final TableBody body) {
endContent(new Event(this) {
public void run() {
eventHandler.endBody(body);
}
});
super.endBody(body);
}
@Override
public void startRow(final TableRow tr) {
startContent(new Event(this) {
public void run() {
eventHandler.startRow(tr);
}
}, false);
super.startRow(tr);
}
@Override
public void endRow(final TableRow tr) {
endContent(new Event(this) {
public void run() {
eventHandler.endRow(tr);
}
});
super.endRow(tr);
}
@Override
public void startCell(final TableCell tc) {
startContent(new Event(this) {
public void run() {
eventHandler.startCell(tc);
}
}, false);
super.startCell(tc);
}
@Override
public void endCell(final TableCell tc) {
endContent(new Event(this) {
public void run() {
eventHandler.endCell(tc);
}
});
super.endCell(tc);
}
@Override
public void startList(final ListBlock lb) {
startContent(new Event(this) {
public void run() {
eventHandler.startList(lb);
}
}, true);
super.startList(lb);
}
@Override
public void endList(final ListBlock lb) {
endContent(new Event(this) {
public void run() {
eventHandler.endList(lb);
}
});
super.endList(lb);
}
@Override
public void startListItem(final ListItem li) {
startContent(new Event(this) {
public void run() {
eventHandler.startListItem(li);
}
}, true);
super.startListItem(li);
}
@Override
public void endListItem(final ListItem li) {
endContent(new Event(this) {
public void run() {
eventHandler.endListItem(li);
}
});
super.endListItem(li);
}
@Override
public void startListLabel(final ListItemLabel listItemLabel) {
startContent(new Event(this) {
public void run() {
eventHandler.startListLabel(listItemLabel);
}
}, true);
super.startListLabel(listItemLabel);
}
@Override
public void endListLabel(final ListItemLabel listItemLabel) {
endContent(new Event(this) {
public void run() {
eventHandler.endListLabel(listItemLabel);
}
});
super.endListLabel(listItemLabel);
}
@Override
public void startListBody(final ListItemBody listItemBody) {
startContent(new Event(this) {
public void run() {
eventHandler.startListBody(listItemBody);
}
}, true);
super.startListBody(listItemBody);
}
@Override
public void endListBody(final ListItemBody listItemBody) {
endContent(new Event(this) {
public void run() {
eventHandler.endListBody(listItemBody);
}
});
super.endListBody(listItemBody);
}
@Override
public void startMarkup() {
startContent(new Event(this) {
public void run() {
eventHandler.startMarkup();
}
}, true);
super.startMarkup();
}
@Override
public void endMarkup() {
endContent(new Event(this) {
public void run() {
eventHandler.endMarkup();
}
});
super.endMarkup();
}
@Override
public void startLink(final BasicLink basicLink) {
startContent(new Event(this) {
public void run() {
eventHandler.startLink(basicLink);
}
}, true);
super.startLink(basicLink);
}
@Override
public void endLink(final BasicLink basicLink) {
endContent(new Event(this) {
public void run() {
eventHandler.endLink(basicLink);
}
});
super.endLink(basicLink);
}
@Override
public void image(final ExternalGraphic eg) {
content(new Event(this) {
public void run() {
eventHandler.image(eg);
}
}, true);
super.image(eg);
}
@Override
public void pageRef() {
content(new Event(this) {
public void run() {
eventHandler.pageRef();
}
}, true);
super.pageRef();
}
@Override
public void startInstreamForeignObject(final InstreamForeignObject ifo) {
startContent(new Event(this) {
public void run() {
eventHandler.startInstreamForeignObject(ifo);
}
}, true);
super.startInstreamForeignObject(ifo);
}
@Override
public void endInstreamForeignObject(final InstreamForeignObject ifo) {
endContent(new Event(this) {
public void run() {
eventHandler.endInstreamForeignObject(ifo);
}
});
super.endInstreamForeignObject(ifo);
}
@Override
public void startFootnote(final Footnote footnote) {
startContent(new Event(this) {
public void run() {
eventHandler.startFootnote(footnote);
}
}, true);
super.startFootnote(footnote);
}
@Override
public void endFootnote(final Footnote footnote) {
endContent(new Event(this) {
public void run() {
eventHandler.endFootnote(footnote);
}
});
super.endFootnote(footnote);
}
@Override
public void startFootnoteBody(final FootnoteBody body) {
startContent(new Event(this) {
public void run() {
eventHandler.startFootnoteBody(body);
}
}, true);
super.startFootnoteBody(body);
}
@Override
public void endFootnoteBody(final FootnoteBody body) {
endContent(new Event(this) {
public void run() {
eventHandler.endFootnoteBody(body);
}
});
super.endFootnoteBody(body);
}
@Override
public void startLeader(final Leader l) {
converters.push(converter);
converter = eventSwallower;
startContent(new Event(this) {
public void run() {
eventHandler.startLeader(l);
}
}, false);
super.startLeader(l);
}
@Override
public void endLeader(final Leader l) {
endContent(new Event(this) {
public void run() {
eventHandler.endLeader(l);
}
});
converter = converters.pop();
super.endLeader(l);
}
@Override
public void startWrapper(final Wrapper wrapper) {
handleStartArtifact(wrapper);
startContent(new Event(this) {
public void run() {
eventHandler.startWrapper(wrapper);
}
}, true);
super.startWrapper(wrapper);
}
@Override
public void endWrapper(final Wrapper wrapper) {
endContent(new Event(this) {
public void run() {
eventHandler.endWrapper(wrapper);
}
});
handleEndArtifact(wrapper);
super.endWrapper(wrapper);
}
@Override
public void startRetrieveMarker(final RetrieveMarker retrieveMarker) {
startContent(new Event(this) {
public void run() {
eventHandler.startRetrieveMarker(retrieveMarker);
}
}, true);
saveState(retrieveMarker);
super.startRetrieveMarker(retrieveMarker);
}
private void saveState(AbstractRetrieveMarker retrieveMarker) {
states.put(retrieveMarker, new State(this));
}
@Override
public void endRetrieveMarker(final RetrieveMarker retrieveMarker) {
endContent(new Event(this) {
public void run() {
eventHandler.endRetrieveMarker(retrieveMarker);
}
});
super.endRetrieveMarker(retrieveMarker);
}
@Override
public void restoreState(final RetrieveMarker retrieveMarker) {
restoreRetrieveMarkerState(retrieveMarker);
content(new Event(this) {
public void run() {
eventHandler.restoreState(retrieveMarker);
}
}, true);
super.restoreState(retrieveMarker);
}
@SuppressWarnings("unchecked")
private void restoreRetrieveMarkerState(AbstractRetrieveMarker retrieveMarker) {
State state = states.get(retrieveMarker);
this.converter = state.converter;
this.converters = (Stack<FOEventHandler>) state.converters.clone();
}
@Override
public void startRetrieveTableMarker(final RetrieveTableMarker retrieveTableMarker) {
startContent(new Event(this) {
public void run() {
eventHandler.startRetrieveTableMarker(retrieveTableMarker);
}
}, true);
saveState(retrieveTableMarker);
super.startRetrieveTableMarker(retrieveTableMarker);
}
@Override
public void endRetrieveTableMarker(final RetrieveTableMarker retrieveTableMarker) {
endContent(new Event(this) {
public void run() {
eventHandler.endRetrieveTableMarker(retrieveTableMarker);
}
});
super.endRetrieveTableMarker(retrieveTableMarker);
}
@Override
public void restoreState(final RetrieveTableMarker retrieveTableMarker) {
restoreRetrieveMarkerState(retrieveTableMarker);
currentNode.add(new Event(this) {
public void run() {
eventHandler.restoreState(retrieveTableMarker);
}
});
super.restoreState(retrieveTableMarker);
}
@Override
public void character(final Character c) {
content(new Event(this) {
public void run() {
eventHandler.character(c);
}
}, true);
super.character(c);
}
@Override
public void characters(final FOText foText) {
content(new Event(this) {
public void run() {
eventHandler.characters(foText);
}
}, foText.length() > 0);
super.characters(foText);
}
@Override
public void startExternalDocument(final ExternalDocument document) {
startContent(new Event(this) {
public void run() {
eventHandler.startExternalDocument(document);
}
}, true);
super.startExternalDocument(document);
}
@Override
public void endExternalDocument(final ExternalDocument document) {
endContent(new Event(this) {
public void run() {
eventHandler.endExternalDocument(document);
}
});
super.endExternalDocument(document);
}
private void handleStartArtifact(CommonAccessibilityHolder fobj) {
if (isArtifact(fobj)) {
converters.push(converter);
converter = eventSwallower;
}
}
private void handleEndArtifact(CommonAccessibilityHolder fobj) {
if (isArtifact(fobj)) {
converter = converters.pop();
}
}
private boolean isArtifact(CommonAccessibilityHolder fobj) {
CommonAccessibility accessibility = fobj.getCommonAccessibility();
return Accessibility.ROLE_ARTIFACT.equalsIgnoreCase(accessibility.getRole());
}
}