diff --git a/pom.xml b/pom.xml index 4213539..b79f043 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.edmcouncil rdf-toolkit - 1.14.1 + 1.14.2 UTF-8 diff --git a/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedJsonLdWriter.java b/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedJsonLdWriter.java index 163550d..449a8a7 100644 --- a/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedJsonLdWriter.java +++ b/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedJsonLdWriter.java @@ -66,6 +66,11 @@ public class SortedJsonLdWriter extends SortedRdfWriter { // Turtle allows "values" in RDF collections private static final Class collectionClass = Value.class; + /** + * RDF Types that are preferred to be used first. + */ + private final List preferredRdfTypes = new ArrayList<>(PREFERRED_RDF_TYPES); + /** * Output stream for this JSON-LD writer. */ @@ -147,6 +152,10 @@ public void startRDF() throws RDFHandlerException { */ @Override public void endRDF() throws RDFHandlerException { + if (suppressNamedIndividuals) { + preferredRdfTypes.remove(Constants.owlNamedIndividual); + } + try { // Sort triples, etc. sortedOntologies = unsortedOntologies.toSorted(collectionClass, comparisonContext); @@ -297,11 +306,14 @@ protected void writeSubjectTriples(Writer out, Resource subject) throws Exceptio List valuesList = new ArrayList<>(); if (!values.isEmpty()) { if (predicate == Constants.RDF_TYPE) { - for (IRI preferredType : PREFERRED_RDF_TYPES) { + for (IRI preferredType : preferredRdfTypes) { if (values.contains(preferredType)) { valuesList.add(preferredType); values.remove(preferredType); } + if (suppressNamedIndividuals) { + values.remove(Constants.owlNamedIndividual); + } } } diff --git a/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedRdfXmlWriter.java b/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedRdfXmlWriter.java index 480d74f..8c02305 100644 --- a/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedRdfXmlWriter.java +++ b/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedRdfXmlWriter.java @@ -38,16 +38,6 @@ import static org.edmcouncil.rdf_toolkit.util.Constants.rdfParseType; import static org.edmcouncil.rdf_toolkit.util.Constants.xsString; -import org.eclipse.rdf4j.model.BNode; -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Literal; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Value; -import org.eclipse.rdf4j.rio.RDFHandlerException; -import org.edmcouncil.rdf_toolkit.model.SortedTurtleObjectList; -import org.edmcouncil.rdf_toolkit.model.SortedTurtlePredicateObjectMap; -import org.edmcouncil.rdf_toolkit.util.Constants; -import org.edmcouncil.rdf_toolkit.util.StringDataTypeOptions; import java.io.OutputStream; import java.io.Writer; import java.nio.charset.StandardCharsets; @@ -58,451 +48,496 @@ import java.util.Set; import java.util.TreeSet; import javax.xml.namespace.QName; +import org.eclipse.rdf4j.model.BNode; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.rio.RDFHandlerException; +import org.edmcouncil.rdf_toolkit.model.SortedTurtleObjectList; +import org.edmcouncil.rdf_toolkit.model.SortedTurtlePredicateObjectMap; +import org.edmcouncil.rdf_toolkit.util.Constants; +import org.edmcouncil.rdf_toolkit.util.StringDataTypeOptions; /** - * Equivalent to Sesame's built-in RDF/XML writer, but the triples are sorted into a consistent order. - * In order to do the sorting, it must be possible to load all of the RDF statements into memory. - * NOTE: comments are suppressed, as there isn't a clear way to sort them along with triples. + * Equivalent to Sesame's built-in RDF/XML writer, but the triples are sorted into a consistent order. In order to do + * the sorting, it must be possible to load all of the RDF statements into memory. NOTE: comments are suppressed, as + * there isn't a clear way to sort them along with triples. */ public class SortedRdfXmlWriter extends SortedRdfWriter { - // TODO: the 'out' parameter in 'write...' methods is not used, and should be refactored out of the code. Perhaps. One day. - - // need to use namespace prefixes generated by the serializer in order to be able to convert all predicates to - // a QName, as required for RDF/XML. - private static final boolean USE_GENERATED_PREFIXES = true; - - // RDF/XML only allows "resources" in RDF collections - private static final Class COLLECTION_CLASS = Resource.class; - - /** - * RDF Types that are preferred to be used first. - */ - private final List preferredRdfTypes = new ArrayList<>(PREFERRED_RDF_TYPES); - - /** Output stream for this RDF/XML writer. */ - // Note: this is an internal Java class, not part of the published API. But easier than writing our own indenter - // here. - private IndentingXMLStreamWriter output = null; - - /** Namespace prefix for the RDF namespace. */ - private String rdfPrefix = "rdf"; - - - /** - * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied output stream. - * - * @param out The OutputStream to write the RDF/XML to. - */ - public SortedRdfXmlWriter(OutputStream out) { - super(out); - this.output = new IndentingXMLStreamWriter(out, DEFAULT_LINE_END); - } - - /** - * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied writer. - * - * @param writer The Writer to write the RDF/XML to. - */ - public SortedRdfXmlWriter(Writer writer) { - super(writer); - this.output = new IndentingXMLStreamWriter(writer, DEFAULT_LINE_END); + // TODO: the 'out' parameter in 'write...' methods is not used, and should be refactored out of the code. Perhaps. One day. + + // need to use namespace prefixes generated by the serializer in order to be able to convert all predicates to + // a QName, as required for RDF/XML. + private static final boolean USE_GENERATED_PREFIXES = true; + + // RDF/XML only allows "resources" in RDF collections + private static final Class COLLECTION_CLASS = Resource.class; + + /** + * RDF Types that are preferred to be used first. + */ + private final List preferredRdfTypes = new ArrayList<>(PREFERRED_RDF_TYPES); + + /** + * Output stream for this RDF/XML writer. + */ + // Note: this is an internal Java class, not part of the published API. But easier than writing our own indenter + // here. + private IndentingXMLStreamWriter output = null; + + /** + * Namespace prefix for the RDF namespace. + */ + private String rdfPrefix = "rdf"; + + + /** + * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied output stream. + * + * @param out The OutputStream to write the RDF/XML to. + */ + public SortedRdfXmlWriter(OutputStream out) { + super(out); + this.output = new IndentingXMLStreamWriter(out, DEFAULT_LINE_END); + } + + /** + * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied writer. + * + * @param writer The Writer to write the RDF/XML to. + */ + public SortedRdfXmlWriter(Writer writer) { + super(writer); + this.output = new IndentingXMLStreamWriter(writer, DEFAULT_LINE_END); + } + + /** + * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied output stream. + * + * @param out The OutputStream to write the RDF/XML to. + * @param options options for the RDF/XML writer. + */ + public SortedRdfXmlWriter(OutputStream out, Map options) { + super(out, options); + String indent = options.containsKey(INDENT) ? ((String) options.get(INDENT)) : null; + String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; + this.output = new IndentingXMLStreamWriter(out, StandardCharsets.UTF_8.name(), indent, true, lineEnd); + } + + /** + * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied writer. + * + * @param writer The Writer to write the RDF/XML to. + * @param options options for the RDF/XML writer. + */ + public SortedRdfXmlWriter(Writer writer, Map options) { + super(writer, options); + String indent = options.containsKey(INDENT) ? ((String) options.get(INDENT)) : null; + String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; + this.output = new IndentingXMLStreamWriter(writer, indent, true, lineEnd); + } + + /** + * Signals the end of the RDF data. This method is called when all data has been reported. + * + * @throws org.eclipse.rdf4j.rio.RDFHandlerException If the RDF handler has encountered an unrecoverable error. + */ + @Override + public void endRDF() throws RDFHandlerException { + if (suppressNamedIndividuals) { + preferredRdfTypes.remove(Constants.owlNamedIndividual); } - /** - * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied output stream. - * - * @param out The OutputStream to write the RDF/XML to. - * @param options options for the RDF/XML writer. - */ - public SortedRdfXmlWriter(OutputStream out, Map options) { - super(out, options); - String indent = options.containsKey(INDENT) ? ((String) options.get(INDENT)) : null; - String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; - this.output = new IndentingXMLStreamWriter(out, StandardCharsets.UTF_8.name(), indent, true, lineEnd); + try { + // Sort triples, etc. + sortedOntologies = unsortedOntologies.toSorted(COLLECTION_CLASS, comparisonContext); + if (sortedOntologies.size() != unsortedOntologies.size()) { + System.err.printf("**** ontologies unexpectedly lost or gained during sorting: %d != %d%n", + sortedOntologies.size(), + unsortedOntologies.size()); + System.err.flush(); + } + + sortedTripleMap = unsortedTripleMap.toSorted(COLLECTION_CLASS, comparisonContext); + compareSortedToUnsortedTripleMap(sortedTripleMap, unsortedTripleMap, "RDF/XML"); // TODO + + sortedBlankNodes = unsortedBlankNodes.toSorted(COLLECTION_CLASS, comparisonContext); + if (sortedBlankNodes.size() != unsortedBlankNodes.size()) { + System.err.printf("**** blank nodes unexpectedly lost or gained during sorting: %d != %d%n", + sortedBlankNodes.size(), + unsortedBlankNodes.size()); + System.err.flush(); + } + + super.endRDF(); + } catch (Exception ex) { + throw new RDFHandlerException("unable to generate/write RDF output", ex); } - - /** - * Creates an RDFWriter instance that will write sorted RDF/XML to the supplied writer. - * - * @param writer The Writer to write the RDF/XML to. - * @param options options for the RDF/XML writer. - */ - public SortedRdfXmlWriter(Writer writer, Map options) { - super(writer, options); - String indent = options.containsKey(INDENT) ? ((String) options.get(INDENT)) : null; - String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; - this.output = new IndentingXMLStreamWriter(writer, indent, true, lineEnd); + } + + private void writeSpecialComments(Writer out, String[] comments) throws Exception { + if ((comments != null) && (comments.length >= 1)) { + List escapedComments = new ArrayList<>(); + for (String comment : comments) { + escapedComments.add(escapeCommentText(comment)); + } + final String indent = output.getIndentationString(); + final String surroundString = "\n" + indent + "####"; + final String joinString = "\n" + indent + "## "; + output.writeEOL(); // add extra EOL before comments + output.writeComment( + surroundString + joinString + String.join(joinString, escapedComments) + surroundString + "\n" + indent); + output.writeEOL(); // add extra EOL after comments } - - /** - * Signals the end of the RDF data. This method is called when all data has - * been reported. - * - * @throws org.eclipse.rdf4j.rio.RDFHandlerException If the RDF handler has encountered an unrecoverable error. - */ - @Override - public void endRDF() throws RDFHandlerException { - if (suppressNamedIndividuals) { - preferredRdfTypes.remove(Constants.owlNamedIndividual); - } - - try { - // Sort triples, etc. - sortedOntologies = unsortedOntologies.toSorted(COLLECTION_CLASS, comparisonContext); - if (sortedOntologies.size() != unsortedOntologies.size()) { - System.err.printf("**** ontologies unexpectedly lost or gained during sorting: %d != %d%n", - sortedOntologies.size(), - unsortedOntologies.size()); - System.err.flush(); - } - - sortedTripleMap = unsortedTripleMap.toSorted(COLLECTION_CLASS, comparisonContext); - compareSortedToUnsortedTripleMap(sortedTripleMap, unsortedTripleMap, "RDF/XML"); // TODO - - sortedBlankNodes = unsortedBlankNodes.toSorted(COLLECTION_CLASS, comparisonContext); - if (sortedBlankNodes.size() != unsortedBlankNodes.size()) { - System.err.printf("**** blank nodes unexpectedly lost or gained during sorting: %d != %d%n", - sortedBlankNodes.size(), - unsortedBlankNodes.size()); - System.err.flush(); + } + + protected void writeHeader(Writer out, SortedTurtleObjectList importList, String[] leadingComments) + throws Exception { + // Get prefixes used for the XML + rdfPrefix = reverseNamespaceTable.get(RDF_NS_URI); + + // Create a sorted list of namespace prefix mappings. + Set prefixes = new TreeSet<>(namespaceTable.keySet()); + + // Write the XML prologue + output.writeStartDocument(output.getXmlEncoding(), "1.0"); + output.writeEOL(); + + // Write the DTD subset, if required + if (useDtdSubset) { + output.startDTD("rdf:RDF"); + if (namespaceTable.size() > 0) { + for (String prefix : prefixes) { + if (USE_GENERATED_PREFIXES || !generatedNamespaceTable.containsKey(prefix)) { + if (prefix.length() >= 1) { + output.writeDtdEntity(prefix, namespaceTable.get(prefix)); } - - super.endRDF(); - } catch (Exception ex) { - throw new RDFHandlerException("unable to generate/write RDF output", ex); + } } + } + output.endDTD(); } - private void writeSpecialComments(Writer out, String[] comments) throws Exception { - if ((comments != null) && (comments.length >= 1)) { - List escapedComments = new ArrayList<>(); - for (String comment : comments) { - escapedComments.add(escapeCommentText(comment)); - } - final String indent = output.getIndentationString(); - final String surroundString = "\n" + indent + "####"; - final String joinString = "\n" + indent + "## "; - output.writeEOL(); // add extra EOL before comments - output.writeComment(surroundString + joinString + String.join(joinString, escapedComments) + surroundString + "\n" + indent); - output.writeEOL(); // add extra EOL after comments - } + // Open the root element. + output.writeStartElement(rdfPrefix, "RDF", RDF_NS_URI); // + + // Write the base IRI, if any. + if (baseIri != null) { + output.writeAttribute("xml", XML_NS_URI, "base", baseIri.stringValue()); } - protected void writeHeader(Writer out, SortedTurtleObjectList importList, String[] leadingComments) - throws Exception { - // Get prefixes used for the XML - rdfPrefix = reverseNamespaceTable.get(RDF_NS_URI); - - // Create a sorted list of namespace prefix mappings. - Set prefixes = new TreeSet<>(namespaceTable.keySet()); - - // Write the XML prologue - output.writeStartDocument(output.getXmlEncoding(), "1.0"); - output.writeEOL(); - - // Write the DTD subset, if required - if (useDtdSubset) { - output.startDTD("rdf:RDF"); - if (namespaceTable.size() > 0) { - for (String prefix : prefixes) { - if (USE_GENERATED_PREFIXES || !generatedNamespaceTable.containsKey(prefix)) { - if (prefix.length() >= 1) { - output.writeDtdEntity(prefix, namespaceTable.get(prefix)); - } - } - } - } - output.endDTD(); + // Write the namespace declarations into the root element. + if (namespaceTable.size() > 0) { + for (String prefix : prefixes) { + if (prefix.equals("") && omitXmlnsNamespace) { + continue; } - - // Open the root element. - output.writeStartElement(rdfPrefix, "RDF", RDF_NS_URI); // - - // Write the base IRI, if any. - if (baseIri != null) { - output.writeAttribute("xml", XML_NS_URI, "base", baseIri.stringValue()); + if ((USE_GENERATED_PREFIXES || !generatedNamespaceTable.containsKey(prefix)) && !"xml".equals(prefix)) { + if (prefix.length() >= 1) { + output.writeNamespace(prefix, namespaceTable.get(prefix)); + } else { + output.writeDefaultNamespace(namespaceTable.get(prefix)); + } } + } + } else { // create RDF namespace at a minimum + output.writeNamespace(rdfPrefix, RDF_NS_URI); + } - // Write the namespace declarations into the root element. - if (namespaceTable.size() > 0) { - for (String prefix : prefixes) { - if (prefix.equals("") && omitXmlnsNamespace) { - continue; - } - if ((USE_GENERATED_PREFIXES || !generatedNamespaceTable.containsKey(prefix)) && !"xml".equals(prefix)) { - if (prefix.length() >= 1) { - output.writeNamespace(prefix, namespaceTable.get(prefix)); - } else { - output.writeDefaultNamespace(namespaceTable.get(prefix)); - } - } - } - } else { // create RDF namespace at a minimum - output.writeNamespace(rdfPrefix, RDF_NS_URI); - } + // RDF/XML sometimes uses the 'xml' prefix, e.g. xml:lang. This prefix is never declared explicitly. + addDefaultNamespacePrefixIfMissing(XML_NS_URI, "xml"); + reverseNamespaceTable.put(XML_NS_URI, "xml"); // need to update reverse namespace table manually - // RDF/XML sometimes uses the 'xml' prefix, e.g. xml:lang. This prefix is never declared explicitly. - addDefaultNamespacePrefixIfMissing(XML_NS_URI, "xml"); - reverseNamespaceTable.put(XML_NS_URI, "xml"); // need to update reverse namespace table manually + // NOTE: have decided I don't need to preserve whitespace in attributes as I don't produce whitespace-sensitive + // attributes in RDF/XML. Also, apparently some less-than-conformant XML applications have problems with it. + // make sure whitespace is preserved, for consistency of formatting + // output.writeAttribute("xml", XML_NS_URI, "space", "preserve"); - // NOTE: have decided I don't need to preserve whitespace in attributes as I don't produce whitespace-sensitive - // attributes in RDF/XML. Also, apparently some less-than-conformant XML applications have problems with it. - // make sure whitespace is preserved, for consistency of formatting - // output.writeAttribute("xml", XML_NS_URI, "space", "preserve"); + output.writeCharacters(""); // force writing of closing angle bracket in root element open tag + output.writeEOL(); // add extra EOL after root element - output.writeCharacters(""); // force writing of closing angle bracket in root element open tag - output.writeEOL(); // add extra EOL after root element + writeSpecialComments(out, leadingComments); + } - writeSpecialComments(out, leadingComments); - } + protected void writeSubjectSeparator(Writer out) { + // nothing to do here for RDF/XML + } - protected void writeSubjectSeparator(Writer out) { - // nothing to do here for RDF/XML + protected void writeSubjectTriples(Writer out, Resource subject) throws Exception { + SortedTurtlePredicateObjectMap poMap = sortedTripleMap.get(subject); + if (poMap == null) { + poMap = new SortedTurtlePredicateObjectMap(); } - protected void writeSubjectTriples(Writer out, Resource subject) throws Exception { - SortedTurtlePredicateObjectMap poMap = sortedTripleMap.get(subject); - if (poMap == null) { - poMap = new SortedTurtlePredicateObjectMap(); + // Try to determine whether to use or an element based on rdf:type value. + // Needed to determine if a type can be used as the XML element name: + SortedTurtleObjectList subjectRdfTypes = poMap.get(RDF_TYPE); + if (subjectRdfTypes != null) { // make a copy so we can remove values safely + subjectRdfTypes = (SortedTurtleObjectList) subjectRdfTypes.clone(); + } + // ignore owl:Thing for the purposes of determining what type to use an element name in RDF/XML + if ((subjectRdfTypes != null) && (subjectRdfTypes.size() >= 2)) { + subjectRdfTypes.remove(owlThing); + } + IRI enclosingElementIRI = rdfDescription; // default value + QName enclosingElementQName = convertIriToQName(enclosingElementIRI, USE_GENERATED_PREFIXES); + for (IRI preferredType : preferredRdfTypes) { // use a preferred rdf:type for the XML element tag name, if possible + // If suppressNamedIndividuals is not set, prioritise owl:NamedIndividual as the preferred RDF/XML element + // name; otherwise, use rdf:Description. + if ((subjectRdfTypes != null) && subjectRdfTypes.contains(preferredType)) { + QName subjectRdfTypeQName = convertIriToQName(preferredType, USE_GENERATED_PREFIXES); + if (subjectRdfTypeQName != null) { + enclosingElementIRI = preferredType; + enclosingElementQName = subjectRdfTypeQName; + break; } + } + } - // Try to determine whether to use or an element based on rdf:type value. - // Needed to determine if a type can be used as the XML element name: - SortedTurtleObjectList subjectRdfTypes = poMap.get(RDF_TYPE); - if (subjectRdfTypes != null) { // make a copy so we can remove values safely - subjectRdfTypes = (SortedTurtleObjectList) subjectRdfTypes.clone(); - } - // ignore owl:Thing for the purposes of determining what type to use an element name in RDF/XML - if ((subjectRdfTypes != null) && (subjectRdfTypes.size() >= 2)) { - subjectRdfTypes.remove(owlThing); - } - IRI enclosingElementIRI = rdfDescription; // default value - QName enclosingElementQName = convertIriToQName(enclosingElementIRI, USE_GENERATED_PREFIXES); - for (IRI preferredType : preferredRdfTypes) { // use a preferred rdf:type for the XML element tag name, if possible - if ((subjectRdfTypes != null) && subjectRdfTypes.contains(preferredType)) { // prioritise owl:NamedIndividual as the preferred RDF/XML element name - QName subjectRdfTypeQName = convertIriToQName(preferredType, USE_GENERATED_PREFIXES); - if (subjectRdfTypeQName != null) { - enclosingElementIRI = preferredType; - enclosingElementQName = subjectRdfTypeQName; - break; - } - } + // If no preferred type, use the type for the XML element tag, if there is only a single rdf:type + if ((rdfDescription.equals(enclosingElementIRI)) && (subjectRdfTypes != null) && (subjectRdfTypes.size() == 1)) { + Value subjectRdfTypeValue = subjectRdfTypes.first(); + if (subjectRdfTypeValue instanceof IRI) { + QName subjectRdfTypeQName = convertIriToQName((IRI) subjectRdfTypeValue, USE_GENERATED_PREFIXES); + if (subjectRdfTypeQName != null) { + enclosingElementIRI = (IRI) subjectRdfTypeValue; + enclosingElementQName = subjectRdfTypeQName; } + } + } - // If no preferred type, use the type for the XML element tag, if there is only a single rdf:type - if ((rdfDescription.equals(enclosingElementIRI)) && (subjectRdfTypes != null) && (subjectRdfTypes.size() == 1)) { - Value subjectRdfTypeValue = subjectRdfTypes.first(); - if (subjectRdfTypeValue instanceof IRI) { - QName subjectRdfTypeQName = convertIriToQName((IRI) subjectRdfTypeValue, USE_GENERATED_PREFIXES); - if (subjectRdfTypeQName != null) { - enclosingElementIRI = (IRI) subjectRdfTypeValue; - enclosingElementQName = subjectRdfTypeQName; - } - } - } + // Write enclosing element. + // The variation used for "rdf:about", or "rdf:nodeID", depends on settings and also whether the subject is a blank node or not. + output.writeStartElement(enclosingElementQName.getPrefix(), enclosingElementQName.getLocalPart(), + enclosingElementQName.getNamespaceURI()); + if (subject instanceof BNode) { + if (!inlineBlankNodes) { + output.writeAttribute(reverseNamespaceTable.get(RDF_NS_URI), RDF_NS_URI, "nodeID", + blankNodeNameMap.get(subject)); + } + } else if (subject instanceof IRI) { + output.writeStartAttribute(reverseNamespaceTable.get(RDF_NS_URI), RDF_NS_URI, "about"); + QName subjectQName = convertIriToQName((IRI) subject, USE_GENERATED_PREFIXES); + if ((subjectQName != null) && (subjectQName.getPrefix() != null) && (subjectQName.getPrefix().length() + >= 1)) { // if a prefix is defined, write out the subject QName using an entity reference + output.writeAttributeEntityRef(subjectQName.getPrefix()); + output.writeAttributeCharacters(((IRI) subject).getLocalName()); + } else { // just write the whole subject IRI + output.writeAttributeCharacters(subject.toString()); + } + output.endAttribute(); + } else { + // this shouldn't occur, but ... + output.writeAttribute( + reverseNamespaceTable.get(RDF_NS_URI), + RDF_NS_URI, + "about", + subject.stringValue()); + } - // Write enclosing element. - // The variation used for "rdf:about", or "rdf:nodeID", depends on settings and also whether the subject is a blank node or not. - output.writeStartElement(enclosingElementQName.getPrefix(), enclosingElementQName.getLocalPart(), enclosingElementQName.getNamespaceURI()); - if (subject instanceof BNode) { - if (!inlineBlankNodes) { - output.writeAttribute(reverseNamespaceTable.get(RDF_NS_URI), RDF_NS_URI, "nodeID", blankNodeNameMap.get(subject)); - } - } else if (subject instanceof IRI) { - output.writeStartAttribute(reverseNamespaceTable.get(RDF_NS_URI), RDF_NS_URI, "about"); - QName subjectQName = convertIriToQName((IRI)subject, USE_GENERATED_PREFIXES); - if ((subjectQName != null) && (subjectQName.getPrefix() != null) && (subjectQName.getPrefix().length() >= 1)) { // if a prefix is defined, write out the subject QName using an entity reference - output.writeAttributeEntityRef(subjectQName.getPrefix()); - output.writeAttributeCharacters(((IRI) subject).getLocalName()); - } else { // just write the whole subject IRI - output.writeAttributeCharacters(subject.toString()); - } - output.endAttribute(); - } else { - // this shouldn't occur, but ... - output.writeAttribute( - reverseNamespaceTable.get(RDF_NS_URI), - RDF_NS_URI, - "about", - subject.stringValue()); + // Write predicate/object pairs rendered first. + for (IRI predicate : firstPredicates) { + if (poMap.containsKey(predicate)) { + SortedTurtleObjectList values = poMap.get(predicate); + if (values != null) { // make a copy, so we don't delete anything from the original + values = (SortedTurtleObjectList) values.clone(); } - - // Write predicate/object pairs rendered first. - for (IRI predicate : firstPredicates) { - if (poMap.containsKey(predicate)) { - SortedTurtleObjectList values = poMap.get(predicate); - if (values != null) { // make a copy, so we don't delete anything from the original - values = (SortedTurtleObjectList) values.clone(); - } - ArrayList valuesList = new ArrayList<>(); - if (predicate == RDF_TYPE && values != null) { // assumes that rdfType is one of the firstPredicates - // no need to state type explicitly if it has been used as an enclosing element name - values.remove(enclosingElementIRI); - } - if (values != null && !values.isEmpty()) { - if (predicate == RDF_TYPE) { - for (IRI preferredType : preferredRdfTypes) { - if (values.contains(preferredType)) { - valuesList.add(preferredType); - values.remove(preferredType); - } - } - } - if (!values.isEmpty()) { - valuesList.addAll(values); - } - } - if (!valuesList.isEmpty()) { - writePredicateAndObjectValues(out, predicate, valuesList); - } - } + ArrayList valuesList = new ArrayList<>(); + if (predicate == RDF_TYPE && values != null) { // assumes that rdfType is one of the firstPredicates + // no need to state type explicitly if it has been used as an enclosing element name + values.remove(enclosingElementIRI); } - - // Write other predicate/object pairs. - for (IRI predicate : poMap.sortedKeys()) { - if (!firstPredicates.contains(predicate)) { - SortedTurtleObjectList values = poMap.get(predicate); - writePredicateAndObjectValues(out, predicate, values); + if (values != null && !values.isEmpty()) { + if (predicate == RDF_TYPE) { + for (IRI preferredType : preferredRdfTypes) { + if (values.contains(preferredType)) { + valuesList.add(preferredType); + values.remove(preferredType); + } + if (suppressNamedIndividuals) { + values.remove(Constants.owlNamedIndividual); + } } + } + if (!values.isEmpty()) { + valuesList.addAll(values); + } } - - // Close enclosing element. - output.writeEndElement(); - if (!inlineBlankNodes || !(subject instanceof BNode)) { - output.writeEOL(); + if (!valuesList.isEmpty()) { + writePredicateAndObjectValues(out, predicate, valuesList); } + } } - protected void writePredicateAndObjectValues(Writer out, IRI predicate, Collection values) throws Exception { - // Get prefixes used for the XML - rdfPrefix = reverseNamespaceTable.get(RDF_NS_URI); - String xmlPrefix = reverseNamespaceTable.get(XML_NS_URI); - - QName predicateQName = convertIriToQName(predicate, USE_GENERATED_PREFIXES); - for (Value value : values) { - if (inlineBlankNodes && (value instanceof BNode)) { - BNode bnode = (BNode) value; - if (isCollection(comparisonContext, bnode, COLLECTION_CLASS)) { - List members = getCollectionMembers(unsortedTripleMap, bnode, COLLECTION_CLASS, comparisonContext); - output.writeStartElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), predicateQName.getNamespaceURI()); - QName rdfParseTypeQName = convertIriToQName(rdfParseType, USE_GENERATED_PREFIXES); - output.writeAttribute(rdfParseTypeQName.getPrefix(), rdfParseTypeQName.getNamespaceURI(), rdfParseTypeQName.getLocalPart(), "Collection"); - for (Value member : members) { - if (member instanceof BNode) { - writeSubjectTriples(out, (Resource) member); - } else if (member instanceof IRI) { - QName rdfDescriptionQName = convertIriToQName(rdfDescription, USE_GENERATED_PREFIXES); - QName rdfAboutQName = convertIriToQName(rdfAbout, USE_GENERATED_PREFIXES); - output.writeStartElement(rdfDescriptionQName.getPrefix(), rdfDescriptionQName.getLocalPart(), rdfDescriptionQName.getNamespaceURI()); - QName memberQName = convertIriToQName((IRI) member, USE_GENERATED_PREFIXES); - if ((memberQName == null) || (memberQName.getPrefix() == null) || (memberQName.getPrefix().length() < 1)) { - output.writeAttribute(rdfAboutQName.getPrefix(), rdfAboutQName.getNamespaceURI(), rdfAboutQName.getLocalPart(), member.stringValue()); - } else { - output.writeStartAttribute(rdfAboutQName.getPrefix(), rdfAboutQName.getNamespaceURI(), rdfAboutQName.getLocalPart()); - output.writeAttributeEntityRef(memberQName.getPrefix()); - output.writeAttributeCharacters(memberQName.getLocalPart()); - output.endAttribute(); - } - output.writeEndElement(); - } else { - QName rdfDescriptionQName = convertIriToQName(rdfDescription, USE_GENERATED_PREFIXES); - output.writeStartElement(rdfDescriptionQName.getPrefix(), rdfDescriptionQName.getLocalPart(), rdfDescriptionQName.getNamespaceURI()); - if (member instanceof Literal) { - if (((Literal)member).getDatatype() != null) { - boolean useExplicit = (stringDataTypeOption == StringDataTypeOptions.EXPLICIT) || !(xsString.equals(((Literal)member).getDatatype()) || rdfLangString.equals(((Literal)member).getDatatype())); - if (useExplicit) { - output.writeStartAttribute(rdfPrefix, RDF_NS_URI, "datatype"); - QName datatypeQName = convertIriToQName(((Literal) member).getDatatype(), - USE_GENERATED_PREFIXES); - if ((datatypeQName == null) || (datatypeQName.getPrefix() == null) || (datatypeQName.getPrefix().length() < 1)) { - output.writeAttributeCharacters(((Literal) member).getDatatype().stringValue()); - } else { - output.writeAttributeEntityRef(datatypeQName.getPrefix()); - output.writeAttributeCharacters(datatypeQName.getLocalPart()); - } - output.endAttribute(); - } - } - if (((Literal)member).getLanguage().isPresent() || ((overrideStringLanguage != null) && (((Literal)member).getDatatype().stringValue().equals(xsString.stringValue())))) { - String lang = overrideStringLanguage == null ? ((Literal)member).getLanguage().get() : overrideStringLanguage; - output.writeAttribute(xmlPrefix, XML_NS_URI, "lang", lang); - } - output.writeCharacters(member.stringValue()); - } else { - output.writeCharacters(member.stringValue()); - } - output.writeEndElement(); - } - } - output.writeEndElement(); - } else { - output.writeStartElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), predicateQName.getNamespaceURI()); - writeSubjectTriples(out, bnode); - output.writeEndElement(); - } - } else { // not an inline blank node` - if ((value instanceof BNode) || (value instanceof IRI)) { - output.writeEmptyElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), predicateQName.getNamespaceURI()); - } else { - output.writeStartElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), predicateQName.getNamespaceURI()); - } - if (value instanceof BNode) { - output.writeAttribute(rdfPrefix, RDF_NS_URI, "nodeID", blankNodeNameMap.get(value)); - } else if (value instanceof IRI) { - output.writeStartAttribute(rdfPrefix, RDF_NS_URI, "resource"); - QName iriQName = convertIriToQName((IRI) value, USE_GENERATED_PREFIXES); - if (iriQName == null) { - output.writeAttributeCharacters(value.stringValue()); + // Write other predicate/object pairs. + for (IRI predicate : poMap.sortedKeys()) { + if (!firstPredicates.contains(predicate)) { + SortedTurtleObjectList values = poMap.get(predicate); + writePredicateAndObjectValues(out, predicate, values); + } + } + + // Close enclosing element. + output.writeEndElement(); + if (!inlineBlankNodes || !(subject instanceof BNode)) { + output.writeEOL(); + } + } + + protected void writePredicateAndObjectValues(Writer out, IRI predicate, Collection values) throws Exception { + // Get prefixes used for the XML + rdfPrefix = reverseNamespaceTable.get(RDF_NS_URI); + String xmlPrefix = reverseNamespaceTable.get(XML_NS_URI); + + QName predicateQName = convertIriToQName(predicate, USE_GENERATED_PREFIXES); + for (Value value : values) { + if (inlineBlankNodes && (value instanceof BNode)) { + BNode bnode = (BNode) value; + if (isCollection(comparisonContext, bnode, COLLECTION_CLASS)) { + List members = getCollectionMembers(unsortedTripleMap, bnode, COLLECTION_CLASS, comparisonContext); + output.writeStartElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), + predicateQName.getNamespaceURI()); + QName rdfParseTypeQName = convertIriToQName(rdfParseType, USE_GENERATED_PREFIXES); + output.writeAttribute(rdfParseTypeQName.getPrefix(), rdfParseTypeQName.getNamespaceURI(), + rdfParseTypeQName.getLocalPart(), "Collection"); + for (Value member : members) { + if (member instanceof BNode) { + writeSubjectTriples(out, (Resource) member); + } else if (member instanceof IRI) { + QName rdfDescriptionQName = convertIriToQName(rdfDescription, USE_GENERATED_PREFIXES); + QName rdfAboutQName = convertIriToQName(rdfAbout, USE_GENERATED_PREFIXES); + output.writeStartElement(rdfDescriptionQName.getPrefix(), rdfDescriptionQName.getLocalPart(), + rdfDescriptionQName.getNamespaceURI()); + QName memberQName = convertIriToQName((IRI) member, USE_GENERATED_PREFIXES); + if ((memberQName == null) || (memberQName.getPrefix() == null) || (memberQName.getPrefix().length() + < 1)) { + output.writeAttribute(rdfAboutQName.getPrefix(), rdfAboutQName.getNamespaceURI(), + rdfAboutQName.getLocalPart(), member.stringValue()); + } else { + output.writeStartAttribute(rdfAboutQName.getPrefix(), rdfAboutQName.getNamespaceURI(), + rdfAboutQName.getLocalPart()); + output.writeAttributeEntityRef(memberQName.getPrefix()); + output.writeAttributeCharacters(memberQName.getLocalPart()); + output.endAttribute(); + } + output.writeEndElement(); + } else { + QName rdfDescriptionQName = convertIriToQName(rdfDescription, USE_GENERATED_PREFIXES); + output.writeStartElement(rdfDescriptionQName.getPrefix(), rdfDescriptionQName.getLocalPart(), + rdfDescriptionQName.getNamespaceURI()); + if (member instanceof Literal) { + if (((Literal) member).getDatatype() != null) { + boolean useExplicit = (stringDataTypeOption == StringDataTypeOptions.EXPLICIT) || !( + xsString.equals(((Literal) member).getDatatype()) || rdfLangString.equals( + ((Literal) member).getDatatype())); + if (useExplicit) { + output.writeStartAttribute(rdfPrefix, RDF_NS_URI, "datatype"); + QName datatypeQName = convertIriToQName(((Literal) member).getDatatype(), + USE_GENERATED_PREFIXES); + if ((datatypeQName == null) || (datatypeQName.getPrefix() == null) || ( + datatypeQName.getPrefix().length() < 1)) { + output.writeAttributeCharacters(((Literal) member).getDatatype().stringValue()); } else { - if ((iriQName.getPrefix() != null) && (iriQName.getPrefix().length() >= 1)) { - output.writeAttributeEntityRef(iriQName.getPrefix()); - output.writeAttributeCharacters(iriQName.getLocalPart()); - } else { - output.writeAttributeCharacters(value.stringValue()); - } + output.writeAttributeEntityRef(datatypeQName.getPrefix()); + output.writeAttributeCharacters(datatypeQName.getLocalPart()); } output.endAttribute(); - } else if (value instanceof Literal) { - if (((Literal)value).getDatatype() != null) { - boolean useExplicit = (stringDataTypeOption == StringDataTypeOptions.EXPLICIT) || !(xsString.equals(((Literal)value).getDatatype()) || rdfLangString.equals(((Literal)value).getDatatype())); - if (useExplicit) { - output.writeStartAttribute(rdfPrefix, RDF_NS_URI, "datatype"); - QName datatypeQName = convertIriToQName(((Literal) value).getDatatype(), - USE_GENERATED_PREFIXES); - if ((datatypeQName == null) || (datatypeQName.getPrefix() == null) || (datatypeQName.getPrefix().length() < 1)) { - output.writeAttributeCharacters(((Literal) value).getDatatype().stringValue()); - } else { - output.writeAttributeEntityRef(datatypeQName.getPrefix()); - output.writeAttributeCharacters(datatypeQName.getLocalPart()); - } - output.endAttribute(); - } - } - if (((Literal)value).getLanguage().isPresent() || ((overrideStringLanguage != null) && (((Literal)value).getDatatype().stringValue().equals(xsString.stringValue())))) { - String lang = overrideStringLanguage == null ? - ((Literal) value).getLanguage().orElse(overrideStringLanguage) : - overrideStringLanguage; - output.writeAttribute(xmlPrefix, XML_NS_URI, "lang", lang); - } - output.writeCharacters(value.stringValue().trim()); // trim the value, because leading/closing spaces will be lost on subsequent parses/serialisations. - } else { - output.writeCharacters(value.stringValue().trim()); // trim the value, because leading/closing spaces will be lost on subsequent parses/serialisations. + } + } + if (((Literal) member).getLanguage().isPresent() || ((overrideStringLanguage != null) + && (((Literal) member).getDatatype().stringValue().equals(xsString.stringValue())))) { + String lang = + overrideStringLanguage == null ? ((Literal) member).getLanguage().get() : overrideStringLanguage; + output.writeAttribute(xmlPrefix, XML_NS_URI, "lang", lang); } - output.writeEndElement(); + output.writeCharacters(member.stringValue()); + } else { + output.writeCharacters(member.stringValue()); + } + output.writeEndElement(); + } + } + output.writeEndElement(); + } else { + output.writeStartElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), + predicateQName.getNamespaceURI()); + writeSubjectTriples(out, bnode); + output.writeEndElement(); + } + } else { // not an inline blank node` + if ((value instanceof BNode) || (value instanceof IRI)) { + output.writeEmptyElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), + predicateQName.getNamespaceURI()); + } else { + output.writeStartElement(predicateQName.getPrefix(), predicateQName.getLocalPart(), + predicateQName.getNamespaceURI()); + } + if (value instanceof BNode) { + output.writeAttribute(rdfPrefix, RDF_NS_URI, "nodeID", blankNodeNameMap.get(value)); + } else if (value instanceof IRI) { + output.writeStartAttribute(rdfPrefix, RDF_NS_URI, "resource"); + QName iriQName = convertIriToQName((IRI) value, USE_GENERATED_PREFIXES); + if (iriQName == null) { + output.writeAttributeCharacters(value.stringValue()); + } else { + if ((iriQName.getPrefix() != null) && (iriQName.getPrefix().length() >= 1)) { + output.writeAttributeEntityRef(iriQName.getPrefix()); + output.writeAttributeCharacters(iriQName.getLocalPart()); + } else { + output.writeAttributeCharacters(value.stringValue()); + } + } + output.endAttribute(); + } else if (value instanceof Literal) { + if (((Literal) value).getDatatype() != null) { + boolean useExplicit = (stringDataTypeOption == StringDataTypeOptions.EXPLICIT) || !( + xsString.equals(((Literal) value).getDatatype()) || rdfLangString.equals( + ((Literal) value).getDatatype())); + if (useExplicit) { + output.writeStartAttribute(rdfPrefix, RDF_NS_URI, "datatype"); + QName datatypeQName = convertIriToQName(((Literal) value).getDatatype(), + USE_GENERATED_PREFIXES); + if ((datatypeQName == null) || (datatypeQName.getPrefix() == null) || (datatypeQName.getPrefix().length() + < 1)) { + output.writeAttributeCharacters(((Literal) value).getDatatype().stringValue()); + } else { + output.writeAttributeEntityRef(datatypeQName.getPrefix()); + output.writeAttributeCharacters(datatypeQName.getLocalPart()); + } + output.endAttribute(); } + } + if (((Literal) value).getLanguage().isPresent() || ((overrideStringLanguage != null) + && (((Literal) value).getDatatype().stringValue().equals(xsString.stringValue())))) { + String lang = overrideStringLanguage == null ? + ((Literal) value).getLanguage().orElse(overrideStringLanguage) : + overrideStringLanguage; + output.writeAttribute(xmlPrefix, XML_NS_URI, "lang", lang); + } + output.writeCharacters(value.stringValue() + .trim()); // trim the value, because leading/closing spaces will be lost on subsequent parses/serialisations. + } else { + output.writeCharacters(value.stringValue() + .trim()); // trim the value, because leading/closing spaces will be lost on subsequent parses/serialisations. } + output.writeEndElement(); + } } + } - protected void writeFooter(Writer out, String[] trailingComments) throws Exception { - writeSpecialComments(out, trailingComments); + protected void writeFooter(Writer out, String[] trailingComments) throws Exception { + writeSpecialComments(out, trailingComments); - output.writeEndElement(); // - output.writeEndDocument(); - } + output.writeEndElement(); // + output.writeEndDocument(); + } - public static String escapeCommentText(String comment) { - if (comment == null) { return null; } - return comment.replace("--", "--"); + public static String escapeCommentText(String comment) { + if (comment == null) { + return null; } + return comment.replace("--", "--"); + } } diff --git a/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedTurtleWriter.java b/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedTurtleWriter.java index 0bb97dc..94bea57 100644 --- a/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedTurtleWriter.java +++ b/src/main/java/org/edmcouncil/rdf_toolkit/writer/SortedTurtleWriter.java @@ -28,6 +28,16 @@ import static org.edmcouncil.rdf_toolkit.comparator.ComparisonUtils.isCollection; import static org.edmcouncil.rdf_toolkit.util.Constants.INDENT; import static org.edmcouncil.rdf_toolkit.util.Constants.LINE_END; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Literal; @@ -39,516 +49,530 @@ import org.edmcouncil.rdf_toolkit.util.Constants; import org.edmcouncil.rdf_toolkit.util.StringDataTypeOptions; import org.edmcouncil.rdf_toolkit.util.TextUtils; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; /** - * Equivalent to Sesame's built-in Turtle writer, but the triples are sorted into a consistent order. - * In order to do the sorting, it must be possible to load all of the RDF statements into memory. - * NOTE: comments are suppressed, as there isn't a clear way to sort them along with triples. + * Equivalent to Sesame's built-in Turtle writer, but the triples are sorted into a consistent order. In order to do the + * sorting, it must be possible to load all of the RDF statements into memory. NOTE: comments are suppressed, as there + * isn't a clear way to sort them along with triples. */ public class SortedTurtleWriter extends SortedRdfWriter { - // no need to use namespace prefixes generated by the serializer for Turtle. - private static final boolean USE_GENERATED_PREFIXES = false; - - // Turtle allows "values" in RDF collections - private static final Class COLLECTION_CLASS = Value.class; - - /** - * RDF Types that are preferred to be used first. - */ - private final List preferredRdfTypes = new ArrayList<>(PREFERRED_RDF_TYPES); - - /** Output stream for this Turtle writer. */ - private final IndentingWriter output; - - /** - * Creates an RDFWriter instance that will write sorted Turtle to the supplied output stream. - * - * @param out The OutputStream to write the Turtle to. - */ - public SortedTurtleWriter(OutputStream out) { - super(out); - this.output = new IndentingWriter(new OutputStreamWriter(out)); - this.output.setLineEnd(DEFAULT_LINE_END); - this.out = this.output; + // no need to use namespace prefixes generated by the serializer for Turtle. + private static final boolean USE_GENERATED_PREFIXES = false; + + // Turtle allows "values" in RDF collections + private static final Class COLLECTION_CLASS = Value.class; + + /** + * RDF Types that are preferred to be used first. + */ + private final List preferredRdfTypes = new ArrayList<>(PREFERRED_RDF_TYPES); + + /** + * Output stream for this Turtle writer. + */ + private final IndentingWriter output; + + /** + * Creates an RDFWriter instance that will write sorted Turtle to the supplied output stream. + * + * @param out The OutputStream to write the Turtle to. + */ + public SortedTurtleWriter(OutputStream out) { + super(out); + this.output = new IndentingWriter(new OutputStreamWriter(out)); + this.output.setLineEnd(DEFAULT_LINE_END); + this.out = this.output; + } + + /** + * Creates an RDFWriter instance that will write sorted Turtle to the supplied writer. + * + * @param writer The Writer to write the Turtle to. + */ + public SortedTurtleWriter(Writer writer) { + super(writer); + this.output = new IndentingWriter(writer); + this.output.setLineEnd(DEFAULT_LINE_END); + this.out = this.output; + } + + /** + * Creates an RDFWriter instance that will write sorted Turtle to the supplied output stream. + * + * @param out The OutputStream to write the Turtle to. + * @param options options for the Turtle writer. + */ + public SortedTurtleWriter(OutputStream out, Map options) { + super(out, options); + this.output = new IndentingWriter(new OutputStreamWriter(out)); + this.out = this.output; + if (options.containsKey(INDENT)) { + this.output.setIndentationString((String) options.get(INDENT)); } - - /** - * Creates an RDFWriter instance that will write sorted Turtle to the supplied writer. - * - * @param writer The Writer to write the Turtle to. - */ - public SortedTurtleWriter(Writer writer) { - super(writer); - this.output = new IndentingWriter(writer); - this.output.setLineEnd(DEFAULT_LINE_END); - this.out = this.output; + String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; + this.output.setLineEnd(lineEnd); + } + + /** + * Creates an RDFWriter instance that will write sorted Turtle to the supplied writer. + * + * @param writer The Writer to write the Turtle to. + * @param options options for the Turtle writer. + */ + public SortedTurtleWriter(Writer writer, Map options) { + super(writer, options); + this.output = new IndentingWriter(writer); + this.out = this.output; + if (options.containsKey(INDENT)) { + this.output.setIndentationString((String) options.get(INDENT)); + } + String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; + this.output.setLineEnd(lineEnd); + } + + /** + * Signals the start of the RDF data. This method is called before any data is reported. + * + * @throws org.eclipse.rdf4j.rio.RDFHandlerException If the RDF handler has encountered an unrecoverable error. + */ + @Override + public void startRDF() throws RDFHandlerException { + super.startRDF(); + output.setIndentationLevel(0); + } + + /** + * Signals the end of the RDF data. This method is called when all data has been reported. + * + * @throws org.eclipse.rdf4j.rio.RDFHandlerException If the RDF handler has encountered an unrecoverable error. + */ + @Override + public void endRDF() throws RDFHandlerException { + if (suppressNamedIndividuals) { + preferredRdfTypes.remove(Constants.owlNamedIndividual); } - /** - * Creates an RDFWriter instance that will write sorted Turtle to the supplied output stream. - * - * @param out The OutputStream to write the Turtle to. - * @param options options for the Turtle writer. - */ - public SortedTurtleWriter(OutputStream out, Map options) { - super(out, options); - this.output = new IndentingWriter(new OutputStreamWriter(out)); - this.out = this.output; - if (options.containsKey(INDENT)) { - this.output.setIndentationString((String) options.get(INDENT)); - } - String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; - this.output.setLineEnd(lineEnd); + try { + // Sort triples, etc. + sortedOntologies = unsortedOntologies.toSorted(COLLECTION_CLASS, comparisonContext); + if (sortedOntologies.size() != unsortedOntologies.size()) { + System.err.printf("**** ontologies unexpectedly lost or gained during sorting: %d != %d%n", + sortedOntologies.size(), + unsortedOntologies.size()); + System.err.flush(); + } + + sortedTripleMap = unsortedTripleMap.toSorted(COLLECTION_CLASS, comparisonContext); + compareSortedToUnsortedTripleMap(sortedTripleMap, unsortedTripleMap, "Turtle"); // TODO + + sortedBlankNodes = unsortedBlankNodes.toSorted(COLLECTION_CLASS, comparisonContext); + if (sortedBlankNodes.size() != unsortedBlankNodes.size()) { + System.err.printf("**** blank nodes unexpectedly lost or gained during sorting: %d != %d%n", + sortedBlankNodes.size(), + unsortedBlankNodes.size()); + System.err.flush(); + } + + super.endRDF(); + } catch (Throwable t) { + throw new RDFHandlerException("unable to generate/write RDF output", t); + } + } + + protected void writeHeader(Writer out, SortedTurtleObjectList importList, String[] leadingComments) + throws Exception { + // Write TopBraid-specific special comments, if any. + if ((baseIri != null) || (importList.size() >= 1)) { + // Write the base IRI, if any. + if (baseIri != null) { + output.write("# baseURI: " + baseIri); + output.writeEOL(); + } + // Write ontology imports, if any. + for (Value anImport : importList) { + output.write("# imports: " + anImport.stringValue()); + output.writeEOL(); + } + output.writeEOL(); } - /** - * Creates an RDFWriter instance that will write sorted Turtle to the supplied writer. - * - * @param writer The Writer to write the Turtle to. - * @param options options for the Turtle writer. - */ - public SortedTurtleWriter(Writer writer, Map options) { - super(writer, options); - this.output = new IndentingWriter(writer); - this.out = this.output; - if (options.containsKey(INDENT)) { - this.output.setIndentationString((String) options.get(INDENT)); - } - String lineEnd = options.containsKey(LINE_END) ? options.get(LINE_END).toString() : DEFAULT_LINE_END; - this.output.setLineEnd(lineEnd); + // Write leading comments, if any. + if ((leadingComments != null) && (leadingComments.length >= 1)) { + output.write("####"); + output.writeEOL(); + for (String line : leadingComments) { + output.write("## " + line); + output.writeEOL(); + } + output.write("####"); + output.writeEOL(); + output.writeEOL(); } - /** - * Signals the start of the RDF data. This method is called before any data - * is reported. - * - * @throws org.eclipse.rdf4j.rio.RDFHandlerException If the RDF handler has encountered an unrecoverable error. - */ - @Override - public void startRDF() throws RDFHandlerException { - super.startRDF(); - output.setIndentationLevel(0); + // Write the base IRI, if any. + if (baseIri != null) { + output.write("@base <" + baseIri + "> ."); + output.writeEOL(); } - /** - * Signals the end of the RDF data. This method is called when all data has - * been reported. - * - * @throws org.eclipse.rdf4j.rio.RDFHandlerException If the RDF handler has encountered an unrecoverable error. - */ - @Override - public void endRDF() throws RDFHandlerException { - if (suppressNamedIndividuals) { - preferredRdfTypes.remove(Constants.owlNamedIndividual); + // Write out prefixes and namespaces IRIs. + if (namespaceTable.size() > 0) { + Set prefixes = new TreeSet<>(namespaceTable.keySet()); + for (String prefix : prefixes) { + if (USE_GENERATED_PREFIXES || !generatedNamespaceTable.containsKey(prefix)) { + output.write("@prefix " + prefix + ": <" + namespaceTable.get(prefix) + "> ."); + output.writeEOL(); } + } + output.writeEOL(); + } + } - try { - // Sort triples, etc. - sortedOntologies = unsortedOntologies.toSorted(COLLECTION_CLASS, comparisonContext); - if (sortedOntologies.size() != unsortedOntologies.size()) { - System.err.printf("**** ontologies unexpectedly lost or gained during sorting: %d != %d%n", - sortedOntologies.size(), - unsortedOntologies.size()); - System.err.flush(); - } - - sortedTripleMap = unsortedTripleMap.toSorted(COLLECTION_CLASS, comparisonContext); - compareSortedToUnsortedTripleMap(sortedTripleMap, unsortedTripleMap, "Turtle"); // TODO - - sortedBlankNodes = unsortedBlankNodes.toSorted(COLLECTION_CLASS, comparisonContext); - if (sortedBlankNodes.size() != unsortedBlankNodes.size()) { - System.err.printf("**** blank nodes unexpectedly lost or gained during sorting: %d != %d%n", - sortedBlankNodes.size(), - unsortedBlankNodes.size()); - System.err.flush(); - } + protected void writeSubjectSeparator(Writer out) { + // nothing to do here for Turtle + } - super.endRDF(); - } catch (Throwable t) { - throw new RDFHandlerException("unable to generate/write RDF output", t); + protected void writeSubjectTriples(Writer out, Resource subject) throws Exception { + SortedTurtlePredicateObjectMap poMap = sortedTripleMap.get(subject); + if (poMap == null) { + poMap = new SortedTurtlePredicateObjectMap(); + } + if (subject instanceof BNode) { + if (inlineBlankNodes) { + if (objectBlankNodes.contains(subject)) { + out.write("["); + } else { + out.write("[]"); } + } else { + out.write("_:" + blankNodeNameMap.get(subject)); + } + } else { + writeIri(out, (IRI) subject); + } + if (out instanceof IndentingWriter) { + IndentingWriter intendedOutput = (IndentingWriter) out; + intendedOutput.writeEOL(); + intendedOutput.increaseIndentation(); + } else { + out.write("\n"); } - protected void writeHeader(Writer out, SortedTurtleObjectList importList, String[] leadingComments) - throws Exception { - // Write TopBraid-specific special comments, if any. - if ((baseIri != null) || (importList.size() >= 1)) { - // Write the base IRI, if any. - if (baseIri != null) { - output.write("# baseURI: " + baseIri); - output.writeEOL(); - } - // Write ontology imports, if any. - for (Value anImport : importList) { - output.write("# imports: " + anImport.stringValue()); - output.writeEOL(); - } - output.writeEOL(); + // Write predicate/object pairs rendered first. + for (IRI predicate : firstPredicates) { + if (poMap.containsKey(predicate)) { + SortedTurtleObjectList values = poMap.get(predicate); + if (values != null) { // make a copy so we don't delete anything from the original + values = (SortedTurtleObjectList) values.clone(); } - - // Write leading comments, if any. - if ((leadingComments != null) && (leadingComments.length >= 1)) { - output.write("####"); output.writeEOL(); - for (String line : leadingComments) { - output.write("## " + line); output.writeEOL(); + List valuesList = new ArrayList<>(); + if (values != null && !values.isEmpty()) { + if (predicate == Constants.RDF_TYPE) { + for (IRI preferredType : preferredRdfTypes) { + if (values.contains(preferredType)) { + valuesList.add(preferredType); + values.remove(preferredType); + } } - output.write("####"); output.writeEOL(); - output.writeEOL(); - } - - // Write the base IRI, if any. - if (baseIri != null) { - output.write("@base <" + baseIri + "> ."); output.writeEOL(); - } - - // Write out prefixes and namespaces IRIs. - if (namespaceTable.size() > 0) { - Set prefixes = new TreeSet<>(namespaceTable.keySet()); - for (String prefix : prefixes) { - if (USE_GENERATED_PREFIXES || !generatedNamespaceTable.containsKey(prefix)) { - output.write("@prefix " + prefix + ": <" + namespaceTable.get(prefix) + "> ."); output.writeEOL(); - } + if (suppressNamedIndividuals) { + values.remove(Constants.owlNamedIndividual); } - output.writeEOL(); + } + valuesList.addAll(values); } + if (!valuesList.isEmpty()) { + writePredicateAndObjectValues(out, predicate, valuesList); + } + } } - protected void writeSubjectSeparator(Writer out) { - // nothing to do here for Turtle + // Write other predicate/object pairs. + for (IRI predicate : poMap.sortedKeys()) { + if (!firstPredicates.contains(predicate)) { + SortedTurtleObjectList values = poMap.get(predicate); + writePredicateAndObjectValues(out, predicate, values); + } } - protected void writeSubjectTriples(Writer out, Resource subject) throws Exception { - SortedTurtlePredicateObjectMap poMap = sortedTripleMap.get(subject); - if (poMap == null) { - poMap = new SortedTurtlePredicateObjectMap(); + // Close statement + boolean unindentBlankNode = inlineBlankNodes && + (subject instanceof BNode) && + objectBlankNodes.contains(subject); + + if (unindentBlankNode) { + if (out instanceof IndentingWriter) { + IndentingWriter intendedOutput = (IndentingWriter) out; + intendedOutput.writeEOL(); + intendedOutput.decreaseIndentation(); + } else { + out.write("\n"); + } + out.write("]"); + if (out instanceof IndentingWriter) { + IndentingWriter indentedOutput = (IndentingWriter) out; + indentedOutput.writeEOL(); + } else { + out.write("\n"); + } + } else { + out.write("."); + if (out instanceof IndentingWriter) { + IndentingWriter indentedOutput = (IndentingWriter) out; + indentedOutput.writeEOL(); + indentedOutput.decreaseIndentation(); + indentedOutput.writeEOL(); // blank line + } else { + out.write("\n\n"); + } + } + } + + protected void writePredicateAndObjectValues(Writer out, IRI predicate, Collection values) throws Exception { + writePredicate(out, predicate); + if (values.size() == 1) { + out.write(" "); + writeObject(out, (Value) values.toArray()[0]); + out.write(" ;"); + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); + } else { + out.write("\n"); + } + } else if (values.size() > 1) { + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); + output.increaseIndentation(); + } else { + out.write("\n"); + } + int numValues = values.size(); + int valueIndex = 0; + for (Value value : values) { + valueIndex += 1; + writeObject(out, value); + if (valueIndex < numValues) { + out.write(" ,"); } - if (subject instanceof BNode) { - if (inlineBlankNodes) { - if (objectBlankNodes.contains(subject)) { - out.write("["); - } else { - out.write("[]"); - } - } else { - out.write("_:" + blankNodeNameMap.get(subject)); - } + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); } else { - writeIri(out, (IRI) subject); + out.write("\n"); } + } + out.write(";"); + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); + output.decreaseIndentation(); + } else { + out.write("\n"); + } + } + } + + protected void writePredicate(Writer out, IRI predicate) throws Exception { + out.write( + convertVerbIriToString( + predicate, + USE_GENERATED_PREFIXES, + true, + false)); + } + + protected void writeIri(Writer out, IRI iri) throws Exception { + out.write( + convertIriToString( + iri, + USE_GENERATED_PREFIXES, + true, + false)); + } + + protected void writeObject(Writer out, Value value) throws Exception { + if (value instanceof BNode) { + writeObject(out, (BNode) value); + } else if (value instanceof IRI) { + writeObject(out, (IRI) value); + } else if (value instanceof Literal) { + writeObject(out, (Literal) value); + } else { + out.write("\"" + value.stringValue() + "\""); + out.write(" "); + } + } + + protected void writeObject(Writer out, BNode bnode) throws Exception { + if (inlineBlankNodes) { + if (isCollection(comparisonContext, bnode, COLLECTION_CLASS)) { + // Open parentheses + out.write("("); if (out instanceof IndentingWriter) { - IndentingWriter intendedOutput = (IndentingWriter) out; - intendedOutput.writeEOL(); - intendedOutput.increaseIndentation(); + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); + output.increaseIndentation(); } else { - out.write("\n"); + out.write("\n"); } - // Write predicate/object pairs rendered first. - for (IRI predicate : firstPredicates) { - if (poMap.containsKey(predicate)) { - SortedTurtleObjectList values = poMap.get(predicate); - if (values != null) { // make a copy so we don't delete anything from the original - values = (SortedTurtleObjectList) values.clone(); - } - List valuesList = new ArrayList<>(); - if (values != null && !values.isEmpty()) { - if (predicate == Constants.RDF_TYPE) { - for (IRI preferredType : preferredRdfTypes) { - if (values.contains(preferredType)) { - valuesList.add(preferredType); - values.remove(preferredType); - } - } - } - valuesList.addAll(values); - } - if (! valuesList.isEmpty()) { - writePredicateAndObjectValues(out, predicate, valuesList); - } - } + // Write collection members + for (Value member : getCollectionMembers(unsortedTripleMap, bnode, COLLECTION_CLASS, comparisonContext)) { + writeObject(out, member); + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); + } else { + out.write("\n"); + } } - // Write other predicate/object pairs. - for (IRI predicate : poMap.sortedKeys()) { - if (!firstPredicates.contains(predicate)) { - SortedTurtleObjectList values = poMap.get(predicate); - writePredicateAndObjectValues(out, predicate, values); - } + // Close parentheses + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.decreaseIndentation(); + out.write(")"); + } else { + out.write(")"); + } + } else { // not a collection + SortedTurtlePredicateObjectMap poMap = sortedTripleMap.get(bnode); + if (poMap == null) { + poMap = new SortedTurtlePredicateObjectMap(); } - // Close statement - boolean unindentBlankNode = inlineBlankNodes && - (subject instanceof BNode) && - objectBlankNodes.contains(subject); - - if (unindentBlankNode) { - if (out instanceof IndentingWriter) { - IndentingWriter intendedOutput = (IndentingWriter) out; - intendedOutput.writeEOL(); - intendedOutput.decreaseIndentation(); - } else { - out.write("\n"); - } - out.write("]"); - if (out instanceof IndentingWriter) { - IndentingWriter indentedOutput = (IndentingWriter) out; - indentedOutput.writeEOL(); - } else { - out.write("\n"); - } + // Open brackets + out.write("["); + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.writeEOL(); + output.increaseIndentation(); } else { - out.write("."); - if (out instanceof IndentingWriter) { - IndentingWriter indentedOutput = (IndentingWriter) out; - indentedOutput.writeEOL(); - indentedOutput.decreaseIndentation(); - indentedOutput.writeEOL(); // blank line - } else { - out.write("\n\n"); - } + out.write("\n"); } - } - protected void writePredicateAndObjectValues(Writer out, IRI predicate, Collection values) throws Exception { - writePredicate(out, predicate); - if (values.size() == 1) { - out.write(" "); - writeObject(out, (Value) values.toArray()[0]); - out.write(" ;"); - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - } else { - out.write("\n"); - } - } else if (values.size() > 1) { - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - output.increaseIndentation(); - } else { - out.write("\n"); - } - int numValues = values.size(); - int valueIndex = 0; - for (Value value : values) { - valueIndex += 1; - writeObject(out, value); - if (valueIndex < numValues) { out.write(" ,"); } - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - } else { - out.write("\n"); - } - } - out.write(";"); - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - output.decreaseIndentation(); - } else { - out.write("\n"); - } + // Write predicate/object pairs rendered first. + for (IRI predicate : firstPredicates) { + if (poMap.containsKey(predicate)) { + SortedTurtleObjectList values = poMap.get(predicate); + writePredicateAndObjectValues(out, predicate, values); + } } - } - protected void writePredicate(Writer out, IRI predicate) throws Exception { - out.write( - convertVerbIriToString( - predicate, - USE_GENERATED_PREFIXES, - true, - false)); - } - - protected void writeIri(Writer out, IRI iri) throws Exception { - out.write( - convertIriToString( - iri, - USE_GENERATED_PREFIXES, - true, - false)); - } + // Write other predicate/object pairs. + for (IRI predicate : poMap.sortedKeys()) { + if (!firstPredicates.contains(predicate)) { + SortedTurtleObjectList values = poMap.get(predicate); + writePredicateAndObjectValues(out, predicate, values); + } + } - protected void writeObject(Writer out, Value value) throws Exception { - if (value instanceof BNode) { - writeObject(out, (BNode) value); - } else if (value instanceof IRI) { - writeObject(out, (IRI)value); - } else if (value instanceof Literal) { - writeObject(out, (Literal)value); + // Close brackets + if (out instanceof IndentingWriter) { + IndentingWriter output = (IndentingWriter) out; + output.decreaseIndentation(); + out.write("]"); } else { - out.write("\"" + value.stringValue() + "\""); - out.write(" "); + out.write("]"); } + } + } else { // no inlining of blank nodes + if (unsortedTripleMap.containsKey(bnode)) { + out.write("_:" + blankNodeNameMap.get(bnode)); + } else { + System.out.println("**** blank node not a subject: " + bnode.stringValue()); + System.out.flush(); + out.write("[]"); // last resort - this should never happen + } } - - protected void writeObject(Writer out, BNode bnode) throws Exception { - if (inlineBlankNodes) { - if (isCollection(comparisonContext, bnode, COLLECTION_CLASS)) { - // Open parentheses - out.write("("); - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - output.increaseIndentation(); - } else { - out.write("\n"); - } - - // Write collection members - for (Value member : getCollectionMembers(unsortedTripleMap, bnode, COLLECTION_CLASS, comparisonContext)) { - writeObject(out, member); - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - } else { - out.write("\n"); - } - } - - // Close parentheses - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.decreaseIndentation(); - out.write(")"); - } else { - out.write(")"); - } - } else { // not a collection - SortedTurtlePredicateObjectMap poMap = sortedTripleMap.get(bnode); - if (poMap == null) { poMap = new SortedTurtlePredicateObjectMap(); } - - // Open brackets - out.write("["); - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.writeEOL(); - output.increaseIndentation(); - } else { - out.write("\n"); - } - - // Write predicate/object pairs rendered first. - for (IRI predicate : firstPredicates) { - if (poMap.containsKey(predicate)) { - SortedTurtleObjectList values = poMap.get(predicate); - writePredicateAndObjectValues(out, predicate, values); - } - } - - // Write other predicate/object pairs. - for (IRI predicate : poMap.sortedKeys()) { - if (!firstPredicates.contains(predicate)) { - SortedTurtleObjectList values = poMap.get(predicate); - writePredicateAndObjectValues(out, predicate, values); - } - } - - // Close brackets - if (out instanceof IndentingWriter) { - IndentingWriter output = (IndentingWriter)out; - output.decreaseIndentation(); - out.write("]"); - } else { - out.write("]"); - } - } - } else { // no inlining of blank nodes - if (unsortedTripleMap.containsKey(bnode)) { - out.write("_:" + blankNodeNameMap.get(bnode)); - } else { - System.out.println("**** blank node not a subject: " + bnode.stringValue()); System.out.flush(); - out.write("[]"); // last resort - this should never happen - } - } + } + + protected void writeObject(Writer out, IRI iri) throws Exception { + writeIri(out, iri); + } + + protected void writeObject(Writer out, Literal literal) throws Exception { + if (literal == null) { + out.write("null"); + } else if (literal.getLanguage().isPresent() || ((overrideStringLanguage != null) && (literal.getDatatype() + .stringValue().equals(Constants.xsString.stringValue())))) { + writeString(out, literal.stringValue()); + String lang = overrideStringLanguage == null ? literal.getLanguage().get() : overrideStringLanguage; + out.write("@" + lang); + } else if (literal.getDatatype() != null) { + boolean useExplicit = + (stringDataTypeOption == StringDataTypeOptions.EXPLICIT) || !(Constants.xsString.equals(literal.getDatatype()) + || Constants.rdfLangString.equals(literal.getDatatype())); + writeString(out, literal.stringValue()); + if (useExplicit) { + out.write("^^"); + writeIri(out, literal.getDatatype()); + } + } else { + writeString(out, literal.stringValue()); } + } - protected void writeObject(Writer out, IRI iri) throws Exception { - writeIri(out, iri); + protected void writeString(Writer out, String str) throws Exception { + if (str == null) { + return; } - - protected void writeObject(Writer out, Literal literal) throws Exception { - if (literal == null) { - out.write("null"); - } else if (literal.getLanguage().isPresent() || ((overrideStringLanguage != null) && (literal.getDatatype().stringValue().equals(Constants.xsString.stringValue())))) { - writeString(out, literal.stringValue()); - String lang = overrideStringLanguage == null ? literal.getLanguage().get() : overrideStringLanguage; - out.write("@" + lang); - } else if (literal.getDatatype() != null) { - boolean useExplicit = (stringDataTypeOption == StringDataTypeOptions.EXPLICIT) || !(Constants.xsString.equals(literal.getDatatype()) || Constants.rdfLangString.equals(literal.getDatatype())); - writeString(out, literal.stringValue()); - if (useExplicit) { - out.write("^^"); - writeIri(out, literal.getDatatype()); - } - } else { - writeString(out, literal.stringValue()); + if (TextUtils.isMultilineString(str)) { // multi-line string + if (str.contains("\"")) { // string contains double quote chars + if (str.contains("'")) { // string contains both single and double quote chars + out.write("\"\"\""); + out.write(escapeString(str).replaceAll("\"", "\\\\\"")); + out.write("\"\"\""); + } else { // string contains double quote chars but no single quote chars + out.write("'''"); + out.write(escapeString(str)); + out.write("'''"); } - } - - protected void writeString(Writer out, String str) throws Exception { - if (str == null) { return; } - if (TextUtils.isMultilineString(str)) { // multi-line string - if (str.contains("\"")) { // string contains double quote chars - if (str.contains("'")) { // string contains both single and double quote chars - out.write("\"\"\""); - out.write(escapeString(str).replaceAll("\"", "\\\\\"")); - out.write("\"\"\""); - } else { // string contains double quote chars but no single quote chars - out.write("'''"); - out.write(escapeString(str)); - out.write("'''"); - } - } else { // string has no double quote chars - out.write("\"\"\""); - out.write(escapeString(str)); - out.write("\"\"\""); - } - } else { // single-line string - if (str.contains("\"")) { // string contains double quote chars - if (str.contains("'")) { // string contains both single and double quote chars - out.write("\""); - out.write(escapeString(str).replaceAll("\"", "\\\\\"")); - out.write("\""); - } else { // string contains double quote chars but no single quote chars - out.write("'"); - out.write(escapeString(str)); - out.write("'"); - } - } else { // string has no double quote chars - out.write("\""); - out.write(escapeString(str)); - out.write("\""); - } + } else { // string has no double quote chars + out.write("\"\"\""); + out.write(escapeString(str)); + out.write("\"\"\""); + } + } else { // single-line string + if (str.contains("\"")) { // string contains double quote chars + if (str.contains("'")) { // string contains both single and double quote chars + out.write("\""); + out.write(escapeString(str).replaceAll("\"", "\\\\\"")); + out.write("\""); + } else { // string contains double quote chars but no single quote chars + out.write("'"); + out.write(escapeString(str)); + out.write("'"); } + } else { // string has no double quote chars + out.write("\""); + out.write(escapeString(str)); + out.write("\""); + } } - - protected void writeFooter(Writer out, String[] trailingComments) throws Exception { - // Write traiing comments, if any. - if ((trailingComments != null) && (trailingComments.length >= 1)) { - output.write("####"); output.writeEOL(); - for (String line : trailingComments) { - output.write("## " + line); output.writeEOL(); - } - output.write("####"); output.writeEOL(); - } + } + + protected void writeFooter(Writer out, String[] trailingComments) throws Exception { + // Write traiing comments, if any. + if ((trailingComments != null) && (trailingComments.length >= 1)) { + output.write("####"); + output.writeEOL(); + for (String line : trailingComments) { + output.write("## " + line); + output.writeEOL(); + } + output.write("####"); + output.writeEOL(); } + } - private String escapeString(String str) { - if (str == null) { return null; } - return str.replaceAll("\\\\", "\\\\\\\\"); + private String escapeString(String str) { + if (str == null) { + return null; } + return str.replaceAll("\\\\", "\\\\\\\\"); + } }