import { STATE_NEW , STATE_TRANSLATED , STATE_FINAL } from '../api/constants' ; import { ITranslationMessagesFile } from '../api/i-translation-messages-file' ; import { INormalizedMessage } from '../api/i-normalized-message' ; import { ITransUnit } from '../api/i-trans-unit' ; import { INote } from '../api/i-note' ; import { DOMUtilities } from './dom-utilities' ; import { ParsedMessage } from './parsed-message' ; import { AbstractTransUnit } from './abstract-trans-unit' ; import { Xliff2MessageParser } from './xliff2-message-parser' ; import { AbstractMessageParser } from './abstract-message-parser' ; import { isNullOrUndefined } from 'util' ; /**
 * Created by martin on 04.05.2017.
 * A Translation Unit in an XLIFF 2.0 file.
 */ export class Xliff2TransUnit extends AbstractTransUnit implements ITransUnit { constructor ( _element , _id , _translationMessagesFile ) { super ( _element , _id , _translationMessagesFile ) ; } public sourceContent ( ) { const sourceElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; return DOMUtilities . getXMLContent ( sourceElement ) ; } param newContent public setSourceContent ( newContent ) { let source = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; if ( ! source ) { const segment = DOMUtilities . getFirstElementByTagName ( this . _element , 'segment' ) ; source = segment . parentNode . appendChild ( this . _element . ownerDocument . createElement ( 'source' ) ) ; } DOMUtilities . replaceContentWithXMLContent ( source , newContent ) ; } /**
     * Return a parser used for normalized messages.
     */ protected messageParser ( ) { return new Xliff2MessageParser ( ) ; } /**
     * The original text value, that is to be translated, as normalized message.
     */ public createSourceContentNormalized ( ) { const sourceElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; if ( sourceElement ) { return this . messageParser ( ) . createNormalizedMessageFromXML ( sourceElement , null ) ; } else { return null ; } } /**
     * the translated value (containing all markup, depends on the concrete format used).
     */ public targetContent ( ) { const targetElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; return DOMUtilities . getXMLContent ( targetElement ) ; } /**
     * the translated value, but all placeholders are replaced with {{n}} (starting at 0)
     * and all embedded html is replaced by direct html markup.
     */ targetContentNormalized ( ) { const targetElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; return new Xliff2MessageParser ( ) . createNormalizedMessageFromXML ( targetElement , this . sourceContentNormalized ( ) ) ; } /**
     * State of the translation as stored in the xml.
     */ public nativeTargetState ( ) { const segmentElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'segment' ) ; if ( segmentElement ) { return segmentElement . getAttribute ( 'state' ) ; } else { return null ; } } param nativeState protected setNativeTargetState ( nativeState ) { const segmentElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'segment' ) ; if ( segmentElement ) { segmentElement . setAttribute ( 'state' , nativeState ) ; } } param state returns throws protected mapStateToNativeState ( state ) { switch ( state ) { case STATE_NEW : return 'initial' ; case STATE_TRANSLATED : return 'translated' ; case STATE_FINAL : return 'final' ; default : throw new Error ( 'unknown state ' + state ) ; } } param nativeState protected mapNativeStateToState ( nativeState ) { switch ( nativeState ) { case 'initial' : return STATE_NEW ; case 'translated' : return STATE_TRANSLATED ; case 'reviewed' : return STATE_TRANSLATED ; case 'final' : return STATE_FINAL ; default : return STATE_NEW ; } } /**
     * All the source elements in the trans unit.
     * The source element is a reference to the original template.
     * It contains the name of the template file and a line number with the position inside the template.
     * It is just a help for translators to find the context for the translation.
     * This is set when using Angular 4.0 or greater.
     * Otherwise it just returns an empty array.
     */ public sourceReferences ( ) { const noteElements = this . _element . getElementsByTagName ( 'note' ) ; const sourceRefs = [ ] ; for ( let i = 0 ; i < noteElements . length ; i ++ ) { const noteElem = noteElements . item ( i ) ; if ( noteElem . getAttribute ( 'category' ) === 'location' ) { const sourceAndPos = DOMUtilities . getPCDATA ( noteElem ) ; sourceRefs . push ( this . parseSourceAndPos ( sourceAndPos ) ) ; } } return sourceRefs ; } param sourceAndPos return private parseSourceAndPos ( sourceAndPos ) { const index = sourceAndPos . lastIndexOf ( ':' ) ; if ( index < 0 ) { return { sourcefile : sourceAndPos , linenumber : 0 } ; } else { return { sourcefile : sourceAndPos . substring ( 0 , index ) , linenumber : this . parseLineNumber ( sourceAndPos . substring ( index + 1 ) ) } ; } } private parseLineNumber ( lineNumberString ) { return Number . parseInt ( lineNumberString , 10 ) ; } param sourceRefs public setSourceReferences ( sourceRefs ) { this . removeAllSourceReferences ( ) ; let notesElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'notes' ) ; if ( sourceRefs . length === 0 && ! isNullOrUndefined ( notesElement ) && notesElement . childNodes . length === 0 ) { notesElement . parentNode . removeChild ( notesElement ) ; return ; } if ( isNullOrUndefined ( notesElement ) ) { notesElement = this . _element . ownerDocument . createElement ( 'notes' ) ; this . _element . insertBefore ( notesElement , this . _element . childNodes . item ( 0 ) ) ; } sourceRefs . forEach ( ( ref ) => { const note = this . _element . ownerDocument . createElement ( 'note' ) ; note . setAttribute ( 'category' , 'location' ) ; note . appendChild ( this . _element . ownerDocument . createTextNode ( ref . sourcefile + ':' + ref . linenumber . toString ( 10 ) ) ) ; notesElement . appendChild ( note ) ; } ) ; } private removeAllSourceReferences ( ) { const noteElements = this . _element . getElementsByTagName ( 'note' ) ; const toBeRemoved = [ ] ; for ( let i = 0 ; i < noteElements . length ; i ++ ) { const elem = noteElements . item ( i ) ; if ( elem . getAttribute ( 'category' ) === 'location' ) { toBeRemoved . push ( elem ) ; } } toBeRemoved . forEach ( ( elem ) => { elem . parentNode . removeChild ( elem ) ; } ) ; } /**
     * The description set in the template as value of the i18n-attribute.
     * e.g. i18n="mydescription".
     * In xliff 2.0 this is stored as a note element with attribute category="description".
     */ public description ( ) { const noteElem = this . findNoteElementWithCategoryAttribute ( 'description' ) ; if ( noteElem ) { return DOMUtilities . getPCDATA ( noteElem ) ; } else { return null ; } } param description public setDescription ( description ) { const noteElem = this . findNoteElementWithCategoryAttribute ( 'description' ) ; if ( description ) { if ( isNullOrUndefined ( noteElem ) ) { this . createNoteElementWithCategoryAttribute ( 'description' , description ) ; } else { DOMUtilities . replaceContentWithXMLContent ( noteElem , description ) ; } } else { if ( ! isNullOrUndefined ( noteElem ) ) { this . removeNoteElementWithCategoryAttribute ( 'description' ) ; } } } param attrValue return private findNoteElementWithCategoryAttribute ( attrValue ) { const noteElements = this . _element . getElementsByTagName ( 'note' ) ; for ( let i = 0 ; i < noteElements . length ; i ++ ) { const noteElem = noteElements . item ( i ) ; if ( noteElem . getAttribute ( 'category' ) === attrValue ) { return noteElem ; } } return null ; } return private findAllAdditionalNoteElements ( ) { const noteElements = this . _element . getElementsByTagName ( 'note' ) ; const result = [ ] ; for ( let i = 0 ; i < noteElements . length ; i ++ ) { const noteElem = noteElements . item ( i ) ; const fromAttribute = noteElem . getAttribute ( 'category' ) ; if ( fromAttribute !== 'description' && fromAttribute !== 'meaning' ) { result . push ( noteElem ) ; } } return result ; } param attrValue param content return private createNoteElementWithCategoryAttribute ( attrValue , content ) { let notesElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'notes' ) ; if ( isNullOrUndefined ( notesElement ) ) { notesElement = this . _element . ownerDocument . createElement ( 'notes' ) ; this . _element . appendChild ( notesElement ) ; } const noteElement = this . _element . ownerDocument . createElement ( 'note' ) ; if ( attrValue ) { noteElement . setAttribute ( 'category' , attrValue ) ; } if ( content ) { DOMUtilities . replaceContentWithXMLContent ( noteElement , content ) ; } notesElement . appendChild ( noteElement ) ; return noteElement ; } private removeNotesElementIfEmpty ( ) { const notesElement = DOMUtilities . getFirstElementByTagName ( this . _element , 'notes' ) ; if ( notesElement ) { const childNote = DOMUtilities . getFirstElementByTagName ( this . _element , 'note' ) ; if ( ! childNote ) { notesElement . parentNode . removeChild ( notesElement ) ; } } } param attrValue private removeNoteElementWithCategoryAttribute ( attrValue ) { const noteElement = this . findNoteElementWithCategoryAttribute ( attrValue ) ; if ( noteElement ) { noteElement . parentNode . removeChild ( noteElement ) ; } this . removeNotesElementIfEmpty ( ) ; } /**
     * Remove all note elements where attribute "from" is not description or meaning.
     */ private removeAllAdditionalNoteElements ( ) { const noteElements = this . findAllAdditionalNoteElements ( ) ; noteElements . forEach ( ( noteElement ) => { noteElement . parentNode . removeChild ( noteElement ) ; } ) ; this . removeNotesElementIfEmpty ( ) ; } /**
     * The meaning (intent) set in the template as value of the i18n-attribute.
     * This is the part in front of the | symbol.
     * e.g. i18n="meaning|mydescription".
     * In xliff 2.0 this is stored as a note element with attribute category="meaning".
     */ public meaning ( ) { const noteElem = this . findNoteElementWithCategoryAttribute ( 'meaning' ) ; if ( noteElem ) { return DOMUtilities . getPCDATA ( noteElem ) ; } else { return null ; } } param meaning public setMeaning ( meaning ) { const noteElem = this . findNoteElementWithCategoryAttribute ( 'meaning' ) ; if ( meaning ) { if ( isNullOrUndefined ( noteElem ) ) { this . createNoteElementWithCategoryAttribute ( 'meaning' , meaning ) ; } else { DOMUtilities . replaceContentWithXMLContent ( noteElem , meaning ) ; } } else { if ( ! isNullOrUndefined ( noteElem ) ) { this . removeNoteElementWithCategoryAttribute ( 'meaning' ) ; } } } /**
     * Get all notes of the trans-unit.
     * Notes are remarks made by a translator.
     * (description and meaning are not included here!)
     */ public notes ( ) { const noteElememts = this . findAllAdditionalNoteElements ( ) ; return noteElememts . map ( elem => { return { from : elem . getAttribute ( 'category' ) , text : DOMUtilities . getPCDATA ( elem ) } ; } ) ; } /**
     * Test, wether setting of notes is supported.
     * If not, setNotes will do nothing.
     * xtb does not support this, all other formats do.
     */ public supportsSetNotes ( ) { return true ; } param newNotes public setNotes ( newNotes ) { if ( ! isNullOrUndefined ( newNotes ) ) { this . checkNotes ( newNotes ) ; } this . removeAllAdditionalNoteElements ( ) ; if ( ! isNullOrUndefined ( newNotes ) ) { newNotes . forEach ( ( note ) => { this . createNoteElementWithCategoryAttribute ( note . from , note . text ) ; } ) ; } } param translation protected translateNative ( translation ) { let target = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; if ( ! target ) { const source = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; target = source . parentNode . appendChild ( this . _element . ownerDocument . createElement ( 'target' ) ) ; } DOMUtilities . replaceContentWithXMLContent ( target , < string > translation ) ; this . setTargetState ( STATE_TRANSLATED ) ; } /**
     * Copy source to target to use it as dummy translation.
     * Returns a changed copy of this trans unit.
     * receiver is not changed.
     * (internal usage only, a client should call importNewTransUnit on ITranslationMessageFile)
     */ public cloneWithSourceAsTarget ( isDefaultLang , copyContent , targetFile ) { const element = < Element > this . _element . cloneNode ( true ) ; const clone = new Xliff2TransUnit ( element , this . _id , targetFile ) ; clone . useSourceAsTarget ( isDefaultLang , copyContent ) ; return clone ; } /**
     * Copy source to target to use it as dummy translation.
     * (internal usage only, a client should call createTranslationFileForLang on ITranslationMessageFile)
     */ public useSourceAsTarget ( isDefaultLang , copyContent ) { const source = DOMUtilities . getFirstElementByTagName ( this . _element , 'source' ) ; let target = DOMUtilities . getFirstElementByTagName ( this . _element , 'target' ) ; if ( ! target ) { target = source . parentNode . appendChild ( this . _element . ownerDocument . createElement ( 'target' ) ) ; } if ( isDefaultLang || copyContent ) { const sourceString = DOMUtilities . getXMLContent ( source ) ; let newTargetString = sourceString ; if ( ! this . isICUMessage ( sourceString ) ) { newTargetString = this . translationMessagesFile ( ) . getNewTransUnitTargetPraefix ( ) + sourceString + this . translationMessagesFile ( ) . getNewTransUnitTargetSuffix ( ) ; } DOMUtilities . replaceContentWithXMLContent ( target , newTargetString ) ; } else { DOMUtilities . replaceContentWithXMLContent ( target , '' ) ; } const segment = DOMUtilities . getFirstElementByTagName ( this . _element , 'segment' ) ; if ( segment ) { if ( isDefaultLang ) { segment . setAttribute ( 'state' , this . mapStateToNativeState ( STATE_FINAL ) ) ; } else { segment . setAttribute ( 'state' , this . mapStateToNativeState ( STATE_NEW ) ) ; } } } }