This pages provides the functions that are required to translate an Abstract Syntax Tree of a SemwidgQL query, defined by SemwidQL's syntax (Railroad diagrams, ANTLR v3 Grammar) into a SPARQL query.
FUNCTION ARRAY translateSemwidgQL ( STRING semwidgQuery, ARRAY namespaces, ARRAY namedResources ) {
NODE ast = getAbstractSyntaxTreeFromQuery( semwidgQuery );
ast = replaceNamedResourcesWithURIs( ast, namedResources );
ARRAY queries = [];
WALK ast RECURSIVELY AND SAVE CURRENT NODE IN node {
IF node IS A SimpleQuery {
STRING sparqlQuery = buildQuery( node, namespaces );
queries.push( sparqlQuery );
}
}
RETURN queries;
}
FUNCTION STRING buildQuery ( NODE simpleQuery, ARRAY namespaces ) {
STRING query = '';
query += buildPrefixPart( simpleQuery, namespaces );
query += buildSelectPart( simpleQuery );
query += buildWherePart( simpleQuery );
query += buildGroupPart( simpleQuery );
RETURN query;
}
FUNCTION STRING buildPrefixPart ( NODE simpleQuery, ARRAY namespaces ) {
STRING query = '';
FOR EVERY USED NAMESPACE ns IN simpleQuery {
query += 'PREFIX ' + ns + ': <' + namespaces[ns] + '>';
}
RETURN query;
}
FUNCTION STRING buildSelectPart ( NODE simpleQuery ) {
STRING query = 'SELECT DISTINCT ';
ARRAY resourcesAndProperties = getUsedResourcesAndProperties( simpleQuery );
ARRAY hiddenElements = getHiddenElements( simpleQuery );
BOOLEAN aggregate = IS getGroupExpressions( simpleQuery ) NOT EMPTY;
FOR EACH NODE element IN resourcesAndProperties {
BOOLEAN hidden = IS element PART OF hiddenElements;
IF NOT hidden {
STRING variableName = convertQueryElementToVariableName( element );
STRING aggregateFunction = getAggregateExpressions( element );
IF element IS ANY Property OR Wildcard {
IF aggregate {
query += aggregateFunction + ' (' + variableName + ') AS ' + variableName;
} ELSE {
query += variableName;
}
} ELSE {
STRING resourceURI = element.getUri();
IF aggregate {
query += aggregateFunction + ' (<' + resourceURI + '>) AS ' + variableName;
} ELSE {
query += '(<' + resourceURI + '>) AS ' + variableName;
}
}
}
}
RETURN query;
}
FUNCTION STRING buildWherePart ( NODE simpleQuery ) {
STRING query = 'WHERE {' + buildPathExpression( simpleQuery.getResource(), simpleQuery.getPathExpression() ) + '}';
RETURN query;
}
FUNCTION STRING buildPathExpression ( NODE previousElement, NODE pathExpression ) {
STRING query = '';
IF pathExpression IS NOT EMPTY {
IF pathExpression IS A MultiplePathExpression {
FOR EACH NODE pathEx IN pathExpression {
query += buildPathExpression( previousElement, pathEx );
}
} ELSE {
NODE prop = pathExpression.getFirstProperty();
IF previousElement IS A Resource {
query += buildFirstTriple( previousElement, prop );
query += buildFilter( previousElement );
} ELSE {
query += buildTriple( previousElement, prop );
}
query += buildFilter( prop );
}
} ELSE {
IF previousElement IS A ForwardWildcard {
STRING variableName = convertQueryElementToVariableName( previousElement );
query += variableName + ' ?p ?o . ';
}
}
RETURN query;
}
FUNCTION STRING buildFirstTriple ( NODE elem1, NODE elem2 ) {
STRING query = '';
STRING value1;
IF elem1 IS A Wildcard {
value1 = convertQueryElementToVariableName( elem1 );
} ELSE {
value1 = elem1.getValue();
}
IF elem2 IS A ForwardProperty OR ForwardPropertyWithFilter {
query += value1 + ' ' + elem2.getValue() + ' ' + convertQueryElementToVariableName( elem2 ) + ' . ';
} ELSE IF elem2 IS A ReversedProperty OR ReversedPropertyWithFilter {
query += convertQueryElementToVariableName( elem2 ) + ' ' + elem2.getValue() + ' ' + value1 + ' . ';
} ELSE IF elem2 IS A ForwardWildcard OR ForwardWildcardWithFilter {
STRING propetyName = convertQueryElementToVariableName( elem1 );
query += value1 + ' ' + getUniqueVariableName( elem1, elem2 ) + ' ' + convertQueryElementToVariableName( elem2 ) + ' . ';
} ELSE IF elem2 IS A ReversedWildcard OR ReversedWildcardWithFilter {
STRING propetyName = convertQueryElementToVariableName( elem2 );
query += convertQueryElementToVariableName( elem2 ) + ' ' + getUniqueVariableName( elem2, elem1 ) + ' ' + value1 + ' . ';
}
RETURN query;
}
FUNCTION STRING buildFilter ( NODE property ) {
STRING query = '';
IF property HAS FILTER EXPRESSION {
ARRAY filterExpressions = property.getFilter();
// filter array has form [filter expression, conditional operator, filter expression, conditional operator, ..., filter expression]
FOR EACH NODE filterExpression IN filterExpressions {
query += buildFilterTriples( property, filterExpression );
}
ARRAY realFilters = getRealFilters( filterExpressions );
ARRAY pseudoFilters = getPseudoFilters( filterExpressions );
IF realFilters IS NOT EMPTY {
query += 'FILTER (';
FOR EACH NODE filterExpression, conditionalOperator IN realFilters {
query += buildFilterExpression( property, filterExpression );
query += conditionalOperator.getValue();
}
query += ') . ';
}
FOR EACH NODE filterExpression IN pseudoFilters {
IF filterExpression IS A Timeinterval {
STRING variableName = convertQueryElementToVariableName( property );
query += 'BIND(FLOOR((xsd:dateTime(' + variableName + ') - xsd:dateTime("1970-01-01T00:00:00")) / (' + filterExpression.getTimeintervalInSeconds() + ')) AS ' + variableName + '_timeinterval)';
}
}
}
RETURN query;
}
FUNCTION STRING buildFilterTriples ( NODE property, NODE filterExpression ) {
STRING query = '':
IF filterExpression IS A RelationalExpression {
ARRAY properties = [ property ].concat( filterExpression.getProperties() );
FOR EACH prop1, prop2 IN properties {
query += buildTriple( prop1, prop2 );
}
ARRAY values = filterExpression.getValue();
FOR EACH value IN values {
IF value IS A SimpleQuery {
query += buildPathExpression( value.getResource(), value.getPathExpression() );
}
}
} ELSE IF filterExpression IS A Self {
ARRAY values = filterExpression.getValue();
FOR EACH value IN values {
IF value IS A SimpleQuery {
query += buildPathExpression( value.getResource(), value.getPathExpression() );
}
}
} ELSE IF filterExpression IS A Type {
STRING variableName = convertQueryElementToVariableName( property );
query += variableName + ' rdf:type ?TypeOf_' + variableName.substr( 1 ) ' . ';
// .substr( 1 ) removes the first char ('?') from the string
}
RETURN query;
}
FUNCTION STRING buildFilterExpression ( NODE property, NODE filterExpression ) {
STRING query = "";
STRING variableName = convertQueryElementToVariableName( property );
IF filterExpression IS A Language {
query += buildFilterExpressionLanguage( filterExpression, variableName );
} ELSE IF filterExpression IS A Type {
query += buildFilterExpressionType( filterExpression, variableName );
} ELSE IF filterExpression IS A Self {
query += buildFilterExpressionSelf( filterExpression, variableName );
} ELSE IF filterExpression IS A RelationalExpression {
query += buildFilterExpressionRelationalExpression( filterExpression, variableName );
} ELSE IF filterExpression IS A Timestart {
query += buildFilterExpressionTimestart( filterExpression, variableName );
} ELSE IF filterExpression IS A Timeend {
query += buildFilterExpressionTimeend( filterExpression, variableName );
}
RETURN query;
}
FUNCTION STRING buildFilterExpressionLanguage ( NODE filterExpression, STRING variableName ) {
STRING query = "";
query += "lang(" + variableName + ") = "" || langMatches(lang(" + variableName + "), " + filterExpression.getLanguage() + ")";
RETURN query;
}
FUNCTION STRING buildFilterExpressionType ( NODE filterExpression, STRING variableName ) {
STRING query = "";
query += "?TypeOf_" + variableName.substr( 1 ) + " " + filterExpression.getOperator + " " + filterExpression.getType();
RETURN query;
}
FUNCTION STRING buildFilterExpressionSelf ( NODE filterExpression, STRING variableName ) {
STRING query = "";
ARRAY values = filterExpression.getValue();
ARRAY lastProperties = [];
FOR EACH value IN values {
IF value IS A SimpleQuery {
IF value.getPathExpression() IS NOT EMPTY {
lastProperties.push( getLastPropertiesOfPathExpression( value.getPathExpression() ) );
}
}
}
IF lastProperties IS EMPTY {
// filterExpression does not contain nested queries with path expressions
IF filterExpression.getOperator() IS "~" {
// Contains Operator
query += "CONTAINS(LCASE(STR(" + variableName + ")), LCASE(STR(";
FOR EACH NODE value IN values {
IF value IS ANY Resource {
query += convertQueryElementToVariableName( value.getResource() );
} ELSE {
query += value + " ";
}
}
query += ")))";
} ELSE {
IF value[0] IS A String {
query += "STR(" + variableName + ") ";
} ELSE {
query += variableName + " ";
}
query += filterExpression.getOperator() + " ";
FOR EACH NODE value IN values {
IF value IS A SimpleQuery {
query += value.getResource().getValue() ;
} ELSE {
query += value + " ";
}
}
}
} ELSE {
// filterExpression contains nested queries with path expressions
// if the nested query contains (another nested query with) a MultiplePathExpression, the number of filters must be calculated. Filter expressions are concatenated with a logical disjunction ("||").
INT numberOfFilters = 1;
FOR EACH ARRAY property IN lastProperties {
numberOfFilters *= property.length;
}
ARRAY propertyIndices = [];
INT loops = lastProperties.length;
FOR i = 0 TO loops {
propertyIndices.push( 0 );
}
FOR i = 0 TO numberOfFilters {
IF filterExpression.getOperator() IS "~" {
// Contains Operator
query += "CONTAINS(LCASE(STR(" + variableName + ")), LCASE(";
INT simpleQueryIndex = 0;
FOR EACH NODE value IN values {
IF value IS A SimpleQuery {
ARRAY simpleQuerysProperties = lastProperties[simpleQueryIndex];
NODE simpleQuerysProperty = simpleQuerysProperties[propertyIndices[simpleQueryIndex]];
simpleQueryIndex++;
query += convertQueryElementToVariableName( simpleQuerysProperty ) + " ";
} ELSE {
query += value + " ";
}
}
query += "))";
} ELSE {
IF values CONTAINS A StringLiteral {
query += "STR(" + variableName + ") ";
} ELSE {
query += variableName + " ";
}
query += filterExpression.getOperator() + " ";
INT simpleQueryIndex = 0;
FOR EACH NODE value IN values {
IF value IS A SimpleQuery {
ARRAY simpleQuerysProperties = lastProperties[simpleQueryIndex];
NODE simpleQuerysProperty = simpleQuerysProperties[propertyIndices[simpleQueryIndex]];
simpleQueryIndex++;
query += convertQueryElementToVariableName( simpleQuerysProperty ) + " ";
} ELSE {
query += value + " ";
}
}
}
// Define last properties indices for next iteration
BOOLEAN increaseNext = true;
FOR j = 0 TO loops {
INT val = propertyIndices[j];
INT length = lastProperties[j].length;
IF increaseNext IS true {
propertyIndices[j] = (val + 1) % length;
IF val + 1 IS propertyIndices[j] {
increaseNext = false;
} ELSE {
increaseNext = true;
}
}
}
IF THIS IS NOT LAST ITERATION {
query += " || ";
}
}
}
RETURN query;
}
FUNCTION STRING buildFilterExpressionRelationalExpression ( NODE filterExpression, STRING variableName ) {
STRING query = "";
ARRAY values = filterExpression.getValue();
ARRAY lastProperties = [];
FOR EACH value IN values {
IF value IS A SimpleQuery {
IF value.getPathExpression() IS NOT EMPTY {
lastProperties.push( getLastPropertiesOfPathExpression( value.getPathExpression() ) );
}
}
}
ARRAY properties = filterExpression.getProperties();
NODE lastProperty = LAST ELEMENT OF properties;
STRING lastPropertyName = convertQueryElementToVariableName( lastProperty );
IF lastProperties IS EMPTY {
// filterExpression does not contain nested queries with path expressions
IF filterExpression.getOperator() IS "~" {
// Contains Operator
query += "CONTAINS(LCASE(STR(" + lastPropertyName + ")), LCASE(STR(";
FOR EACH NODE value IN values {
IF value IS ANY Resource {
query += convertQueryElementToVariableName( value.getResource() );
} ELSE {
query += value + " ";
}
}
query += ")))";
} ELSE {
IF values CONTAINS A StringLiteral {
query += "STR(" + lastPropertyName + ") ";
} ELSE {
query += lastPropertyName + " ";
}
query += filterExpression.getOperator() + " ";
FOR EACH value IN values {
IF value IS A SimpleQuery {
IF value.getPathExpression() IS NOT EMPTY {
query += value.getResource().getValue() + " ";
} ELSE {
query += value + " ";
}
}
}
}
} ELSE {
// filterExpression contains nested queries with path expressions
// if the nested query contains (another nested query with) a MultiplePathExpression, the number of filters must be calculated. Filter expressions are concatenated with a logical disjunction ("||").
INT numberOfFilters = 1;
FOR EACH ARRAY property IN lastProperties {
numberOfFilters *= property.length;
}
ARRAY propertyIndices = [];
INT loops = lastProperties.length;
FOR INT i = 0 TO loops {
propertyIndices.push( 0 );
}
FOR INT i = 0 TO numberOfFilters {
IF filterExpression.getOperator() IS "~" {
// Contains Operator
query += "CONTAINS(LCASE(STR(" + lastPropertyName + ")), LCASE(";
INT simpleQueryIndex = 0;
FOR EACH NODE value IN values {
IF value IS A SimpleQuery {
ARRAY simpleQuerysProperties = lastProperties[simpleQueryIndex];
NODE simpleQuerysProperty = simpleQuerysProperties[propertyIndices[simpleQueryIndex]];
simpleQueryIndex++;
query += convertQueryElementToVariableName( simpleQuerysProperty ) + " ";
} ELSE {
query += value + " ";
}
}
query += "))";
} ELSE {
IF values CONTAINS A StringLiteral {
query += "STR(" + lastPropertyName + ") ";
} ELSE {
query += lastPropertyName + " ";
}
query += filterExpression.getOperator() + " ";
INT simpleQueryIndex = 0;
FOR EACH NODE value IN values {
IF value IS A SimpleQuery {
ARRAY simpleQuerysProperties = lastProperties[simpleQueryIndex];
NODE simpleQuerysProperty = simpleQuerysProperties[propertyIndices[simpleQueryIndex]];
simpleQueryIndex++;
query += convertQueryElementToVariableName( simpleQuerysProperty ) + " ";
} ELSE {
query += value + " ";
}
}
}
// Define last properties indices for next iteration
BOOLEAN increaseNext = true;
FOR INT j = 0 TO loops {
INT val = propertyIndices[j];
INT length = lastProperties[j].length;
IF increaseNext IS true {
propertyIndices[j] = (val + 1) % length;
IF val + 1 IS propertyIndices[j] {
increaseNext = false;
} ELSE {
increaseNext = true;
}
}
}
IF THIS IS NOT LAST ITERATION {
query += " || ";
}
}
}
RETURN query;
}
FUNCTION STRING buildFilterExpressionTimestart ( NODE filterExpression, STRING variableName ) {
STRING query = "";
query += "xsd:dateTime(" + variableName + ") >= " + readDateExpression( filterExpression.getTimestart() );
RETURN query;
}
FUNCTION STRING buildFilterExpressionTimeend ( NODE filterExpression, STRING variableName ) {
STRING query = "";
query += "xsd:dateTime(" + variableName + ") <= " + readDateExpression( filterExpression.getTimeend() );
RETURN query;
}
FUNCTION STRING buildTriple = ( NODE elem1, NODE elem2 ) {
STRING query = '';
STRING elem1Name = convertQueryElementToVariableName( elem1 );
STRING elem2Name = convertQueryElementToVariableName( elem2 );
IF elem2 IS A ForwardProperty OR ForwardPropertyWithFilter {
query += elem1Name + ' ' + elem2.getValue() + ' ' + elem2Name + ' . ';
} ELSE IF elem2 IS A ReversedProperty OR ReversedPropertyWithFilter {
query += elem2Name + ' ' + elem2.getValue() + ' ' + elem1Name + ' . ';
} ELSE IF elem2 IS A ForwardWildcard OR ForwardWildcardWithFilter {
query += elem1Name + ' ' + '?PropertyOf_' + elem1Name.substr(1) + '__' + elem2Name.substr(1) + ' ' + elem2Name + ' . ';
} ELSE IF elem2 IS A ReversedWildcard OR ReversedWildcardWithFilter {
query += elem2Name + ' ' + '?PropertyOf_' + elem2Name.substr(1) + '__' + elem1Name.substr(1) + ' ' + elem1Name + ' . ';
}
RETURN query;
};
FUNCTION STRING buildGroupPart ( NODE simpleQuery ) {
STRING query = '';
ARRAY groupExpressions = getGroupExpressions( simpleQuery );
IF groupExpressions IS NOT EMPTY {
NODE groupExpression;
FOR EACH MAP groupExpression IN groupExpressions {
IF groupExpression.get( 'filter' ) IS A Timeinterval {
STRING name = convertQueryElementToVariableName( groupExpression.get( 'element' ) ) + '_timeinterval';
query += 'GROUP BY ' + name;
query += 'ORDER BY DESC(' + name + ')';
}
}
}
RETURN query;
};
FUNCTION ARRAY getGroupExpressions ( NODE node ) {
// Returns an array of hashmaps that contain a node's and all its child nodes' filter
// expressions of type TimeInterval. The hashmap also contains a pointer to the respective node
// MAP groupExpression;
// groupExpression.map( 'filter', filter );
// groupExpression.map( 'element', node );
// groupExpressions.push( groupExpression );
};
FUNCTION ARRAY getUsedResourcesAndProperties ( NODE simpleQuery ) {
// Returns an array of all elements in simpleQuery that are not part of a filter expression
// Example:
// {r1.ns:p1(ns:p2 < {r2.ns:p3}).[ns:p4(ns:p5 < 10), ns:p6.ns:p7]}
// + + - - - + - + +
// returns an array containing the corresponding elements to r1, ns:p1, ns:p4, ns:p6 and ns:p7
}
FUNCTION ARRAY getHiddenElements ( NODE simpleQuery ) {
// Returns an array of all elements in simpleQuery where the keyword @hide has the value TRUE
// Example:
// {r1.ns:p1(@hide = false).[ns:p2(@hide = true), ns:p3.ns:p4(@hide = true)]}
// - - + - +
// returns an array containing the corresponding elements to ns:p2 and ns:p4
}
FUNTION NODE replaceNamedResourcesWithURIs ( NODE ast, ARRAY namedResources ) {
// Walks the Abstract Syntax Tree recursively and replaces every NamedResource with a
// QualifiedResource or a PrefixedResource, respectively, depending on the corresponding value
// in namedResources. The resulting Abstract Syntax Tree is returned.
}
FUNCTION NODE getAbstractSyntaxTreeFromQuery ( STRING semwidgQuery ) {
// Returns a revised version of the Abstract Syntax Tree of the SemwidgQL query which is
// defined by the SemwidgQL EBNF Syntax. To simplify the work with the AST, its nodes are
// consolidated and converted into objects (or data structures, depending on the used
// programming language. This pseudo code assumes that an object orientated programming
// language is used).
}
FUNCTION ARRAY getLastPropertiesOfPathExpression ( NODE pathExpression) {
// Returns an array of the last property of a PathExpression. If the pathExpression ends with
// a MultiplePathExpression, the array contains the last properties of the path expressions of
// the MultiplePathExpression.
}
FUNCTION STRING convertQueryElementToVariableName ( NODE node ) {
// Returns a unique but reproducible variable name for a node of the queries AST, starting
// with a question mark ("?").
// The reference implementation uses the name of the node plus a double underscore "__" plus a
// suffix that depends on the node's postion in the AST.
// Node name:
//
// If the node is defined by a fully quallified URI, enclosing angle brackets ("<", ">") are
// removed, as well as a possible trailing slash ("/"). Then, the part after the last slash or
// hash ("#") is used as name.
//
// -> MyResource
// -> MyProperty
//
// If the node is defined by a prefixed URI, the part after the last colon (":") is used as
// name.
//
// ns:MyProperty -> MyProperty
//
// If the node is a wildcard resource or property ("*"), the name is "wildcard".
//
// * -> wildcard
//
// If the node is a reversed resource or property an "Of" is appended to the name.
//
// ^ns:MyProperty -> MyPropertyOf
// Suffix:
//
// The suffix starts with an R if the node is a resource or a P if the node is a property.
// The algorithm walks the AST from top to the regarding node for which the suffix should be
// created. If the currently visited node is a "Resource", "Property", "PathExpression",
// "MultiplePathExpression", or a "SimpleQuery", an underscore ("_") plus the index of the
// node in the list of its siblings is appended.
//
// r1.ns:p1.^ns:p2 -> SimpleQuery (0) (irrelevant nodes were omitted)
// | |
// Resource (0) PathExpression (1)
// | | |
// "r1" Propery (0) Property (1)
// | |
// "ns:p1" "ns:p2"
//
// r1 -> R_0_0, ns:p1 -> P_0_1_0, ns:p2 -> P_0_1_1
// Complete name:
//
// r1.ns:p1.^ns:p2
//
// Name of ns:p2 would be: ?p2Of__P_0_1_1
}
FUNCTION ARRAY getRealFilters ( ARRAY filterExpressions ) {
// Returns an array of filter expressions whose types are not Aggregate, Hide, or Timeinterval
}
FUNCTION ARRAY getPseudoFilters ( ARRAY filterExpressions ) {
// Returns an array of filter expressions whose types are Aggregate, Hide, or Timeinterval
}
FUNCTION STRING readDateExpression ( STRING expressionString ) {
// Returns a SPARQL compliant version of the date expression
// DateExpression
// : ('now'|Iso8601DateExpression) ( '+' | '-' ) (Number ('s'|'m'|'h'|'d'|'w') Character*)*
// "now" is converted into "now()", a date expression in ISO 8601 format is encapsulated into
// a xsd:dateTime typecasting ("xsd:dateTime(Iso8601DateExpression)").
// The last parts are converted into seconds each and summed up. Instead of only 's', 'm',
// 'h', 'd', or 'w', sec or seconds etc. are also valid units.
// (s = 1, m = 60, h = 3600, d = 86400, w = 604800, everything else = 0)
// Example:
// now - 1h 30m -> now() - 5400
//
// 2016-01-01T12:00:00 - 60 min -> xsd:dateTime("2016-01-01T12:00:00.000") - 3600
}