1   package org.daisy.pipeline.nlp.calabash.impl;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.List;
6   import java.util.regex.Matcher;
7   import java.util.regex.Pattern;
8   
9   import org.daisy.pipeline.nlp.lexing.LexService.Sentence;
10  import org.daisy.pipeline.nlp.lexing.LexService.TextBoundaries;
11  
12  /**
13   * This class transforms references to a single string (i.e. two indexes) into a
14   * list of references to an array of strings (i.e. a list of four indexes). It
15   * is used for splitting XML content (array of strings) with the LexService
16   * interface (single string inputs).
17   */
18  public class StringComposer {
19  
20  	static public class TextPointer {
21  		// inclusive index of the segment in which the first character is
22  		public int firstSegment;
23  		// inclusive index of the first character
24  		public int firstIndex;
25  		// inclusive index of the segment in which the last character is
26  		public int lastSegment;
27  		// EXLUSIVE index of the last character
28  		public int lastIndex;
29  
30  		public boolean properNoun;
31  	}
32  
33  	static public class SentencePointer {
34  		public TextPointer boundaries;
35  		public List<TextPointer> content; //optional
36  	}
37  
38  	private List<SentencePointer> mSentencePointers = new ArrayList<SentencePointer>();
39  	private List<TextPointer> mWordPointers = new ArrayList<TextPointer>();
40  	private int[] mCoordinates = new int[2];
41  	private Matcher mSpaceMatcher = Pattern.compile("[\\p{Z}\\s]+").matcher("");
42  	private Matcher mSpaceMatcherEnd = Pattern.compile("[\\p{Z}\\s]+$").matcher("");
43  
44  	public String concat(Collection<String> segments) {
45  		StringBuilder sb = new StringBuilder();
46  		for (String segment : segments) {
47  			if (segment != null)
48  				sb.append(segment);
49  		}
50  		return sb.toString();
51  	}
52  
53  	/**
54  	 * The result cannot be kept after another call as it might be recycled.
55  	 * 
56  	 * The function trims the sentences and the words when making the pointers.
57  	 * No pointers is made for empty sentences and empty words.
58  	 * 
59  	 * isLexMark[i] means that the corresponding segment at index "i" can be
60  	 * ignored, since it is a lexing mark, rather than a true part of the
61  	 * original text.
62  	 */
63  	public List<SentencePointer> makePointers(List<Sentence> sentences, List<String> segments,
64  	        boolean[] isLexMark) {
65  
66  		//pre-allocate some pointers
67  		int maxContentSize = 0;
68  		for (Sentence s : sentences)
69  			if (s.words != null)
70  				maxContentSize += s.words.size();
71  		for (int size = mWordPointers.size(); size < maxContentSize; ++size)
72  			mWordPointers.add(new TextPointer());
73  
74  		//build a reference free of any lexer-helper
75  		StringBuilder sb = new StringBuilder();
76  		for (int i = 0; i < segments.size(); ++i) {
77  			if (isLexMark[i])
78  				for (int k = segments.get(i).length(); k > 0; --k)
79  					sb.append(" ");
80  			else if (segments.get(i) != null)
81  				sb.append(segments.get(i));
82  		}
83  		String ref = sb.toString();
84  
85  		//make the pointers
86  		int contentIndex = 0;
87  		int nrOfSents = 0;
88  		int left, right;
89  		int[] coord;
90  		for (int i = 0; i < sentences.size(); ++i) {
91  			if (mSentencePointers.size() == nrOfSents) {
92  				SentencePointer sp = new SentencePointer();
93  				sp.boundaries = new TextPointer();
94  				mSentencePointers.add(sp);
95  			}
96  			Sentence sentence = sentences.get(i);
97  			SentencePointer sp = mSentencePointers.get(nrOfSents);
98  
99  			left = trimLeft(sentence.boundaries.left, ref);
100 			right = trimRight(sentence.boundaries.right, ref);
101 			if (left >= right) {
102 				//empty sentence
103 				continue;
104 			}
105 
106 			coord = findLeft(segments, left);
107 			sp.boundaries.firstSegment = coord[0];
108 			sp.boundaries.firstIndex = coord[1];
109 
110 			coord = findRight(segments, right);
111 			sp.boundaries.lastSegment = coord[0];
112 			sp.boundaries.lastIndex = coord[1];
113 			++nrOfSents;
114 
115 			if (sentence.words == null || sentence.words.size() == 0) {
116 				sp.content = null;
117 			} else {
118 				int contentLeft = contentIndex;
119 				for (int j = 0; j < sentence.words.size(); ++j) {
120 					TextBoundaries word = sentence.words.get(j);
121 					TextPointer tp = mWordPointers.get(contentIndex);
122 
123 					left = trimLeft(word.left, ref);
124 					right = trimRight(word.right, ref);
125 
126 					if (left >= right) {
127 						continue; //empty word
128 					}
129 
130 					coord = findLeft(segments, left);
131 					tp.firstSegment = coord[0];
132 					tp.firstIndex = coord[1];
133 
134 					coord = findRight(segments, right);
135 					tp.lastSegment = coord[0];
136 					tp.lastIndex = coord[1];
137 
138 					++contentIndex;
139 				}
140 				if (contentLeft != contentIndex)
141 					sp.content = mWordPointers.subList(contentLeft, contentIndex);
142 				else
143 					sp.content = null;
144 			}
145 		}
146 
147 		return mSentencePointers.subList(0, nrOfSents);
148 	}
149 
150 	private int trimLeft(int left, String ref) {
151 		mSpaceMatcher.reset(ref.substring(left));
152 		if (mSpaceMatcher.lookingAt()) {
153 			left += mSpaceMatcher.end();
154 		}
155 		return left;
156 	}
157 
158 	private int trimRight(int right, String ref) {
159 		mSpaceMatcherEnd.reset(ref.substring(0, right));
160 		if (mSpaceMatcherEnd.find()) {
161 			right = mSpaceMatcherEnd.start();
162 		}
163 		return right;
164 	}
165 
166 	private int[] findLeft(List<String> segments, int index) {
167 		int res[] = findInArray(segments, index);
168 		if (res[1] == segments.get(res[0]).length()) {
169 			res[0]++;
170 			res[1] = 0;
171 		}
172 		return res;
173 	}
174 
175 	private int[] findRight(List<String> segments, int index) {
176 		int[] res = findInArray(segments, index - 1);
177 		res[1]++;
178 		if (res[1] == 0) {
179 			res[0]--;
180 			res[1] = segments.get(res[0]).length();
181 		}
182 		return res;
183 	}
184 
185 	private int[] findInArray(List<String> segments, int index) {
186 		mCoordinates[0] = 0;
187 		while ((segments.get(mCoordinates[0]) == null)
188 		        || (index >= segments.get(mCoordinates[0]).length())) {
189 			if (segments.get(mCoordinates[0]) != null)
190 				index -= segments.get(mCoordinates[0]).length();
191 			++mCoordinates[0];
192 		}
193 		mCoordinates[1] = index;
194 		return mCoordinates;
195 	}
196 }