Source: @@data/parseTTL.js

/**
 * @file Logic of parsing .ttl files
 * @module @@data/parseTTL
 */
import { Parser } from 'n3';
import { invertObj } from 'ramda';
import possiblePrefixes from '@@constants/possible-prefixes';
import { parsePrefix } from './parsePrefix';

export const parseTTL = ttlString =>
  getQuads(ttlString)
    .then(({quads, prefixes}) => {
      const {data, usedPrefixes} = parseQuads(quads, Object.assign(possiblePrefixes, invertObj(prefixes)));
      return {
        data,
        __prefixes__: Object.assign(usedPrefixes, invertObj(prefixes))
      };
    });

const getQuads = ttlString => new Promise((res, err) => {
  const parser = new Parser();
  const quads = [];

  parser.parse(ttlString, (error, quad, prefixes) => {
    error && err(error);
    if (quad) {
      quads.push(quad);
    } else if (!error) {
      res({quads, prefixes});
    }
  });
});

const parseQuads = (quads, prefixes) => {
  const usedPrefixes = {};
  const usedAliases = {};
  let prefix = iri => {
    if (iri.match(/\/instance$/)) return iri;
    try {
      const url = new URL(iri);
      if (!url.host) {
        return iri;
      }
    } catch (e) {
      // Bad URL
      return iri;
    }

    const {alias, prefixIri, suffix} = parsePrefix(prefixes, iri);

    let i = 0;
    let newAliasRoot = alias;
    let newAlias = newAliasRoot;
    while (usedAliases[newAlias] && usedAliases[newAlias] !== prefixIri) {
      newAlias = `${newAliasRoot}${i++}`;
    }
    usedAliases[newAlias] = prefixIri;
    usedPrefixes[prefixIri] = newAlias;

    return `${newAlias}:${suffix}`;
  };
  const predicateSpecIRI = prefix('http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate');
  const hasWeightIRI = prefix('http://onto.fel.cvut.cz/ontologies/dataset-descriptor/s-p-o-summary/hasWeight');
  const typeIRI = prefix('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
  const objectSpecIRI = prefix('http://www.w3.org/1999/02/22-rdf-syntax-ns#object');
  const subjectSpecIRI = prefix('http://www.w3.org/1999/02/22-rdf-syntax-ns#subject');

  const edgePredicates = [predicateSpecIRI, hasWeightIRI, objectSpecIRI, subjectSpecIRI];
  const isEdge = quad => edgePredicates.includes(quad.predicate.id);

  const classMapping = {
    // {
    //   [blankNodeId]: className
    // }
  };
  quads.forEach(quad => {
    quad.object.id = prefix(quad.object.id);
    quad.subject.id = prefix(quad.subject.id);
    quad.predicate.id = prefix(quad.predicate.id);
    if (quad.predicate.id === typeIRI) {
      // Empty node --> className mapping
      classMapping[quad.subject.id] = quad.object.id;
    }
  });


  const predicateKeys = {
    [predicateSpecIRI]: 'predicate',
    [objectSpecIRI]: 'object',
    [subjectSpecIRI]: 'subject',
    [hasWeightIRI]: 'weight'
  };

  let edges = Object.values(
    quads.filter(isEdge).reduce((acc, quad) => {
      const predicateId = quad.subject.id;
      const key = predicateKeys[quad.predicate.id];
      const value = classMapping[quad.object.id] || quad.object.id;

      acc[predicateId] = acc[predicateId] || {};
      Object.assign(acc[predicateId], {[key]: value});

      return acc;
    }, {})
  );

  const existingEdges = {};
  edges = edges.filter(({subject, predicate, object}) => {
    const exists = existingEdges[`${subject}${predicate}${object}`];
    existingEdges[`${subject}${predicate}${object}`] = true;
    return !exists;
  });

  const hasOutgoingEdges = edges.reduce((acc, {subject}) => Object.assign(acc, {[subject]: true}), {});

  // Populated classes with existing outgoing relationships (no outgoing relationships --> data property)
  const data = edges
    .filter(({subject}) => hasOutgoingEdges[subject])
    .reduce((acc, {predicate, object, subject}) => {
      if (!acc[subject]) {
        acc[subject] = {
          dataProperties: {},
          objectProperties: {}
        };
      }

      // Data property
      if (!hasOutgoingEdges[object]) {
        if (!acc[subject].dataProperties[predicate]) {
          acc[subject].dataProperties[predicate] = [];
        }
        acc[subject].dataProperties[predicate].push(object);
      } else {
        if (!acc[subject].objectProperties[predicate]) {
          acc[subject].objectProperties[predicate] = [];
        }
        acc[subject].objectProperties[predicate].push(object);
      }

      return acc;
    }, {});

  return {data, usedPrefixes};
};