1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.sf.okapi.lib.translation;
18
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.LinkedHashSet;
22 import java.util.List;
23 import java.util.Stack;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.w3c.dom.Element;
30 import org.w3c.dom.NamedNodeMap;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NodeList;
33
34 import net.sf.okapi.common.HTMLCharacterEntities;
35 import net.sf.okapi.common.Util;
36 import net.sf.okapi.common.filterwriter.XLIFFContent;
37 import net.sf.okapi.common.query.QueryResult;
38 import net.sf.okapi.common.resource.Code;
39 import net.sf.okapi.common.resource.TextFragment;
40 import net.sf.okapi.common.resource.TextFragment.TagType;
41
42
43
44
45
46
47
48 public class QueryUtil {
49 private static final Pattern TAG = Pattern.compile("(<(br|u)(\\s+)id=['\"](.*?)['\"](\\s*?)/?>)|(</u>)", Pattern.CASE_INSENSITIVE);
50
51 private static final Pattern HTML_SPAN = Pattern.compile("\\<span\\s*?(.*?)>|\\</span>",
52 Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
53
54 private static final Pattern CR = Pattern.compile("&(?:#(\\S+?)|\\w*?);");
55 private final Logger logger = LoggerFactory.getLogger(getClass());
56
57 private final StringBuilder codesMarkers;
58 private final XLIFFContent fmt;
59 private List<Code> codes;
60 private HTMLCharacterEntities entities;
61
62 public QueryUtil () {
63 codesMarkers = new StringBuilder();
64 fmt = new XLIFFContent();
65 }
66
67
68
69
70
71
72 public boolean hasCode () {
73 if ( codes == null ) return false;
74 return !codes.isEmpty();
75 }
76
77
78
79
80
81
82
83
84
85 public String separateCodesFromText (TextFragment frag) {
86
87 codesMarkers.setLength(0);
88 codes = frag.getCodes();
89
90 String text = frag.getCodedText();
91 if (!frag.hasCode()) {
92 return text;
93 }
94
95
96 StringBuilder tmp = new StringBuilder(text.length() - (codes.size() * 2));
97 for (int i = 0; i < text.length(); i++) {
98 switch (text.charAt(i)) {
99 case TextFragment.MARKER_OPENING:
100 case TextFragment.MARKER_CLOSING:
101 case TextFragment.MARKER_ISOLATED:
102 codesMarkers.append(text.charAt(i));
103 codesMarkers.append(text.charAt(++i));
104 break;
105 default:
106 tmp.append(text.charAt(i));
107 }
108 }
109
110 return tmp.toString();
111 }
112
113
114
115
116
117
118
119
120
121
122 public TextFragment createNewFragmentWithCodes (String plainText) {
123 return new TextFragment(plainText + codesMarkers, codes);
124 }
125
126
127
128
129
130
131
132 public List<String> toCodedHTML(List<TextFragment> frags) {
133 List<String> html = new ArrayList<>(frags.size());
134 for (TextFragment frag : frags) {
135 html.add(toCodedHTML(frag));
136 }
137 return html;
138 }
139
140
141
142
143
144
145
146
147 public String toCodedHTML (TextFragment fragment) {
148 if ( fragment == null ) {
149 return "";
150 }
151 Code code;
152 String text = fragment.getCodedText();
153 StringBuilder sb = new StringBuilder();
154 for ( int i = 0; i < text.length(); i++ ) {
155 switch (text.charAt(i)) {
156 case TextFragment.MARKER_OPENING:
157 code = fragment.getCode(text.charAt(++i));
158 sb.append("<u id='");
159 sb.append(code.getId());
160 sb.append("'>");
161 break;
162 case TextFragment.MARKER_CLOSING:
163 i++;
164 sb.append("</u>");
165 break;
166 case TextFragment.MARKER_ISOLATED:
167 code = fragment.getCode(text.charAt(++i));
168 switch ( code.getTagType() ) {
169 case OPENING:
170 sb.append("<br id='b");
171 sb.append(code.getId());
172 sb.append("'/>");
173 break;
174 case CLOSING:
175 sb.append("<br id='e");
176 sb.append(code.getId());
177 sb.append("'/>");
178 break;
179 case PLACEHOLDER:
180 sb.append("<br id='p");
181 sb.append(code.getId());
182 sb.append("'/>");
183 break;
184 }
185 break;
186 case '&':
187 sb.append("&");
188 break;
189 case '<':
190 sb.append("<");
191 break;
192 default:
193 sb.append(text.charAt(i));
194 }
195 }
196 return sb.toString();
197 }
198
199
200
201
202
203
204
205
206
207 public TextFragment fromCodedHTMLToFragment (String text,
208 TextFragment fragment)
209 {
210 if ( Util.isEmpty(text) ) {
211 if ( fragment != null ) {
212 fragment.setCodedText("", true);
213 return fragment;
214 }
215 else {
216 return new TextFragment();
217 }
218 }
219
220 text = unescapeCharacterReferences(text);
221
222 ArrayList<Code> codes = new ArrayList<>();
223 text = this.translateCodedHTMLToFragment(text, codes);
224
225
226
227 StringBuilder sb = new StringBuilder(text);
228 removeSpans(sb);
229
230
231 if ( fragment != null ) {
232 fragment.setCodedText(sb.toString(), codes, true);
233 return fragment;
234 }
235 else {
236 return new TextFragment(sb.toString(), codes);
237 }
238 }
239
240 private String unescapeCharacterReferences(String text) {
241 initHTMLCharacterEntities();
242
243 Matcher crMatch = CR.matcher(text);
244 StringBuilder result = new StringBuilder(text.length());
245
246 int textIndex = 0;
247 String numeric;
248 int character;
249 while (crMatch.find()) {
250 if (crMatch.start() > 0) {
251 result.append(text, textIndex, crMatch.start());
252 }
253 textIndex = crMatch.end();
254
255
256 numeric = crMatch.group(1);
257 if (numeric != null) {
258 try {
259 if (numeric.charAt(0) == 'x') {
260
261 character = Integer.parseInt(numeric.substring(1), 16);
262 } else {
263
264 character = Integer.parseInt(numeric);
265 }
266 } catch (NumberFormatException e) {
267 character = '?';
268 }
269 result.append((char) character);
270 continue;
271 }
272
273
274 character = entities.lookupReference(crMatch.group(0));
275 if ( character == -1 ) {
276
277
278 continue;
279 }
280 result.append((char) character);
281 }
282 result.append(text.substring(textIndex));
283
284 return result.toString();
285 }
286
287 private String translateCodedHTMLToFragment(String text, ArrayList<Code> codes) {
288 Matcher tagMatches = TAG.matcher(text);
289 StringBuilder result = new StringBuilder(text.length());
290
291 Code code;
292 int codeId;
293 int textIndex = 0;
294 while (tagMatches.find()) {
295 if (tagMatches.start() > 0) {
296 result.append(text, textIndex, tagMatches.start());
297 }
298 textIndex = tagMatches.end();
299
300
301 if (tagMatches.group(1) == null) {
302
303 code = new Code(TagType.CLOSING, "Xpt", null);
304 codes.add(code);
305
306
307 result.append((char) TextFragment.MARKER_CLOSING);
308 result.append(TextFragment.toChar(codes.size() - 1));
309 continue;
310 }
311
312
313 if ("u".equals(tagMatches.group(2))) {
314
315 codeId = Util.strToInt(tagMatches.group(4), -1);
316
317
318 code = new Code(TagType.OPENING, "Xpt", null);
319 code.setId(codeId);
320 codes.add(code);
321
322
323 result.append((char) TextFragment.MARKER_OPENING);
324 result.append(TextFragment.toChar(codes.size() - 1));
325 continue;
326 }
327
328
329 codeId = Util.strToInt(tagMatches.group(4).substring(1), -1);
330
331
332 TagType tagType = TagType.PLACEHOLDER;
333 char c = tagMatches.group(4).charAt(0);
334 switch (c) {
335 case 'b':
336 tagType = TagType.OPENING;
337 break;
338 case 'e':
339 tagType = TagType.CLOSING;
340 break;
341 case 'p':
342 tagType = TagType.PLACEHOLDER;
343 break;
344 default:
345 logger.warn(String.format("ID ('%s') of isolated code modified.", c));
346 }
347 code = new Code(tagType, "Xph", null);
348 code.setId(codeId);
349 codes.add(code);
350
351
352 result.append((char) TextFragment.MARKER_ISOLATED);
353 result.append(TextFragment.toChar(codes.size() - 1));
354 }
355 result.append(text.substring(textIndex));
356
357 return result.toString();
358 }
359
360
361
362
363
364
365
366
367
368
369 public String fromCodedHTML (String htmlText,
370 TextFragment fragment,
371 boolean addMissingCodes)
372 {
373 return fromCodedHTML(htmlText, fragment, addMissingCodes, true);
374 }
375
376
377
378
379
380
381
382
383
384
385 public String fromCodedHTML (String htmlText,
386 TextFragment fragment,
387 boolean addMissingCodes,
388 boolean removeSpans)
389 {
390 if ( Util.isEmpty(htmlText) ) {
391 return "";
392 }
393
394 htmlText = unescapeCharacterReferences(htmlText);
395
396
397 ArrayList<String> newCodes = new ArrayList<>(fragment.getCodes().size());
398 String codedText = translateCodedHTML(fragment, htmlText, newCodes);
399
400 StringBuilder sb = new StringBuilder(codedText);
401
402
403
404 if ( removeSpans ) {
405 removeSpans(sb);
406 }
407
408
409 if ( addMissingCodes) {
410 ArrayList<String> oriCodes = getCodeIdAttrValues(fragment);
411 if ( newCodes.size() < oriCodes.size() ) {
412 for (String tmp : oriCodes) {
413 if (!newCodes.contains(tmp)) {
414 switch (tmp.charAt(0)) {
415 case 'o':
416 sb.append((char) TextFragment.MARKER_OPENING);
417 sb.append(TextFragment.toChar(fragment.getIndex(Integer.parseInt(tmp.substring(1)))));
418 break;
419 case 'c':
420 sb.append((char) TextFragment.MARKER_CLOSING);
421 sb.append(TextFragment.toChar(fragment.getIndexForClosing(Integer.parseInt(tmp.substring(1)))));
422 break;
423 case 'b':
424 case 'e':
425 case 'p':
426 sb.append((char) TextFragment.MARKER_ISOLATED);
427 sb.append(TextFragment.toChar(fragment.getIndex(Integer.parseInt(tmp.substring(1)))));
428 break;
429 }
430 }
431 }
432 }
433 }
434
435 return sb.toString();
436 }
437
438 private static void removeSpans(StringBuilder sb) {
439 Matcher m = HTML_SPAN.matcher(sb.toString());
440 int offset = 0;
441 while ( m.find() ) {
442 sb.delete(m.start() - offset, m.end() - offset);
443 offset += m.end() - m.start();
444 }
445 }
446
447 private void initHTMLCharacterEntities() {
448 if ( entities == null ) {
449 entities = new HTMLCharacterEntities();
450 entities.ensureInitialization(true);
451 }
452 }
453
454 private static String translateCodedHTML(TextFragment fragment, String text, ArrayList<String> newCodes) {
455 Stack<Integer> stack = new Stack<>();
456
457 Matcher tagMatches = TAG.matcher(text);
458 StringBuilder result = new StringBuilder(text.length());
459
460 int codeId;
461 int textIndex = 0;
462 char isoType;
463 while (tagMatches.find()) {
464 if (tagMatches.start() > 0) {
465 result.append(text, textIndex, tagMatches.start());
466 }
467 textIndex = tagMatches.end();
468
469
470 if (tagMatches.group(1) == null) {
471 if ( stack.isEmpty() ) {
472
473
474 continue;
475 }
476
477 codeId = stack.pop();
478 newCodes.add("c" + codeId);
479
480 result.append((char) TextFragment.MARKER_CLOSING);
481 result.append(TextFragment.toChar(fragment.getIndexForClosing(codeId)));
482 continue;
483 }
484
485
486 if ("u".equals(tagMatches.group(2))) {
487
488 codeId = Util.strToInt(tagMatches.group(4), -1);
489 stack.push(codeId);
490 newCodes.add("o" + codeId);
491
492 result.append((char) TextFragment.MARKER_OPENING);
493 result.append(TextFragment.toChar(fragment.getIndex(codeId)));
494 continue;
495 }
496
497
498 isoType = tagMatches.group(4).charAt(0);
499 codeId = Util.strToInt(tagMatches.group(4).substring(1), -1);
500 newCodes.add(isoType + Integer.toString(codeId));
501
502 result.append((char) TextFragment.MARKER_ISOLATED);
503 switch (isoType) {
504 case 'b':
505 result.append(TextFragment.toChar(fragment.getIndexForOpening(codeId)));
506 break;
507 case 'e':
508 result.append(TextFragment.toChar(fragment.getIndexForClosing(codeId)));
509 break;
510 default:
511 result.append(TextFragment.toChar(fragment.getIndex(codeId)));
512 break;
513 }
514 }
515 result.append(text.substring(textIndex));
516
517 return result.toString();
518 }
519
520 private static ArrayList<String> getCodeIdAttrValues(TextFragment fragment) {
521 ArrayList<String> oriCodes = new ArrayList<>(fragment.getCodes().size());
522 for ( Code code : fragment.getCodes() ) {
523 switch ( code.getTagType() ) {
524 case OPENING:
525 oriCodes.add("o" + code.getId());
526 break;
527 case CLOSING:
528 oriCodes.add("c" + code.getId());
529 break;
530 case PLACEHOLDER:
531 switch ( code.getTagType() ) {
532 case OPENING:
533 oriCodes.add("b" + code.getId());
534 break;
535 case CLOSING:
536 oriCodes.add("e" + code.getId());
537 break;
538 case PLACEHOLDER:
539 oriCodes.add("p" + code.getId());
540 break;
541 }
542 break;
543 }
544 }
545 return oriCodes;
546 }
547
548
549
550
551
552
553
554
555
556 public String toXLIFF (TextFragment fragment) {
557 if ( fragment == null ) {
558 return "";
559 }
560 fmt.setContent(fragment);
561 return fmt.toString();
562 }
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599 public TextFragment fromXLIFF (Element elem,
600 TextFragment original)
601 {
602 NodeList list = elem.getChildNodes();
603 int lastId = -1;
604 int id = -1;
605 Node node;
606 Stack<Integer> stack = new Stack<>();
607 StringBuilder buffer = new StringBuilder();
608
609
610 for ( int i = 0; i < list.getLength(); i++ ) {
611 node = list.item(i);
612 switch ( node.getNodeType() ) {
613 case Node.TEXT_NODE:
614 buffer.append(node.getNodeValue());
615 break;
616 case Node.ELEMENT_NODE:
617 NamedNodeMap map = node.getAttributes();
618 switch (node.getNodeName()) {
619 case "bpt":
620 id = getRawIndex(lastId, map.getNamedItem("id"));
621 stack.push(id);
622 buffer.append(new String(new char[]{TextFragment.MARKER_OPENING,
623 TextFragment.toChar(original.getIndex(id))}));
624 break;
625 case "ept":
626 buffer.append(new String(new char[]{ TextFragment.MARKER_CLOSING,
627 TextFragment.toChar(original.getIndexForClosing(stack.pop()))}));
628 break;
629 case "ph":
630 id = getRawIndex(lastId, map.getNamedItem("id"));
631 buffer.append(new String(new char[]{ TextFragment.MARKER_ISOLATED,
632 TextFragment.toChar(original.getIndex(id))}));
633 break;
634 case "it":
635 Node pos = map.getNamedItem("pos");
636 if (pos == null) {
637 id = getRawIndex(lastId, map.getNamedItem("id"));
638 buffer.append(new String(new char[]{ TextFragment.MARKER_ISOLATED,
639 TextFragment.toChar(original.getIndex(id))}));
640 } else if (pos.getNodeValue().equals("begin")) {
641 id = getRawIndex(lastId, map.getNamedItem("id"));
642 buffer.append(new String(new char[]{ TextFragment.MARKER_OPENING,
643 TextFragment.toChar(original.getIndex(id))}));
644 } else {
645 id = getRawIndex(lastId, map.getNamedItem("id"));
646 buffer.append(new String(new char[]{ TextFragment.MARKER_CLOSING,
647 TextFragment.toChar(original.getIndexForClosing(id))}));
648 }
649 break;
650 }
651 break;
652 }
653 }
654
655 return new TextFragment(buffer.toString(), original.getCodes());
656 }
657
658
659
660
661
662
663
664
665
666
667
668 public static ArrayList<QueryResult> removeDuplicates (List<QueryResult> queryResults) {
669
670
671 LinkedHashSet<QueryResult> dupRemove = new LinkedHashSet<>(queryResults.size());
672 Collections.sort(queryResults);
673 dupRemove.addAll(queryResults);
674 return new ArrayList<>(dupRemove);
675 }
676
677 private int getRawIndex (int lastIndex, Node attr)
678 {
679 if ( attr == null ) {
680 return ++lastIndex;
681 }
682 return Integer.parseInt(attr.getNodeValue());
683 }
684 }