2013-05-31 2 views
1

Я пытался исправить эту проблему в течение нескольких дней, и я не могу ее получить. В принципе, мой код должен читать файл .csv, созданный wmic, и сохранять его в структуре. Я могу читать данные, и они хранятся, но после каждого символа данные имеют дополнительное пространство. Я попытался переключиться на Unicode-версии функций и использовать широкие строки, но они еще больше испортили данные (они превратили «n» в «ÿ»).C++ getline добавочные пространства

Вот код, который я думаю, это вопрос:

system("wmic product get name,version,installdate,vendor /format:csv > product.txt"); 

std::ifstream infoFile("./program.txt"); // The file wmic wrote in csv format. 

if(infoFile.is_open()) 
{ 
    std::string line; 
    int lineNum = 0; 

    while(getline(infoFile, line)) 
    { 
     lineNum++; 
     std::cout << "\nLine #" << lineNum << ":" << std::endl; 

     Program temp; 
     std::istringstream lineStream(line); 
     std::string cell; 
     int counter = 0; 
     int cellNum = 0; 

     while(getline(linestream, cell, ',')) 
     { 
      cellNum++; 
      std::cout << "\nCell #" << cellNum << ":" << cell << std::endl; 

      switch(counter) 
      { 
      case 0: 
       break; 
      case 1: 
       temp.installDate = cell; 
       break; 
      case 2: 
       temp.name = cell; 
       break; 
      case 3: 
       temp.vendor = cell; 
       break; 
      case 4: 
       temp.version = cell; 
       break; 
      default: 
       std::cout << "GetProductInfo(): Invalid switch value: " << counter << std::endl; 
       break; 
      } 
      counter++; 
     } 

     information->push_back(temp); // Vector to save all of the programs. 
    } 

    infoFile.close(); 
} 
else 
{ 
    std::cout << "GetProductInfo(): Failed to open the input file." << std::endl; 
    return 1; 
} 

return 0; 
} 

Edit: ОК, я пытаюсь написать (0A FF FE 0D 00) BOM, поскольку он не писался до. Я пишу массив char с шестнадцатеричными значениями, но добавляется дополнительный 0x0D (FF FE 0D 00 0D 0A). Он также сохраняет внутренние переменные с дополнительными пробелами. То, что может не будет проблемой, поскольку я могу изменить свой код для его учета, но это было бы не оптимальным. Есть идеи?

Редактировать2: Так что, я думаю, мне не нужна спецификация. Моя главная проблема сейчас - просто прочитать файл UTF-16LE и сохранить данные в структуре без лишних пробелов. Мне нужна помощь, чтобы сделать это правильно, так как я хотел бы выяснить, как предотвратить это в будущем. Спасибо за помощь всем, эта ошибка имеет решающее значение.

+0

Имеет ли файл program.txt дополнительные пробелы при открытии его внутри редактора? Или сразу после того, как вы прочитали его в своей программе? –

+0

Вы перенаправляетесь на 'product.txt', но читаете из' program.txt'. Это намеренно? – celtschk

ответ

4

Это сильно пахнет проблемой текстового кодирования, поэтому я пошел вперед и попытался выполнить команду, которую вы предоставили, и, конечно же, выходной файл закодирован в UCS16LE. (Это 16-битные символы, little-endian.) Попробуйте открыть файл в шестнадцатеричном редакторе, чтобы увидеть, как он выглядит на самом деле.

Вы были на правильном пути, пытаясь использовать широкие строки, но иметь дело с Unicode может быть сложным. Следующие несколько параграфов дадут вам несколько советов о том, как с этим бороться, но если вам нужно быстрое и простое решение, переходите к концу.

Есть две вещи, которые нужно соблюдать. Во-первых, убедитесь, что вы также используете широкие потоки, например, wcout. Стоит лить каждого символа в int, чтобы дважды проверить, что нет проблемы с форматированием вывода.

Во-вторых, формат wcout, wstring и т. Д. Не является стандартным. В некоторых компиляторах это 2 байта на символ, а на других - 4. Обычно вы можете изменить это в своих настройках компилятора. C++ 11 также предоставляет std :: u16string и std :: u32string, которые более подробно описывают их размер.

Чтение текста в Юникоде, к сожалению, может быть довольно сложным с библиотекой C++, потому что даже если у вас есть нужный размер строки, вам нужно иметь дело с спецификациями и форматами endian, не говоря уже о канонизации.

Есть библиотеки, которые могут помочь с этим, но самым простым решением может быть просто открыть файл txt в «Блокноте», выбрать «Сохранить как», а затем выбрать более удобную для вас кодировку, такую ​​как ANSI.

Редактировать: Если вас не устраивает быстрое и грязное решение, и вы не хотите использовать лучшую библиотеку Unicode, вы можете сделать это со стандартной библиотекой, но только если вы используете компилятор, который поддерживает C++ 11, например Visual Studio 2012.

В C++ 11 добавлены некоторые грани codecvt для обработки преобразований между различными типами файлов Unicode. Это должно соответствовать вашей цели, но базовый дизайн этой части библиотеки был спроектирован в дни или годы, и их может быть довольно сложно понять. Держись за штаны.

Ниже строки, где вы открываете ifstream, добавьте этот код:

infoFile.imbue(std::locale(infoFile.getloc(), new std::codecvt_utf16<char, 0x10FFFF, std::consume_header>)); 

Я знаю, что выглядит немного страшно. То, что он делает, - это сделать «локаль» из копии существующего языка, а затем добавить «грань» в локаль, которая обрабатывает преобразование формата.

«Локали» обрабатывают целую кучу вещей, в основном связанных с локализацией (например, как акцентировать валюту, например «100,00» против «100,00»). Каждое из правил в локали называется фасет. В стандартной библиотеке C++ кодирование файлов рассматривается как один из этих аспектов.

(Фон: ретроспективно, вероятно, было не очень мудрой идеей смешать кодировку файлов с локализацией, но в то время, когда эта часть библиотеки была разработана, кодирование файлов обычно диктовалось языком программы , так вот как мы попали в эту ситуацию.)

Таким образом, конструктор locale берет копию по умолчанию locale, созданный файловым потоком в качестве первого параметра, а вторым параметром является новый фасет.

codecvt_utf16 - это фасет для преобразования в и из utf-16. Первым параметром является «широкий» тип, то есть тип, используемый программой, а не тип, используемый в потоке байтов. Я указал здесь char и работает с Visual Studio, но на самом деле он не действует в соответствии со стандартом. Я позабочусь об этом позже.

Второй параметр - это максимальное значение Unicode, которое вы хотите принять, не вызывая ошибки, и в обозримом будущем 0x10FFFF представляет собой самый большой символ Юникода.

Последний параметр - это битовая маска, которая изменяет поведение фасета.Я думал, что std::consume_header будет особенно полезен для вас, так как wmic выводит спецификацию (по крайней мере, на моей машине). Это будет потреблять эту спецификацию и выбрать, следует ли рассматривать ее как поток мало или big-endian в зависимости от того, что она получает.

Вы также заметите, что я создаю фасет в стеке с new, но я не звоню delete в любом месте. Это не очень безопасный способ создания библиотеки на современном C++, но, как я уже сказал, локали являются довольно старой частью библиотеки.

Будьте уверены, что вам не нужно delete этот аспект. На самом деле это не очень хорошо документировано (поскольку локали настолько редко используются на практике), но фасет по умолчанию будет автоматически delete d по языку, к которому он привязан.

Теперь, помните, как я сказал, что недействительно использовать char как широкий тип? В стандарте говорится, что вы должны использовать whcar_t, char16_t или char32_t, и если вы хотите поддерживать символы, отличные от ASCII, вы обязательно захотите это сделать. Самый простой способ сделать это в силе будет использовать wchar_t, изменить ifstream, string, cout и istringstream к wifstream, wstring, wcout и wistringstream, то убедитесь, что постоянные ваши строки/полукокса имеют перед ними в L, как так :

std::wcout << L"\nLine #" << lineNum << L":" << line << std::endl; 

Все эти изменения необходимы, чтобы использовать широкие струны. Однако также будьте осторожны, что консоль Windows не может обрабатывать символы, отличные от ANSI, поэтому, если вы попытаетесь вывести такой символ (когда я запустил код, я попал в символ ™), поток wcout будет недействительным и перестанет выдавать что-либо. Если вы выводите файл, это не должно быть проблемой.

Возможно, вы можете сказать, что я не особенно волнуюсь об этой части стандартной библиотеки. На практике большинство людей, которые хотят использовать Unicode, будут использовать другую библиотеку (например, те, что я упомянул в комментариях), или перескакивают свои собственные кодеры/декодеры.

+0

Спасибо за очень информативный ответ! Есть ли способ автоматизировать преобразование кодировки файлов? Я пытаюсь написать инструмент, который не требует вмешательства пользователя. – IWillByte

+0

Существует множество способов автоматизировать его. Каноническая библиотека называется ICU, хотя она немного сложна. QTextStream Qt проще в использовании.Если вы довольны просто взломом, и вам нужно только поддерживать версии Windows с PowerShell, вы можете использовать командлет [Set-Content] (http://www.powershelladmin.com/wiki/Convert_from_most_encodings_to_utf8_with_powershell). –

+0

О.К., я пытаюсь написать спецификацию (FF FE 0D 00 0A), поскольку она не писалась ранее. Я пишу массив char с шестнадцатеричными значениями, но добавляется дополнительный 0x0D (FF FE 0D 00 0D 0A). Есть идеи? Еще раз спасибо за помощь. – IWillByte

0

Если данные не имеют каких-либо пробелов, нужно вы можете использовать мой пример:

std::string s = "test, delim, "; 
std::string delims = ", "; 

size_t pos = 0; 
std::string token; 

while((pos=s.find(delimiter))!=std::string::npos)) 
{ token = s.substr(0,pos); 
    std::cout<<token<<std::endl; 
    s.erase(0, pos + delimiter.length()); 
} 
std::cout<<s<<std::endl //last word 

В качестве альтернативы, вы можете использовать strtok из cstring библиотеки. Вы также можете проверить мой вопрос, это довольно то же самое: strtok() analogue in C++

+0

Мне нужны пробелы, потому что я сохраняю имена программ и поставщиков, которые часто имеют несколько слов. – IWillByte

+0

Вы можете попробовать метод 'erase' для строк, чтобы удалить последний charcter. – ka2m

+0

Что это значит? Если вы говорите, что он удалит последний символ ячейки, это не будет работать, потому что после каждого символа есть пробел, а не только последний. – IWillByte

0

Если данные имеют дополнительное пространство после каждый характера, я предполагаю, что это означает, что она также имеет дополнительное пространство после регулярного пространства.

Таким образом, вы можете безопасно стереть каждое пространство (каждый символ, фактически), который не имеет другого пространства прямо перед ним. Это предполагает, что у вас нет двух пробелов в строке исходных данных, но если вы это сделаете, вам нужен только один дополнительный флаг, чтобы позаботиться об этом.

Так что ваш код может стать чем-то вроде этого:

while(getline(infoFile, line)) 
{ 
    int lsize = line.size(), at = 1; 
    for(int i = 1; i < lsize; ++i) 
     if(line[i-1] == ' ') line[at++] = line[i]; 
     // if there is no space behind it, skip it, it is a broken space itself! 
    line.resize(at); 

    lineNum++; 
    // std::cout << "\nLine #"... 

Я понимаю, что это не completelly идеал, как вы на самом деле не останавливая основную проблему от случаться, но учитывая, что вы пытались в течение нескольких дней, это, по крайней мере, эффективно смягчает проблему, фиксируя ее после ее возникновения.

Проверьте live demo.