Moved match/* to response/match/*
diff --git a/src/main/java/de/ids_mannheim/korap/response/match/DocIdentifier.java b/src/main/java/de/ids_mannheim/korap/response/match/DocIdentifier.java
new file mode 100644
index 0000000..8680742
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/response/match/DocIdentifier.java
@@ -0,0 +1,26 @@
+package de.ids_mannheim.korap.response.match;
+import java.util.*;
+import java.util.regex.*;
+
+
+public class DocIdentifier {
+ protected String corpusID, docID;
+
+ public String getCorpusID () {
+ return this.corpusID;
+ };
+
+ public void setCorpusID (String id) {
+ if (id != null && !id.contains("!"))
+ this.corpusID = id;
+ };
+
+ public String getDocID () {
+ return this.docID;
+ };
+
+ public void setDocID (String id) {
+ if (id != null && !id.contains("!"))
+ this.docID = id;
+ };
+};
diff --git a/src/main/java/de/ids_mannheim/korap/response/match/HighlightCombinator.java b/src/main/java/de/ids_mannheim/korap/response/match/HighlightCombinator.java
new file mode 100644
index 0000000..adce408
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/response/match/HighlightCombinator.java
@@ -0,0 +1,185 @@
+package de.ids_mannheim.korap.response.match;
+
+import de.ids_mannheim.korap.response.Match;
+import de.ids_mannheim.korap.response.match.HighlightCombinatorElement;
+import java.util.*;
+import java.io.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/*
+ Public class for combining highlighting elements
+*/
+public class HighlightCombinator {
+
+ // Logger (use the Match class)
+ private final static Logger log = LoggerFactory.getLogger(Match.class);
+
+ // This advices the java compiler to ignore all loggings
+ public static final boolean DEBUG = false;
+
+ private LinkedList<HighlightCombinatorElement> combine;
+ private Stack<Integer> balanceStack = new Stack<>();
+ private Stack<Integer> tempStack = new Stack<>();
+
+ // Empty constructor
+ public HighlightCombinator () {
+ this.combine = new LinkedList<>();
+ };
+
+ // Return the combination list
+ public LinkedList<HighlightCombinatorElement> list () {
+ return this.combine;
+ };
+
+ // get the first element (without removing)
+ public HighlightCombinatorElement getFirst () {
+ return this.combine.getFirst();
+ };
+
+ // get the last element (without removing)
+ public HighlightCombinatorElement getLast () {
+ return this.combine.getLast();
+ };
+
+ // get an element by index (without removing)
+ public HighlightCombinatorElement get (int index) {
+ return this.combine.get(index);
+ };
+
+ // Get the size of the combinator stack
+ public short size () {
+ return (short) this.combine.size();
+ };
+
+ // Add primary data to the stack
+ public void addString (String characters) {
+ this.combine.add(new HighlightCombinatorElement(characters));
+ };
+
+ // Add opening highlight combinator to the stack
+ public void addOpen (int number) {
+ this.combine.add(new HighlightCombinatorElement((byte) 1, number));
+ this.balanceStack.push(number);
+ };
+
+ // Add closing highlight combinator to the stack
+ public void addClose (int number) {
+ HighlightCombinatorElement lastComb;
+
+ // Clean up temporary stack
+ this.tempStack.clear();
+
+ // Check if there is an opening tag at least
+ if (this.balanceStack.empty()) {
+ if (DEBUG)
+ log.trace("The balance stack is empty");
+ return;
+ };
+
+ // Just some debug information
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder("Stack for checking with class ");
+ sb.append(number).append(" is ");
+ for (int s : this.balanceStack) {
+ sb.append('[').append(s).append(']');
+ };
+ log.trace(sb.toString());
+ };
+
+ // class number of the last element
+ // It's already ensured the stack is not empty
+ int eold = this.balanceStack.pop();
+
+ // the closing element is not balanced, i.e. the last element differs
+ while (eold != number) {
+
+ // Retrieve last combinator on stack
+ lastComb = this.combine.peekLast();
+
+ if (DEBUG)
+ log.trace("Closing element is unbalanced - {} " +
+ "!= {} with lastComb {}|{}|{}",
+ eold,
+ number,
+ lastComb.type,
+ lastComb.number,
+ lastComb.characters);
+
+ // combinator is opening and the number is not equal to the last
+ // element on the balanceStack
+ if (lastComb.type == 1 && lastComb.number == eold) {
+
+ // Remove the last element - it's empty and uninteresting!
+ this.combine.removeLast();
+ }
+
+ // combinator is either closing (??) or another opener
+ else {
+
+ if (DEBUG)
+ log.trace("close element a) {}", eold);
+
+ // Add a closer for the old element (this has following elements)
+ this.combine.add(new HighlightCombinatorElement((byte) 2, eold, false));
+ };
+
+ // add this element number temporarily on the stack
+ tempStack.push(eold);
+
+ // Check next element
+ eold = this.balanceStack.pop();
+ };
+
+ // Get last combinator on the stack
+ lastComb = this.combine.peekLast();
+
+ if (DEBUG) {
+ log.trace("LastComb: " +
+ lastComb.type +
+ '|' +
+ lastComb.number +
+ '|' + lastComb.characters +
+ " for " +
+ number);
+ log.trace("Stack for checking 2: {}|{}|{}|{}",
+ lastComb.type,
+ lastComb.number,
+ lastComb.characters,
+ number);
+ };
+
+ if (lastComb.type == 1 && lastComb.number == number) {
+ while (lastComb.type == 1 && lastComb.number == number) {
+ // Remove the damn thing - It's empty and uninteresting!
+ this.combine.removeLast();
+ lastComb = this.combine.peekLast();
+ };
+ }
+ else {
+ if (DEBUG)
+ log.trace("close element b) {}", number);
+
+ // Add a closer
+ this.combine.add(new HighlightCombinatorElement((byte) 2, number));
+ };
+
+ // Fetch everything from the tempstack and reopen it
+ for (int e : tempStack) {
+ if (DEBUG)
+ log.trace("Reopen element {}", e);
+ combine.add(new HighlightCombinatorElement((byte) 1, e));
+ balanceStack.push(e);
+ };
+ };
+
+ // Get all combined elements as a string
+ public String toString () {
+ StringBuilder sb = new StringBuilder();
+ for (HighlightCombinatorElement e : combine) {
+ sb.append(e.toString()).append("\n");
+ };
+ return sb.toString();
+ };
+};
diff --git a/src/main/java/de/ids_mannheim/korap/response/match/HighlightCombinatorElement.java b/src/main/java/de/ids_mannheim/korap/response/match/HighlightCombinatorElement.java
new file mode 100644
index 0000000..ab67ed5
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/response/match/HighlightCombinatorElement.java
@@ -0,0 +1,158 @@
+package de.ids_mannheim.korap.response.match;
+
+import org.apache.lucene.util.FixedBitSet;
+import de.ids_mannheim.korap.response.Match;
+import de.ids_mannheim.korap.response.match.Relation;
+import static de.ids_mannheim.korap.util.KorapString.*;
+import java.util.*;
+import java.io.*;
+
+/*
+ Class for elements with highlighting information
+*/
+public class HighlightCombinatorElement {
+
+ // Type 0: Textual data
+ // Type 1: Opening
+ // Type 2: Closing
+ public byte type;
+
+ public int number = 0;
+
+ public String characters;
+ public boolean terminal = true;
+
+ // Constructor for highlighting elements
+ public HighlightCombinatorElement (byte type, int number) {
+ this.type = type;
+ this.number = number;
+ };
+
+ // Constructor for highlighting elements,
+ // that may not be terminal, i.e. they were closed and will
+ // be reopened for overlapping issues.
+ public HighlightCombinatorElement (byte type, int number, boolean terminal) {
+ this.type = type;
+ this.number = number;
+ this.terminal = terminal;
+ };
+
+ // Constructor for textual data
+ public HighlightCombinatorElement (String characters) {
+ this.type = (byte) 0;
+ this.characters = characters;
+ };
+
+ // Return html fragment for this combinator element
+ public String toHTML (Match match, FixedBitSet level, byte[] levelCache) {
+ // Opening
+ if (this.type == 1) {
+ StringBuilder sb = new StringBuilder();
+ if (this.number == -1) {
+ sb.append("<mark>");
+ }
+
+ else if (this.number < -1) {
+ sb.append("<span xml:id=\"")
+ .append(match.getPosID(match.getClassID(this.number)))
+ .append("\">");
+ }
+
+ else if (this.number >= 256) {
+ sb.append("<span ");
+ if (this.number < 2048) {
+ sb.append("title=\"")
+ .append(match.getAnnotationID(this.number))
+ .append('"');
+ }
+ else {
+ Relation rel = match.getRelationID(this.number);
+ sb.append("xlink:title=\"")
+ .append(rel.annotation)
+ .append("\" xlink:type=\"simple\" xlink:href=\"#")
+ .append(match.getPosID(rel.ref))
+ .append('"');
+ };
+ sb.append('>');
+ }
+
+ // Highlight - < 256
+ else {
+ // Get the first free level slot
+ byte pos;
+ if (levelCache[this.number] != '\0') {
+ pos = levelCache[this.number];
+ }
+ else {
+ pos = (byte) level.nextSetBit(0);
+ level.clear(pos);
+ levelCache[this.number] = pos;
+ };
+ sb.append("<mark class=\"class-")
+ .append(this.number)
+ .append(" level-")
+ .append(pos)
+ .append("\">");
+ };
+ return sb.toString();
+ }
+ // Closing
+ else if (this.type == 2) {
+ if (this.number < -1 || this.number >= 256)
+ return "</span>";
+
+ if (this.number == -1)
+ return "</mark>";
+
+ if (this.terminal)
+ level.set((int) levelCache[this.number]);
+ return "</mark>";
+ };
+
+ // HTML encode primary data
+ return escapeHTML(this.characters);
+ };
+
+ // Return bracket fragment for this combinator element
+ public String toBrackets (Match match) {
+ if (this.type == 1) {
+ StringBuilder sb = new StringBuilder();
+
+ // Match
+ if (this.number == -1) {
+ sb.append("[");
+ }
+
+ // Identifier
+ else if (this.number < -1) {
+ sb.append("{#");
+ sb.append(match.getClassID(this.number));
+ sb.append(':');
+ }
+
+ // Highlight, Relation, Span
+ else {
+ sb.append("{");
+ if (this.number >= 256) {
+ if (this.number < 2048)
+ sb.append(match.getAnnotationID(this.number));
+ else {
+ Relation rel = match.getRelationID(this.number);
+ sb.append(rel.annotation);
+ sb.append('>').append(rel.ref);
+ };
+ sb.append(':');
+ }
+ else if (this.number != 0)
+ sb.append(this.number).append(':');
+ };
+ return sb.toString();
+ }
+ else if (this.type == 2) {
+ if (this.number == -1)
+ return "]";
+ return "}";
+ };
+ return this.characters;
+ };
+};
diff --git a/src/main/java/de/ids_mannheim/korap/response/match/MatchIdentifier.java b/src/main/java/de/ids_mannheim/korap/response/match/MatchIdentifier.java
new file mode 100644
index 0000000..5b954d6
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/response/match/MatchIdentifier.java
@@ -0,0 +1,97 @@
+package de.ids_mannheim.korap.response.match;
+import java.util.*;
+import java.util.regex.*;
+
+public class MatchIdentifier extends DocIdentifier {
+ private int startPos, endPos = -1;
+
+ private ArrayList<int[]> pos = new ArrayList<>(8);
+
+ Pattern idRegex = Pattern.compile(
+ "^match-(?:([^!]+?)!)?" +
+ "([^!]+)-p([0-9]+)-([0-9]+)" +
+ "((?:\\(-?[0-9]+\\)-?[0-9]+--?[0-9]+)*)" +
+ "(?:c.+?)?$");
+ Pattern posRegex = Pattern.compile(
+ "\\(([0-9]+)\\)([0-9]+)-([0-9]+)");
+
+ public MatchIdentifier () {};
+
+ public MatchIdentifier (String id) {
+ Matcher matcher = idRegex.matcher(id);
+ if (matcher.matches()) {
+ this.setCorpusID(matcher.group(1));
+ this.setDocID(matcher.group(2));
+ this.setStartPos(Integer.parseInt(matcher.group(3)));
+ this.setEndPos(Integer.parseInt(matcher.group(4)));
+
+ if (matcher.group(5) != null) {
+ matcher = posRegex.matcher(matcher.group(5));
+ while (matcher.find()) {
+ this.addPos(
+ Integer.parseInt(matcher.group(2)),
+ Integer.parseInt(matcher.group(3)),
+ Integer.parseInt(matcher.group(1))
+ );
+ };
+ };
+ };
+ };
+
+ public int getStartPos () {
+ return this.startPos;
+ };
+
+ public void setStartPos (int pos) {
+ if (pos >= 0)
+ this.startPos = pos;
+ };
+
+ public int getEndPos () {
+ return this.endPos;
+ };
+
+ public void setEndPos (int pos) {
+ if (pos >= 0)
+ this.endPos = pos;
+ };
+
+ public void addPos(int start, int end, int number) {
+ if (start >= 0 && end >= 0 && number >= 0)
+ this.pos.add(new int[]{start, end, number});
+ };
+
+ public ArrayList<int[]> getPos () {
+ return this.pos;
+ };
+
+ public String toString () {
+ if (this.docID == null) return null;
+
+ StringBuilder sb = new StringBuilder("match-");
+
+ // Get prefix string corpus/doc
+ if (this.corpusID != null)
+ sb.append(this.corpusID).append('!');
+
+ sb.append(this.docID);
+
+ sb.append('-');
+ sb.append(this.getPositionString());
+ return sb.toString();
+ };
+
+
+ public String getPositionString () {
+ StringBuilder sb = new StringBuilder();
+ sb.append('p').append(this.startPos).append('-').append(this.endPos);
+
+ // Get Position information
+ for (int[] i : this.pos) {
+ sb.append('(').append(i[2]).append(')');
+ sb.append(i[0]).append('-').append(i[1]);
+ };
+
+ return sb.toString();
+ };
+};
diff --git a/src/main/java/de/ids_mannheim/korap/response/match/PosIdentifier.java b/src/main/java/de/ids_mannheim/korap/response/match/PosIdentifier.java
new file mode 100644
index 0000000..8606aa6
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/response/match/PosIdentifier.java
@@ -0,0 +1,34 @@
+package de.ids_mannheim.korap.response.match;
+import java.util.*;
+
+public class PosIdentifier extends DocIdentifier {
+ private int pos;
+
+ public PosIdentifier () {};
+
+ public void setPos (int pos) {
+ if (pos >= 0)
+ this.pos = pos;
+ };
+
+ public int getPos () {
+ return this.pos;
+ };
+
+ public String toString () {
+ if (this.docID == null) return null;
+
+ StringBuilder sb = new StringBuilder("word-");
+
+ // Get prefix string corpus/doc
+ if (this.corpusID != null) {
+ sb.append(this.corpusID).append('!');
+ };
+ sb.append(this.docID);
+
+ sb.append("-p");
+ sb.append(this.pos);
+
+ return sb.toString();
+ };
+};
diff --git a/src/main/java/de/ids_mannheim/korap/response/match/Relation.java b/src/main/java/de/ids_mannheim/korap/response/match/Relation.java
new file mode 100644
index 0000000..1587ed3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/response/match/Relation.java
@@ -0,0 +1,13 @@
+package de.ids_mannheim.korap.response.match;
+
+/**
+ * Class for relational highlights.
+ */
+public class Relation {
+ public int ref;
+ public String annotation;
+ public Relation (String annotation, int ref) {
+ this.annotation = annotation;
+ this.ref = ref;
+ };
+};