/* * #! * 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.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.Set; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.topicmaps.core.AssociationIF; import net.ontopia.topicmaps.core.AssociationRoleIF; import net.ontopia.topicmaps.core.OccurrenceIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.core.TopicMapStoreIF; import net.ontopia.topicmaps.core.TopicNameIF; import net.ontopia.topicmaps.core.VariantNameIF; import net.ontopia.topicmaps.entry.TopicMapReferenceIF; import net.ontopia.utils.CompactHashSet; import net.ontopia.utils.OntopiaRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; /** * PUBLIC: Exports partial topic maps using an approach inspired * by the XTM Fragment Interchange 0.1 specification, but different. * * <p>Note that the TopicMapFragmentWriterIF implementation is not * this class, but XTMTopicMapFragmentWriter. * * <p><b>WARNING:</b> This class is not thread-safe. * * @see XTMTopicMapFragmentWriter */ public class XTMFragmentExporter extends XTMTopicMapExporter { public static final String VIRTUAL_URN = "urn:x-oks-virtual:"; protected static final String EMPTY_NAMESPACE = ""; protected static final String EMPTY_LOCALNAME = ""; protected LocatorHandlerIF locator_handler; protected Collection reifiers; // topics reifying tm constructs (for export) protected Set alreadyExported; // objids topics & assocs already exported protected boolean use_local_ids; // see setUseLocalIds private String tmid; private static Logger log = LoggerFactory.getLogger(XTMTopicMapExporter.class.getName()); /** * PUBLIC: Initializes the exporter. */ public XTMFragmentExporter() { export_srclocs = true; locator_handler = null; reifiers = new ArrayList(); alreadyExported = new CompactHashSet(); } public XTMFragmentExporter(String tmid) { this(); this.tmid = tmid; } /** * EXPERIMENTAL: Sets the locator handler. Currently only used for * occurrences. */ public void setLocatorHandler(LocatorHandlerIF locator_handler) { this.locator_handler = locator_handler; } /** * PUBLIC: Controls whether or not internal references of the * form '#id' will be used. TMRAP cannot use this, whereas when * self-contained XTM fragment files are produced this should be * used. */ public void setUseLocalIds(boolean use_local_ids) { this.use_local_ids = use_local_ids; } /** * PUBLIC: Whether or not internal references of the form '#id' will * be exported. */ public boolean getUseLocalIds() { return use_local_ids; } /** * PUBLIC: Exports an XTM Fragment (complete with root element) to * the given ContentHandler, containing all the topics retrieved * from the Iterator. Duplicates do not cause problems. */ public void exportAll(Iterator it, ContentHandler dh) throws SAXException { startTopicMap(dh); exportTopics(it, dh); endTopicMap(dh); } /** * PUBLIC: Exports a set of topics without any wrapping element. */ public void exportTopics(Iterator it, ContentHandler dh) throws SAXException { while (it.hasNext()) { TopicIF topic = (TopicIF) it.next(); if (filterOk(topic)) writeTopic(topic, dh); } } /** * PUBLIC: Exports a set of topic names without any wrapping * element. */ public void exportTopicNames(Iterator it, ContentHandler dh) throws SAXException { while (it.hasNext()) writeTopicNames((TopicIF) it.next(), dh); } /** * PUBLIC: Outputs the <topicMap ...> start tag. */ public void startTopicMap(ContentHandler dh) throws SAXException { reifiers.clear(); alreadyExported.clear(); dh.startDocument(); // TOPICMAP // Calculate attributes atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xmlns", "CDATA", "http://www.topicmaps.org/xtm/1.0/"); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xmlns:xlink", "CDATA", "http://www.w3.org/1999/xlink"); // Output element dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicMap", atts); } /** * PUBLIC: Outputs the </topicMap> end tag. */ public void endTopicMap(ContentHandler dh) throws SAXException { while (!reifiers.isEmpty()) { Iterator it = reifiers.iterator(); reifiers = new ArrayList(); // for reifiers found during this export run exportTopics(it, dh); } // Close element dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicMap"); dh.endDocument(); } // --- Internal stuff //-------------------------------------------------------------------- // Methods used on Topics //-------------------------------------------------------------------- protected void writeTopic(TopicIF topic, ContentHandler dh) throws SAXException { String objid = topic.getObjectId(); if (alreadyExported.contains(objid)) return; alreadyExported.add(objid); super.writeTopic(topic, dh); Iterator it = topic.getRoles().iterator(); while (it.hasNext()) { AssociationRoleIF role = (AssociationRoleIF) it.next(); if (filterOk(role.getAssociation())) writeAssociation(role.getAssociation(), dh); } } protected void writeTopicNames(TopicIF topic, ContentHandler dh) throws SAXException { String objid = topic.getObjectId(); if (alreadyExported.contains(objid)) return; alreadyExported.add(objid); atts.clear(); addId(atts, topic); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topic", atts); if (topic.getSubjectLocators().size() > 0 || topic.getSubjectIdentifiers().size() > 0 || (topic.getItemIdentifiers().size() > 0 && export_srclocs)) writeSubjectIdentity(topic, dh); if ((topic.getTopicNames()).size() > 0) writeTopicNames(topic.getTopicNames(),dh); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topic"); } protected void writeTopicNames(Collection names, ContentHandler dh) throws SAXException { //Get names, and sort the out. Iterator iter = names.iterator(); while (iter.hasNext()) { TopicNameIF basename = (TopicNameIF)iter.next(); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "baseName", EMPTY_ATTR_LIST); if ((basename.getScope()).size() > 0) writeScope(basename.getScope(), dh); //Write baseNameString dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "baseNameString", EMPTY_ATTR_LIST); if (basename.getValue() != null && !basename.getValue().equals("")) { char[] chars = basename.getValue().toCharArray(); dh.characters(chars, 0, chars.length); } dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "baseNameString"); //Write variant if ((basename.getVariants()).size() > 0) writeVariants(basename.getVariants(), dh); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "baseName"); } } protected void writeTopicRef(TopicIF topic, ContentHandler dh) throws SAXException { // FIXME: non-URI locators if (!topic.getSubjectLocators().isEmpty()) { LocatorIF sub = (LocatorIF) topic.getSubjectLocators().iterator().next(); atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", sub.getAddress()); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef"); return; } if (!topic.getSubjectIdentifiers().isEmpty()) { LocatorIF ind = (LocatorIF) topic.getSubjectIdentifiers().iterator().next(); atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", ind.getAddress()); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIndicatorRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIndicatorRef"); return; } LocatorIF base = topic.getTopicMap().getStore().getBaseAddress(); String reference = null; // try to find an ID for this topic if (base != null) { String baseaddr = base.getExternalForm(); Iterator it = topic.getItemIdentifiers().iterator(); while (it.hasNext()) { LocatorIF loc = (LocatorIF) it.next(); reference = loc.getExternalForm(); if (!use_local_ids) break; if (reference.startsWith(baseaddr)) { reference = reference.substring(baseaddr.length()); // gives "#id" break; } } } // if no ID or source locator found, create a virtual one, // but do not assign it to the object if (reference == null) reference = makeVirtualReference(topic); // now we can output atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", reference); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef"); } protected void writeSubjectIdentity(TopicIF topic, ContentHandler dh) throws SAXException { dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIdentity", EMPTY_ATTR_LIST); boolean identityFound = false; // Subject address(es) Iterator it = topic.getSubjectLocators().iterator(); while (it.hasNext()) { LocatorIF subject = (LocatorIF) it.next(); String notation = subject.getNotation(); if (notation != null && notation.equals("URI")) { atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", subject.getAddress()); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef"); identityFound = true; } else reportInvalidLocator(subject); } // Subject indicators it = topic.getSubjectIdentifiers().iterator(); while (it.hasNext()) { LocatorIF indicator = (LocatorIF) it.next(); String notation = indicator.getNotation(); if (notation != null && notation.equals("URI")) { atts.clear(); String ref = getSubjectIndicatorRef(topic, indicator); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", ref); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIndicatorRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIndicatorRef"); if (ref.startsWith("#")) { atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", indicator.getAddress()); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIndicatorRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIndicatorRef"); } identityFound = true; } else reportInvalidLocator(indicator); } // Source locators (only if configured) if (export_srclocs) { it = topic.getItemIdentifiers().iterator(); if (it.hasNext()){ while (it.hasNext()) { LocatorIF srcloc = (LocatorIF) it.next(); String notation = srcloc.getNotation(); if (notation != null && notation.equals("URI")) { atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", srcloc.getAddress()); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef"); identityFound = true; } else reportInvalidLocator(srcloc); } } } // output virtual source locator if no other identity found if (!identityFound) { String reference = makeVirtualReference(topic); atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", reference); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "topicRef"); } dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "subjectIdentity"); } protected void writeVariants(Collection variants, ContentHandler dh) throws SAXException { Iterator iter = variants.iterator(); while (iter.hasNext()) { VariantNameIF var = (VariantNameIF)iter.next(); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "variant", EMPTY_ATTR_LIST); // write parameters if (var.getScope().size() > 0) writeParameters(var, dh); else { // FIXME : XTM violation! } // write variantName writeVariantName(var, dh); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "variant"); } } protected void writeOccurrences(Collection occurrences, ContentHandler dh) throws SAXException { Iterator iter = occurrences.iterator(); while (iter.hasNext()) { OccurrenceIF occr = (OccurrenceIF)iter.next(); TopicIF reifier = occr.getReifier(); if (reifier != null) { reifiers.add(reifier); // make sure this is exported, too } atts.clear(); addId(atts, occr); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "occurrence", atts); if (occr.getType() != null) writeInstanceOf(occr, dh); if (occr.getScope().size() > 0) writeScope(occr.getScope(), dh); // write resourceRef LocatorIF occloc = occr.getLocator(); if (occloc != null) { if (locator_handler != null) occloc = locator_handler.process(occloc); String notation = occloc.getNotation(); if (notation != null && notation.equals("URI")) { atts.clear(); atts.addAttribute(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "xlink:href", "CDATA", occloc.getAddress()); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef", atts); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceRef"); } else reportInvalidLocator(occloc); } // write resourceData else { dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceData", EMPTY_ATTR_LIST); if (occr.getValue() != null && !occr.getValue().equals("")) { char[] chars = occr.getValue().toCharArray(); dh.characters(chars, 0, chars.length); } dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "resourceData"); } dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "occurrence"); } } //-------------------------------------------------------------------- // Methods used on associations //-------------------------------------------------------------------- protected void writeAssociation(AssociationIF assoc, ContentHandler dh) throws SAXException { String objid = assoc.getObjectId(); if (alreadyExported.contains(objid)) return; alreadyExported.add(objid); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "association", EMPTY_ATTR_LIST); if (assoc.getType() != null) writeInstanceOf(assoc, dh); if (assoc.getScope().size() > 0) writeScope(assoc.getScope(), dh); if (assoc.getRoles().size() > 0 ) writeMembers(assoc, dh); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "association"); } protected void writeMembers(AssociationIF assoc, ContentHandler dh) throws SAXException { Iterator iter = assoc.getRoles().iterator(); while (iter.hasNext()){ AssociationRoleIF role = (AssociationRoleIF)iter.next(); dh.startElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "member", EMPTY_ATTR_LIST); if (role.getType() != null) writeRoleSpec(role.getType(), dh); if (role.getPlayer() != null) writeTopicRef(role.getPlayer(), dh); dh.endElement(EMPTY_NAMESPACE, EMPTY_LOCALNAME, "member"); } } //------------------------------------------------------------------- // Virtual locators //-------------------------------------------------------------------- public static boolean isVirtualReference(String address) { return address.startsWith(VIRTUAL_URN); } public static String resolveVirtualReference(String address, String tmid) { String topicMapIndex = XTMFragmentExporter.sourceTopicMapFromVirtualReference(address); if (!topicMapIndex.equals(tmid)) throw new OntopiaRuntimeException("Topic map IDs do not match, requested=" + topicMapIndex + ", current=" + tmid); return address.substring(address.indexOf('#') + 1); } public static String sourceTopicMapFromVirtualReference(String address) { int index = address.indexOf('#'); return address.substring(XTMFragmentExporter.VIRTUAL_URN.length(), index); } protected String makeVirtualReference(TopicIF topic) { String topicmap_id = tmid; if (topicmap_id == null) { // get hold of topic map reference and get topic map id from that TopicMapIF tm = topic.getTopicMap(); TopicMapStoreIF store = tm.getStore(); TopicMapReferenceIF ref = store.getReference(); if (ref != null) { topicmap_id = ref.getId(); //! if (topicmap_id == null) //! throw new OntopiaRuntimeException("Not able to get the topic map reference from topic: " + topic); } } if (topicmap_id == null) topicmap_id = ""; return makeVirtualReference(topic, topicmap_id); } public static String makeVirtualReference(TopicIF topic, String topicmap_id) { return VIRTUAL_URN + topicmap_id + "#" + topic.getObjectId(); } //-------------------------------------------------------------------- // LocatorHandlerIF //-------------------------------------------------------------------- /** * EXPERIMENTAL: Handler class used for processing locators. */ public interface LocatorHandlerIF { public LocatorIF process(LocatorIF locator); } }