writeTerm method

String writeTerm(
  1. RdfTerm term, {
  2. Map<String, String> prefixesByIri = const {},
  3. Map<BlankNodeTerm, String> blankNodeLabels = const {},
  4. String? baseUri,
  5. bool isPredicate = false,
})

Convert RDF terms to Turtle syntax string representation

Implementation

String writeTerm(
  RdfTerm term, {
  Map<String, String> prefixesByIri = const {},
  Map<BlankNodeTerm, String> blankNodeLabels = const {},
  String? baseUri,
  bool isPredicate = false,
}) {
  switch (term) {
    case IriTerm _:
      if (term == Rdf.type) {
        return 'a';
      } else {
        // Check if the predicate is a known prefix
        final iri = term.iri;
        final (
          baseIri,
          localPart,
        ) = RdfNamespaceMappings.extractNamespaceAndLocalPart(
          iri,
          allowNumericLocalNames: _options.useNumericLocalNames,
        );

        // If we have a valid local part
        if (localPart.isNotEmpty || baseIri == iri) {
          final prefix = prefixesByIri[baseIri];
          if (prefix != null) {
            // Handle empty prefix specially
            return prefix.isEmpty ? ':$localPart' : '$prefix:$localPart';
          } else {
            final prefix = prefixesByIri[iri];
            if (prefix != null) {
              return prefix.isEmpty ? ':' : '$prefix:';
            }
          }
        }
      }

      // For predicates or terms with baseUri:
      // - For predicates, always use prefixes if they exist (handled above)
      // - For subject/object under baseUri, check if there's a better prefix
      //   that is longer than baseUri, if not, use relative IRI

      // If we have a baseUri and this term starts with it
      if (baseUri != null && term.iri.startsWith(baseUri)) {
        // For non-predicates that start with baseUri, check if there's a
        // namespace prefix that's longer than baseUri
        if (!isPredicate) {
          bool betterPrefixExists = false;
          for (final entry in prefixesByIri.entries) {
            final namespace = entry.key;
            // If there's a namespace that:
            // 1. Is a prefix of this IRI
            // 2. Is longer than baseUri
            // 3. Has an associated prefix
            if (term.iri.startsWith(namespace) &&
                namespace.length > baseUri.length) {
              betterPrefixExists = true;
              break;
            }
          }

          // If no better prefix exists, use relative IRI
          if (!betterPrefixExists) {
            final localPart = term.iri.substring(baseUri.length);
            return '<$localPart>';
          }
          // Otherwise, fall through to the full IRI case below
          // (which might still find a prefix to use)
        }
      }

      return '<${term.iri}>';
    case BlankNodeTerm blankNode:
      // Use the pre-generated label for this blank node
      var label = blankNodeLabels[blankNode];
      if (label == null) {
        // This shouldn't happen if all blank nodes were collected correctly
        _log.warning(
          'No label generated for blank node, using fallback label',
        );
        label = 'b${identityHashCode(blankNode)}';
        blankNodeLabels[blankNode] = label;
      }
      return '_:$label';
    case LiteralTerm literal:
      // Special cases for native Turtle literal representations
      if (literal.datatype == Xsd.integer) {
        return literal.value;
      }
      if (literal.datatype == Xsd.decimal) {
        return literal.value;
      }
      if (literal.datatype == Xsd.boolean) {
        return literal.value;
      }

      var escapedLiteralValue = _escapeTurtleString(literal.value);

      if (literal.language != null) {
        return '"$escapedLiteralValue"@${literal.language}';
      }
      if (literal.datatype != Xsd.string) {
        return '"$escapedLiteralValue"^^${writeTerm(literal.datatype, prefixesByIri: prefixesByIri, blankNodeLabels: blankNodeLabels)}';
      }
      return '"$escapedLiteralValue"';
  }
}