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 net.sf.okapi.common.IdGenerator;
20 import net.sf.okapi.common.XMLEventsReader;
21 import net.sf.okapi.common.resource.TextFragment;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import javax.xml.namespace.QName;
26 import javax.xml.stream.XMLEventReader;
27 import javax.xml.stream.XMLStreamException;
28 import javax.xml.stream.events.Attribute;
29 import javax.xml.stream.events.Characters;
30 import javax.xml.stream.events.EndElement;
31 import javax.xml.stream.events.StartElement;
32 import javax.xml.stream.events.XMLEvent;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.Deque;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.Set;
42
43 import static net.sf.okapi.filters.openxml.XMLEventHelpers.LOCAL_REGULAR_HYPHEN_VALUE;
44 import static net.sf.okapi.filters.openxml.XMLEventHelpers.hasPreserveWhitespace;
45 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isEndElement;
46 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isGraphicsProperty;
47 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isLineBreakStartEvent;
48 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isNoBreakHyphenStartEvent;
49 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isPageBreak;
50 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isParagraphEndEvent;
51 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isParagraphPropertiesStartEvent;
52 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isParagraphStartEvent;
53 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isRunPropsStartEvent;
54 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isRunStartEvent;
55 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isTabStartEvent;
56 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isTextPath;
57 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isTextStartEvent;
58 import static net.sf.okapi.filters.openxml.XMLEventHelpers.isWhitespace;
59
60
61
62
63
64 class RunParser implements Parser<RunBuilder> {
65 private final Logger LOGGER = LoggerFactory.getLogger(getClass());
66
67 private static final String UNEXPECTED_STRUCTURE = "Unexpected structure: ";
68
69 private static final int COMPLEX_FIELD_INITIAL_NESTING_LEVEL = 1;
70
71 private static final QName COMPLEX_FIELD_CHARACTER_TYPE = Namespaces.WordProcessingML.getQName("fldCharType");
72 private static final String COMPLEX_FIELD_CHARACTER = "fldChar";
73 private static final String COMPLEX_FIELD_INSTRUCTION = "instrText";
74 private static final String BEGIN = "begin";
75 private static final String SEPARATE = "separate";
76 private static final String END = "end";
77 private static final String EMPTY = "";
78
79 private StartElementContext startElementContext;
80 private final IdGenerator nestedTextualIds;
81 private final StyleDefinitions styleDefinitions;
82 private final StyleOptimisation styleOptimisation;
83 private final NumberingDefinitions numberingDefinitions;
84 private final ContentCategoriesDetection contentCategoriesDetection;
85 private final String paragraphStyle;
86
87 private final RunBuilder runBuilder;
88
89 private final StrippableAttributes drawingRunPropertiesStrippableAttributes;
90 private final StrippableAttributes wordRunRevisions;
91 private final StrippableAttributes wordParagraphRevisions;
92
93 private final SkippableElements defaultSkippableElements;
94 private final SkippableElements blockPropertiesSkippableElements;
95 private final RunSkippableElements runSkippableElements;
96 private final BlockSkippableElements blockSkippableElements;
97
98 private int complexFieldNestingLevel;
99
100 RunParser(
101 final StartElementContext startElementContext,
102 final IdGenerator nestedTextualIds,
103 final StyleDefinitions styleDefinitions,
104 final StyleOptimisation styleOptimisation,
105 final NumberingDefinitions numberingDefinitions,
106 final ContentCategoriesDetection contentCategoriesDetection,
107 final String paragraphStyle,
108 final boolean hidden
109 ) {
110 this.drawingRunPropertiesStrippableAttributes = new StrippableAttributes.DrawingRunProperties(
111 startElementContext.getConditionalParameters(),
112 startElementContext.getEventFactory()
113 );
114 this.wordRunRevisions = new StrippableAttributes.WordRunRevisions(
115 startElementContext.getEventFactory()
116 );
117 this.wordParagraphRevisions = new StrippableAttributes.WordParagraphRevisions(
118 startElementContext.getEventFactory()
119 );
120
121 this.startElementContext = new StartElementContext(
122 this.wordRunRevisions.strip(startElementContext.getStartElement()),
123 startElementContext
124 );
125 this.nestedTextualIds = nestedTextualIds;
126 this.styleDefinitions = styleDefinitions;
127 this.styleOptimisation = styleOptimisation;
128 this.numberingDefinitions = numberingDefinitions;
129 this.contentCategoriesDetection = contentCategoriesDetection;
130 this.paragraphStyle = paragraphStyle;
131
132 runBuilder = new RunBuilder(this.startElementContext, styleDefinitions);
133 runBuilder.setHidden(hidden);
134
135 defaultSkippableElements = new SkippableElements.Default();
136 blockPropertiesSkippableElements = new SkippableElements.RevisionProperty(
137 new SkippableElements.Property(
138 new SkippableElements.Default(
139 SkippableElement.RunProperty.RUN_PROPERTY_RTL_DML,
140 SkippableElement.RunProperty.RUN_PROPERTY_LANGUAGE,
141 SkippableElement.RevisionProperty.RUN_PROPERTY_INSERTED_PARAGRAPH_MARK,
142 SkippableElement.RevisionProperty.RUN_PROPERTY_MOVED_PARAGRAPH_TO,
143 SkippableElement.RevisionProperty.RUN_PROPERTY_MOVED_PARAGRAPH_FROM,
144 SkippableElement.RevisionProperty.PARAGRAPH_PROPERTIES_CHANGE,
145 SkippableElement.RevisionProperty.RUN_PROPERTIES_CHANGE
146 ),
147 startElementContext.getConditionalParameters()
148 ),
149 startElementContext.getConditionalParameters()
150 );
151 runSkippableElements = new RunSkippableElements(startElementContext);
152 blockSkippableElements = new BlockSkippableElements(startElementContext);
153 }
154
155 public RunBuilder parse() throws XMLStreamException {
156 log("startRun: " + startElementContext.getStartElement());
157
158
159 XMLEvent firstChild = startElementContext.getEventReader().nextTag();
160 if (isRunPropsStartEvent(firstChild)) {
161 parseRunProperties(firstChild.asStartElement());
162 } else if (isEndElement(firstChild, startElementContext.getStartElement())) {
163
164 return endRunParsing(firstChild.asEndElement());
165 } else {
166
167 startRunParsing(firstChild);
168 if ((paragraphStyle == null || paragraphStyle.isEmpty()) &&
169 !startElementContext.getConditionalParameters().getTranslateWordInExcludeStyleMode() && !runBuilder.isHidden()) {
170 runBuilder.setHidden(true);
171 }
172 if (!startElementContext.getConditionalParameters().getTranslateWordInExcludeHighlightMode() && !runBuilder.isHidden()) {
173 runBuilder.setHidden(true);
174 }
175 }
176 while (startElementContext.getEventReader().hasNext()) {
177 XMLEvent e = startElementContext.getEventReader().nextEvent();
178 log("processRun: " + e);
179 if (isEndElement(e, startElementContext.getStartElement())) {
180 return endRunParsing(e.asEndElement());
181 } else {
182 startRunParsing(e);
183 }
184 }
185 throw new IllegalStateException("Invalid content? Unterminated run");
186 }
187
188 private RunBuilder endRunParsing(EndElement e) {
189 runBuilder.flushText();
190 runBuilder.flushMarkup();
191 this.runBuilder.setEndEvent(e);
192 clarifyRunProperties();
193 return this.runBuilder;
194 }
195
196 private void clarifyRunProperties() {
197 final Set<QName> skippableProperties = new HashSet<>();
198 if (this.startElementContext.getConditionalParameters().getCleanupAggressively()) {
199
200
201
202
203
204
205 if (!this.runBuilder.nonWhitespaceTextPresent()) {
206 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_VERTICAL_ALIGNMENT_WPML.toName());
207 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_VERTICAL_ALIGNMENT_SML.toName());
208 }
209 }
210 final String runText = this.runBuilder.firstRunText();
211 final RunProperties preCombinedRunProperties = this.runBuilder.preCombinedRunProperties(this.paragraphStyle);
212 final RunProperties runProperties = this.runBuilder.getRunProperties();
213 final RunFonts runFonts;
214 if (runProperties.contains(RunFonts.NAME)) {
215 runFonts = runProperties.fontsRunProperty().getRunFonts();
216 } else {
217 runFonts = new RunFonts(preCombinedRunProperties.fontsRunProperty().getRunFonts());
218 }
219 if (!runText.isEmpty()) {
220 this.contentCategoriesDetection.performFor(runFonts, runText);
221 }
222 this.runBuilder.detectedRunFonts(runFonts);
223 if (!runFonts.containsDetectedNonComplexScriptContentCategories()) {
224 if (canBeSkipped(SkippableElement.RunProperty.RUN_PROPERTY_BOLD.toName(), preCombinedRunProperties, runProperties)) {
225 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_BOLD.toName());
226 }
227 if (canBeSkipped(SkippableElement.RunProperty.RUN_PROPERTY_ITALICS.toName(), preCombinedRunProperties, runProperties)) {
228 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_ITALICS.toName());
229 }
230 if (canBeSkipped(SkippableElement.RunProperty.RUN_PROPERTY_FONT_SIZE.toName(), preCombinedRunProperties, runProperties)) {
231 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_FONT_SIZE.toName());
232 }
233 }
234 if (!runFonts.containsDetectedComplexScriptContentCategories()) {
235 if (canBeSkipped(SkippableElement.RunProperty.RUN_PROPERTY_COMPLEX_SCRIPT_BOLD.toName(), preCombinedRunProperties, runProperties)) {
236 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_COMPLEX_SCRIPT_BOLD.toName());
237 }
238 if (canBeSkipped(SkippableElement.RunProperty.RUN_PROPERTY_COMPLEX_SCRIPT_ITALICS.toName(), preCombinedRunProperties, runProperties)) {
239 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_COMPLEX_SCRIPT_ITALICS.toName());
240 }
241 if (canBeSkipped(SkippableElement.RunProperty.RUN_PROPERTY_COMPLEX_SCRIPT_FONT_SIZE.toName(), preCombinedRunProperties, runProperties)) {
242 skippableProperties.add(SkippableElement.RunProperty.RUN_PROPERTY_COMPLEX_SCRIPT_FONT_SIZE.toName());
243 }
244 }
245 if (!skippableProperties.isEmpty()) {
246 this.runBuilder.setRunProperties(runProperties.filteredBy(p -> !skippableProperties.contains(p.getName())));
247 }
248 this.runBuilder.refineCombinedRunProperties(this.paragraphStyle, runFonts);
249 }
250
251 private static boolean canBeSkipped(
252 final QName name,
253 final RunProperties preCombinedRunProperties,
254 final RunProperties runProperties
255 ) {
256 final boolean v;
257 if (preCombinedRunProperties.contains(name) && runProperties.contains(name)) {
258 final RunProperty pcrp = (RunProperty) preCombinedRunProperties.filteredBy(p -> p.getName().equals(name)).properties().get(0);
259 final RunProperty rp = (RunProperty) runProperties.filteredBy(p -> p.getName().equals(name)).properties().get(0);
260 v = pcrp.equals(rp);
261 } else {
262 v = true;
263 }
264 return v;
265 }
266
267 private void startRunParsing(final XMLEvent e) throws XMLStreamException {
268 if (isParagraphStartEvent(e)) {
269 parseNestedBlock(e.asStartElement());
270 }
271
272 else if (isComplexFieldBegin(e)) {
273 parseComplexField(e.asStartElement());
274 this.runBuilder.setContainsComplexFields(true);
275 } else {
276 if (!parseSkippableElements(e)) {
277 parseContent(processTranslatableAttributes(e), startElementContext.getEventReader());
278 }
279 }
280 }
281
282 private void parseRunProperties(StartElement startElement) throws XMLStreamException {
283 StartElementContext runPropertiesElementContext = new StartElementContext(startElement, runBuilder.getStartElementContext());
284 final RunProperties direct = new RunPropertiesParser(runPropertiesElementContext, runSkippableElements).parse();
285 final String runStyle = null == direct.getRunStyleProperty()
286 ? null
287 : direct.getRunStyleProperty().value();
288 runBuilder.setRunProperties(
289 direct.minified(
290 this.styleDefinitions.combinedRunProperties(
291 this.paragraphStyle,
292 runStyle,
293 new RunProperties.Default(
294 runPropertiesElementContext.getEventFactory(),
295 startElement.getName().getPrefix(),
296 startElement.getName().getNamespaceURI(),
297 startElement.getName().getLocalPart()
298 )
299 )
300 )
301 );
302
303 if (runStyle != null) {
304 runBuilder.setRunStyle(runStyle);
305 if (startElementContext.getConditionalParameters().getTranslateWordInExcludeStyleMode()) {
306 runBuilder.setHidden(startElementContext.getConditionalParameters().isWordExcludedStyle(runStyle));
307 } else {
308 runBuilder.setHidden(!startElementContext.getConditionalParameters().isWordExcludedStyle(runStyle));
309 }
310 }
311
312 RunProperty.HighlightRunProperty highlightRunProperty = runBuilder.getRunProperties().getHighlightProperty();
313 if (highlightRunProperty != null) {
314 if (startElementContext.getConditionalParameters().getTranslateWordInExcludeHighlightMode() && !runBuilder.isHidden()) {
315 runBuilder.setHidden(startElementContext.getConditionalParameters().isWordHighlight(highlightRunProperty.asColorValue()));
316 } else {
317 runBuilder.setHidden(!startElementContext.getConditionalParameters().isWordHighlight(highlightRunProperty.asColorValue()));
318 }
319 }
320
321 RunProperty.ColorRunProperty colorRunProperty = runBuilder.getRunProperties().getColorRunProperty();
322 if (colorRunProperty != null) {
323 if (startElementContext.getConditionalParameters().getTranslateWordExcludeColors() && !runBuilder.isHidden()) {
324 runBuilder.setHidden(startElementContext.getConditionalParameters().isWordExcludedColor(colorRunProperty.asColorValue()));
325 }
326 if (startElementContext.getConditionalParameters().getTranslateExcelExcludeColors() && !runBuilder.isHidden()) {
327 runBuilder.setHidden(startElementContext.getConditionalParameters().isExcelExcludedColor(colorRunProperty.asColorValue()));
328 }
329 }
330 }
331
332 private void parseNestedBlock(final StartElement startElement) throws XMLStreamException {
333 log("Nested block start event: " + startElement);
334 runBuilder.flushText();
335 StartElementContext blockElementContext = new StartElementContext(startElement, startElementContext);
336
337 final StyleDefinitions styleDefinitions;
338 final StyleOptimisation styleOptimisation;
339 final ContentCategoriesDetection contentCategoriesDetection;
340 if (Namespace.PREFIX_A.equals(startElement.getName().getPrefix())) {
341 styleDefinitions = new StyleDefinitions.Empty();
342 styleOptimisation = new StyleOptimisation.Default(
343 new StyleOptimisation.Bypass(),
344 this.startElementContext.getConditionalParameters(),
345 this.startElementContext.getEventFactory(),
346 this.startElementContext.presetColorValues(),
347 this.startElementContext.highlightColorValues(),
348 this.startElementContext.systemColorValues(),
349 this.startElementContext.indexedColors(),
350 this.startElementContext.theme(),
351 new QName(startElement.getName().getNamespaceURI(), ParagraphBlockProperties.PPR, startElement.getName().getPrefix()),
352 new QName(startElement.getName().getNamespaceURI(), RunProperties.DEF_RPR, startElement.getName().getPrefix()),
353 Collections.emptyList(),
354 styleDefinitions
355 );
356 contentCategoriesDetection = new ContentCategoriesDetection.NonApplicable();
357 } else {
358 styleDefinitions = runBuilder.getStyleDefinitions();
359 styleOptimisation = this.styleOptimisation;
360 contentCategoriesDetection = this.contentCategoriesDetection;
361 }
362 BlockParser nestedBlockParser = new BlockParser(
363 blockElementContext,
364 nestedTextualIds,
365 styleDefinitions,
366 styleOptimisation,
367 numberingDefinitions,
368 contentCategoriesDetection
369 );
370 Block nested = nestedBlockParser.parse();
371 clarifyStartElementContextWith(nested.deferredEvents());
372 nested.optimiseStyles();
373 runBuilder.setContainsNestedItems(true);
374 if (nested.hasVisibleRunContent()) {
375
376 runBuilder.addToMarkup(startElementContext.getEventFactory().createCharacters(
377 TextFragment.makeRefMarker(nestedTextualIds.createId())));
378 runBuilder.getNestedTextualItems().add(nested);
379 } else {
380
381 for (XMLEvent nestedEvent : nested.getEvents()) {
382 runBuilder.addToMarkup(nestedEvent);
383 }
384
385 for (Chunk chunk : nested.getChunks()) {
386 if (chunk instanceof Run) {
387 runBuilder.getNestedTextualItems().addAll(((Run) chunk).getNestedTextualItems());
388 } else if (chunk instanceof RunContainer) {
389 for (Chunk nestedChunk : ((RunContainer) chunk).getChunks()) {
390 if (nestedChunk instanceof Run) {
391 runBuilder.getNestedTextualItems().addAll(((Run) nestedChunk).getNestedTextualItems());
392 }
393 }
394 }
395 }
396 }
397 }
398
399 private void clarifyStartElementContextWith(final Collection<XMLEvent> deferredEvents) {
400 if (!deferredEvents.isEmpty()) {
401 this.startElementContext = new StartElementContext(
402 this.startElementContext.getStartElement(),
403 this.startElementContext.getParentStartElement(),
404 new PrioritisedXMLEventReader(
405 new ConsumableXMLEventsReader(new XMLEventsReader(deferredEvents)),
406 this.startElementContext.getEventReader()
407 ),
408 this.startElementContext.presetColorValues(),
409 this.startElementContext.highlightColorValues(),
410 this.startElementContext.systemColorValues(),
411 this.startElementContext.indexedColors(),
412 this.startElementContext.theme(),
413 this.startElementContext.getEventFactory(),
414 this.startElementContext.getConditionalParameters()
415 );
416 }
417 }
418
419 private void parseComplexField(final StartElement startElement) throws XMLStreamException {
420 String code = EMPTY;
421 boolean extractable = false;
422 boolean atComplexFieldResult = false;
423 final Deque<XMLEvent> deferredEvents = new LinkedList<>();
424
425 this.complexFieldNestingLevel++;
426 parseComplexFieldBegin(startElement);
427
428 while (startElementContext.getEventReader().hasNext()) {
429 final XMLEvent e = startElementContext.getEventReader().nextEvent();
430 if (isComplexFieldEnd(e)) {
431 if (!extractable) {
432 parseSimpleElement(e.asStartElement());
433 } else {
434 endComplexFieldParsing(e.asStartElement(), deferredEvents);
435 }
436 this.complexFieldNestingLevel--;
437 return;
438 }
439 if (isComplexFieldInstruction(e)) {
440 final String prevCode = code;
441 code = parseComplexFieldCode(e.asStartElement());
442 if (prevCode.isEmpty()) {
443 extractable = startElementContext.getConditionalParameters().tsComplexFieldDefinitionsToExtract.contains(code);
444 }
445 continue;
446 }
447 if (isComplexFieldSeparate(e)) {
448 parseSimpleElement(e.asStartElement());
449 atComplexFieldResult = true;
450 continue;
451 }
452 if (isComplexFieldBegin(e)) {
453 if (extractable && !deferredEvents.isEmpty()) {
454 parseContentFrom(deferredEvents);
455 }
456 parseComplexField(e.asStartElement());
457 continue;
458 }
459 if (!extractable || !atComplexFieldResult) {
460 if (parseSkippableElements(e)) {
461 continue;
462 }
463 this.runBuilder.addToMarkup(stripRevisionAttributes(e));
464 continue;
465 }
466 if (isParagraphEndEvent(e)) {
467
468 if (!deferredEvents.isEmpty()) {
469 parseContentFrom(deferredEvents);
470 }
471 deferredEvents.add(e);
472 continue;
473 }
474 if (!deferredEvents.isEmpty()) {
475 if (isParagraphPropertiesStartEvent(e)) {
476 StartElementContext blockPropertiesElementContext = new StartElementContext(
477 e.asStartElement(),
478 this.startElementContext
479 );
480 final ParagraphBlockProperties blockProperties = new MarkupComponentParser()
481 .parseParagraphBlockProperties(
482 blockPropertiesElementContext,
483 this.drawingRunPropertiesStrippableAttributes,
484 this.blockPropertiesSkippableElements
485 );
486 deferredEvents.addAll(blockProperties.getEvents());
487 } else {
488 if (parseSkippableElements(e)) {
489 continue;
490 }
491 deferredEvents.add(e);
492 }
493 continue;
494 }
495 parseContent(
496 processTranslatableAttributes(stripRevisionAttributes(e)),
497 startElementContext.getEventReader()
498 );
499 }
500 }
501
502 private static boolean isComplexFieldBegin(XMLEvent e) {
503 return XMLEventHelpers.isStartElement(e, COMPLEX_FIELD_CHARACTER)
504 && BEGIN.equals(XMLEventHelpers.getAttributeValue(e.asStartElement(), COMPLEX_FIELD_CHARACTER_TYPE));
505 }
506
507 private static boolean isComplexFieldInstruction(XMLEvent e) {
508 return XMLEventHelpers.isStartElement(e, COMPLEX_FIELD_INSTRUCTION);
509 }
510
511 private static boolean isComplexFieldSeparate(XMLEvent e) {
512 return XMLEventHelpers.isStartElement(e, COMPLEX_FIELD_CHARACTER)
513 && SEPARATE.equals(XMLEventHelpers.getAttributeValue(e.asStartElement(), COMPLEX_FIELD_CHARACTER_TYPE));
514 }
515
516 private static boolean isComplexFieldEnd(XMLEvent e) {
517 return XMLEventHelpers.isStartElement(e, COMPLEX_FIELD_CHARACTER)
518 && END.equals(XMLEventHelpers.getAttributeValue(e.asStartElement(), COMPLEX_FIELD_CHARACTER_TYPE));
519 }
520
521 private void parseComplexFieldBegin(final StartElement startElement) throws XMLStreamException {
522 this.runBuilder.addToMarkup(startElement);
523 while (this.startElementContext.getEventReader().hasNext()) {
524 final XMLEvent event = this.startElementContext.getEventReader().nextEvent();
525 this.runBuilder.addToMarkup(event);
526 if (event.isEndElement() && event.asEndElement().getName().equals(startElement.getName())) {
527 return;
528 }
529 }
530 throw new IllegalStateException(UNEXPECTED_STRUCTURE + startElement);
531 }
532
533 private void parseSimpleElement(final StartElement startElement) throws XMLStreamException {
534 this.runBuilder.addToMarkup(startElement);
535 final XMLEvent event = this.startElementContext.getEventReader().nextEvent();
536
537 if (!event.isEndElement() || !event.asEndElement().getName().equals(startElement.getName())) {
538 throw new IllegalStateException(UNEXPECTED_STRUCTURE + event);
539 }
540 this.runBuilder.addToMarkup(event);
541 }
542
543 private boolean parseSkippableElements(final XMLEvent e) throws XMLStreamException {
544 if (runSkippableElements.skip(e)) {
545 return true;
546 } else if (blockSkippableElements.skip(e)) {
547 return true;
548 }
549 return false;
550 }
551
552 private void endComplexFieldParsing(final StartElement startElement, final Deque<XMLEvent> deferredEvents) throws XMLStreamException {
553 if (goesAfterAnotherRun(deferredEvents)) {
554
555 parseContentFrom(deferredEvents);
556 parseSimpleElement(startElement);
557 } else {
558 final Collection<XMLEvent> subtractedEvents = subtractAllFromLastOpeningRun(deferredEvents);
559 parseContentFrom(subtractedEvents);
560 parseSimpleElement(startElement);
561 if (COMPLEX_FIELD_INITIAL_NESTING_LEVEL == this.complexFieldNestingLevel) {
562 this.runBuilder.addDeferredEvents(deferredEvents);
563 } else {
564 parseToFirstClosingRun();
565 parseContentFrom(deferredEvents);
566 }
567 }
568 }
569
570 private boolean goesAfterAnotherRun(final Deque<XMLEvent> deferredEvents) {
571 final Iterator<XMLEvent> deferredEventsIterator = deferredEvents.descendingIterator();
572
573 while (deferredEventsIterator.hasNext()) {
574 final XMLEvent event = deferredEventsIterator.next();
575
576 if (event.isEndElement() && XMLEventHelpers.LOCAL_RUN.equals(event.asEndElement().getName().getLocalPart())) {
577 return true;
578 }
579 }
580
581 return false;
582 }
583
584 private void parseContentFrom(final Collection<XMLEvent> deferredEvents) throws XMLStreamException {
585 final XMLEventReader eventReader = new ConsumableXMLEventsReader(new XMLEventsReader(deferredEvents));
586 while (eventReader.hasNext()) {
587 parseContent(
588 processTranslatableAttributes(stripRevisionAttributes(eventReader.nextEvent())),
589 eventReader
590 );
591 }
592 }
593
594 private Deque<XMLEvent> subtractAllFromLastOpeningRun(final Deque<XMLEvent> deferredEvents) {
595 final Deque<XMLEvent> subtraction = new LinkedList<>();
596 final Iterator<XMLEvent> deferredEventsIterator = deferredEvents.descendingIterator();
597
598 while (deferredEventsIterator.hasNext()) {
599 final XMLEvent event = deferredEventsIterator.next();
600 subtraction.addFirst(event);
601 deferredEventsIterator.remove();
602 if (isRunStartEvent(event)) {
603 break;
604 }
605 }
606
607 return subtraction;
608 }
609
610 private void parseToFirstClosingRun() throws XMLStreamException {
611 while (this.startElementContext.getEventReader().hasNext()) {
612 final XMLEvent event = this.startElementContext.getEventReader().nextEvent();
613 parseContent(
614 processTranslatableAttributes(stripRevisionAttributes(event)),
615 this.startElementContext.getEventReader()
616 );
617 if (event.isEndElement()
618 && XMLEventHelpers.LOCAL_RUN.equals(event.asEndElement().getName().getLocalPart())) {
619 return;
620 }
621 }
622 }
623
624 private String parseComplexFieldCode(final StartElement startElement) throws XMLStreamException {
625 this.runBuilder.addToMarkup(startElement);
626 String codeName = EMPTY;
627 while (this.startElementContext.getEventReader().hasNext()) {
628 final XMLEvent event = this.startElementContext.getEventReader().nextEvent();
629 this.runBuilder.addToMarkup(event);
630
631 if (event.isEndElement() && event.asEndElement().getName().equals(startElement.getName())) {
632 return codeName;
633 }
634
635 if (codeName.isEmpty() && event.isCharacters()) {
636 codeName = complexFieldCodeName(event.asCharacters());
637 }
638 }
639 throw new IllegalStateException(UNEXPECTED_STRUCTURE + startElement);
640 }
641
642 private String complexFieldCodeName(final Characters characters) {
643
644 String data = characters.getData().trim();
645 int fieldCodeNameLength = data.indexOf(" ");
646
647 String fieldCodeName;
648 if (fieldCodeNameLength > 0) {
649 fieldCodeName = data.substring(0, fieldCodeNameLength);
650 } else {
651 fieldCodeName = data;
652 }
653 return fieldCodeName;
654 }
655
656 private XMLEvent stripRevisionAttributes(XMLEvent e) {
657 if (isParagraphStartEvent(e)) {
658 return wordParagraphRevisions.strip(e.asStartElement());
659 } else if (isRunStartEvent(e)) {
660 return wordRunRevisions.strip(e.asStartElement());
661 } else {
662 return e;
663 }
664 }
665
666 private void parseContent(final XMLEvent e, final XMLEventReader eventReader) throws XMLStreamException {
667
668 if (isTextStartEvent(e)) {
669
670 runBuilder.flushMarkup();
671 parseText(e.asStartElement(), eventReader);
672
673 } else if (startElementContext.getConditionalParameters().getAddTabAsCharacter() && isTabStartEvent(e) && !runBuilder.isHidden()) {
674
675 runBuilder.flushMarkup();
676 runBuilder.addText("\t");
677 defaultSkippableElements.skip(new StartElementContext(
678 e.asStartElement(),
679 eventReader,
680 startElementContext.presetColorValues(),
681 startElementContext.highlightColorValues(),
682 startElementContext.systemColorValues(),
683 startElementContext.indexedColors(),
684 startElementContext.theme(),
685 null,
686 startElementContext.getConditionalParameters()
687 ));
688
689 } else if (startElementContext.getConditionalParameters().getAddLineSeparatorCharacter() && isLineBreakStartEvent(e) && !isPageBreak(e.asStartElement()) && !runBuilder.isHidden()) {
690
691 runBuilder.flushMarkup();
692 char replacement = startElementContext.getConditionalParameters().getLineSeparatorReplacement();
693 runBuilder.addText(String.valueOf(replacement));
694 defaultSkippableElements.skip(new StartElementContext(
695 e.asStartElement(),
696 eventReader,
697 startElementContext.presetColorValues(),
698 startElementContext.highlightColorValues(),
699 startElementContext.systemColorValues(),
700 startElementContext.indexedColors(),
701 startElementContext.theme(),
702 null,
703 startElementContext.getConditionalParameters()
704 ));
705
706 } else if (startElementContext.getConditionalParameters().getReplaceNoBreakHyphenTag() && isNoBreakHyphenStartEvent(e)) {
707
708 runBuilder.flushMarkup();
709 runBuilder.addText(LOCAL_REGULAR_HYPHEN_VALUE);
710 defaultSkippableElements.skip(new StartElementContext(
711 e.asStartElement(),
712 eventReader,
713 startElementContext.presetColorValues(),
714 startElementContext.highlightColorValues(),
715 startElementContext.systemColorValues(),
716 startElementContext.indexedColors(),
717 startElementContext.theme(),
718 null,
719 startElementContext.getConditionalParameters()
720 ));
721
722 } else if (!isWhitespace(e) || runBuilder.preservingWhitespace()) {
723
724
725
726
727 runBuilder.flushText();
728 runBuilder.setTextPreservingWhitespace(false);
729
730 if (XMLEventHelpers.isParagraphStartEvent(e)) {
731 runBuilder.addToMarkup(
732 new MarkupComponent.StyledStart(
733 startElementContext.getEventFactory(),
734 styleDefinitions,
735 e.asStartElement()
736 )
737 );
738 } else if (XMLEventHelpers.isParagraphPropertiesStartEvent(e)) {
739 runBuilder.addToMarkup(
740 new MarkupComponentParser().parseParagraphBlockProperties(
741 new StartElementContext(
742 e.asStartElement(),
743 eventReader,
744 startElementContext.presetColorValues(),
745 startElementContext.highlightColorValues(),
746 startElementContext.systemColorValues(),
747 startElementContext.indexedColors(),
748 startElementContext.theme(),
749 startElementContext.getEventFactory(),
750 startElementContext.getConditionalParameters()
751 ),
752 this.drawingRunPropertiesStrippableAttributes,
753 this.blockPropertiesSkippableElements
754 )
755 );
756 } else if (XMLEventHelpers.isParagraphEndEvent(e)) {
757 runBuilder.addToMarkup(
758 MarkupComponentFactory.createEndMarkupComponent(
759 e.asEndElement()
760 )
761 );
762 } else if (!parseSkippableElements(e)) {
763 runBuilder.addToMarkup(e);
764 }
765 }
766 }
767
768 private void parseText(StartElement startEvent, XMLEventReader events) throws XMLStreamException {
769
770 runBuilder.setTextPreservingWhitespace(runBuilder.isTextPreservingWhitespace() || hasPreserveWhitespace(startEvent));
771
772 while (events.hasNext()) {
773 XMLEvent e = events.nextEvent();
774 if (isEndElement(e, startEvent)) {
775 return;
776 } else if (e.isCharacters()) {
777 String text = e.asCharacters().getData();
778 if (text.trim().length() > 0) {
779 runBuilder.nonWhitespaceTextPresent(true);
780 }
781 runBuilder.addText(text);
782 }
783 }
784 }
785
786
787
788
789 private XMLEvent processTranslatableAttributes(XMLEvent e) {
790 if (!e.isStartElement()) return e;
791 StartElement startEl = e.asStartElement();
792
793
794
795 if (isGraphicsProperty(startEl) && !startElementContext.getConditionalParameters().getTranslateWordExcludeGraphicMetaData()) {
796 startEl = processTranslatableAttribute(startEl, "name");
797 startEl = processTranslatableAttribute(startEl, "title");
798 startEl = processTranslatableAttribute(startEl, "descr");
799 } else if (isTextPath(startEl)) {
800 startEl = processTranslatableAttribute(startEl, "string");
801 }
802 return startEl;
803 }
804
805 private StartElement processTranslatableAttribute(StartElement startEl, String attrName) {
806 List<Attribute> newAttrs = new ArrayList<>();
807 Iterator<?> it = startEl.getAttributes();
808 boolean dirty = false;
809 while (it.hasNext()) {
810 Attribute a = (Attribute) it.next();
811 if (a.getName().getLocalPart().equals(attrName)) {
812 runBuilder.setContainsNestedItems(true);
813 runBuilder.getNestedTextualItems().add(new TranslatableAttributeText(a.getValue()));
814 newAttrs.add(startElementContext.getEventFactory().createAttribute(a.getName(),
815 TextFragment.makeRefMarker(nestedTextualIds.createId())));
816 dirty = true;
817 } else {
818 newAttrs.add(a);
819 }
820 }
821 return dirty ?
822 startElementContext.getEventFactory().createStartElement(startEl.getName(), newAttrs.iterator(), startEl.getNamespaces()) :
823 startEl;
824 }
825
826 private void log(String s) {
827 LOGGER.debug(s);
828 }
829 }