michielbdejong/solid-panes

View on GitHub
src/outline/queryByExample.js

Summary

Maintainability
D
2 days
Test Coverage
// The query-by-example functionality in the tabulator
// was the ability to expore a bit of the web in outline mode,
// select a ceratain set of fields in the tree,
// then pres "find all" which would then generte a SPARQL query
// to find all other places which had the same pattern.
// Fields could be optional by pressing th ewhite optoional button

var UI = require('solid-ui')

module.exports = {
  makeQueryRow,
  QuerySource,
  viewAndSaveQuery // Main function to generate and use the query
}

var optionalSubqueriesIndex = []

function predParentOf (node) {
  var n = node
  while (true) {
    if (n.getAttribute('predTR')) {
      return n
    } else if (n.previousSibling && n.previousSibling.nodeName === 'TR') {
      n = n.previousSibling
    } else {
      console.log('Could not find predParent')
      return node
    }
  }
}

function makeQueryRow (q, tr, constraint) {
  var kb = UI.store
  // predtr = predParentOf(tr)
  // var nodes = tr.childNodes
  // var n = tr.childNodes.length
  var inverse = tr.AJAR_inverse
  // var hasVar = 0
  let parentVar, level, pat

  function makeRDFStatement (freeVar, parent) {
    if (inverse) {
      return new UI.rdf.Statement(freeVar, st.predicate, parent)
    } else {
      return new UI.rdf.Statement(parent, st.predicate, freeVar)
    }
  }

  var optionalSubqueryIndex = null

  for (level = tr.parentNode; level; level = level.parentNode) {
    if (typeof level.AJAR_statement !== 'undefined') {
      // level.AJAR_statement
      level.setAttribute('bla', level.AJAR_statement) // @@? -timbl
      // UI.log.debug("Parent TR statement="+level.AJAR_statement + ", var=" + level.AJAR_variable)
      /* for(let c=0;c<level.parentNode.childNodes.length;c++) //This makes sure the same variable is used for a subject
      if(level.parentNode.childNodes[c].AJAR_variable)
        level.AJAR_variable = level.parentNode.childNodes[c].AJAR_variable; */
      if (!level.AJAR_variable) {
        makeQueryRow(q, level)
      }
      parentVar = level.AJAR_variable
      var predLevel = predParentOf(level)
      if (predLevel.getAttribute('optionalSubqueriesIndex')) {
        optionalSubqueryIndex = predLevel.getAttribute(
          'optionalSubqueriesIndex'
        )
        pat = optionalSubqueriesIndex[optionalSubqueryIndex]
      }
      break
    }
  }

  if (!pat) {
    pat = q.pat
  }

  var predtr = predParentOf(tr)
  // /////OPTIONAL KLUDGE///////////
  var opt = predtr.getAttribute('optional')
  if (!opt) {
    if (optionalSubqueryIndex) {
      predtr.setAttribute('optionalSubqueriesIndex', optionalSubqueryIndex)
    } else {
      predtr.removeAttribute('optionalSubqueriesIndex')
    }
  }
  if (opt) {
    var optForm = kb.formula()
    optionalSubqueriesIndex.push(optForm)
    predtr.setAttribute(
      'optionalSubqueriesIndex',
      optionalSubqueriesIndex.length - 1
    )
    pat.optional.push(optForm)
    pat = optForm
  }

  // //////////////////////////////

  var st = tr.AJAR_statement

  var constraintVar = tr.AJAR_inverse ? st.subject : st.object // this is only used for constraints
  var hasParent = true
  if (constraintVar.isBlank && constraint) {
    window.alert(
      'You cannot constrain a query with a blank node. No constraint will be added.'
    )
  }
  if (!parentVar) {
    hasParent = false
    parentVar = inverse ? st.object : st.subject // if there is no parents, uses the sub/obj
  }
  // UI.log.debug('Initial variable: '+tr.AJAR_variable)
  const v = tr.AJAR_variable
    ? tr.AJAR_variable
    : kb.variable(UI.utils.newVariableName())
  q.vars.push(v)
  v.label = hasParent ? parentVar.label : UI.utils.label(parentVar)
  v.label += ' ' + UI.utils.predicateLabelForXML(st.predicate, inverse)
  const pattern = makeRDFStatement(v, parentVar)
  // alert(pattern)
  v.label = v.label.slice(0, 1).toUpperCase() + v.label.slice(1) // init cap

  // See ../rdf/sparql.js
  // This should only work on literals but doesn't.
  function ConstraintEqualTo (value) {
    this.describe = function (varstr) {
      return varstr + ' = ' + value.toNT()
    }
    this.test = function (term) {
      return value.sameTerm(term)
    }
    return this
  }

  if (constraint) {
    // binds the constrained variable to its selected value
    pat.constraints[v] = new ConstraintEqualTo(constraintVar)
  }
  UI.log.info('Pattern: ' + pattern)
  pattern.tr = tr
  tr.AJAR_pattern = pattern // Cross-link UI and query line
  tr.AJAR_variable = v
  // UI.log.debug('Final variable: '+tr.AJAR_variable)
  UI.log.debug('Query pattern: ' + pattern)
  pat.statements.push(pattern)
  return v
} // makeQueryRow

function saveQuery (selection, qs) {
  // var qs = outline.qs // @@
  var q = new UI.rdf.Query()
  var n = selection.length
  var i, sel, st, tr
  for (i = 0; i < n; i++) {
    sel = selection[i]
    tr = sel.parentNode
    st = tr.AJAR_statement
    UI.log.debug('Statement ' + st)
    if (sel.getAttribute('class').indexOf('pred') >= 0) {
      UI.log.info('   We have a predicate')
      makeQueryRow(q, tr)
    }
    if (sel.getAttribute('class').indexOf('obj') >= 0) {
      UI.log.info('   We have an object')
      makeQueryRow(q, tr, true)
    }
  }
  qs.addQuery(q)

  function resetOutliner (pat) {
    var n = pat.statements.length
    var pattern, tr
    for (let i = 0; i < n; i++) {
      pattern = pat.statements[i]
      tr = pattern.tr
      // UI.log.debug('tr: ' + tr.AJAR_statement);
      if (typeof tr !== 'undefined') {
        tr.AJAR_pattern = null // TODO: is this == to whats in current version?
        tr.AJAR_variable = null
      }
    }
    for (const x in pat.optional) {
      resetOutliner(pat.optional[x])
    }
  }
  resetOutliner(q.pat)
  // NextVariable=0;
  return q
} // saveQuery

// When the user asks for all list of all matching parts of the data
//
function viewAndSaveQuery (outline, selection) {
  var qs = outline.qs
  UI.log.info('outline.doucment is now ' + outline.document.location)
  var q = saveQuery(selection, qs)
  /*
  if (tabulator.isExtension) {
    // tabulator.drawInBestView(q)
  } else
  */

  for (let i = 0; i < qs.listeners.length; i++) {
    qs.listeners[i].getActiveView().view.drawQuery(q)
    qs.listeners[i].updateQueryControls(qs.listeners[i].getActiveView())
  }
}

/**
 * The QuerySource object stores a set of listeners and a set of queries.
 * It keeps the listeners aware of those queries that the source currently
 * contains, and it is then up to the listeners to decide what to do with
 * those queries in terms of displays.
 * Not used 2010-08 -- TimBL
 * @class QuerySource
 * @author jambo
 */

function QuerySource () {
  /**
   * stores all of the queries currently held by this source,
   * indexed by ID number.
   */
  this.queries = []
  /**
   * stores the listeners for a query object.
   * @see TabbedContainer
   */
  this.listeners = []

  /**
   * add a Query object to the query source--It will be given an ID number
   * and a name, if it doesn't already have one. This subsequently adds the
   * query to all of the listeners the QuerySource knows about.
   */
  this.addQuery = function (q) {
    var i
    if (q.name === null || q.name === '') {
      q.name = 'Query #' + (this.queries.length + 1)
    }
    q.id = this.queries.length
    this.queries.push(q)
    for (i = 0; i < this.listeners.length; i++) {
      if (this.listeners[i] !== null) {
        this.listeners[i].addQuery(q)
      }
    }
  }

  /**
   * Remove a Query object from the source.  Tells all listeners to also
   * remove the query.
   */
  this.removeQuery = function (q) {
    var i
    for (i = 0; i < this.listeners.length; i++) {
      if (this.listeners[i] !== null) {
        this.listeners[i].removeQuery(q)
      }
    }
    if (this.queries[q.id] !== null) {
      delete this.queries[q.id]
    }
  }

  /**
   * adds a "Listener" to this QuerySource - that is, an object
   * which is capable of both adding and removing queries.
   * Currently, only the TabbedContainer class is added.
   * also puts all current queries into the listener to be used.
   */
  this.addListener = function (listener) {
    var i
    this.listeners.push(listener)
    for (i = 0; i < this.queries.length; i++) {
      if (this.queries[i] !== null) {
        listener.addQuery(this.queries[i])
      }
    }
  }
  /**
   * removes listener from the array of listeners, if it exists! Also takes
   * all of the queries from this source out of the listener.
   */
  this.removeListener = function (listener) {
    var i
    for (i = 0; i < this.queries.length; i++) {
      if (this.queries[i] !== null) {
        listener.removeQuery(this.queries[i])
      }
    }

    for (i = 0; i < this.listeners.length; i++) {
      if (this.listeners[i] === listener) {
        delete this.listeners[i]
      }
    }
  }
}