All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.okapi.lib.xliff2.writer.XLIFFWriter Maven / Gradle / Ivy

/*===========================================================================
  Copyright (C) 2011-2014 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
===========================================================================*/

package net.sf.okapi.lib.xliff2.writer;

import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.xml.namespace.NamespaceContext;

import net.sf.okapi.lib.xliff2.Const;
import net.sf.okapi.lib.xliff2.InvalidParameterException;
import net.sf.okapi.lib.xliff2.NSContext;
import net.sf.okapi.lib.xliff2.Util;
import net.sf.okapi.lib.xliff2.changeTracking.ChangeTrack;
import net.sf.okapi.lib.xliff2.changeTracking.Item;
import net.sf.okapi.lib.xliff2.changeTracking.Revision;
import net.sf.okapi.lib.xliff2.changeTracking.Revisions;
import net.sf.okapi.lib.xliff2.core.Directionality;
import net.sf.okapi.lib.xliff2.core.Fragment;
import net.sf.okapi.lib.xliff2.core.IExtChild;
import net.sf.okapi.lib.xliff2.core.IWithChangeTrack;
import net.sf.okapi.lib.xliff2.core.IWithExtAttributes;
import net.sf.okapi.lib.xliff2.core.IWithExtElements;
import net.sf.okapi.lib.xliff2.core.IWithMetadata;
import net.sf.okapi.lib.xliff2.core.IWithNotes;
import net.sf.okapi.lib.xliff2.core.IWithValidation;
import net.sf.okapi.lib.xliff2.core.InheritedData;
import net.sf.okapi.lib.xliff2.core.MidFileData;
import net.sf.okapi.lib.xliff2.core.Note;
import net.sf.okapi.lib.xliff2.core.Notes;
import net.sf.okapi.lib.xliff2.core.Part;
import net.sf.okapi.lib.xliff2.core.Part.GetTarget;
import net.sf.okapi.lib.xliff2.core.Segment;
import net.sf.okapi.lib.xliff2.core.Skeleton;
import net.sf.okapi.lib.xliff2.core.StartFileData;
import net.sf.okapi.lib.xliff2.core.StartGroupData;
import net.sf.okapi.lib.xliff2.core.StartXliffData;
import net.sf.okapi.lib.xliff2.core.Store;
import net.sf.okapi.lib.xliff2.core.Unit;
import net.sf.okapi.lib.xliff2.glossary.GlossEntry;
import net.sf.okapi.lib.xliff2.glossary.Translation;
import net.sf.okapi.lib.xliff2.its.AnnotatorsRef;
import net.sf.okapi.lib.xliff2.its.DataCategories;
import net.sf.okapi.lib.xliff2.its.ITSWriter;
import net.sf.okapi.lib.xliff2.matches.Match;
import net.sf.okapi.lib.xliff2.metadata.IMetadataItem;
import net.sf.okapi.lib.xliff2.metadata.Meta;
import net.sf.okapi.lib.xliff2.metadata.MetaGroup;
import net.sf.okapi.lib.xliff2.metadata.Metadata;
import net.sf.okapi.lib.xliff2.reader.Event;
import net.sf.okapi.lib.xliff2.validation.Rule;
import net.sf.okapi.lib.xliff2.validation.Rule.Normalization;
import net.sf.okapi.lib.xliff2.validation.Rule.Type;
import net.sf.okapi.lib.xliff2.validation.Validation;

/**
 * Provides the methods to create an XLIFF document or to re-write an existing one.
 */
public class XLIFFWriter implements Closeable {

	private PrintWriter writer = null;
    private String lb = System.getProperty("line.separator");
    private boolean useIndentation = false;
    private boolean useInsignificantParts = false;
    private boolean indentNonUnit = (useIndentation && !useInsignificantParts);
    private String indent;
    private String nonUnitLb;
    private boolean inDocument;
    private boolean inFile;
    private StartFileData startFileData;
    private boolean withData = true;
    private String sourceLang;
    private String targetLang;
    private Stack nsStack;
    private Stack context;
    private Stack groupStack;
    private int autoFileId;
    private int autoGroupId;
    private ITSWriter itsWriter;
    private ExtensionsWriter extWriter;

    /**
     * Creates a new document with a given file, with a source language and a target language.
     * @param file the file where to create this document. If needed directories will be created
     * automatically to create this file.
     * @param sourceLang the source language for this document (can be null if set later).
     * @param targetLang the target language for this document (can be null).
     * @throws XLIFFWriterException if an error occurs. 
     */
    public void create (File file,
    	String sourceLang,
    	String targetLang)
    {
		try {
			// Create the directories if needed
			File dir = file.getParentFile();
			if ( dir != null ) {
				if ( !dir.exists() ) {
					if ( !dir.mkdirs() ) {
						throw new XLIFFWriterException("Could not create one or more directories for "+file);
					}
				}
			}
			// Create the file
			create(new OutputStreamWriter(
				new BufferedOutputStream(new FileOutputStream(file)), StandardCharsets.UTF_8),
				sourceLang, targetLang);
		}
		catch ( FileNotFoundException e ) {
			throw new XLIFFWriterException(String.format("Cannot create the document (%s).", file), e);
		}
    }

    /**
     * Creates a new XLIFF document
     * @param file the output file where to write this document.
     * @param sourceLang the source language for this document (can be null if set later).
     */
    public void create (File file,
    	String sourceLang)
    {
    	create(file, sourceLang, null);
    }
    
    /**
     * Creates a new XLIFF document.
     * @param output the writer to use to output this document.
     * @param sourceLang the source language for this document (can be null if set later).
     * @param targetLang the target language for this document (can be null).
     */
    public void create (Writer output,
    	String sourceLang,
    	String targetLang)
    {
    	this.sourceLang = sourceLang;
    	this.targetLang = targetLang;
    	writer = new PrintWriter(output);
		indent = "";
		nonUnitLb = (useInsignificantParts ? "" : lb);
		inFile = false;
		inDocument = false;
		autoFileId = 0;
		autoGroupId = 0;
		itsWriter = new ITSWriter();
		
		nsStack = new Stack<>();
		nsStack.push(new NSContext("", Const.NS_XLIFF_CORE20));
//System.out.println("pushINIT:"+nsStack.peek().toString());

		groupStack = new Stack<>();
	}
    
    /**
     * Creates a new document for a given writer and a source language.
     * @param output the writer to use to output this document.
     * @param sourceLang the source language for this document (cannot be null or empty).
     */
    public void create (Writer output,
    	String sourceLang)
    {
    	create(output, sourceLang, null);
    }

    /**
     * Sets the flag indicating whether or not to output original data entries.
     * @param withOriginalData true to output the original data, false otherwise.
     */
    public void setWithOriginalData (boolean withOriginalData) {
   		this.withData = withOriginalData;
    }
    
    /**
     * Gets the flag indicating whether or not to output original data entries.
     * @return true if the output is done with the original data, false otherwise.
     */
    public boolean getWithOriginalData () {
    	return withData;
    }
    
    /**
     * Sets the line break to use for this document.
     * You must set this before calling any of the create() methods.
     * By default the line-break used is the one of the OS.
     * @param lineBreak the line break to use for this document.
     */
    public void setLineBreak (String lineBreak) {
    	lb = lineBreak;
    	nonUnitLb = (useInsignificantParts ? "" : lb);    	
    }
    
    /**
     * Gets the line break to use for this document.
     * @return the line break to use for this document.
     */
    public String getLineBreak () {
    	return lb;
    }
    
    /**
     * Sets whether or not indentations should be used for this document.
     * @param useIndentation true to use indentation, false to not use indentations.
     */
    public void setUseIndentation (boolean useIndentation) {
    	this.useIndentation = useIndentation;
    	indentNonUnit = (useIndentation && !useInsignificantParts);
    	nonUnitLb = (useInsignificantParts ? "" : lb);
    }
    
    public void setUseInsignificantParts (boolean useInsignificantParts) {
    	this.useInsignificantParts = useInsignificantParts;
    	indentNonUnit = (useIndentation && !useInsignificantParts);
    	nonUnitLb = (useInsignificantParts ? "" : lb);
    }

    /**
     * Closes the output document.
     * 

If {@link #writeEndDocument()} has not been called, it is called automatically. */ @Override public void close () { if ( writer != null ) { if ( inDocument ) { writeEndDocument(); } writer.close(); writer = null; } } /** * Writes a given event. * @param event the event to output. */ public void writeEvent (Event event) { switch ( event.getType() ) { case START_XLIFF: StartXliffData dd = event.getStartXliffData(); sourceLang = dd.getSourceLanguage(); if (( dd.getTargetLanguage() == null ) && ( targetLang != null )) { dd.setTargetLanguage(targetLang); } else { targetLang = dd.getTargetLanguage(); } writeStartDocument(dd, null); break; case START_FILE: writeStartFile(event.getStartFileData()); break; case SKELETON: writeSkeleton(event.getSkeletonData()); break; case MID_FILE: writeMidFile(event.getMidFileData()); break; case START_GROUP: writeStartGroup(event.getStartGroupData()); break; case TEXT_UNIT: writeUnit(event.getUnit()); break; case END_GROUP: writeEndGroup(); break; case END_FILE: writeEndFile(); break; case END_XLIFF: writeEndDocument(); break; case INSIGNIFICANT_PART: if ( useInsignificantParts ) { writeText(event.getInsingnificantPartData().getData()); } break; case END_DOCUMENT: case START_DOCUMENT: break; } } /** * Writes a raw text, only \n is converted to the output line-break. * @param text the text to write. */ private void writeText (String text) { writer.print(text.replace("\n", lb)); } /** * Writes a <unit> element. *

If {@link #writeStartFile(StartFileData)} has not been called yet, it is called automatically. * @param unit the {@link Unit} object to write. */ public void writeUnit (Unit unit) { // Check if there is something to write if ( unit.getPartCount() == 0 ) { return; // Do not output the unit } // A unit must have at least one part if ( unit.getSegmentCount() == 0 ) { // create a default segment unit.appendSegment(); } if ( !inFile ) writeStartFile(null); // Start context.push(new InheritedData(unit)); // Namespace context nsStack.push(nsStack.peek().clone()); //System.out.println("push:"+nsStack.peek().toString()); writer.print(indent+" 1) parentAR = context.elementAt(context.size()-2).getAnnotatorsRef(); writer.print(itsWriter.outputAttributes(unit, unitAR, parentAR)); // Extension attributes writeExtAttributes(unit); writer.print(">"+lb); if ( useIndentation ) indent += " "; // Unit-level stand-off entries for ITS writer.print(itsWriter.outputStandOffElements(indent, lb, unit)); writeMatches(unit); writeGlossary(unit); writeMetadata(unit); writeValidation(unit); writeChangeTracking(unit); // Unit-level extension elements writeExtElements(unit); // Unit-level notes if needed writeNotes(unit); // Unit-level original data store if ( withData ) { writeOriginalData(unit.getStore()); } for ( Part part : unit ) { Segment seg = null; if ( part.isSegment() ) { seg = (Segment)part; writer.print(indent+"<"+Const.ELEM_SEGMENT); if ( seg.getId() != null ) { writer.print(" id=\"" + seg.getId() + "\""); } if ( seg.getCanResegment() != context.peek().getCanResegment() ) { writer.print(" " + Const.ATTR_CANRESEGMENT+"=\"" + (seg.getCanResegment() ? Const.VALUE_YES : Const.VALUE_NO) + "\""); } if ( !seg.getState().equals(Segment.STATE_DEFAULT) || ( seg.getSubState() != null )) { writer.print(" " + Const.ATTR_STATE+"=\"" + seg.getState().toString() + "\""); } if ( seg.getSubState() != null ) { writer.print(" " + Const.ATTR_SUBSTATE+"=\"" + Util.toXML(seg.getSubState(), true) + "\""); } } else { writer.print(indent+"<"+Const.ELEM_IGNORABLE); if ( part.getId() != null ) { writer.print(" id=\"" + part.getId() + "\""); } } writer.print(">"+lb); if ( useIndentation ) indent += " "; // Source writeFragment(Const.ELEM_SOURCE, unit, part.getSource(), 0, part.getPreserveWS(), unit.getSourceDir()); // Target if ( part.hasTarget() ) { writeFragment(Const.ELEM_TARGET, unit, part.getTarget(GetTarget.CREATE_EMPTY), part.getTargetOrder(), part.getPreserveWS(), unit.getTargetDir()); } if ( useIndentation ) indent = indent.substring(1); if ( seg != null ) writer.print(indent+""+lb); else writer.print(indent+""+lb); } // Use non-unit formatting for the data after if ( useIndentation ) indent = indent.substring(1); writer.print(indent+""+lb); context.pop(); nsStack.pop(); //System.out.println("pop:"+nsStack.peek().toString()); } private void writeChangeTracking (IWithChangeTrack parent) { if ( parent.hasChangeTrack() ) { writer.print(indent + ChangeTrack.getCompleteOpeningTag(true) + lb); if ( useIndentation ) { indent += " "; } for ( Revisions revs : parent.getChangeTrack() ) { writeRevisions(revs); } if ( indentNonUnit ) { indent = indent.substring(1); } writer.print(indent + ChangeTrack.getClosingTag() + lb); } } private void writeRevisions (Revisions revisions) { writer.print(indent + "<" + revisions.getOpeningTagName()); writer.print(revisions.getAttributesString()); writeExtAttributes(revisions); writer.print(">" + lb); if ( useIndentation ) { indent += " "; } for ( Revision rev : revisions ) { writeRevision(rev); } if ( indentNonUnit ) { indent = indent.substring(1); } writer.print(indent + revisions.getClosingTag() + lb); } private void writeRevision (Revision revision) { writer.print(indent + "<" + revision.getOpeningTagName()); writer.print(revision.getAttributesString()); writeExtAttributes(revision); writer.print(">" + lb); if ( useIndentation ) { indent += " "; } for ( Item item : revision ) { writeItem(item); } if ( indentNonUnit ) { indent = indent.substring(1); } writer.print(indent + revision.getClosingTag() + lb); } private void writeItem (Item item) { writer.print(indent + "<" + item.getOpeningTagName()); writer.print(item.getAttributesString()); writeExtAttributes(item); writer.print(">"); writer.print(item.getText()); writer.print(item.getClosingTag() + lb); } private void writeMatches (Unit unit) { if ( !unit.hasMatch() ) return; writer.print(indent+""+lb); for ( Match match : unit.getMatches() ) { writer.print(indent+""+lb); // metadata writeMetadata(match); // originalData if ( withData ) { writeOriginalData(match.getStore()); } // Source and target writeFragment("source", unit, match.getSource(), 0, false, Directionality.INHERITED); writeFragment("target", unit, match.getTarget(), 0, false, Directionality.INHERITED); // Extension elements writeExtElements(match); writer.print(indent+""+lb); } writer.print(""+lb); } private void writeGlossary (Unit unit) { if ( !unit.hasGlossEntry() ) return; nsStack.push(nsStack.peek().clone()); writer.print(indent+""+lb); for ( GlossEntry entry : unit.getGlossary() ) { nsStack.push(nsStack.peek().clone()); writer.print(indent+""+lb); // Term writer.print(indent+""+Util.toXML(entry.getTerm().getText(), false)+""+lb); // Translations for ( Translation trans : entry ) { writer.print(indent+""+Util.toXML(trans.getText(), false)+""+lb); } // Definition if (( entry.getDefinition() != null ) && ( entry.getDefinition().getText() != null )) { writer.print(indent+""+Util.toXML(entry.getDefinition().getText(), false)+""+lb); } // Extension elements writeExtElements(entry); writer.print(indent+""+lb); nsStack.pop(); } writer.print(""+lb); nsStack.pop(); } private void writeMetadata (IWithMetadata parent) { if ( !parent.hasMetadata() ) return; NSContext nsCtx = nsStack.push(nsStack.peek().clone()); String ns = ""; String prefix = nsCtx.getPrefix(Const.NS_XLIFF_METADATA20); if ( prefix == null ) { // Not in scope so we write it prefix = Const.PREFIX_METADATA; ns = " xmlns:"+prefix+"=\""+Const.NS_XLIFF_METADATA20+"\""; // Make sure it is now in the context nsCtx.put(prefix, Const.NS_XLIFF_METADATA20); } writer.print(indent+"<"+prefix+":metadata"); Metadata md = parent.getMetadata(); if ( md.getId() != null ) writer.print(" id=\""+md.getId()+"\""); writer.print(ns+">"+lb); // Write the content for ( MetaGroup group : md ) { writeMetaGroup(group, prefix); } writer.print(""+lb); nsStack.pop(); } private void writeMetaGroup (MetaGroup group, String prefix) { writer.print(indent+"<"+prefix+":metaGroup"); if ( group.getId() != null ) writer.print(" id=\""+group.getId()+"\""); if ( group.getCategory() != null ) writer.print(" category=\""+Util.toXML(group.getCategory(), true)+"\""); if ( group.getAppliesTo() != MetaGroup.AppliesTo.UNDEFINED ) { writer.print(" appliesTo=\""+group.getAppliesTo().toString()+"\""); } writer.print(">"+lb); // Go through the list of items for this group for ( IMetadataItem item : group ) { if ( item.isGroup() ) { writeMetaGroup((MetaGroup)item, prefix); } else { Meta m = (Meta)item; writer.print(indent+"<"+prefix+":meta"); if ( m.getType() != null ) writer.print(" type=\""+Util.toXML(m.getType(), true)+"\""); writer.print(">"); writer.print(Util.toXML(m.getData(), false)); // No cp element in meta writer.print(""+lb); } } writer.print(indent+""+lb); } private void writeValidation (IWithValidation parent) { if ( !parent.hasValidation() ) return; Validation validation = parent.getValidation(); // First check if there are non-inherited rules // If not: there is nothing to write if ( !validation.hasNonInheritedRule() ) return; // If there are non-inherited rules, then we can write NSContext nsCtx = nsStack.push(nsStack.peek().clone()); String ns = ""; String prefix = nsCtx.getPrefix(Const.NS_XLIFF_VALIDATION20); if ( prefix == null ) { // Not in scope so we write it prefix = Const.PREFIX_VALIDATION; ns = " xmlns:"+prefix+"=\""+Const.NS_XLIFF_VALIDATION20+"\""; // Make sure it is now in the context nsCtx.put(prefix, Const.NS_XLIFF_VALIDATION20); } writer.print(indent+"<"+prefix+":validation"); writeExtAttributes(validation); writer.print(ns+">"+lb); // Write the content for ( Rule rule : validation ) { writeValidationRule(rule, prefix); } writer.print(""+lb); nsStack.pop(); } private void writeValidationRule (Rule rule, String prefix) { if ( rule.isInherited() ) return; // Skip inherited rules nsStack.push(nsStack.peek().clone()); writer.print(indent+"<"+prefix+":rule"); if ( rule.getType() != Type.CUSTOM ) { writer.print(" "+rule.getType()+"=\""+Util.toXML(rule.getData(), true)+"\""); } if ( !rule.isEnabled() ) { writer.print(" disabled=\""+(rule.isEnabled() ? "no" : "yes" )+"\""); } if ( !rule.isCaseSensitive() ) { writer.print(" caseSensitive=\""+(rule.isCaseSensitive() ? "yes" : "no" )+"\""); } if ( rule.getNormalization() != Normalization.NFC ) { writer.print(" normalization=\""+rule.getNormalization().toString()+"\""); } // Type-specific attributes switch ( rule.getType() ) { case CUSTOM: case ISNOTPRESENT: // Do nothing break; case ISPRESENT: if ( rule.getOccurs() > 0 ) { writer.print(" occurs=\""+rule.getOccurs()+"\""); } // Then fall thru case ENDSWITH: case STARTSWITH: if ( rule.getExistsInSource() ) { writer.print(" existsInSource=\""+(rule.getExistsInSource() ? "yes" : "no" )+"\""); } break; } // Extension attributes writeExtAttributes(rule); writer.print("/>"+lb); nsStack.pop(); } private void writeExtAttributes (IWithExtAttributes parent) { if ( !parent.hasExtAttribute() ) return; if ( extWriter == null ) { extWriter = new ExtensionsWriter(getLineBreak()); } writer.print(extWriter.buildExtAttributes(parent.getExtAttributes(), nsStack)); } /** * Writes the notes for an object that implements the {@link IWithNotes} interface. * @param parent the object with the notes. */ private void writeNotes (IWithNotes parent) { if ( parent.getNoteCount() == 0 ) { return; } Notes notes = parent.getNotes(); writer.print(indent+""+lb); if ( useIndentation ) indent += " "; for ( Note note : notes ) { writer.print(indent+""+Util.toXML(note.getText(), false)); writer.print(""+lb); nsStack.pop(); } if ( useIndentation ) indent = indent.substring(1); writer.print(indent+""+lb); nsStack.pop(); } private void writeExtElements (IWithExtElements parent) { if ( !parent.hasExtElements() ) return; if ( extWriter == null ) { extWriter = new ExtensionsWriter(getLineBreak()); } writer.print(extWriter.buildExtElements(parent, nsStack)); } /** * Writes the start of the document (the <xliff> tag). * @param docData the document data. * @param comment an optional comment that is placed just after the <xliff> tag * (use null for no comment). */ public void writeStartDocument (StartXliffData docData, String comment) { if ( docData == null ) { docData = new StartXliffData("2.0"); } else { // If possible: update the source and target language from the original document if ( docData.getSourceLanguage() != null ) { sourceLang = docData.getSourceLanguage(); } if ( docData.getTargetLanguage() != null ) { targetLang = docData.getTargetLanguage(); } } writer.print(""+lb); writer.print(":"+nsStack.peek().toString()); if ( Util.isNoE(sourceLang) ) { throw new InvalidParameterException("Source language cannot be null or empty."); } writer.print(" "+Const.ATTR_SRCLANG+"=\""+sourceLang+"\""); if ( !Util.isNoE(targetLang) ) { writer.print(" "+Const.ATTR_TRGLANG+"=\""+targetLang+"\""); } writeExtAttributes(docData); writer.print(">"+nonUnitLb); if ( indentNonUnit ) indent += " "; inDocument = true; // Extra comment at the top if needed if ( !Util.isNoE(comment) ) { writer.print(indent+""+nonUnitLb); } } /** * Writes the end of the document (the </xliff> tag). *

If {@link #writeEndFile()} has not been called, it is called automatically. */ public void writeEndDocument () { if ( inFile ) { writeEndFile(); } if ( inDocument ) { if ( indentNonUnit ) indent = indent.substring(1); writer.print(""+nonUnitLb); nsStack.pop(); //System.out.println("pop:"+nsStack.peek().toString()); inDocument = false; } } /** * Sets the {@link StartFileData} object to use for the next {@link #writeStartFile(StartFileData)} call. * You can use this method to set the file-level data to use next time the {@link #writeStartFile(StartFileData)} * method is automatically call or called with a null parameter. This can be use to avoid writing the start of the * <file> element directly in case there is no data to extract for example. * This object stays available until the next {@link #writeEndFile()} call. * @param startFileData the object to use for the next {@link #writeStartFile(StartFileData)} call. */ public void setStartFileData (StartFileData startFileData) { this.startFileData = startFileData; } /** * Writes the start of a <file> element. *

If needed {@link #writeStartDocument(StartXliffData, String)} is called automatically before. * @param newFileData the data associated with the start of a XLIFF <file> element (can be null). * You can use {@link #setStartFileData(StartFileData)} to set this parameter before calling this * method (then use null here). */ public void writeStartFile (StartFileData newFileData) { if ( !inDocument ) writeStartDocument(null, null); // Create a file data if none is provided if ( startFileData == null ) { if ( newFileData == null ) { startFileData = new StartFileData(null); } else startFileData = newFileData; } // Ensure file id if ( startFileData.getId() == null ) { autoFileId++; startFileData.setId("f"+autoFileId); } // Start context = new Stack<>(); context.push(new InheritedData()); // Defaults context.push(new InheritedData(startFileData)); // Then the context for this file // Namespace context nsStack.push(nsStack.peek().clone()); //System.out.println("push:"+nsStack.peek().toString()); writer.print(indent+"<"+Const.ELEM_FILE); // Id (required) writer.print(" id=\""+Util.toXML(startFileData.getId(), true)+"\""); writeInheritedAttributes(startFileData); // Original if ( !Util.isNoE(startFileData.getOriginal()) ) { writer.print(" original=\""+Util.toXML(startFileData.getOriginal(), true)+"\""); } // Extension attributes writeExtAttributes(startFileData); // End writer.print(">"+nonUnitLb); if ( indentNonUnit ) indent += " "; inFile = true; } /** * Writes the mid part of an <file> document. *

If needed {@link #writeStartFile(StartFileData)} is called automatically before. * @param midFileData the data associated with a mid part of a <file> element (can be null). */ public void writeMidFile (MidFileData midFileData) { if ( !inFile ) writeStartFile(null); if ( midFileData != null ) { writeExtElements(midFileData); writeMetadata(midFileData); writeValidation(midFileData); writeChangeTracking(midFileData); writeNotes(midFileData); } } private void writeInheritedAttributes (InheritedData data) { // Get the parent context (size()-1 is the current) InheritedData parentData = context.elementAt(context.size()-2); // CanResegment if ( data.getCanResegment() != parentData.getCanResegment() ) { writer.print(" canResegment=\"" + (data.getCanResegment() ? Const.VALUE_YES : Const.VALUE_NO) + "\""); } // Translate if ( data.getTranslate() != parentData.getTranslate() ) { writer.print(" translate=\"" + (data.getTranslate() ? Const.VALUE_YES : Const.VALUE_NO) + "\""); } // Source dDirectionality writeDirectionality(Const.ATTR_SRCDIR, data.getSourceDir(), parentData.getSourceDir()); // Target dDirectionality writeDirectionality(Const.ATTR_TRGDIR, data.getTargetDir(), parentData.getTargetDir()); } /** * Writes the element. *

If {@link #writeStartFile(StartFileData)} has not been called, it is called automatically. *

If the skeleton attribute was used in <file>, an error occurs. * @param skelData the {@link Skeleton} object (must not be null). * @throws XLIFFWriterException if an error occurs. */ public void writeSkeleton (Skeleton skelData) { if ( !inFile ) writeStartFile(null); // Do we need the skeleton? List list = skelData.getChildren(); if ((( list != null ) && list.isEmpty()) && ( skelData.getHref() == null )) { // Do not write an empty skeleton return; } // Start tag writer.print(indent+"<"+Const.ELEM_SKELETON); if ( indentNonUnit ) indent += " "; // Output either href (if present) or the content. if ( skelData.getHref() != null ) { writer.print(" href=\""+Util.toXML(skelData.getHref(), true)+"\">"); } else { writer.print(">"); // Content if ( extWriter == null ) { extWriter = new ExtensionsWriter(getLineBreak()); } writer.print(extWriter.buildExtChildren(skelData.getChildren(), null)); } // End tag if ( indentNonUnit ) indent = indent.substring(1); writer.print(""+nonUnitLb); } /** * Writes the end of the file (the </file> tag). *

Nothing is written if no file has been started. */ public void writeEndFile () { if ( inFile ) { while ( groupStack.size() > 0 ) { writeEndGroup(); } if ( indentNonUnit ) indent = indent.substring(1); writer.print(indent+""+nonUnitLb); nsStack.pop(); //System.out.println("pop:"+nsStack.peek().toString()); inFile = false; startFileData = null; } } /** * Writes the start of a <group> element. *

If needed {@link #writeStartFile(StartFileData)} is called before. * @param startGroupData the data associated with the group (can be null). */ public void writeStartGroup (StartGroupData startGroupData) { if ( !inFile ) writeStartFile(null); // Create a default group data if needed if ( startGroupData == null ) { autoGroupId++; startGroupData = new StartGroupData("g"+autoGroupId); } // Start context.push(new InheritedData(startGroupData)); // Namespace context nsStack.push(nsStack.peek().clone()); //System.out.println("push:"+nsStack.peek().toString()); writer.print(indent+"<"+Const.ELEM_GROUP); // Id if ( startGroupData.getId() != null ) { writer.print(" "+Const.ATTR_ID+"=\""+Util.toXML(startGroupData.getId(), true)+"\""); } writeInheritedAttributes(startGroupData); // Name if ( startGroupData.getName() != null ) { writer.print(" "+Const.ATTR_NAME+"=\""+Util.toXML(startGroupData.getName(), true)+"\""); } // Type if ( startGroupData.getType() != null ) { writer.print(" "+Const.ATTR_TYPE+"=\""+Util.toXML(startGroupData.getType(), true)+"\""); } // Extension attributes writeExtAttributes(startGroupData); writer.print(">"+nonUnitLb); if ( indentNonUnit ) indent += " "; groupStack.push(startGroupData); writeExtElements(startGroupData); writeMetadata(startGroupData); writeValidation(startGroupData); writeChangeTracking(startGroupData); writeNotes(startGroupData); } /** * Writes the end of a <group> element. */ public void writeEndGroup () { // Close this group groupStack.pop(); context.pop(); if ( indentNonUnit ) indent = indent.substring(1); writer.print(indent+""+nonUnitLb); nsStack.pop(); //System.out.println("pop:"+nsStack.peek().toString()); } /** * Writes a fragment. * @param name the name of the element (source or target) * @param fragment the fragment. * @param order the order information (must be 0 or less for source fragments) * @param preserveSpace true if xml:space should be set to 'preserve' * @param inheritedDir the inherited directionality */ private void writeFragment (String name, Unit parent, Fragment fragment, int order, boolean preserveSpace, Directionality inheritedDir) { if ( order > 0 ) { writer.print(indent+String.format("<%s order=\"%d\"", name, order)); } else { writer.print(indent+"<"+name); } if ( preserveSpace ) { writer.print(" xml:space=\"preserve\""); } else { // only write default if we must explicitly override // the parent if (parent.getPreserveWS()) { writer.print(" xml:space=\"default\""); } } writer.print(">"+fragment.toXLIFF(nsStack, context, withData)); writer.print(""+lb); } private void writeOriginalData (Store store) { if ( !store.hasCTagWithData() ) { return; // Nothing to write out } // Else: write the data store.calculateDataToIdsMap(); Map map = store.getOutsideRepresentationMap(); writer.print(indent+"<"+Const.ELEM_ORIGINALDATA+">"+lb); if ( useIndentation ) indent += " "; for ( String originalDataKey : map.keySet() ) { String id = map.get(originalDataKey); // The original data is the key during output writer.print(indent+"<"+Const.ELEM_DATA+" "+Const.ATTR_ID+"=\""+id+"\""); // Directionality (default is AUTO) switch ( originalDataKey.charAt(originalDataKey.length()-1) ) { case 'r': writer.print(" "+Const.ATTR_DIR+"=\"rtl\""); break; case 'l': writer.print(" "+Const.ATTR_DIR+"=\"ltr\""); break; } // Data itself (minus the direction flag) writer.print(">"+Util.toSafeXML(originalDataKey.substring(0, originalDataKey.length()-1))); writer.print(""+lb); } if ( useIndentation ) indent = indent.substring(1); writer.print(indent+""+lb); store.setOutsideRepresentationMap(map); } /** * Gets the namespace context for this writer. * @return the namespace context for this writer. */ public NamespaceContext getNamespaceContext () { return nsStack.peek(); } private void writeDirectionality (String name, Directionality value, Directionality context) { if ( value == context ) return; // Not needed writer.print(String.format(" %s=\"%s\"", name, value.toString())); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy