/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* !#
*/
package net.ontopia.topicmaps.xml;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Iterator;
import net.ontopia.xml.PrettyPrinter;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.topicmaps.core.AssociationIF;
import net.ontopia.topicmaps.core.AssociationRoleIF;
import net.ontopia.topicmaps.core.DataTypes;
import net.ontopia.topicmaps.core.TopicNameIF;
import net.ontopia.topicmaps.core.OccurrenceIF;
import net.ontopia.topicmaps.core.TMObjectIF;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.core.TypedIF;
import net.ontopia.topicmaps.core.ScopedIF;
import net.ontopia.topicmaps.core.VariantNameIF;
import net.ontopia.topicmaps.core.ReifiableIF;
import net.ontopia.topicmaps.utils.PSI;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* INTERNAL: Exports topic maps to the XTM 2.0 or 2.1 interchange format.
*/
public class XTM2TopicMapExporter extends AbstractTopicMapExporter {
protected boolean export_itemids = false;
protected AttributesImpl atts;
protected static final AttributesImpl EMPTY_ATTR_LIST =
new AttributesImpl();
protected static final String EMPTY_NAMESPACE = "";
protected static final String EMPTY_LOCALNAME = "";
private final boolean xtm21Mode;
public XTM2TopicMapExporter() {
this(false);
}
/**
* EXPERIMENTAL: XTM 2.0 or XTM 2.1 output.
*
* @param xtm21 {@code true} to enable XTM 2.1, otherwise XTM 2.0
* will be written.
*/
public XTM2TopicMapExporter(final boolean xtm21) {
this.xtm21Mode = xtm21;
this.atts = new AttributesImpl();
}
public void setExportItemIdentifiers(boolean export_itemids) {
this.export_itemids = export_itemids;
}
/**
* INTERNAL: Default export method. An XML pretty printer is used and the
* output is written to stdout.
*/
public void export(TopicMapIF tm) throws SAXException, IOException {
PrintStream stream = new PrintStream(new BufferedOutputStream(System.out));
export(tm, new PrettyPrinter(stream));
}
/**
* INTERNAL: Traverses a Topic Map and emits SAX document handler events
* according to the Topic Map interchange format to the given document
* handler.
*/
public void export(TopicMapIF tm, ContentHandler dh) throws SAXException {
dh.startDocument();
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xmlns", "CDATA", "http://www.topicmaps.org/xtm/");
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "version", "CDATA", xtm21Mode ? "2.1" : "2.0");
addReifier(atts, tm);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicMap", atts);
writeReifier(tm, dh);
writeItemIdentities(tm, dh);
Iterator it = filterCollection(tm.getTopics()).iterator();
while (it.hasNext())
write((TopicIF) it.next(), dh);
it = filterCollection(tm.getAssociations()).iterator();
while (it.hasNext())
write((AssociationIF) it.next(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicMap");
dh.endDocument();
}
// --- INTERNAL
private void write(TopicIF topic, ContentHandler dh) throws SAXException {
final Collection<LocatorIF> iids = topic.getItemIdentifiers();
final Collection<LocatorIF> sids = topic.getSubjectIdentifiers();
final Collection<LocatorIF> slos = topic.getSubjectLocators();
atts.clear();
if (!xtm21Mode || (iids.isEmpty() && sids.isEmpty() && slos.isEmpty()))
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "id", "CDATA", getElementId(topic));
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topic", atts);
writeItemIdentities(topic, dh); // this method has export_itemids test
write(sids, "subjectIdentifier", dh);
write(slos, "subjectLocator", dh);
Iterator it = filterCollection(topic.getTypes()).iterator();
if (it.hasNext()) {
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "instanceOf", EMPTY_ATTR_LIST);
while (it.hasNext())
writeTopicRef((TopicIF) it.next(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "instanceOf");
}
it = filterCollection(topic.getTopicNames()).iterator();
while (it.hasNext())
write((TopicNameIF) it.next(), dh);
it = filterCollection(topic.getOccurrences()).iterator();
while (it.hasNext())
write((OccurrenceIF) it.next(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topic");
}
private void write(TopicNameIF bn, ContentHandler dh) throws SAXException {
atts.clear();
addReifier(atts, bn);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "name", atts);
writeReifier(bn, dh);
writeItemIdentities(bn, dh);
writeType(bn, dh);
writeScope(bn, dh);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "value", EMPTY_ATTR_LIST);
write(bn.getValue(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "value");
Iterator it = filterCollection(bn.getVariants()).iterator();
while (it.hasNext()) {
VariantNameIF vn = (VariantNameIF) it.next();
write(vn, dh);
}
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "name");
}
private void write(VariantNameIF vn, ContentHandler dh) throws SAXException {
atts.clear();
addReifier(atts, vn);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "variant", atts);
writeReifier(vn, dh);
writeItemIdentities(vn, dh);
writeScope(vn, dh);
atts.clear();
if (vn.getDataType().equals(DataTypes.TYPE_URI)) {
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", vn.getLocator().getExternalForm());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef", atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef");
} else {
addDatatype(atts, vn.getDataType());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceData", atts);
write(vn.getValue(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceData");
}
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "variant");
}
private void write(OccurrenceIF occ, ContentHandler dh) throws SAXException{
atts.clear();
addReifier(atts, occ);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "occurrence", atts);
writeReifier(occ, dh);
writeItemIdentities(occ, dh);
writeType(occ, dh);
writeScope(occ, dh);
atts.clear();
if (occ.getDataType().equals(DataTypes.TYPE_URI)) {
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", occ.getLocator().getExternalForm());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef", atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef");
} else {
addDatatype(atts, occ.getDataType());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceData", atts);
write(occ.getValue(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceData");
}
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "occurrence");
}
private void write(AssociationIF assoc, ContentHandler dh)
throws SAXException {
Collection roles = filterCollection(assoc.getRoles());
if (roles.isEmpty())
return; // don't export empty assocs; they aren't valid
atts.clear();
addReifier(atts, assoc);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "association", atts);
writeReifier(assoc, dh);
writeItemIdentities(assoc, dh);
writeType(assoc, dh);
writeScope(assoc, dh);
Iterator it = assoc.getRoles().iterator();
while (it.hasNext())
write((AssociationRoleIF) it.next(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "association");
}
private void write(AssociationRoleIF role, ContentHandler dh)
throws SAXException {
atts.clear();
addReifier(atts, role);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "role", atts);
writeReifier(role, dh);
writeItemIdentities(role, dh);
writeType(role, dh);
writeTopicRef(role.getPlayer(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "role");
}
private void write(Collection<LocatorIF> locators, String element, ContentHandler dh)
throws SAXException {
for (LocatorIF loc: locators) {
atts.clear();
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", loc.getExternalForm());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, element, atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, element);
}
}
private void write(String str, ContentHandler dh) throws SAXException {
if (str != null && !str.equals("")) {
char[] chars = str.toCharArray();
dh.characters(chars, 0, chars.length);
}
}
private void writeType(TypedIF obj, ContentHandler dh) throws SAXException {
if (obj.getType() == null ||
((obj instanceof TopicNameIF) && isDefaultNameType(obj.getType())))
return;
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "type", EMPTY_ATTR_LIST);
writeTopicRef(obj.getType(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "type");
}
private void writeReifier(final ReifiableIF reifiable, final ContentHandler dh) throws SAXException {
if (!xtm21Mode // Reifier attribute was used already.
|| reifiable.getReifier() == null) {
return;
}
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "reifier", EMPTY_ATTR_LIST);
writeTopicRef(reifiable.getReifier(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "reifier");
}
private void writeTopicRef(final TopicIF topic, final ContentHandler dh)
throws SAXException {
atts.clear();
if (!xtm21Mode) {
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", "#" + getElementId(topic));
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef", atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef");
}
else {
// XTM 2.1
// 1st try: Write subject identifier reference
Iterator<LocatorIF> iter = topic.getSubjectIdentifiers().iterator();
if (iter.hasNext()) {
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", iter.next().getExternalForm());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIdentifierRef", atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIdentifierRef");
}
else {
iter = topic.getSubjectLocators().iterator();
// 2nd try: Write subject locator reference
if (iter.hasNext()) {
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", iter.next().getExternalForm());
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectLocatorRef", atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectLocatorRef");
}
else {
// 3rd: Neither sid nor slo found, write an item identifier or generate an id
iter = topic.getItemIdentifiers().iterator();
final String ref = iter.hasNext() ? iter.next().getExternalForm() : "#" + getElementId(topic);
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "href", "CDATA", ref);
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef", atts);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef");
}
}
}
}
private void writeScope(ScopedIF obj, ContentHandler dh)
throws SAXException {
Iterator it = obj.getScope().iterator();
if (!it.hasNext())
return;
dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "scope", EMPTY_ATTR_LIST);
while (it.hasNext())
writeTopicRef((TopicIF) it.next(), dh);
dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "scope");
}
private void writeItemIdentities(TMObjectIF obj, ContentHandler dh)
throws SAXException {
if (export_itemids)
write(obj.getItemIdentifiers(), "itemIdentity", dh);
}
private boolean isDefaultNameType(TopicIF type) {
return type.getSubjectIdentifiers().contains(PSI.getSAMNameType());
}
private void addReifier(AttributesImpl atts, ReifiableIF reified) {
if (xtm21Mode || reified.getReifier() == null) {
return;
}
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "reifier", "CDATA",
"#" + getElementId(reified.getReifier()));
}
private void addDatatype(AttributesImpl atts, LocatorIF datatype) {
if (!datatype.equals(DataTypes.TYPE_STRING))
atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "datatype", "CDATA", datatype.getExternalForm());
}
}