1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.sf.okapi.filters.openxml;
18
19 import static net.sf.okapi.filters.openxml.XMLEventHelpers.LOCAL_BREAK;
20 import static net.sf.okapi.filters.openxml.XMLEventHelpers.LOCAL_TAB;
21 import static net.sf.okapi.filters.openxml.XMLEventHelpers.createQName;
22
23 import java.util.ArrayDeque;
24 import java.util.ArrayList;
25 import java.util.Deque;
26 import java.util.List;
27 import java.util.Map;
28
29 import javax.xml.namespace.QName;
30 import javax.xml.stream.XMLEventFactory;
31 import javax.xml.stream.events.Attribute;
32 import javax.xml.stream.events.XMLEvent;
33
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import net.sf.okapi.common.LocaleId;
38 import net.sf.okapi.common.resource.Code;
39 import net.sf.okapi.common.resource.Segment;
40 import net.sf.okapi.common.resource.TextContainer;
41 import net.sf.okapi.common.resource.TextFragment;
42 import net.sf.okapi.common.resource.TextPart;
43
44
45
46
47
48
49
50
51 class BlockTextUnitWriter implements TextUnitWriter {
52 private static final String EMPTY = "";
53 private final Logger LOGGER = LoggerFactory.getLogger(BlockTextUnitWriter.class);
54
55 private final ConditionalParameters parameters;
56 private final XMLEventFactory eventFactory;
57 private final QName runName;
58 private final QName textName;
59 private final RunPropertiesPairWithDetectedRunFonts baseRunPropertiesPairWithDetectedRunFonts;
60 private final List<XMLEvents> hiddenCodes;
61 private final Map<Integer, XMLEvents> visibleCodes;
62 private final AdditiveCollection<XMLEvent> xmlEvents;
63 private final RunPropertiesClarification runPropertiesClarification;
64 private final Deque<RunPropertiesPairWithDetectedRunFonts> currentRunPropertiesPairWithDetectedRunFonts;
65 private StringBuilder textContent;
66 private boolean runIsOpen;
67
68 BlockTextUnitWriter(
69 final ConditionalParameters parameters,
70 final XMLEventFactory eventFactory,
71 final LocaleId locale,
72 final QName runName,
73 final QName textName,
74 final RunPropertiesPairWithDetectedRunFonts baseRunPropertiesPairWithDetectedRunFonts,
75 final List<XMLEvents> hiddenCodes,
76 final Map<Integer, XMLEvents> visibleCodes,
77 final AdditiveCollection<XMLEvent> xmlEvents,
78 final RunPropertiesClarification runPropertiesClarification
79 ) {
80 this(
81 parameters,
82 eventFactory,
83 locale,
84 runName,
85 textName,
86 baseRunPropertiesPairWithDetectedRunFonts,
87 hiddenCodes,
88 visibleCodes,
89 xmlEvents,
90 runPropertiesClarification,
91 new ArrayDeque<>()
92 );
93 }
94
95 BlockTextUnitWriter(
96 final ConditionalParameters parameters,
97 final XMLEventFactory eventFactory,
98 final LocaleId locale,
99 final QName runName,
100 final QName textName,
101 final RunPropertiesPairWithDetectedRunFonts baseRunPropertiesPairWithDetectedRunFonts,
102 final List<XMLEvents> hiddenCodes,
103 final Map<Integer, XMLEvents> visibleCodes,
104 final AdditiveCollection<XMLEvent> xmlEvents,
105 final RunPropertiesClarification runPropertiesClarification,
106 final Deque<RunPropertiesPairWithDetectedRunFonts> currentRunPropertiesPairWithDetectedRunFonts
107 ) {
108 this.parameters = parameters;
109 this.eventFactory = eventFactory;
110 this.runName = runName;
111 this.textName = textName;
112 this.baseRunPropertiesPairWithDetectedRunFonts = baseRunPropertiesPairWithDetectedRunFonts;
113 this.hiddenCodes = hiddenCodes;
114 this.visibleCodes = visibleCodes;
115 this.xmlEvents = xmlEvents;
116 this.runPropertiesClarification = runPropertiesClarification;
117 this.currentRunPropertiesPairWithDetectedRunFonts = currentRunPropertiesPairWithDetectedRunFonts;
118 }
119
120 public void write(TextContainer tc) {
121 this.textContent = new StringBuilder();
122 boolean firstSegmentWritten = false;
123
124 for (TextPart textPart : tc) {
125 if (textPart.isSegment()) {
126 Segment segment = (Segment) textPart;
127
128 if (!firstSegmentWritten) {
129 writeFirstSegment(tc.getFirstSegment());
130 firstSegmentWritten = true;
131 continue;
132 }
133
134 writeSegment(segment);
135
136 } else {
137
138
139
140 writeSegment(new Segment("text_part", textPart.getContent()));
141 }
142 }
143
144 flushText(true);
145 }
146
147 private void writeFirstSegment(final Segment segment) {
148 for (final XMLEvents events : this.hiddenCodes) {
149 this.xmlEvents.addAll(events.getEvents());
150 }
151 writeSegment(segment);
152 }
153
154 private void writeSegment(Segment segment) {
155 try {
156 TextFragment content = segment.getContent();
157 String codedText = content.getCodedText();
158 List<Code> codes = content.getCodes();
159 for (int i = 0; i < codedText.length(); i++) {
160 char c = codedText.charAt(i);
161 if (TextFragment.isMarker(c)) {
162 int codeIndex = TextFragment.toIndex(codedText.charAt(++i));
163 writeCode(codes.get(codeIndex));
164 }
165 else {
166 writeChar(c);
167 }
168 }
169 }
170 catch (Exception e) {
171 LOGGER.error("Threw {} writing segment id {} '{}'", e.getClass().getSimpleName(),
172 segment.getId(), segment.toString());
173 throw e;
174 }
175 }
176
177 private void writeChar(char c) {
178 textContent.append(c);
179 }
180
181 private void writeCode(Code code) {
182
183 if (code.isMarkerMasking()) {
184 String data = code.getData();
185 for (int i = 0; i < data.length(); i++) {
186 char c = data.charAt(i);
187 writeChar(c);
188 }
189 return;
190 }
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 int id = code.getId();
206 XMLEvents codeEvents = visibleCodes.get(id);
207 switch (code.getTagType()) {
208 case OPENING:
209 flushText(true);
210 if (codeEvents instanceof RunPropertiesPairWithDetectedRunFonts) {
211 this.currentRunPropertiesPairWithDetectedRunFonts.push((RunPropertiesPairWithDetectedRunFonts) codeEvents);
212 }
213 else if (codeEvents instanceof RunContainer) {
214 RunContainer rc = (RunContainer)codeEvents;
215 this.xmlEvents.addAll(rc.startMarkupEvents());
216 this.currentRunPropertiesPairWithDetectedRunFonts.push(rc.defaultRunPropertiesPairWithDetectedRunFonts());
217 }
218 else {
219 throw new IllegalStateException("Unexpected code contents for opening code '" +
220 code.toString() + "':" + codeEvents );
221 }
222 break;
223 case PLACEHOLDER:
224
225
226 boolean isRunMarkup = (codeEvents instanceof Run.Markup);
227 if (isRunMarkup) {
228 flushText(false);
229 flushRunStart();
230 } else {
231 flushText(true);
232 }
233 this.xmlEvents.addAll(codeEvents.getEvents());
234 break;
235 case CLOSING:
236 flushText(true);
237 if (codeEvents instanceof RunPropertiesPairWithDetectedRunFonts) {
238
239 this.currentRunPropertiesPairWithDetectedRunFonts.pop();
240 }
241 else if (codeEvents instanceof RunContainer) {
242 RunContainer rc = (RunContainer) codeEvents;
243 this.xmlEvents.addAll(rc.endMarkupEvents());
244 this.currentRunPropertiesPairWithDetectedRunFonts.pop();
245 }
246 else {
247 throw new IllegalStateException("Unexpected code contents for closing code '" +
248 code.toString() + "':" + codeEvents );
249 }
250
251 break;
252 }
253 }
254
255 private void flushRunStart() {
256 if (!runIsOpen) {
257 writeRunStart();
258 runIsOpen = true;
259 }
260 }
261
262 private void flushRunEnd() {
263 if (runIsOpen) {
264 writeRunEnd();
265 runIsOpen = false;
266 }
267 }
268
269 private void flushText(boolean terminateRun) {
270 final RunPropertiesPairWithDetectedRunFonts rppwdrf = runPropertiesPairWithDetectedRunFonts();
271 if (textContent.length() > 0) {
272 final String text = textContent.toString();
273 this.runPropertiesClarification.prepareContextWith(
274 rppwdrf.combined(),
275 rppwdrf.detectedRunFonts(),
276 text
277 );
278 flushRunStart();
279 writeRunText(text);
280 textContent = new StringBuilder();
281 } else {
282 this.runPropertiesClarification.prepareContextWith(
283 rppwdrf.combined(),
284 rppwdrf.detectedRunFonts(),
285 EMPTY
286 );
287 }
288 if (terminateRun) {
289 flushRunEnd();
290 }
291 }
292
293 private RunPropertiesPairWithDetectedRunFonts runPropertiesPairWithDetectedRunFonts() {
294 return this.currentRunPropertiesPairWithDetectedRunFonts.isEmpty()
295 ? this.baseRunPropertiesPairWithDetectedRunFonts
296 : this.currentRunPropertiesPairWithDetectedRunFonts.peek();
297 }
298
299 private void writeRunStart() {
300 final RunPropertiesPairWithDetectedRunFonts rpp = runPropertiesPairWithDetectedRunFonts();
301 xmlEvents.add(eventFactory.createStartElement(runName, null, null));
302
303 runPropertiesClarification.performFor(rpp.direct());
304 xmlEvents.addAll(rpp.direct().getEvents());
305
306 runPropertiesClarification.performFor(rpp.combined());
307 xmlEvents.addAll(rpp.combined().getEvents());
308 }
309
310
311
312 private void writeRunText(String text) {
313
314
315
316
317 if (textName.getPrefix().isEmpty()) {
318 writeTextIfNeeded(new StringBuilder(text));
319 return;
320 }
321
322
323
324
325 StringBuilder sb = new StringBuilder();
326 for (char c : text.toCharArray()) {
327 if (c == parameters.getLineSeparatorReplacement() && parameters.getAddLineSeparatorCharacter()) {
328 writeTextIfNeeded(sb);
329 sb.setLength(0);
330 writeLineBreak();
331 } else if (c == '\t' && parameters.getAddTabAsCharacter()
332 && Namespace.PREFIX_W.equals(this.textName.getPrefix())) {
333 writeTextIfNeeded(sb);
334 sb.setLength(0);
335 writeTab();
336 } else {
337 sb.append(c);
338 }
339 }
340 writeTextIfNeeded(sb);
341 }
342
343 private void writeTextIfNeeded(StringBuilder buffer) {
344 if (buffer.length() > 0) {
345 flushRunStart();
346 writeText(buffer.toString());
347 }
348 }
349
350 private void writeTab() {
351 QName br = createQName(LOCAL_TAB, textName);
352 xmlEvents.add(eventFactory.createStartElement(br, null, null));
353 xmlEvents.add(eventFactory.createEndElement(br, null));
354 }
355
356 private void writeLineBreak() {
357
358
359
360 if (Namespace.PREFIX_A.equals(this.textName.getPrefix())) {
361
362 flushRunEnd();
363 writeRunStart();
364 writeText("");
365 writeRunEnd();
366 } else {
367 writeRunEnd();
368 writeRunStart();
369 }
370 QName br = createQName(LOCAL_BREAK, textName);
371 xmlEvents.add(eventFactory.createStartElement(br, null, null));
372 if (Namespace.PREFIX_A.equals(this.textName.getPrefix())) {
373 final RunPropertiesPairWithDetectedRunFonts rpp = runPropertiesPairWithDetectedRunFonts();
374 runPropertiesClarification.performFor(rpp.direct());
375 xmlEvents.addAll(rpp.direct().getEvents());
376 }
377 xmlEvents.add(eventFactory.createEndElement(br, null));
378 }
379
380 private void writeText(String text) {
381 boolean needsPreserveSpace = needsXmlSpacePreserve(text);
382 ArrayList<Attribute> attrs = new ArrayList<>();
383
384 if (needsPreserveSpace && !Namespaces.DrawingML.containsName(textName)) {
385 attrs.add(eventFactory.createAttribute("xml", Namespaces.XML.getURI(), "space", "preserve"));
386 }
387 xmlEvents.add(eventFactory.createStartElement(textName, attrs.iterator(), null));
388 xmlEvents.add(eventFactory.createCharacters(text));
389 xmlEvents.add(eventFactory.createEndElement(textName, null));
390 }
391
392 private void writeRunEnd() {
393 xmlEvents.add(eventFactory.createEndElement(runName, null));
394 }
395
396
397
398
399
400
401
402
403 static boolean needsXmlSpacePreserve(String text) {
404 for (char c : text.toCharArray()) {
405
406
407 if (Character.isWhitespace(c) || c == '\u00A0') return true;
408 }
409 return false;
410 }
411 }