<commit_before>/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
/**
 *
 */
package org.biojava.nbio.core.sequence.io;

import org.biojava.nbio.core.sequence.Strand;
import org.biojava.nbio.core.sequence.features.FeatureInterface;
import org.biojava.nbio.core.sequence.features.Qualifier;
import org.biojava.nbio.core.sequence.location.template.AbstractLocation;
import org.biojava.nbio.core.sequence.location.template.Point;
import org.biojava.nbio.core.sequence.template.AbstractSequence;
import org.biojava.nbio.core.sequence.template.Compound;
import org.biojava.nbio.core.util.StringManipulationHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;

/**
 * @author mckeee1
 *
 */
public class GenericInsdcHeaderFormat<S extends AbstractSequence<C>, C extends Compound> {
	protected static final int MAX_WIDTH = 80;
	protected static final int QUALIFIER_INDENT = 21;
	protected static final String QUALIFIER_INDENT_STR = "                     ";
	protected static final String QUALIFIER_INDENT_TMP = "     %s                ";
	private static final String lineSep = "%n";

	/**
	 * Format a feature qualifier using the MAX_WIDTH (default 80)
	 * @param key
	 * @param value
	 * @param quote
	 */
	private String _write_feature_qualifier(String key, String value, boolean quote) {
		String line = "";
		if(null == value) {
			line = QUALIFIER_INDENT_STR + "/" + key + lineSep;
			return line;
		}
		if(quote) {  // quote should be true for numerics
			line = QUALIFIER_INDENT_STR + "/" + key + "=\"" + value + "\"";
		} else {
			line = QUALIFIER_INDENT_STR + "/" + key + "=" + value;
		}
		if(line.length() <= MAX_WIDTH) {
			return line + lineSep;
		}
		String goodlines = "";
		while(!"".equals(line.replaceAll("^\\s+", ""))) {
			if(line.length() <= MAX_WIDTH) {
				goodlines += line + lineSep;
				break;
			}
			//Insert line break...
			int index;
			for(index = Math.min(line.length()-1, MAX_WIDTH); index > QUALIFIER_INDENT ; index--) {
				if(' ' == line.charAt(index)) {
					break;
				}
			}
			if(' ' != line.charAt(index)) {
				//no nice place to break...
				index = MAX_WIDTH;
			}
			assert index <= MAX_WIDTH;
			goodlines += line.substring(0,index) + lineSep;
			line = QUALIFIER_INDENT_STR + line.substring(index).replaceAll("^\\s+", "");
		}
		return goodlines;
	}
	/**
	 * Split a feature location into lines (break at commas).
	 * @param location
	 */
	private String _wrap_location(String location) {
		int length = MAX_WIDTH - QUALIFIER_INDENT;
		if(location.length() <= length) {
			return location;
		}
		int index = location.substring(length).lastIndexOf(",");
		if(-1 == index) {
			//No good place to split (!)
			return location;
		}
		return location.substring(0,index+1) + lineSep + QUALIFIER_INDENT_STR + _wrap_location(location.substring(index+1));
	}
	/**
	 * Write a single SeqFeature object to features table.
	 * @param feature
	 * @param record_length
	 */
	protected String _write_feature(FeatureInterface<AbstractSequence<C>, C> feature, int record_length) {
		String location = _insdc_feature_location_string(feature, record_length);
		String f_type = feature.getType().replace(" ", "_");
		StringBuilder sb = new StringBuilder();
		Formatter formatter = new Formatter(sb,Locale.US);
		formatter.format(QUALIFIER_INDENT_TMP, f_type);
		String line = formatter.toString().substring(0, QUALIFIER_INDENT) + _wrap_location(location) + lineSep;
		formatter.close();

		//Now the qualifiers...
		for(List<Qualifier>  qualifiers : feature.getQualifiers().values()) {
			for(Qualifier q : qualifiers){
				line += _write_feature_qualifier(q.getName(), q.getValue(), q.needsQuotes());
			}
		}
		return line;
		/*
		self.handle.write(line)
		#Now the qualifiers...
		for key, values in feature.qualifiers.items():
			if isinstance(values, list) or isinstance(values, tuple):
				for value in values:
					self._write_feature_qualifier(key, value)
			elif values:
				#String, int, etc
				self._write_feature_qualifier(key, values)<commit_msg>Fixed issue #886 by escaping percent symbols when feature attributes are created<commit_after>/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
/**
 *
 */
package org.biojava.nbio.core.sequence.io;

import org.biojava.nbio.core.sequence.Strand;
import org.biojava.nbio.core.sequence.features.FeatureInterface;
import org.biojava.nbio.core.sequence.features.Qualifier;
import org.biojava.nbio.core.sequence.location.template.AbstractLocation;
import org.biojava.nbio.core.sequence.location.template.Point;
import org.biojava.nbio.core.sequence.template.AbstractSequence;
import org.biojava.nbio.core.sequence.template.Compound;
import org.biojava.nbio.core.util.StringManipulationHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;

/**
 * @author mckeee1
 *
 */
public class GenericInsdcHeaderFormat<S extends AbstractSequence<C>, C extends Compound> {
	protected static final int MAX_WIDTH = 80;
	protected static final int QUALIFIER_INDENT = 21;
	protected static final String QUALIFIER_INDENT_STR = "                     ";
	protected static final String QUALIFIER_INDENT_TMP = "     %s                ";
	private static final String lineSep = "%n";

	/**
	 * Format a feature qualifier using the MAX_WIDTH (default 80)
	 * @param key
	 * @param value
	 * @param quote
	 */
	private String _write_feature_qualifier(String key, String value, boolean quote) {
		String line = "";
		if(null == value) {
			line = QUALIFIER_INDENT_STR + "/" + key + lineSep;
			return line;
		}
		if(quote) {  // quote should be true for numerics
			line = QUALIFIER_INDENT_STR + "/" + key + "=\"" + value + "\"";
		} else {
			line = QUALIFIER_INDENT_STR + "/" + key + "=" + value;
		}
		if(line.length() <= MAX_WIDTH) {
			return line + lineSep;
		}
		String goodlines = "";
		while(!"".equals(line.replaceAll("^\\s+", ""))) {
			if(line.length() <= MAX_WIDTH) {
				goodlines += line + lineSep;
				break;
			}
			//Insert line break...
			int index;
			for(index = Math.min(line.length()-1, MAX_WIDTH); index > QUALIFIER_INDENT ; index--) {
				if(' ' == line.charAt(index)) {
					break;
				}
			}
			if(' ' != line.charAt(index)) {
				//no nice place to break...
				index = MAX_WIDTH;
			}
			assert index <= MAX_WIDTH;
			goodlines += line.substring(0,index) + lineSep;
			line = QUALIFIER_INDENT_STR + line.substring(index).replaceAll("^\\s+", "");
		}
		return goodlines;
	}
	/**
	 * Split a feature location into lines (break at commas).
	 * @param location
	 */
	private String _wrap_location(String location) {
		int length = MAX_WIDTH - QUALIFIER_INDENT;
		if(location.length() <= length) {
			return location;
		}
		int index = location.substring(length).lastIndexOf(",");
		if(-1 == index) {
			//No good place to split (!)
			return location;
		}
		return location.substring(0,index+1) + lineSep + QUALIFIER_INDENT_STR + _wrap_location(location.substring(index+1));
	}
	/**
	 * Write a single SeqFeature object to features table.
	 * @param feature
	 * @param record_length
	 */
	protected String _write_feature(FeatureInterface<AbstractSequence<C>, C> feature, int record_length) {
		String location = _insdc_feature_location_string(feature, record_length);
		String f_type = feature.getType().replace(" ", "_");
		StringBuilder sb = new StringBuilder();
		Formatter formatter = new Formatter(sb,Locale.US);
		formatter.format(QUALIFIER_INDENT_TMP, f_type);
		String line = formatter.toString().substring(0, QUALIFIER_INDENT) + _wrap_location(location) + lineSep;
		formatter.close();

		//Now the qualifiers...
		for(List<Qualifier>  qualifiers : feature.getQualifiers().values()) {
			for(Qualifier q : qualifiers){
				line += _write_feature_qualifier(q.getName().replaceAll("%", "%%"), q.getValue().replaceAll("%", "%%"), q.needsQuotes());
			}
		}
		return line;
		/*
		self.handle.write(line)
		#Now the qualifiers...
		for key, values in feature.qualifiers.items():
			if isinstance(values, list) or isinstance(values, tuple):
				for value in values:
					self._write_feature_qualifier(key, value)
			elif values:
				#String, int, etc
				self._write_feature_qualifier(key, values)
{'fraction_non_alphanumeric': 0.12883566770020313, 'fraction_numerical': 0.0033144445632417407, 'mean_word_length': 3.594302554027505, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5016724', 'n_tokens_mistral': 3246, 'n_tokens_neox': 2951, 'n_words': 750}