Quelle est la meilleure façon de créer un fichier à largeur fixe en C #. J'ai un tas de champs avec des longueurs à écrire. Dites 20,80.10,2 etc. tous alignés à gauche. Y a-t-il un moyen facile de faire ceci?
Vous pouvez utiliser string.Format pour remplir facilement une valeur avec des espaces, par exemple.
string a = String.Format("|{0,5}|{1,5}|{2,5}", 1, 20, 300);
string b = String.Format("|{0,-5}|{1,-5}|{2,-5}", 1, 20, 300);
// 'a' will be equal to "| 1| 20| 300|"
// 'b' will be equal to "|1 |20 |300 |"
Il s'agit d'un système que j'ai créé pour un module d'écriture de fichier à largeur fixe configurable. Il est configuré avec un fichier XML, la partie pertinente ressemblant à ceci:
<WriteFixedWidth Table="orders" StartAt="1" Output="Return">
<Position Start="1" Length="17" Name="Unique Identifier"/>
<Position Start="18" Length="3" Name="Error Flag"/>
<Position Start="21" Length="16" Name="Account Number" Justification="right"/>
<Position Start="37" Length="8" Name="Member Number"/>
<Position Start="45" Length="4" Name="Product"/>
<Position Start="49" Length="3" Name="Paytype"/>
<Position Start="52" Length="9" Name="Transit Routing Number"/>
</WriteFixedWidth>
StartAt indique au programme si vos positions sont basées sur 0 ou 1. J'ai rendu cela configurable car je copierais les décalages des spécifications et je voulais que la configuration ressemble autant que possible aux spécifications, quel que soit l'index de départ choisi par l'auteur.
L'attribut Name sur les balises Position fait référence aux noms des colonnes dans un DataTable.
Le code suivant a été écrit pour .Net 3.5, en utilisant LINQ-to-XML, donc la méthode a supposé qu'il serait passé un XElement avec la configuration ci-dessus, que vous pouvez obtenir après avoir utilisé XDocument.Load(filename)
pour charger le Fichier XML, puis appelez .Descendants("WriteFixedWidth")
sur l'objet XDocument
pour obtenir l'élément de configuration.
public void WriteFixedWidth(System.Xml.Linq.XElement CommandNode, DataTable Table, Stream outputStream)
{
StreamWriter Output = new StreamWriter(outputStream);
int StartAt = CommandNode.Attribute("StartAt") != null ? int.Parse(CommandNode.Attribute("StartAt").Value) : 0;
var positions = from c in CommandNode.Descendants(Namespaces.Integration + "Position")
orderby int.Parse(c.Attribute("Start").Value) ascending
select new
{
Name = c.Attribute("Name").Value,
Start = int.Parse(c.Attribute("Start").Value) - StartAt,
Length = int.Parse(c.Attribute("Length").Value),
Justification = c.Attribute("Justification") != null ? c.Attribute("Justification").Value.ToLower() : "left"
};
int lineLength = positions.Last().Start + positions.Last().Length;
foreach (DataRow row in Table.Rows)
{
StringBuilder line = new StringBuilder(lineLength);
foreach (var p in positions)
line.Insert(p.Start,
p.Justification == "left" ? (row.Field<string>(p.Name) ?? "").PadRight(p.Length,' ')
: (row.Field<string>(p.Name) ?? "").PadLeft(p.Length,' ')
);
Output.WriteLine(line.ToString());
}
Output.Flush();
}
Le moteur est StringBuilder, qui est plus rapide que la concaténation de chaînes immuables ensemble, surtout si vous traitez des fichiers de plusieurs mégaoctets.
Essayez FileHelpers: www.filehelpers.com
Voici un exemple: http://www.filehelpers.com/quick_start_fixed.html
Utilisez la fonction .PadRight (pour les données alignées à gauche) de la classe String. Alors:
handle.WriteLine(s20.PadRight(20));
handle.WriteLine(s80.PadRight(80));
handle.WriteLine(s10.PadRight(10));
handle.WriteLine(s2.PadRight(2));
Vous pouvez utiliser http://www.filehelpers.com/
J'utilise une méthode d'extension sur chaîne, oui, le commentaire XML peut sembler OTT pour cela, mais si vous voulez que d'autres développeurs réutilisent ...
public static class StringExtensions
{
/// <summary>
/// FixedWidth string extension method. Trims spaces, then pads right.
/// </summary>
/// <param name="self">extension method target</param>
/// <param name="totalLength">The length of the string to return (including 'spaceOnRight')</param>
/// <param name="spaceOnRight">The number of spaces required to the right of the content.</param>
/// <returns>a new string</returns>
/// <example>
/// This example calls the extension method 3 times to construct a string with 3 fixed width fields of 20 characters,
/// 2 of which are reserved for empty spacing on the right side.
/// <code>
///const int colWidth = 20;
///const int spaceRight = 2;
///string headerLine = string.Format(
/// "{0}{1}{2}",
/// "Title".FixedWidth(colWidth, spaceRight),
/// "Quantity".FixedWidth(colWidth, spaceRight),
/// "Total".FixedWidth(colWidth, spaceRight));
/// </code>
/// </example>
public static string FixedWidth(this string self, int totalLength, int spaceOnRight)
{
if (totalLength < spaceOnRight) spaceOnRight = 1; // handle silly use.
string s = self.Trim();
if (s.Length > (totalLength - spaceOnRight))
{
s = s.Substring(0, totalLength - spaceOnRight);
}
return s.PadRight(totalLength);
}
}
Vous pouvez utiliser un StreamWriter et dans l'appel Write (chaîne) utiliser String.Format () pour créer une chaîne qui est la largeur correcte pour le champ donné.
La réponse de Darren à cette question m'a inspiré à utiliser des méthodes d'extension, mais au lieu d'étendre String
, j'ai étendu StringBuilder
. J'ai écrit deux méthodes:
public static StringBuilder AppendFixed(this StringBuilder sb, int length, string value)
{
if (String.IsNullOrWhiteSpace(value))
return sb.Append(String.Empty.PadLeft(length));
if (value.Length <= length)
return sb.Append(value.PadLeft(length));
else
return sb.Append(value.Substring(0, length));
}
public static StringBuilder AppendFixed(this StringBuilder sb, int length, string value, out string rest)
{
rest = String.Empty;
if (String.IsNullOrWhiteSpace(value))
return sb.AppendFixed(length, value);
if (value.Length > length)
rest = value.Substring(length);
return sb.AppendFixed(length, value);
}
La première ignore silencieusement une chaîne trop longue et coupe simplement la fin de celle-ci, et la seconde retourne la partie coupée via le paramètre out
de la méthode.
Exemple:
string rest;
StringBuilder clientRecord = new StringBuilder();
clientRecord.AppendFixed(40, doc.ClientName, out rest);
clientRecord.AppendFixed(40, rest);
clientRecord.AppendFixed(40, doc.ClientAddress, out rest);
clientRecord.AppendFixed(40, rest);
Les différents messages de remplissage/formatage antérieurs fonctionneront suffisamment, mais vous pourriez être intéressé par la mise en œuvre d'ISerializable.
Voici un article msdn sur Sérialisation d'objets dans .NET
Vous ne pouvez pas utiliser un fichier texte standard? Vous pouvez relire les données ligne par ligne.
utilisez String.Format ()
Essayez d'utiliser myString.PadRight (totalLengthForField, '')
Vous voulez dire que vous voulez remplir tous les chiffres à droite avec des espaces?
Si c'est le cas, String.PadRight ou String.Format devrait vous mettre sur la bonne voie.
Vous pouvez utiliser ASCIIEncoding.UTF8.GetBytes (texte) pour le convertir en un tableau d'octets. Ensuite, écrivez les tableaux d'octets dans le fichier en tant qu'enregistrement de taille fixe.
UTF8 varie dans le nombre d'octets requis pour représenter certains caractères, UTF16 est un peu plus prévisible, 2 octets par caractère.