
eu.fbk.knowledgestore.server.http.jaxrs.RenderUtils Maven / Gradle / Ivy
Show all versions of ks-server-http Show documentation
package eu.fbk.knowledgestore.server.http.jaxrs;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.escape.Escaper;
import com.google.common.html.HtmlEscapers;
import com.google.common.net.UrlEscapers;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Record;
import eu.fbk.knowledgestore.server.http.UIConfig;
import eu.fbk.knowledgestore.vocabulary.KS;
import eu.fbk.knowledgestore.vocabulary.NIF;
import eu.fbk.knowledgestore.vocabulary.NWR;
/**
* Collection of utility methods for rendering various kinds of object to HTML.
*/
public final class RenderUtils {
private static final boolean CHAR_OFFSET_HACK = Boolean.parseBoolean(System.getProperty(
"ks.charOffsetHack", "false"))
|| Boolean.parseBoolean(MoreObjects.firstNonNull(System.getenv("KS_CHAR_OFFSET_HACK"),
"false"));
private static final AtomicInteger COUNTER = new AtomicInteger(0);
/**
* Render a generic object, returning the corresponding HTML string. Works for null objects,
* RDF {@code Value}s, {@code Record}s, {@code BindingSet}s and {@code Iterable}s of the
* former.
*
* @param object
* the object to render.
* @return the rendered HTML string
*/
public static String render(final Object object) {
try {
final StringBuilder builder = new StringBuilder();
render(object, builder);
return builder.toString();
} catch (final IOException ex) {
throw new Error(ex); // should not happen
}
}
/**
* Render a generic object, emitting the corresponding HTML string to the supplied
* {@code Appendable} object. Works for null objects, RDF {@code Value}s, {@code Record}s,
* {@code BindingSet}s and {@code Iterable}s of the former.
*
* @param object
* the object to render.
*/
@SuppressWarnings("unchecked")
public static T render(final Object object, final T out)
throws IOException {
if (object instanceof URI) {
render((URI) object, null, out);
} else if (object instanceof Literal) {
final Literal literal = (Literal) object;
out.append("").append(literal.stringValue()).append("");
} else if (object instanceof BNode) {
final BNode bnode = (BNode) object;
out.append("_:").append(bnode.getID());
} else if (object instanceof Record) {
final Record record = (Record) object;
out.append("\nID ");
render(record.getID(), out);
out.append(" \n");
for (final URI property : Ordering.from(Data.getTotalComparator()).sortedCopy(
record.getProperties())) {
out.append("");
render(property, out);
out.append(" ");
final List \n");
}
out.append("
");
} else if (object instanceof BindingSet) {
render(ImmutableSet.of(object));
} else if (object instanceof Iterable>) {
final Iterable> iterable = (Iterable>) object;
boolean isEmpty = true;
boolean isIterableOfSolutions = true;
for (final Object element : iterable) {
isEmpty = false;
if (!(element instanceof BindingSet)) {
isIterableOfSolutions = false;
break;
}
}
if (!isEmpty) {
if (!isIterableOfSolutions) {
String separator = "";
for (final Object element : (Iterable>) object) {
out.append(separator);
render(element, out);
separator = "
";
}
} else {
Joiner.on("").appendTo(out,
renderSolutionTable(null, (Iterable) object).iterator());
}
}
} else if (object != null) {
out.append(object.toString());
}
return out;
}
public static T render(final URI uri, @Nullable final URI selection,
final T out) throws IOException {
out.append("").append(RenderUtils.shortenURI(uri)).append("");
return out;
}
public static T renderText(final String text, final String contentType,
final T out) throws IOException {
if (contentType.equals("text/plain")) {
out.append("\n").append(RenderUtils.escapeHtml(text))
.append("\n\n");
} else {
// TODO: only XML enabled by default - should be generalized / made more robust
out.append("")
.append(RenderUtils.escapeHtml(text)).append("
");
}
return out;
}
public static T renderText(final String text,
final List mentions, @Nullable final URI selection, final boolean canSelect,
final boolean onlyMention, final UIConfig config, final T out) throws IOException {
final List lines = Lists.newArrayList(Splitter.on('\n').split(text));
if (CHAR_OFFSET_HACK) {
for (int i = 0; i < lines.size(); ++i) {
lines.set(i, lines.get(i).replaceAll("\\s+", " ") + " ");
}
}
int lineStart = CHAR_OFFSET_HACK ? 0 : -1;
int lineOffset = 0;
int mentionIndex = 0;
boolean anchorAdded = false;
out.append("\n");
for (final String l : lines) {
final String line = CHAR_OFFSET_HACK ? l.trim() : l;
lineStart += CHAR_OFFSET_HACK ? 0 : 1;
boolean mentionFound = false;
while (mentionIndex < mentions.size()) {
final Record mention = mentions.get(mentionIndex);
final Integer begin = mention.getUnique(NIF.BEGIN_INDEX, Integer.class);
final Integer end = mention.getUnique(NIF.END_INDEX, Integer.class);
String cssStyle = null;
for (final UIConfig.Category category : config.getMentionCategories()) {
if (category.getCondition().evalBoolean(mention)) {
cssStyle = category.getStyle();
break;
}
}
if (cssStyle == null || begin == null || end == null
|| begin < lineStart + lineOffset) {
++mentionIndex;
continue;
}
if (end > lineStart + line.length()) {
break;
}
final boolean selected = mention.getID().equals(selection)
|| mention.get(KS.REFERS_TO, URI.class).contains(selection);
if (!mentionFound) {
out.append("");
}
out.append(RenderUtils.escapeHtml(line.substring(lineOffset, begin - lineStart)));
out.append(" values = mention.get(property, Value.class);
if (!values.isEmpty()) {
out.append(separator)
.append(Data.toString(property, Data.getNamespaceMap()))
.append(" = ");
for (final Value value : values) {
if (!KS.MENTION.equals(value)
&& !NWR.TIME_OR_EVENT_MENTION.equals(value)
&& !NWR.ENTITY_MENTION.equals(value)) {
out.append(" ").append(
Data.toString(value, Data.getNamespaceMap()));
}
}
separator = "\n";
}
}
out.append("\">");
out.append(RenderUtils.escapeHtml(line.substring(begin - lineStart, end
- lineStart)));
out.append("");
lineOffset = end - lineStart;
++mentionIndex;
mentionFound = true;
}
if (mentionFound || !onlyMention) {
if (!mentionFound) {
out.append("
\n");
}
out.append(RenderUtils.escapeHtml(line.substring(lineOffset, line.length())));
out.append("
\n");
}
lineStart += line.length();
lineOffset = 0;
}
out.append("\n");
return out;
}
/**
* Render in a streaming-way the solutions of a SPARQL SELECT query to an HTML table, emitting
* an iterable with of HTML fragments.
*
* @param variables
* the variables to render in the table, in the order they should be rendered; if
* null, variables will be automatically extracted from the solutions and all the
* variables in alphanumeric order will be emitted
* @param solutions
* the solutions to render
*/
public static Iterable renderSolutionTable(final List variables,
final Iterable extends BindingSet> solutions) {
final List actualVariables;
if (variables != null) {
actualVariables = ImmutableList.copyOf(variables);
} else {
final Set variableSet = Sets.newHashSet();
for (final BindingSet solution : solutions) {
variableSet.addAll(solution.getBindingNames());
}
actualVariables = Ordering.natural().sortedCopy(variableSet);
}
final int width = 75 / actualVariables.size();
final StringBuilder builder = new StringBuilder();
builder.append("\n");
for (final String variable : actualVariables) {
builder.append("")
.append(escapeHtml(variable)).append(" ");
}
final Iterable header = ImmutableList.of(builder.toString());
final Iterable footer = ImmutableList.of("
");
final Function renderer = new Function() {
@Override
public String apply(final BindingSet bindings) {
if (Thread.interrupted()) {
throw new IllegalStateException("Interrupted");
}
final StringBuilder builder = new StringBuilder();
builder.append("");
for (final String variable : actualVariables) {
builder.append("");
try {
render(bindings.getValue(variable), builder);
} catch (final IOException ex) {
throw new Error(ex);
}
builder.append(" ");
}
builder.append(" \n");
return builder.toString();
}
};
return Iterables.concat(header, Iterables.transform(solutions, renderer), footer);
}
public static T renderMultisetTable(final T out,
final Multiset> multiset, final String elementHeader,
final String occurrencesHeader, @Nullable final String linkTemplate)
throws IOException {
final String tableID = "table" + COUNTER.getAndIncrement();
out.append("\n");
out.append("\n").append(MoreObjects.firstNonNull(elementHeader, "Value"))
.append(" ")
.append(MoreObjects.firstNonNull(occurrencesHeader, "Occurrences"))
.append(" \n\n");
out.append("\n");
for (final Object element : multiset.elementSet()) {
final int occurrences = multiset.count(element);
out.append("");
RenderUtils.render(element, out);
out.append(" ");
if (linkTemplate == null) {
out.append(Integer.toString(occurrences));
} else {
final Escaper esc = UrlEscapers.urlFormParameterEscaper();
final String e = esc.escape(Data.toString(element, Data.getNamespaceMap()));
final String u = linkTemplate.replace("${element}", e);
out.append("")
.append(Integer.toString(occurrences)).append("");
}
out.append(" \n");
}
out.append("\n
\n");
out.append("");
return out;
}
public static T renderRecordsTable(final T out,
final Iterable records, @Nullable List propertyURIs,
@Nullable final String extraOptions) throws IOException {
// Extract the properties to show if not explicitly supplied
if (propertyURIs == null) {
final Set uriSet = Sets.newHashSet();
for (final Record record : records) {
uriSet.addAll(record.getProperties());
}
propertyURIs = Ordering.from(Data.getTotalComparator()).sortedCopy(uriSet);
}
// Emit the table
final String tableID = "table" + COUNTER.getAndIncrement();
out.append("\n");
out.append("\nURI ");
for (final URI propertyURI : propertyURIs) {
out.append("").append(RenderUtils.shortenURI(propertyURI)).append(" ");
}
out.append(" \n\n\n");
for (final Record record : records) {
out.append("").append(RenderUtils.render(record.getID())).append(" ");
for (final URI propertyURI : propertyURIs) {
out.append("").append(RenderUtils.render(record.get(propertyURI)))
.append(" ");
}
out.append(" \n");
}
out.append("\n
\n");
out.append("");
return out;
}
public static T renderRecordsAggregateTable(final T out,
final Iterable records, @Nullable final Predicate propertyFilter,
@Nullable final String linkTemplate, @Nullable final String extraOptions)
throws IOException {
// Aggregate properties and values
final Map> properties = Maps.newHashMap();
final Map