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 java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.nio.charset.StandardCharsets;
27 import java.util.LinkedHashMap;
28 import java.util.Stack;
29 import java.util.TreeMap;
30 import java.util.zip.ZipEntry;
31 import java.util.zip.ZipFile;
32 import java.util.zip.ZipOutputStream;
33
34 import javax.xml.stream.XMLEventFactory;
35 import javax.xml.stream.XMLInputFactory;
36 import javax.xml.stream.XMLOutputFactory;
37 import javax.xml.stream.XMLStreamException;
38
39 import com.acumenvelocity.ath.common.Log;
40
41 import net.sf.okapi.common.DefaultLocalePair;
42 import net.sf.okapi.common.Event;
43 import net.sf.okapi.common.EventType;
44 import net.sf.okapi.common.IParameters;
45 import net.sf.okapi.common.ISkeleton;
46 import net.sf.okapi.common.LocaleId;
47 import net.sf.okapi.common.Util;
48 import net.sf.okapi.common.encoder.EncoderManager;
49 import net.sf.okapi.common.exceptions.OkapiFileNotFoundException;
50 import net.sf.okapi.common.exceptions.OkapiIOException;
51 import net.sf.okapi.common.filters.fontmappings.FontMappings;
52 import net.sf.okapi.common.filterwriter.GenericFilterWriter;
53 import net.sf.okapi.common.filterwriter.IFilterWriter;
54 import net.sf.okapi.common.resource.DocumentPart;
55 import net.sf.okapi.common.resource.Ending;
56 import net.sf.okapi.common.resource.ITextUnit;
57 import net.sf.okapi.common.resource.Property;
58 import net.sf.okapi.common.resource.StartDocument;
59 import net.sf.okapi.common.resource.StartSubDocument;
60 import net.sf.okapi.common.resource.TextContainer;
61 import net.sf.okapi.common.resource.TextFragment;
62 import net.sf.okapi.common.skeleton.GenericSkeleton;
63 import net.sf.okapi.common.skeleton.GenericSkeletonWriter;
64 import net.sf.okapi.common.skeleton.ISkeletonWriter;
65 import net.sf.okapi.common.skeleton.ZipSkeleton;
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 public class OpenXMLFilterWriter implements IFilterWriter {
82 private ConditionalParameters cparams;
83 private final XMLInputFactory inputFactory;
84 private final XMLOutputFactory outputFactory;
85 private final XMLEventFactory eventFactory;
86 private final DispersedTranslations dispersedTranslations;
87
88 private String outputPath;
89 private Document.General document;
90 private ZipOutputStream zipOut;
91 private byte[] buffer;
92 private LocaleId sourceLocale;
93 private LocaleId targetLocale;
94 private FontMappings fontMappings;
95 private File tempFile;
96 private File tempZip;
97
98 private EncoderManager encoderManager;
99 private ZipEntry subDocEntry;
100 private IFilterWriter subDocWriter;
101 private ISkeletonWriter subSkelWriter;
102 private final TreeMap<Integer, SubDocumentValues> tmSubDoc = new TreeMap<>();
103 private int ndxSubDoc = 0;
104 private OutputStream outputStream;
105
106
107
108
109
110
111
112
113
114
115 public OpenXMLFilterWriter() {
116 this(
117 new ConditionalParameters(),
118 XMLInputFactory.newInstance(),
119 XMLOutputFactory.newInstance(),
120 XMLEventFactory.newInstance(),
121 new DispersedTranslations.Default()
122 );
123 OpenXMLFilter.configure(this.inputFactory, this.cparams);
124 }
125
126 OpenXMLFilterWriter(
127 final ConditionalParameters cparams,
128 final XMLInputFactory inputFactory,
129 final XMLOutputFactory outputFactory,
130 final XMLEventFactory eventFactory,
131 final DispersedTranslations dispersedTranslations
132 ) {
133 this.cparams = cparams;
134 this.inputFactory = inputFactory;
135 this.outputFactory = outputFactory;
136 this.eventFactory = eventFactory;
137 this.dispersedTranslations = dispersedTranslations;
138 }
139
140
141
142
143 @Override
144 public void cancel () {
145
146 }
147
148
149
150
151 @Override
152 public void close () {
153 if ( zipOut == null ) return;
154 IOException err = null;
155 InputStream orig = null;
156 OutputStream dest = null;
157 try {
158
159 if (document != null){
160 document.close();
161 document = null;
162 }
163
164
165 zipOut.close();
166 zipOut = null;
167
168
169
170
171 if ( tempZip != null ) {
172 dest = new FileOutputStream(outputPath);
173 orig = new FileInputStream(tempZip);
174 int len;
175 while ( (len = orig.read(buffer)) > 0 ) {
176 dest.write(buffer, 0, len);
177 }
178 }
179 buffer = null;
180 }
181 catch ( IOException e ) {
182 err = e;
183 }
184 finally {
185
186 if ( dest != null ) {
187 try {
188 dest.close();
189 }
190 catch ( IOException e ) {
191 err = e;
192 }
193 dest = null;
194 }
195 if ( orig != null ) {
196 try {
197 orig.close();
198 } catch ( IOException e ) {
199 err = e;
200 }
201 orig = null;
202 if ( err != null ) {
203 throw new OkapiIOException("Error closing MS Office 2007 file.");
204 } else {
205 if ( tempZip != null ) {
206 tempZip.delete();
207 tempZip = null;
208 }
209 }
210 }
211 }
212 }
213
214
215
216
217 @Override
218 public String getName () {
219 return "OpenXMLZipFilterWriter";
220 }
221
222 @Override
223 public EncoderManager getEncoderManager () {
224 if ( encoderManager == null ) {
225 encoderManager = new EncoderManager();
226 encoderManager.setMapping(OpenXMLFilter.MIME_TYPE, "net.sf.okapi.common.encoder.XMLEncoder");
227 encoderManager.setDefaultOptions(null, OpenXMLFilter.ENCODING.name(), OpenXMLFilter.LINE_BREAK);
228 }
229 return encoderManager;
230 }
231
232 @Override
233 public ISkeletonWriter getSkeletonWriter () {
234 return subSkelWriter;
235 }
236
237
238
239
240
241
242 @Override
243 public Event handleEvent (Event event) {
244 switch ( event.getEventType() ) {
245 case START_DOCUMENT:
246 processStartDocument((StartDocument)event.getResource());
247 break;
248 case DOCUMENT_PART:
249 processDocumentPart(event);
250 break;
251 case END_DOCUMENT:
252 processEndDocument();
253 break;
254 case START_SUBDOCUMENT:
255 processStartSubDocument((StartSubDocument)event.getResource());
256 break;
257 case END_SUBDOCUMENT:
258 processEndSubDocument((Ending)event.getResource());
259 break;
260 case TEXT_UNIT:
261 final ITextUnit textUnit = event.getTextUnit();
262 final Property sheetNameProperty = textUnit.getProperty(ExcelWorksheetTransUnitProperty.SHEET_NAME.getKeyName());
263 final Property cellReferenceProperty = textUnit.getProperty(ExcelWorksheetTransUnitProperty.CELL_REFERENCE.getKeyName());
264 if (null != sheetNameProperty && null != cellReferenceProperty) {
265 final CrossSheetCellReference crossSheetCellReference = new CrossSheetCellReference(
266 sheetNameProperty.getValue(),
267 new CellReference(cellReferenceProperty.getValue())
268 );
269 if (this.dispersedTranslations.presentFor(crossSheetCellReference)) {
270 final ITextUnit tu = event.getTextUnit().clone();
271 if (tu.hasTarget(this.targetLocale)) {
272 final TextContainer tc = tu.getTarget(this.targetLocale);
273 tc.setContent(new TextFragment(tc.getUnSegmentedContentCopy().getText()));
274 final GenericSkeleton skeleton = new GenericSkeleton();
275 tu.setSkeleton(skeleton);
276 skeleton.addContentPlaceholder(tu);
277 this.dispersedTranslations.update(
278 crossSheetCellReference,
279 this.subDocWriter.getSkeletonWriter().processTextUnit(tu)
280 );
281 }
282 }
283 }
284 case START_GROUP:
285 case END_GROUP:
286 case START_SUBFILTER:
287 case END_SUBFILTER:
288 try {
289 subDocWriter.handleEvent(event);
290 } catch(Throwable e) {
291
292
293 Log.warn(getClass(), "Error processing {} '{}': {}", event.getEventType(),
294 event.getResource().getId(), e.getMessage());
295 }
296 break;
297 case CANCELED:
298 break;
299 default:
300 break;
301 }
302 return event;
303 }
304
305 @Override
306 public void setOptions (LocaleId language,
307 String defaultEncoding)
308 {
309 targetLocale = language;
310 this.dispersedTranslations.prepareFor(this.targetLocale);
311 }
312
313 @Override
314 public void setOutput (String path) {
315 outputPath = path;
316 }
317
318 @Override
319 public void setOutput (OutputStream output) {
320 this.outputStream = output;
321 }
322
323
324
325
326
327
328
329 private void processStartDocument (StartDocument res) {
330 try {
331 buffer = new byte[2048];
332 sourceLocale = res.getLocale();
333 this.fontMappings = this.cparams.fontMappings().applicableTo(
334 new DefaultLocalePair(this.sourceLocale, this.targetLocale)
335 );
336 ZipSkeleton skel = (ZipSkeleton)res.getSkeleton();
337 ZipFile zipTemp = skel.getOriginal();
338 File fZip = new File(zipTemp.getName());
339 this.document = new Document.General(
340 this.cparams,
341 this.inputFactory,
342 this.outputFactory,
343 this.eventFactory,
344 this.dispersedTranslations,
345 res.getFilterId(),
346 fZip.toURI(),
347 this.sourceLocale,
348 res.getEncoding(),
349 null,
350 null,
351 null
352 );
353 document.open();
354
355 tempZip = null;
356
357 boolean useTemp = false;
358 File f;
359 OutputStream os;
360 if (outputStream == null) {
361 f = new File(outputPath);
362 if ( f.exists() ) {
363
364 useTemp = !f.delete();
365 }
366 if (useTemp) {
367
368
369
370 tempZip = File.createTempFile("~okapi-24_zfwTmpZip_", null);
371 os = new FileOutputStream(tempZip.getAbsolutePath());
372 } else {
373 Util.createDirectories(outputPath);
374 os = new FileOutputStream(outputPath);
375 }
376 } else {
377 os = outputStream;
378 }
379
380
381 zipOut = new ZipOutputStream(os);
382 }
383 catch ( FileNotFoundException e ) {
384 throw new OkapiFileNotFoundException("Existing file could not be overwritten.", e);
385 }
386 catch ( IOException | XMLStreamException e) {
387 throw new OkapiIOException("File could not be written.", e);
388 }
389 }
390
391 private void processEndDocument () {
392 close();
393 }
394
395
396
397
398
399
400 private void processDocumentPart (Event event) {
401 DocumentPart documentPart = (DocumentPart) event.getResource();
402
403 if ( documentPart.getSkeleton() instanceof ZipSkeleton ) {
404 ZipSkeleton skeleton = (ZipSkeleton) documentPart.getSkeleton();
405
406 try {
407 zipOut.putNextEntry(new ZipEntry(skeleton.getEntry().getName()));
408 if (skeleton instanceof MarkupZipSkeleton) {
409 clarify(((MarkupZipSkeleton) skeleton).markup());
410 ((MarkupZipSkeleton) skeleton).to(zipOut);
411 } else {
412
413 final String modifiedContents = skeleton.getModifiedContents();
414 if (modifiedContents != null) {
415 zipOut.write(modifiedContents.getBytes(StandardCharsets.UTF_8));
416 } else {
417 copyFrom(document.inputStreamFor(skeleton.getEntry()));
418 }
419 }
420 zipOut.closeEntry();
421 }
422 catch (IOException | XMLStreamException e ) {
423 throw new OkapiIOException("Error writing zip file entry.");
424 }
425 }
426 else {
427 subDocWriter.handleEvent(event);
428 }
429 }
430
431 private void clarify(final Markup markup) throws XMLStreamException, IOException {
432 markup.apply(this.fontMappings);
433 Nameable nameableMarkupComponent = markup.nameableComponent();
434
435 if (null != nameableMarkupComponent) {
436 final MarkupClarificationConfiguration mcc = new MarkupClarificationConfiguration(
437 this.cparams,
438 this.eventFactory,
439 this.document.presetColorValues(),
440 this.document.highlightColorValues(),
441 this.document.systemColorValues(),
442 this.document.indexedColors(),
443 this.document.mainPartTheme(),
444 this.sourceLocale,
445 this.targetLocale,
446 this.dispersedTranslations
447 );
448 mcc.prepareFor(nameableMarkupComponent);
449 new MarkupClarification(mcc).performFor(markup);
450 }
451 }
452
453
454
455
456
457
458
459
460
461
462
463
464 private void processStartSubDocument (StartSubDocument res) {
465 ndxSubDoc++;
466
467
468 try {
469 tempFile = File.createTempFile("~okapi-25_zfwTmp"+ndxSubDoc+"_", null);
470
471 ISkeleton skel = res.getSkeleton();
472 ConditionalParameters conditionalParameters = (ConditionalParameters) res.getFilterParameters();
473 if (skel instanceof ZipSkeleton) {
474 subDocEntry = ((ZipSkeleton) res.getSkeleton()).getEntry();
475 if (document.isStyledTextPart(subDocEntry)) {
476 subSkelWriter = new StyledTextSkeletonWriter(
477 conditionalParameters,
478 this.eventFactory,
479 this.document.presetColorValues(),
480 this.document.highlightColorValues(),
481 this.document.systemColorValues(),
482 this.document.indexedColors(),
483 this.document.mainPartTheme(),
484 this.sourceLocale,
485 this.targetLocale,
486 this.dispersedTranslations,
487 this.fontMappings,
488 this.document.styleDefinitionsFor(this.subDocEntry),
489 innerGenericSkeletonWriter()
490 );
491 } else {
492 subSkelWriter = new GenericSkeletonWriter();
493 }
494 } else {
495 subDocEntry = new ZipEntry(res.getName());
496 subSkelWriter = new GenericSkeletonWriter();
497 }
498 } catch (IOException | XMLStreamException e) {
499 throw new OkapiIOException("Error opening temporary zip output file.");
500 }
501
502 subDocWriter = new GenericFilterWriter(subSkelWriter, getEncoderManager());
503 subDocWriter.setOptions(targetLocale, OpenXMLFilter.ENCODING.name());
504 subDocWriter.setOutput(tempFile.getAbsolutePath());
505
506 StartDocument sd = new StartDocument("sd");
507 sd.setLineBreak(OpenXMLFilter.LINE_BREAK);
508 sd.setSkeleton(res.getSkeleton());
509 sd.setLocale(sourceLocale);
510 subDocWriter.handleEvent(new Event(EventType.START_DOCUMENT, sd));
511 SubDocumentValues subDocumentValues = new SubDocumentValues(
512 subDocEntry,
513 subDocWriter,
514 subSkelWriter,
515 tempFile
516 );
517 tmSubDoc.put(ndxSubDoc, subDocumentValues);
518 }
519
520 private GenericSkeletonWriter innerGenericSkeletonWriter() {
521 return new GenericSkeletonWriter(
522 sourceLocale,
523 targetLocale,
524 getEncoderManager(),
525 false,
526 false,
527 new LinkedHashMap<>(),
528 new Stack<>(),
529 OpenXMLFilter.ENCODING.name(),
530 0,
531 null
532 );
533 }
534
535
536
537
538
539
540 private void processEndSubDocument (Ending res) {
541 try {
542 SubDocumentValues subDocumentValues = tmSubDoc.get(ndxSubDoc--);
543 subDocEntry = subDocumentValues.zipEntry;
544 subDocWriter = subDocumentValues.filterWriter;
545 subSkelWriter = subDocumentValues.skeletonWriter;
546 tempFile = subDocumentValues.tempFile;
547
548 subDocWriter.handleEvent(new Event(EventType.END_DOCUMENT, res));
549 subDocWriter.close();
550
551
552 zipOut.putNextEntry(new ZipEntry(subDocEntry.getName()));
553 copyFrom(new FileInputStream(tempFile));
554 zipOut.closeEntry();
555
556 tempFile.delete();
557 }
558 catch ( IOException e ) {
559 throw new OkapiIOException("Error closing zip output file.");
560 }
561 }
562
563 private void copyFrom(final InputStream inputStream) throws IOException {
564 int len;
565 while ((len = inputStream.read(buffer)) > 0) {
566 zipOut.write(buffer, 0, len);
567 }
568 inputStream.close();
569 }
570
571 @Override
572 public void setParameters(IParameters params)
573 {
574 this.cparams = (ConditionalParameters)params;
575 }
576
577 @Override
578 public ConditionalParameters getParameters()
579 {
580 return cparams;
581 }
582
583 private static class SubDocumentValues {
584 private final ZipEntry zipEntry;
585 private final IFilterWriter filterWriter;
586 private final ISkeletonWriter skeletonWriter;
587 private final File tempFile;
588
589 SubDocumentValues(
590 final ZipEntry zipEntry,
591 final IFilterWriter filterWriter,
592 final ISkeletonWriter skeletonWriter,
593 final File tempFile
594 ) {
595 this.zipEntry = zipEntry;
596 this.filterWriter = filterWriter;
597 this.skeletonWriter = skeletonWriter;
598 this.tempFile = tempFile;
599 }
600 }
601 }