2008-09-17 13 views

Odpowiedz

51

Można użyć string.Format na łatwe pad wartość ze spacjami np

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 |" 
+8

Jest jednak jedno zastrzeżenie: jeśli dane są dłuższe niż określona szerokość, nie zostaną obcięte. Będziesz musiał użyć danych. Podciąganie (0, szerokość) na dowolnym polu potencjalnie dłuższym niż szerokość, co powoduje, że otrzymasz szczegółowe (i powtarzające się/nie-DRY) szybkie. – brianary

+0

Tylko że jestem powolny, ponieważ tęskniłem za nim, ale jeśli wynik jest wyświetlany, to czcionka musi zostać poprawiona również szerokość – ScruffyDuck

0

Nie możesz użyć standardowego pliku tekstowego? Możesz odczytywać linię danych po linii.

+0

Niekoniecznie, plik tekstowy nie może być odczytany przez innego C# aplikacji, ale główną ramę, która potrzebuje specjalnie sformatowany plik tekstowy. –

0

Spróbuj użyć myString.PadRight (totalLengthForField, ' „)

1

Można użyć StreamWriter oraz w Write (string) stosowanie połączeń String. Formatuj(), aby utworzyć ciąg o właściwej szerokości dla danego pola.

7

Użyj funkcji .PadRight (dla wyrównanych do lewej strony) klasy String. A więc:

handle.WriteLine(s20.PadRight(20)); 
handle.WriteLine(s80.PadRight(80)); 
handle.WriteLine(s10.PadRight(10)); 
handle.WriteLine(s2.PadRight(2)); 
0

Można użyć ASCIIEncoding.UTF8.GetBytes (tekst), aby przekonwertować go na tablicę bajtów. Następnie wypisz tablice bajtów do pliku jako rekord o stałym rozmiarze.

UTF8 różni się liczbą bajtów wymaganych do przedstawienia niektórych znaków, UTF16 jest nieco bardziej przewidywalny, 2 bajty na znak.

25

Jest to system, zrobiłem dla konfigurowalnego modułu stałej szerokości zapisu plików. Jest skonfigurowany z pliku XML, odpowiednia część wygląda tak:

<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 informuje program, czy Twoje pozycje są oparte na 0 lub 1-na podstawie. Zrobiłem to konfigurowalnym, ponieważ kopiowałem z offsetu ze specyfikacji i chciałem, aby konfiguracja była jak najbardziej zbliżona do specyfikacji, bez względu na to, jaki indeks początkowy wybrał autor.

Atrybut Name na znacznikach Position odnosi się do nazw kolumn w DataTable.

Poniższy kod został napisany dla .Net 3.5, używając LINQ-do-XML, więc metoda zakładała, że ​​zostanie przekazany XElement z powyższą konfiguracją, którą możesz uzyskać po użyciu XDocument.Load(filename) w celu załadowania pliku XML, a następnie wywołania .Descendants("WriteFixedWidth") na obiekcie XDocument, aby uzyskać konfigurację element.

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(); 
    } 

Silnik jest StringBuilder, który jest szybszy niż złączenie niezmienne ciągi razem, zwłaszcza jeśli jesteś przetwarzania plików multi-megabajt.

+3

Po prostu muszę powiedzieć, że to genialny pomysł, uwielbiam to! +1 – Mohgeroth

+0

Nie mogę odwoływać się do 'Namespaces.Integration', czy możesz podać więcej szczegółów na ten temat. – vikas

+0

Vikas, można znaleźć więcej informacji tutaj, https://msdn.microsoft.com/en-us/library/bb353813(v=vs.110).aspx. Jeśli twój dokument XML używa przestrzeni nazw, to wtedy używa się Namespaces.Integration. – CodingSerge

2

Używam metodę rozszerzenia na ciąg, tak XML komentowania może wydawać OTT za to, ale jeśli chcesz innych deweloperów do ponownego wykorzystania ...

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); 
    } 
} 
+0

... i można użyć standardowych rzeczy .NET FileStream do napisania pliku oczywiście. – Darren

0

odpowiedź Darrena na to pytanie zainspirowało używać metod rozszerzeń, ale zamiast rozszerzać String, rozszerzyłem StringBuilder. Napisałem dwie metody:

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); 
} 

Pierwszy dyskretnie pomija zbyt długi łańcuch i po prostu odcina koniec, a drugi powraca odciąć część poprzez out parametr metody.

Przykład:

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); 
Powiązane problemy