/*
* Copyright (C) 2014-2015, VistaTEC or third-party contributors as indicated
* by the @author tags or express copyright attribution statements applied by
* the authors. All third-party contributions are distributed under license by
* VistaTEC.
*
* This file is part of Ocelot.
*
* Ocelot is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Ocelot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, write to:
*
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301
* USA
*
* Also, see the full LGPL text here: <http://www.gnu.org/copyleft/lesser.html>
*/
package com.vistatec.ocelot.xliff.okapi;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import net.sf.okapi.lib.xliff2.Const;
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.Fragment;
import net.sf.okapi.lib.xliff2.core.MTag;
import net.sf.okapi.lib.xliff2.core.Part;
import net.sf.okapi.lib.xliff2.core.Segment;
import net.sf.okapi.lib.xliff2.core.Tag;
import net.sf.okapi.lib.xliff2.core.TagType;
import net.sf.okapi.lib.xliff2.core.Unit;
import net.sf.okapi.lib.xliff2.its.IITSItem;
import net.sf.okapi.lib.xliff2.its.ITSItems;
import net.sf.okapi.lib.xliff2.its.ITSWriter;
import net.sf.okapi.lib.xliff2.its.LocQualityIssue;
import net.sf.okapi.lib.xliff2.its.LocQualityIssues;
import net.sf.okapi.lib.xliff2.its.Provenances;
import net.sf.okapi.lib.xliff2.reader.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vistatec.ocelot.config.UserProvenance;
import com.vistatec.ocelot.events.ProvenanceAddEvent;
import com.vistatec.ocelot.events.api.OcelotEventQueue;
import com.vistatec.ocelot.its.model.LanguageQualityIssue;
import com.vistatec.ocelot.its.model.Provenance;
import com.vistatec.ocelot.its.model.okapi.OkapiProvenance;
import com.vistatec.ocelot.segment.model.OcelotSegment;
import com.vistatec.ocelot.segment.model.okapi.FragmentVariant;
import com.vistatec.ocelot.segment.model.okapi.Note;
import com.vistatec.ocelot.segment.model.okapi.OkapiSegment;
import com.vistatec.ocelot.xliff.XLIFFWriter;
/**
* Write out XLIFF 2.0 files.
*/
public class OkapiXLIFF20Writer implements XLIFFWriter {
private static final Logger LOG = LoggerFactory.getLogger(OkapiXLIFF20Writer.class);
private final OkapiXLIFF20Parser parser;
private final UserProvenance userProvenance;
private final OcelotEventQueue eventQueue;
public OkapiXLIFF20Writer(OkapiXLIFF20Parser parser, UserProvenance userProvenance,
OcelotEventQueue eventQueue) {
this.parser = parser;
this.userProvenance = userProvenance;
this.eventQueue = eventQueue;
}
@Override
public void updateSegment(OcelotSegment seg) {
OkapiSegment okapiSeg = (OkapiSegment) seg;
Segment unitPart = this.parser.getSegmentUnitPart(okapiSeg.eventNum);
if (unitPart == null) {
LOG.error("Failed to find Okapi Unit Part associated with segment #"+okapiSeg.getSegmentNumber());
} else if (unitPart.isSegment()) {
if (okapiSeg.hasOriginalTarget()) {
FragmentVariant targetFrag = (FragmentVariant) okapiSeg.getTarget();
Fragment updatedOkapiFragment = targetFrag.getUpdatedOkapiFragment(unitPart.getTarget());
unitPart.setTarget(updatedOkapiFragment);
manageRevision(this.parser.getSegmentEvent(okapiSeg.getSegmentNumber()), unitPart, parser.getTargetVersion(okapiSeg.eventNum));
}
updateITSLQIAnnotations(unitPart, okapiSeg);
if (!haveAddedOcelotProvAnnotation(unitPart, okapiSeg)) {
updateITSProvAnnotations(unitPart, okapiSeg);
}
FragmentVariant source = (FragmentVariant) okapiSeg.getSource();
source.updateSegmentAtoms(unitPart);
FragmentVariant target = (FragmentVariant) okapiSeg.getTarget();
target.updateSegmentAtoms(unitPart);
target.setAtomsHighlightedText();
} else {
LOG.error("Unit part associated with Segment was not an Okapi Segment!");
LOG.error("Failed to update Unit Part for segment #"+okapiSeg.getSegmentNumber());
}
}
private void updateNotes(Event segmentEvent, OkapiSegment okapiSeg) {
if(segmentEvent.isUnit()){
Unit unit = segmentEvent.getUnit();
Note ocelotNote = null;
if(okapiSeg.getNotes() != null){
ocelotNote = okapiSeg.getNotes().getOcelotNote();
}
if(ocelotNote != null){
net.sf.okapi.lib.xliff2.core.Note okapiNote = findOcelotNoteInUnit(unit);
//CASE 1 - note created for this segment
if(okapiNote == null){
okapiNote = new net.sf.okapi.lib.xliff2.core.Note(ocelotNote.getContent());
okapiNote.setId(ocelotNote.getId());
unit.addNote(okapiNote);
//CASE 2 - note changed for this segment
} else {
okapiNote.setText(ocelotNote.getContent());
}
} else {
net.sf.okapi.lib.xliff2.core.Note okapiNote = findOcelotNoteInUnit(unit);
//CASE 3 - note deleted for this segment
if(okapiNote != null){
unit.getNotes().remove(okapiNote);
}
}
}
}
private net.sf.okapi.lib.xliff2.core.Note findOcelotNoteInUnit(Unit unit ){
net.sf.okapi.lib.xliff2.core.Note note = null;
if(unit.getNotes() != null){
for(net.sf.okapi.lib.xliff2.core.Note currNote: unit.getNotes()){
if(currNote.getId().startsWith(Note.OCELOT_ID_PREFIX)){
note = currNote;
break;
}
}
}
return note;
}
private void manageRevision(Event event, Segment unitPart, TargetVersion nextVersion) {
if(event.isUnit()){
Unit unit = event.getUnit();
Item item = null;
Date now = new Date();
if(!unit.hasChangeTrack()){
ChangeTrack changeTrack = new ChangeTrack();
unit.setChangeTrack(changeTrack);
Revisions revisions = createTargetRevisions(nextVersion.getVersion());
changeTrack.add(revisions);
Revision revision = createCurrentRevision(nextVersion.getVersion(), parser.getRevisionDateFormatter().format(now));
revisions.add(revision);
item = new Item();
item.setProperty(Item.PROPERTY_CONTENT_VALUE);
revision.add(item);
} else {
Revisions targetRevisions = null;
Iterator<Revisions> revsIt = unit.getChangeTrack().iterator();
Revisions revs = null;
while(revsIt.hasNext()) {
revs = revsIt.next();
if(revs.getAppliesTo().equals(Const.ELEM_TARGET)){
targetRevisions = revs;
break;
}
}
if(targetRevisions == null){
targetRevisions = createTargetRevisions(nextVersion.getVersion());
unit.getChangeTrack().add(targetRevisions);
}
Revision currentRevision = null;
for(Revision rev: targetRevisions){
if(rev.getVersion().equals(nextVersion.getVersion())){
currentRevision = rev;
break;
}
}
if(currentRevision == null){
currentRevision = createCurrentRevision(nextVersion.getVersion(), parser.getRevisionDateFormatter().format(now));
targetRevisions.add(currentRevision);
targetRevisions.setCurrentVersion(nextVersion.getVersion());
}
Iterator<Item> itemsIt = currentRevision.iterator();
Item currItem = null;
while(itemsIt.hasNext()){
currItem = itemsIt.next();
if(currItem.getProperty().equals(Item.PROPERTY_CONTENT_VALUE)){
item = currItem;
break;
}
}
if(item == null){
item = new Item();
item.setProperty(Item.PROPERTY_CONTENT_VALUE);
currentRevision.add(item);
}
}
item.setText(unitPart.getTarget().toString());
nextVersion.setUpdated(true);
}
}
private Revisions createTargetRevisions(String version){
Revisions targetRevisions = new Revisions();
targetRevisions.setAppliesTo(Const.ELEM_TARGET);
targetRevisions.setCurrentVersion(version);
return targetRevisions;
}
private Revision createCurrentRevision(String version, String date){
Revision revision = new Revision();
revision.setVersion(version);
revision.setDatetime(date);
return revision;
}
/**
* Records the Ocelot ITS Provenance record to the Okapi segment
* representation used when saving the file using the Okapi XLIFF 2.0 writer.
* @param unitPart - Okapi representation of the segment to annotate
* @param seg - Ocelot segment
*/
private void updateITSProvAnnotations(Part unitPart, OcelotSegment seg) {
Provenances okapiOcelotProv = getOkapiOcelotProvenance(seg);
if (okapiOcelotProv != null) {
ITSWriter.annotate(unitPart.getTarget(), 0, -1, okapiOcelotProv);
Provenance ocelotProv = new OkapiProvenance(okapiOcelotProv.getList().get(0));
eventQueue.post(new ProvenanceAddEvent(ocelotProv, seg, true));
}
}
/**
* Constructs the Okapi representation of the Ocelot ITS Provenance record
* if the reviewer's profile is set.
* @param seg - Ocelot segment
* @return - Okapi ITS Provenance Object or null if profile {@link UserProvenance} is not set
*/
private Provenances getOkapiOcelotProvenance(OcelotSegment seg) {
if (userProvenance.isEmpty()) {
return null;
}
String ocelotProvId = "OcelotProv" + seg.getSegmentNumber();
Provenances okapiProvGroup = new Provenances(ocelotProvId);
net.sf.okapi.lib.xliff2.its.Provenance okapiProv
= new net.sf.okapi.lib.xliff2.its.Provenance();
okapiProv.setRevPerson(userProvenance.getRevPerson());
okapiProv.setRevOrg(userProvenance.getRevOrg());
okapiProv.setRevTool("http://open.vistatec.com/ocelot");
okapiProv.setProvRef(userProvenance.getProvRef());
okapiProvGroup.getList().add(okapiProv);
return okapiProvGroup;
}
private boolean haveAddedOcelotProvAnnotation(Part unitPart, OcelotSegment seg) {
boolean haveAddedUserProv = seg.hasOcelotProvenance();
List<Tag> targetTags = unitPart.getTarget().getOwnTags();
for (Tag tag : targetTags) {
if (tag.isMarker()) {
MTag mtag = (MTag) tag;
if (mtag.hasITSItem()) {
Provenances provMetadata = (Provenances) mtag.getITSItems()
.get(net.sf.okapi.lib.xliff2.its.Provenance.class);
if (provMetadata != null
&& provMetadata.getGroupId().matches("OcelotProv[0-9]*")) {
haveAddedUserProv = true;
}
}
}
}
return haveAddedUserProv;
}
/**
* Update LQI annotations on the segment. TODO: separate from non-LQI updates
* @param unitPart - Okapi representation of the segment
* @param seg - Ocelot segment
*/
public void updateITSLQIAnnotations(Part unitPart, OcelotSegment seg) {
removeExistingLqiAnnotationsFromSegment(unitPart);
if (!seg.getLQI().isEmpty()) {
String ocelotLqiId = "OcelotLQI" + seg.getSegmentNumber();
LocQualityIssues newOkapiLqiGroup = convertOcelotToOkapiLqi(
seg.getLQI(), ocelotLqiId);
ITSWriter.annotate(unitPart.getTarget(), 0, -1, newOkapiLqiGroup);
}
}
private void removeExistingLqiAnnotationsFromSegment(Part unitPart) {
List<Tag> sourceTags = unitPart.getSource().getOwnTags();
List<Tag> targetTags = unitPart.getTarget().getOwnTags();
removeExistingLqiAnnotations(unitPart, false, sourceTags);
removeExistingLqiAnnotations(unitPart, true, targetTags);
}
private void removeExistingLqiAnnotations(Part unitPart, boolean isTarget, List<Tag> tags) {
for (Tag tag : tags) {
if (tag.isMarker()) {
MTag mtag = (MTag) tag;
if (mtag.hasITSItem()) {
ITSItems items = mtag.getITSItems();
IITSItem itsLqiItem = items.get(LocQualityIssue.class);
if (itsLqiItem != null) {
// Don't delete the LQI issues for opening tags so we
// can find the corresponding closing tag and delete it as well
if (mtag.getTagType() == TagType.CLOSING ||
mtag.getTagType() == TagType.STANDALONE) {
items.remove(itsLqiItem);
}
// TODO: Assumes MTag is only used for ITS metadata
if (items.size() <= 1) {
Fragment frag = isTarget ?
unitPart.getTarget() : unitPart.getSource();
frag.remove(mtag);
}
}
}
}
}
}
private LocQualityIssues convertOcelotToOkapiLqi(List<LanguageQualityIssue> ocelotLqi, String ocelotLqiId) {
LocQualityIssues newLqiGroup = new LocQualityIssues(ocelotLqiId);
for (LanguageQualityIssue lqi : ocelotLqi) {
LocQualityIssue newLqi = new LocQualityIssue();
newLqi.setType(lqi.getType());
newLqi.setComment(lqi.getComment());
newLqi.setSeverity(lqi.getSeverity());
if (lqi.getProfileReference() != null) {
newLqi.setProfileRef(lqi.getProfileReference().toString());
}
newLqi.setEnabled(lqi.isEnabled());
newLqiGroup.getList().add(newLqi);
}
return newLqiGroup;
}
@Override
public void save(File file) throws IOException, UnsupportedEncodingException {
net.sf.okapi.lib.xliff2.writer.XLIFFWriter writer = new net.sf.okapi.lib.xliff2.writer.XLIFFWriter();
StringWriter tmp = new StringWriter();
writer.create(tmp, parser.getSourceLang());
writer.setLineBreak("\n"); //FIXME: OS linebreak detection in XLIFF filter doesn't seem to work (Mac) so we need to set it.
writer.setWithOriginalData(true);
for (Event event : parser.getEvents()) {
writer.writeEvent(event);
}
writer.close();
tmp.close();
Writer outputFile = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file.getAbsolutePath()), "UTF-8"));
outputFile.write(tmp.toString());
outputFile.flush();
outputFile.close();
parser.updateTargetVersions();
}
@Override
public void updateNotes(OcelotSegment seg) {
OkapiSegment okapiSeg = (OkapiSegment) seg;
updateNotes(this.parser.getSegmentEvent(okapiSeg.getSegmentNumber()),
okapiSeg);
}
}