/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hp.hpl.jena.sparql.serializer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.jena.atlas.io.IndentedLineBuffer; import org.apache.jena.atlas.io.IndentedWriter; import com.hp.hpl.jena.graph.Node; import com.hp.hpl.jena.graph.Triple; import com.hp.hpl.jena.sparql.core.BasicPattern; import com.hp.hpl.jena.sparql.core.PathBlock; import com.hp.hpl.jena.sparql.core.TriplePath; import com.hp.hpl.jena.sparql.expr.Expr; import com.hp.hpl.jena.sparql.path.PathWriter; import com.hp.hpl.jena.sparql.syntax.BooleanOperator; import com.hp.hpl.jena.sparql.syntax.Element; import com.hp.hpl.jena.sparql.syntax.Element1; import com.hp.hpl.jena.sparql.syntax.ElementAssign; import com.hp.hpl.jena.sparql.syntax.ElementBind; import com.hp.hpl.jena.sparql.syntax.ElementData; import com.hp.hpl.jena.sparql.syntax.ElementDataset; import com.hp.hpl.jena.sparql.syntax.ElementEventBinOperator; import com.hp.hpl.jena.sparql.syntax.ElementEventFilter; import com.hp.hpl.jena.sparql.syntax.ElementEventGraph; import com.hp.hpl.jena.sparql.syntax.ElementExists; import com.hp.hpl.jena.sparql.syntax.ElementFilter; import com.hp.hpl.jena.sparql.syntax.ElementFnAbsFilter; import com.hp.hpl.jena.sparql.syntax.ElementGroup; import com.hp.hpl.jena.sparql.syntax.ElementMinus; import com.hp.hpl.jena.sparql.syntax.ElementNamedGraph; import com.hp.hpl.jena.sparql.syntax.ElementNotExists; import com.hp.hpl.jena.sparql.syntax.ElementOptional; import com.hp.hpl.jena.sparql.syntax.ElementPathBlock; import com.hp.hpl.jena.sparql.syntax.ElementService; import com.hp.hpl.jena.sparql.syntax.ElementSubQuery; import com.hp.hpl.jena.sparql.syntax.ElementTriplesBlock; import com.hp.hpl.jena.sparql.syntax.ElementUnion; import com.hp.hpl.jena.sparql.syntax.ElementVisitor; import com.hp.hpl.jena.sparql.syntax.RelationalOperator; public class FormatterElement extends FormatterBase implements ElementVisitor { public static final int INDENT = 2 ; /** Control whether to show triple pattern boundaries - creates extra nesting */ public static final boolean PATTERN_MARKERS = false ; // /** Control whether triple patterns always have a final dot - it can be dropped in some cases */ // public static boolean PATTERN_FINAL_DOT = false ; /** Control whether (non-triple) patterns have a final dot - it can be dropped */ public static final boolean GROUP_SEP_DOT = false ; /** Control whether the first item of a group is on the same line as the { */ public static final boolean GROUP_FIRST_ON_SAME_LINE = false ; /** Control pretty printing */ public static final boolean PRETTY_PRINT = true ; /** Control whether disjunction has set of delimiters - as it's a group usually, these aren't needed */ public static final boolean UNION_MARKERS = false ; /** Control whether a group of one is unnested - changes the query syntax tree */ public static final boolean GROUP_UNNEST_ONE = false ; /** Control whether GRAPH indents in a fixed way or based on the layout size */ public static final boolean GRAPH_FIXED_INDENT = true ; /** Control whether NOT EXIST/EXISTS indents in a fixed way or based on the layout size */ public static final boolean ELEMENT1_FIXED_INDENT = true ; /** Control triples pretty printing */ public static final int TRIPLES_SUBJECT_COLUMN = 8 ; // Less than this => rest of triple on the same line // Could be smart and make it depend on the property length as well. Later. public static final int TRIPLES_SUBJECT_LONG = 12 ; public static final int TRIPLES_PROPERTY_COLUMN = 20; public static final int TRIPLES_COLUMN_GAP = 2 ; public FormatterElement(IndentedWriter out, SerializationContext context) { super(out, context) ; } public static void format(IndentedWriter out, SerializationContext cxt, Element el) { FormatterElement fmt = new FormatterElement(out, cxt) ; fmt.startVisit() ; el.visit(fmt) ; fmt.finishVisit() ; } public static String asString(Element el) { SerializationContext cxt = new SerializationContext() ; IndentedLineBuffer b = new IndentedLineBuffer() ; FormatterElement.format(b, cxt, el) ; return b.toString() ; } public boolean topMustBeGroup() { return false ; } @Override public void visit(ElementTriplesBlock el) { if ( el.isEmpty() ) { out.println("# Empty BGP") ; return ; } formatTriples(el.getPattern()) ; } @Override public void visit(ElementPathBlock el) { if ( el.isEmpty() ) { out.println("# Empty BGP") ; return ; } // Could be neater. boolean first = true ; PathBlock pBlk = el.getPattern() ; for ( TriplePath tp : pBlk ) { if ( ! first ) { out.println(" .") ; } first = false ; if ( tp.isTriple() ) { printSubject(tp.getSubject()) ; out.print(" ") ; printProperty(tp.getPredicate()) ; out.print(" ") ; printObject(tp.getObject()) ; } else { printSubject(tp.getSubject()) ; out.print(" ") ; PathWriter.write(out, tp.getPath(), context.getPrologue()) ; out.print(" ") ; printObject(tp.getObject()) ; } } } @Override public void visit(ElementDataset el) { // if ( el.getDataset() != null) // { // DatasetGraph dsNamed = el.getDataset() ; // out.print("DATASET ") ; // out.incIndent(INDENT) ; // Iterator iter = dsNamed.listNames() ; // if ( iter.hasNext() ) // { // boolean first = false ; // for ( ; iter.hasNext() ; ) // { // if ( ! first ) // out.newline() ; // out.print("FROM <") ; // String s = (String)iter.next() ; // out.print(s) ; // out.print(">") ; // } // } // out.decIndent(INDENT) ; // out.newline() ; // } if ( el.getPatternElement() != null ) visitAsGroup(el.getPatternElement()) ; } @Override public void visit(ElementFilter el) { out.print("FILTER ") ; Expr expr = el.getExpr() ; FmtExprSPARQL v = new FmtExprSPARQL(out, context) ; // This assumes that complex expressions are bracketted // (parens) as necessary except for some cases: // Plain variable or constant boolean addParens = false ; if ( expr.isVariable() ) addParens = true ; if ( expr.isConstant() ) addParens = true ; if ( addParens ) out.print("( ") ; v.format(expr) ; if ( addParens ) out.print(" )") ; } @Override public void visit(ElementAssign el) { out.print("LET (") ; out.print("?"+el.getVar().getVarName()) ; out.print(" := ") ; FmtExprSPARQL v = new FmtExprSPARQL(out, context) ; v.format(el.getExpr()) ; out.print(")") ; } @Override public void visit(ElementBind el) { out.print("BIND(") ; FmtExprSPARQL v = new FmtExprSPARQL(out, context) ; v.format(el.getExpr()) ; out.print(" AS ") ; out.print("?"+el.getVar().getVarName()) ; out.print(")") ; } @Override public void visit(ElementUnion el) { if ( el.getElements().size() == 1 ) { // If this is an element of just one, just do it inplace // Can't happen from a parsed query. // Now can :-) // SPARQL 1.1 inline UNION. // Same as OPTIONAL, MINUS out.print("UNION") ; out.incIndent(INDENT) ; out.newline() ; visitAsGroup(el.getElements().get(0)) ; out.decIndent(INDENT) ; return ; } if ( UNION_MARKERS ) { out.print("{") ; out.newline() ; out.pad() ; } out.incIndent(INDENT) ; boolean first = true ; for ( Iterator<Element> iter = el.getElements().listIterator() ; iter.hasNext() ;) { Element subElement = iter.next() ; if ( ! first ) { out.decIndent(INDENT) ; out.newline() ; out.print("UNION") ; out.newline() ; out.incIndent(INDENT) ; } visitAsGroup(subElement) ; first = false ; } out.decIndent(INDENT) ; if ( UNION_MARKERS ) { out.newline() ; out.print("}") ; } } @Override public void visit(ElementGroup el) { if ( GROUP_UNNEST_ONE && el.getElements().size() == 1 ) { // If this is an element of just one, we can remove the {} if it is a group. Element e = el.getElements().get(0) ; visitAsGroup(e) ; return ; } // if ( el.getElements().size() == 1 ) // { // if ( el.getElements().get(0) instanceof ElementSubQuery ) // { // visit((ElementSubQuery)el.getElements().get(0)) ; // return ; // } // } out.print("{") ; out.incIndent(INDENT) ; if ( GROUP_FIRST_ON_SAME_LINE ) out.newline() ; int row1 = out.getRow() ; out.pad() ; boolean first = true ; for ( Iterator<Element> iter = el.getElements().listIterator() ; iter.hasNext() ;) { Element subElement = iter.next() ; if ( ! first ) { // Need to move on after the last thing printed. if ( GROUP_SEP_DOT ) out.print(" . ") ; out.newline() ; } subElement.visit(this) ; first = false ; } out.decIndent(INDENT) ; // Where to put the closing "}" int row2 = out.getRow() ; if ( row1 != row2 ) out.newline() ; else out.print(' ') ; // Finally, close the group. out.print("}") ; } @Override public void visit(ElementOptional el) { out.print("OPTIONAL") ; out.incIndent(INDENT) ; out.newline() ; visitAsGroup(el.getOptionalElement()) ; out.decIndent(INDENT) ; } @Override public void visit(ElementNamedGraph el) { visitNodePattern("GRAPH", el.getGraphNameNode(), el.getElement()) ; } @Override public void visit(ElementService el) { String x = "SERVICE" ; if ( el.getSilent() ) x = "SERVICE SILENT" ; visitNodePattern(x, el.getServiceNode(), el.getElement()) ; } protected void visitNodePattern(String label, Node node, Element subElement) { int len = label.length() ; out.print(label) ; out.print(" ") ; String nodeStr = ( node == null ) ? "*" : slotToString(node) ; out.print(nodeStr) ; len += nodeStr.length() ; if ( GRAPH_FIXED_INDENT ) { out.incIndent(INDENT) ; out.newline() ; // NB and newline } else { out.print(" ") ; len++ ; out.incIndent(len) ; } visitAsGroup(subElement) ; if ( GRAPH_FIXED_INDENT ) out.decIndent(INDENT) ; else out.decIndent(len) ; } private void visitElement1(String label, Element1 el) { int len = label.length() ; out.print(label) ; len += label.length() ; if ( ELEMENT1_FIXED_INDENT ) { out.incIndent(INDENT) ; out.newline() ; // NB and newline } else { out.print(" ") ; len++ ; out.incIndent(len) ; } visitAsGroup(el.getElement()) ; if ( ELEMENT1_FIXED_INDENT ) out.decIndent(INDENT) ; else out.decIndent(len) ; } @Override public void visit(ElementExists el) { visitElement1("EXISTS", el) ; } @Override public void visit(ElementNotExists el) { visitElement1("NOT EXISTS", el) ; } @Override public void visit(ElementMinus el) { out.print("MINUS") ; out.incIndent(INDENT) ; out.newline() ; visitAsGroup(el.getMinusElement()) ; out.decIndent(INDENT) ; } @Override public void visit(ElementSubQuery el) { out.print("{ ") ; out.incIndent(INDENT) ; // Messy - prefixes. el.getQuery().serialize(out) ; out.decIndent(INDENT) ; out.print("}") ; } public void visitAsGroup(Element el) { boolean needBraces = ! ( ( el instanceof ElementGroup ) || ( el instanceof ElementSubQuery ) ) ; if ( needBraces ) { out.print("{ ") ; out.incIndent(INDENT) ; } el.visit(this) ; if ( needBraces ) { out.decIndent(INDENT) ; out.print("}") ; } } // Work variables. // Assumes not threaded int subjectWidth = -1 ; int predicateWidth = -1 ; @Override protected void formatTriples(BasicPattern triples) { if ( ! PRETTY_PRINT ) { super.formatTriples(triples) ; return ; } // TODO RDF Collections - spot the parsers pattern if ( triples.isEmpty() ) return ; setWidths(triples) ; if ( subjectWidth > TRIPLES_SUBJECT_COLUMN ) subjectWidth = TRIPLES_SUBJECT_COLUMN ; if ( predicateWidth > TRIPLES_PROPERTY_COLUMN ) predicateWidth = TRIPLES_PROPERTY_COLUMN ; // Loops: List<Triple> subjAcc = new ArrayList<Triple>() ; // Accumulate all triples with the same subject. Node subj = null ; // Subject being accumulated boolean first = true ; // Print newlines between blocks. int indent = -1 ; for ( Triple t : triples ) { if ( subj != null && t.getSubject().equals(subj) ) { subjAcc.add(t) ; continue ; } if ( subj != null ) { if ( ! first ) out.println() ; formatSameSubject(subj, subjAcc) ; first = false ; // At end of line of a block of triples with same subject. // Drop through and start new block of same subject triples. } // New subject subj = t.getSubject() ; subjAcc.clear() ; subjAcc.add(t) ; } // Flush accumulator if ( subj != null && subjAcc.size() != 0 ) { if ( ! first ) out.println() ; first = false ; formatSameSubject(subj, subjAcc) ; } } private void formatSameSubject(Node subject, List<Triple> triples) { if ( triples == null || triples.size() == 0 ) return ; // Do the first triple. Iterator<Triple> iter = triples.iterator() ; Triple t1 = iter.next() ; // int indent = TRIPLES_SUBJECT_COLUMN+TRIPLES_COLUMN_GAP ; // // Long subject => same line. Works for single triple as well. // int s1_len = printSubject(t1.getSubject()) ; // //int x = out.getCol() ; int indent = subjectWidth + TRIPLES_COLUMN_GAP ; int s1_len = printSubject(t1.getSubject()) ; if ( s1_len > TRIPLES_SUBJECT_LONG ) { // Too long - start a new line. out.incIndent(indent) ; out.println() ; } else { printGap() ; out.incIndent(indent) ; } // Remained of first triple printProperty(t1.getPredicate()) ; printGap() ; printObject(t1.getObject()) ; // Do the rest for ( ; iter.hasNext() ; ) { Triple t = iter.next() ; out.println(" ;") ; printProperty(t.getPredicate()) ; printGap() ; printObject(t.getObject()) ; continue ; // print property list } // Finish off the block. out.decIndent(indent) ; out.print(" .") ; } private void setWidths(BasicPattern triples) { subjectWidth = -1 ; predicateWidth = -1 ; for ( Triple t : triples ) { String s = slotToString(t.getSubject()) ; if ( s.length() > subjectWidth ) subjectWidth = s.length() ; String p = slotToString(t.getPredicate()) ; if ( p.length() > predicateWidth ) predicateWidth = p.length() ; } } private void printGap() { out.print(' ', TRIPLES_COLUMN_GAP) ; } // Indent must be set first. protected int printSubject(Node s) { String str = slotToString(s) ; out.print(str) ; //out.pad(TRIPLES_SUBJECT_COLUMN) ; out.pad(subjectWidth) ; return str.length() ; } // Assumes the indent is TRIPLES_SUBJECT_COLUMN+GAP protected int printProperty(Node p) { String str = slotToString(p) ; out.print(str) ; //out.pad(TRIPLES_PROPERTY_COLUMN) ; out.pad(predicateWidth) ; return str.length() ; } protected int printObject(Node obj) { return printNoCol(obj) ; } protected int printNoCol(Node node) { String str = slotToString(node) ; out.print(str) ; return str.length() ; } @Override public void visit(RelationalOperator relationalOperator) { // TODO Auto-generated method stub } @Override public void visit(ElementEventGraph el) { // TODO Auto-generated method stub } @Override public void visit(ElementEventBinOperator el) { // TODO Auto-generated method stub } @Override public void visit(ElementEventFilter el) { // TODO Auto-generated method stub } @Override public void visit(BooleanOperator booleanOperator) { // TODO Auto-generated method stub } @Override public void visit(ElementFnAbsFilter elementFnAbsFilter) { // TODO Auto-generated method stub } @Override public void visit(ElementData el) { // TODO Auto-generated method stub } }