/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2012 Thomas Cordonnier, Aaron Madlon-Kay 2013-2014 Aaron Madlon-Kay 2014 Alex Buloichik Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.gui.matches; import java.text.DateFormat; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.omegat.core.Core; import org.omegat.core.data.ProjectProperties; import org.omegat.core.data.SourceTextEntry; import org.omegat.core.matching.DiffDriver; import org.omegat.core.matching.DiffDriver.Render; import org.omegat.core.matching.DiffDriver.TextRun; import org.omegat.core.matching.NearString; import org.omegat.util.OStrings; import org.omegat.util.TMXProp; import org.omegat.util.VarExpansion; /** * This class is used to convert a NearString to a text visible in the MatchesTextArea * according to the given template containing variables. * * @author Thomas CORDONNIER * @author Aaron Madlon-Kay */ public class MatchesVarExpansion extends VarExpansion<NearString> { // ------------------------------ definitions ------------------- public static final String VAR_ID = "${id}"; public static final String VAR_SCORE_BASE = "${score}"; public static final String VAR_SCORE_NOSTEM = "${noStemScore}"; public static final String VAR_SCORE_ADJUSTED = "${adjustedScore}"; /** * For backwards compatibility, this variable is an alias for {@link #VAR_CHANGED_ID}. * For the actual creation ID, use {@link #VAR_INITIAL_CREATION_ID}. */ @Deprecated public static final String VAR_CREATION_ID = "${creationId}"; /** * For backwards compatibility, this variable is an alias for {@link #VAR_CHANGED_DATE}. * For the actual creation date, use {@link #VAR_INITIAL_CREATION_DATE}. */ @Deprecated public static final String VAR_CREATION_DATE = "${creationDate}"; public static final String VAR_INITIAL_CREATION_ID = "${initialCreationId}"; public static final String VAR_INITIAL_CREATION_DATE = "${initialCreationDate}"; public static final String VAR_CHANGED_ID = "${changedId}"; public static final String VAR_CHANGED_DATE = "${changedDate}"; public static final String VAR_FUZZY_FLAG = "${fuzzyFlag}"; public static final String VAR_DIFF = "${diff}"; public static final String VAR_DIFF_REVERSED = "${diffReversed}"; private static final String[] MATCHES_VARIABLES = { VAR_ID, VAR_SOURCE_TEXT, VAR_DIFF, VAR_DIFF_REVERSED, VAR_TARGET_TEXT, VAR_SCORE_BASE, VAR_SCORE_NOSTEM, VAR_SCORE_ADJUSTED, VAR_FILE_NAME_ONLY, VAR_FILE_PATH, VAR_FILE_SHORT_PATH, VAR_INITIAL_CREATION_ID, VAR_INITIAL_CREATION_DATE, VAR_CHANGED_ID, VAR_CHANGED_DATE, VAR_FUZZY_FLAG }; public static List<String> getMatchesVariables() { return Collections.unmodifiableList(Arrays.asList(MATCHES_VARIABLES)); } public static final String DEFAULT_TEMPLATE = VAR_ID + ". " + VAR_FUZZY_FLAG + VAR_SOURCE_TEXT + "\n" + VAR_TARGET_TEXT + "\n" + "<" + VAR_SCORE_BASE + "/" + VAR_SCORE_NOSTEM + "/" + VAR_SCORE_ADJUSTED + "% " + VAR_FILE_PATH + ">"; public static final Pattern patternSingleProperty = Pattern.compile("@\\{(.+?)\\}"); public static final Pattern patternPropertyGroup = Pattern.compile("@\\[(.+?)\\]\\[(.+?)\\]\\[(.+?)\\]"); private static Replacer sourceTextReplacer = new Replacer() { public void replace(Result R, NearString match) { R.sourcePos = R.text.indexOf(VAR_SOURCE_TEXT); R.text = R.text.replace(VAR_SOURCE_TEXT, match.source); } }; private static Replacer diffReplacer = new Replacer() { public void replace(Result R, NearString match) { int diffPos = R.text.indexOf(VAR_DIFF); SourceTextEntry ste = Core.getEditor().getCurrentEntry(); if (diffPos != -1 && ste != null) { Render diffRender = DiffDriver.render(match.source, ste.getSrcText(), true); R.diffInfo.put(diffPos, diffRender.formatting); R.text = R.text.replace(VAR_DIFF, diffRender.text); } } }; private static Replacer diffReversedReplacer = new Replacer() { public void replace(Result R, NearString match) { int diffPos = R.text.indexOf(VAR_DIFF_REVERSED); SourceTextEntry ste = Core.getEditor().getCurrentEntry(); if (diffPos != -1 && ste != null) { Render diffRender = DiffDriver.render(ste.getSrcText(), match.source, true); R.diffInfo.put(diffPos, diffRender.formatting); R.text = R.text.replace(VAR_DIFF_REVERSED, diffRender.text); } } }; // ------------------------------ subclasses ------------------- /** Class to store formatted text and indications for other treatments **/ public static class Result { public String text = null; public int sourcePos = -1; public final Map<Integer, List<TextRun>> diffInfo = new HashMap<Integer, List<TextRun>>(); } /** A simple interface for making anonymous functions that perform string replacements. */ private interface Replacer { public void replace(Result R, NearString match); } // ------------------------------ non-static part ------------------- /** A sorted map that ensures styled replacements are performed in the order of appearance. */ private Map<Integer, Replacer> styledComponents = new TreeMap<Integer, Replacer>(); public MatchesVarExpansion (String template) { super(template); } /** * Replace property calls by the corresponding value <br> * Format : @{PropertyName} * in this case, retreive only the property value, name is elsewhere. <br> * Format : @[Property name with *][separator 1][separator2] * in this case, return all properties matching the 1st pattern, * as key=value pairs where = is replaced by separator1 and use separator2 between entries.<br> * Expression \n for new line is accepted in separators. * @param localTemplate Initial template * @param props Map of properties * @return Expanded template */ public String expandProperties (String localTemplate, List<TMXProp> props) { Matcher matcher; while ((matcher = patternSingleProperty.matcher(localTemplate)).find()) { String value = getPropValue(props, matcher.group(1)); localTemplate = localTemplate.replace (matcher.group(), value == null ? "" : value); } while ((matcher = patternPropertyGroup.matcher(localTemplate)).find()) { String patternStr = matcher.group(1), separator1 = matcher.group(2), separator2 = matcher.group(3); separator1 = separator1.replace("\\n","\n"); separator2 = separator2.replace("\\n","\n"); Pattern pattern = Pattern.compile(patternStr.replace("*","(.*)").replace("?","(.)")); StringBuilder res = new StringBuilder(); for (TMXProp me: props) { if (pattern.matcher(me.getType().toString()).matches()) res.append(me.getType()).append(separator1).append(me.getValue()).append(separator2); } if (res.toString().endsWith(separator2)) res.replace(res.toString().lastIndexOf(separator2), res.length(), ""); localTemplate = localTemplate.replace(matcher.group(), res.toString()); } return localTemplate; } private String getPropValue(List<TMXProp> props, String type) { for (TMXProp me : props) { if (type.equals(me.getType())) { return me.getValue(); } } return null; } @Override public String expandVariables (NearString match) { String localTemplate = this.template; // do not modify template directly, so that we can reuse for another change localTemplate = localTemplate.replace(VAR_INITIAL_CREATION_ID, match.creator == null ? "" : match.creator); // VAR_CREATION_ID is an alias for VAR_CHANGED_ID, for backwards compatibility. for (String s : new String[] {VAR_CHANGED_ID, VAR_CREATION_ID}) { localTemplate = localTemplate.replace(s, match.changer == null ? "" : match.changer); } if (match.creationDate > 0) localTemplate = localTemplate.replace(VAR_INITIAL_CREATION_DATE, DateFormat.getInstance().format(new Date (match.creationDate))); else localTemplate = localTemplate.replace(VAR_INITIAL_CREATION_DATE, ""); // VAR_CREATION_DATE is an alias for VAR_CHANGED_DATE, for backwards compatibility. for (String s : new String[] {VAR_CHANGED_DATE, VAR_CREATION_DATE}) { if (match.changedDate > 0) localTemplate = localTemplate.replace(s, DateFormat.getInstance().format(new Date (match.changedDate))); else localTemplate = localTemplate.replace(s, ""); } localTemplate = localTemplate.replace(VAR_SCORE_BASE, Integer.toString(match.scores[0].score)); localTemplate = localTemplate.replace(VAR_SCORE_NOSTEM, Integer.toString(match.scores[0].scoreNoStem)); localTemplate = localTemplate.replace(VAR_SCORE_ADJUSTED, Integer.toString(match.scores[0].adjustedScore)); localTemplate = localTemplate.replace(VAR_TARGET_TEXT, match.translation); localTemplate = localTemplate.replace(VAR_FUZZY_FLAG, match.fuzzyMark ? (OStrings.getString("MATCHES_FUZZY_MARK") + " ") : ""); ProjectProperties props = Core.getProject().getProjectProperties(); if (props != null) { localTemplate = expandFileNames(localTemplate, match.projs, props.getTMRoot()); } return localTemplate; } public Result apply(NearString match, int id) { Result R = new Result(); styledComponents.clear(); // Variables R.text = this.expandVariables(match); R.text = R.text.replace(VAR_ID, Integer.toString(id)); // Properties (<prop type='xxx'>value</prop>) if (match.props != null) { R.text = expandProperties(R.text, match.props); } else { R.text = R.text.replaceAll(patternSingleProperty.pattern(), ""); R.text = R.text.replaceAll(patternPropertyGroup.pattern(), ""); } styledComponents.put(R.text.indexOf(VAR_SOURCE_TEXT), sourceTextReplacer); styledComponents.put(R.text.indexOf(VAR_DIFF), diffReplacer); styledComponents.put(R.text.indexOf(VAR_DIFF_REVERSED), diffReversedReplacer); for (Entry<Integer, Replacer> e : styledComponents.entrySet()) { e.getValue().replace(R, match); } return R; } }