All files dataset2ObjectGraph.ts

87.27% Statements 48/55
75.75% Branches 25/33
90% Functions 9/10
86.66% Lines 39/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136  1x 1x   1x     17x                                 18x 4x             15x 2x 2x 4x                       7x 2x                             3x           3x 5x 1x       1x 7x 7x 7x 2x 5x             3x 3x       6x 15x 15x 16x                       1x         1x   1x     1x 1x 2x 1x 2x 2x 2x                   1x    
import { Dataset } from "@rdfjs/types";
import { frame as parseFrame, fromRDF, NodeObject } from "jsonld";
import { ContextParser } from "jsonld-context-parser";
 
const contextParser = new ContextParser();
 
function isObject(value: unknown): value is NodeObject {
  return typeof value === "object" && !Array.isArray(value) && value !== null;
}
 
interface UnfulfilledLeaf {
  leaf: NodeObject;
  key: string;
}
 
async function evaluateTraversedObject(
  originalOject: NodeObject,
  originalKey: string,
  value: unknown,
  idMap: Record<string, NodeObject>,
  unfulfilledLeafs: UnfulfilledLeaf[],
  idPredicates: Set<string>
) {
  // If value is an object, traverse recursively
  if (isObject(value)) {
    await traverseNodesForIdsAndLeafs(
      value,
      idMap,
      unfulfilledLeafs,
      idPredicates
    );
    // If value is an array, traverse recursively for all items in the array
  } else if (Array.isArray(value)) {
    await Promise.all(
      value.map(async (arrValue) => {
        await evaluateTraversedObject(
          originalOject,
          originalKey,
          arrValue,
          idMap,
          unfulfilledLeafs,
          idPredicates
        );
      })
    );
    // If the value is a string, but its key is also in the idPredicates set, it is an
    // unfulfilled leaf
  } else if (typeof value === "string" && idPredicates.has(originalKey)) {
    unfulfilledLeafs.push({
      leaf: originalOject,
      key: originalKey,
    });
  }
}
 
async function traverseNodesForIdsAndLeafs(
  object: NodeObject,
  idMap: Record<string, NodeObject>,
  unfulfilledLeafs: UnfulfilledLeaf[],
  // All predicates that are made of Ids
  parentIdPredicates?: Set<string>
): Promise<void> {
  // Must have a context
  Iif (!object["@context"] && !parentIdPredicates) {
    throw new Error("Must have a context");
  }
 
  // Get the current idPredicates. If this object does
  // have a context, then recalculate them.
  const idPredicates: Set<string> = parentIdPredicates || new Set<string>();
  if (object["@context"]) {
    const context =
      // The typings for these two libraries disagree, but they are correct
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      (await contextParser.parse(object["@context"])).getContextRaw();
    Object.entries(context).forEach(([key, value]) => {
      Iif (key.charAt(0) === "@") return;
      if (isObject(value) && value["@type"] && value["@type"] === "@id") {
        idPredicates.add(key);
      } else Iif (idPredicates.has(key)) {
        idPredicates.delete(key);
      }
    });
  }
 
  // Record this node's Id
  if (object["@id"]) {
    idMap[object["@id"]] = object;
  }
 
  // Traverse the keys of this Object
  await Promise.all(
    Object.entries(object).map(async ([key, value]) => {
      if (key.charAt(0) === "@") return;
      await evaluateTraversedObject(
        object,
        key,
        value,
        idMap,
        unfulfilledLeafs,
        idPredicates
      );
    })
  );
}
 
export async function dataset2ObjectGraph<ReturnType extends NodeObject>(
  dataset: Dataset,
  frame: NodeObject
): Promise<ReturnType> {
  // Get Framed Object
  const framedObject = await parseFrame(await fromRDF(dataset), frame);
 
  console.log(framedObject);
 
  // Traverse the document, getting the leafs and the ids
  const idMap: Record<string, NodeObject> = {};
  const unfulfilledLeafs: UnfulfilledLeaf[] = [];
  await traverseNodesForIdsAndLeafs(framedObject, idMap, unfulfilledLeafs);
  unfulfilledLeafs.forEach((unfulfilledLeaf) => {
    const leafValue = unfulfilledLeaf.leaf[unfulfilledLeaf.key];
    if (typeof leafValue === "string" && idMap[leafValue]) {
      unfulfilledLeaf.leaf[unfulfilledLeaf.key] = idMap[leafValue];
    } else IEif (Array.isArray(leafValue)) {
      leafValue.forEach((leafArrValue, index) => {
        Iif (typeof leafArrValue === "string" && idMap[leafArrValue]) {
          leafValue[index] = idMap[leafArrValue];
        }
      });
    }
  });
 
  return framedObject as ReturnType;
}