michielbdejong/solid-panes

View on GitHub
src/dataContentPane.js

Summary

Maintainability
D
2 days
Test Coverage
/*      Data content Pane
 **
 **  This pane shows the content of a particular RDF resource
 ** or at least the RDF semantics we attribute to that resource.
 */

// To do:  - Only take data from one graph
//         - Only do forwards not backward?
//         - Expand automatically all the way down
//         - original source view?  Use ffox view source

const UI = require('solid-ui')
const $rdf = require('rdflib')
const ns = UI.ns

module.exports = {
  icon: UI.icons.originalIconBase + 'rdf_flyer.24.gif',

  name: 'dataContents',

  audience: [ns.solid('Developer')],

  label: function (subject, context) {
    if (
      'http://www.w3.org/2007/ont/link#ProtocolEvent' in
      context.session.store.findTypeURIs(subject)
    ) {
      return null
    }
    var n = context.session.store.statementsMatching(
      undefined,
      undefined,
      undefined,
      subject
    ).length
    if (n === 0) return null
    return 'Data (' + n + ')'
  },
  /*
  shouldGetFocus: function(subject) {
      return UI.store.whether(subject, UI.ns.rdf('type'), UI.ns.link('RDFDocument'))
  },
*/
  statementsAsTables: function statementsAsTables (sts, context, initialRoots) {
    var myDocument = context.dom
    // var outliner = context.getOutliner(myDocument)
    var rep = myDocument.createElement('table')
    var sz = UI.rdf.Serializer(context.session.store)
    var res = sz.rootSubjects(sts)
    var roots = res.roots
    var subjects = res.subjects
    var loopBreakers = res.loopBreakers
    for (var x in loopBreakers) {
      console.log('\tdataContentPane: loopbreaker:' + x)
    }
    var doneBnodes = {} // For preventing looping
    var referencedBnodes = {} // Bnodes which need to be named alas

    // The property tree for a single subject or anonymous node
    function propertyTree (subject) {
      // print('Proprty tree for '+subject)
      var rep = myDocument.createElement('table')
      var lastPred = null
      var sts = subjects[sz.toStr(subject)] // relevant statements
      if (!sts) {
        // No statements in tree
        rep.appendChild(myDocument.createTextNode('...')) // just empty bnode as object
        return rep
      }
      sts.sort()
      var same = 0
      var predicateTD // The cell which holds the predicate
      for (var i = 0; i < sts.length; i++) {
        var st = sts[i]
        var tr = myDocument.createElement('tr')
        if (st.predicate.uri !== lastPred) {
          if (lastPred && same > 1) {
            predicateTD.setAttribute('rowspan', '' + same)
          }
          predicateTD = myDocument.createElement('td')
          predicateTD.setAttribute('class', 'pred')
          var anchor = myDocument.createElement('a')
          anchor.setAttribute('href', st.predicate.uri)
          anchor.addEventListener(
            'click',
            UI.widgets.openHrefInOutlineMode,
            true
          )
          anchor.appendChild(
            myDocument.createTextNode(
              UI.utils.predicateLabelForXML(st.predicate)
            )
          )
          predicateTD.appendChild(anchor)
          tr.appendChild(predicateTD)
          lastPred = st.predicate.uri
          same = 0
        }
        same++
        var objectTD = myDocument.createElement('td')
        objectTD.appendChild(objectTree(st.object))
        tr.appendChild(objectTD)
        rep.appendChild(tr)
      }
      if (lastPred && same > 1) predicateTD.setAttribute('rowspan', '' + same)
      return rep
    }

    // Convert a set of statements into a nested tree of tables
    function objectTree (obj) {
      var res, anchor
      switch (obj.termType) {
        case 'NamedNode':
          anchor = myDocument.createElement('a')
          anchor.setAttribute('href', obj.uri)
          anchor.addEventListener(
            'click',
            UI.widgets.openHrefInOutlineMode,
            true
          )
          anchor.appendChild(myDocument.createTextNode(UI.utils.label(obj)))
          return anchor

        case 'Literal':
          if (!obj.datatype || !obj.datatype.uri) {
            res = myDocument.createElement('div')
            res.setAttribute('style', 'white-space: pre-wrap;')
            res.textContent = obj.value
            return res
          } else if (
            obj.datatype.uri ===
            'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral'
          ) {
            res = myDocument.createElement('div')
            res.setAttribute('class', 'embeddedXHTML')
            res.innerHTML = obj.value // Try that  @@@ beware embedded dangerous code
            return res
          }
          return myDocument.createTextNode(obj.value) // placeholder - could be smarter,

        case 'BlankNode':
          if (obj.toNT() in doneBnodes) {
            // Break infinite recursion
            referencedBnodes[obj.toNT()] = true
            const anchor = myDocument.createElement('a')
            anchor.setAttribute('href', '#' + obj.toNT().slice(2))
            anchor.setAttribute('class', 'bnodeRef')
            anchor.textContent = '*' + obj.toNT().slice(3)
            return anchor
          }
          doneBnodes[obj.toNT()] = true // Flag to prevent infinite recursion in propertyTree
          var newTable = propertyTree(obj)
          doneBnodes[obj.toNT()] = newTable // Track where we mentioned it first
          if (
            UI.utils.ancestor(newTable, 'TABLE') &&
            UI.utils.ancestor(newTable, 'TABLE').style.backgroundColor ===
              'white'
          ) {
            newTable.style.backgroundColor = '#eee'
          } else {
            newTable.style.backgroundColor = 'white'
          }
          return newTable

        case 'Collection':
          res = myDocument.createElement('table')
          res.setAttribute('class', 'collectionAsTables')
          for (var i = 0; i < obj.elements.length; i++) {
            var tr = myDocument.createElement('tr')
            res.appendChild(tr)
            tr.appendChild(objectTree(obj.elements[i]))
          }
          return res
        case 'Graph':
          res = context.session.paneRegistry
            .byName('dataContents')
            .statementsAsTables(obj.statements, context)
          res.setAttribute('class', 'nestedFormula')
          return res
        case 'Variable':
          res = myDocument.createTextNode('?' + obj.uri)
          return res
      }
      throw new Error('Unhandled node type: ' + obj.termType)
    }

    // roots.sort()

    if (initialRoots) {
      roots = initialRoots.concat(
        roots.filter(function (x) {
          for (var i = 0; i < initialRoots.length; i++) {
            // Max 2
            if (x.sameTerm(initialRoots[i])) return false
          }
          return true
        })
      )
    }
    for (var i = 0; i < roots.length; i++) {
      var tr = myDocument.createElement('tr')
      rep.appendChild(tr)
      var subjectTD = myDocument.createElement('td')
      tr.appendChild(subjectTD)
      var TDTree = myDocument.createElement('td')
      tr.appendChild(TDTree)
      var root = roots[i]
      if (root.termType === 'BlankNode') {
        subjectTD.appendChild(myDocument.createTextNode(UI.utils.label(root))) // Don't recurse!
      } else {
        subjectTD.appendChild(objectTree(root)) // won't have tree
      }
      TDTree.appendChild(propertyTree(root))
    }
    for (var bNT in referencedBnodes) {
      // Add number to refer to
      const table = doneBnodes[bNT]
      // let tr = myDocument.createElement('tr')
      var anchor = myDocument.createElement('a')
      anchor.setAttribute('id', bNT.slice(2))
      anchor.setAttribute('class', 'bnodeDef')
      anchor.textContent = bNT.slice(3) + ')'
      table.insertBefore(anchor, table.firstChild)
    }
    return rep
  }, // statementsAsTables
  // View the data in a file in user-friendly way
  render: function (subject, context) {
    var myDocument = context.dom

    function alternativeRendering () {
      var sz = UI.rdf.Serializer(context.session.store)
      var res = sz.rootSubjects(sts)
      var roots = res.roots
      var p = {}
      p.render = function (s2) {
        var div = myDocument.createElement('div')
        div.setAttribute('class', 'withinDocumentPane')
        var plist = kb.statementsMatching(s2, undefined, undefined, subject)
        outliner.appendPropertyTRs(div, plist, false, function (
          _pred,
          _inverse
        ) {
          return true
        })
        return div
      }
      for (var i = 0; i < roots.length; i++) {
        var tr = myDocument.createElement('TR')
        var root = roots[i]
        tr.style.verticalAlign = 'top'
        var td = outliner.outlineObjectTD(root, undefined, tr)
        tr.appendChild(td)
        div.appendChild(tr)
        outliner.outlineExpand(td, root, { pane: p })
      }
    }

    function mainRendering () {
      var initialRoots = [] // Ordering: start with stuff about this doc
      if (kb.holds(subject, undefined, undefined, subject)) {
        initialRoots.push(subject)
      }
      // Then about the primary topic of the document if any
      var ps = kb.any(subject, UI.ns.foaf('primaryTopic'), undefined, subject)
      if (ps) initialRoots.push(ps)
      div.appendChild(
        context.session.paneRegistry
          .byName('dataContents')
          .statementsAsTables(sts, context, initialRoots)
      )
    }

    var outliner = context.getOutliner(myDocument)
    var kb = context.session.store
    var div = myDocument.createElement('div')
    div.setAttribute('class', 'dataContentPane')
    // Because of smushing etc, this will not be a copy of the original source
    // We could instead either fetch and re-parse the source,
    // or we could keep all the pre-smushed triples.
    var sts = kb.statementsMatching(undefined, undefined, undefined, subject) // @@ slow with current store!

    if ($rdf.keepThisCodeForLaterButDisableFerossConstantConditionPolice) {
      alternativeRendering()
    } else {
      mainRendering()
    }
    return div
  }
}