3

I have a class that has a string field input which contains UTF-8 characters. My class also has a method toString. I want to save instances of the class to a file using the method toString. The problem is that strange symbols are being written in the file:

my $dest = "output.txt";

print "\nBefore saving to file\n" . $message->toString() . "\n";

open (my $fh, '>>:encoding(UTF-8)', $dest) 
    or die "Cannot open $dest : $!";

lock($fh);
print $fh $message->toString();
unlock($fh);
close $fh;       

The first print works fine

Input: {"paramkey":"message","paramvalue":"здравейте"}

is being printed to the console. The problem is when I write to the file:

Input: {"paramkey":"message","paramvalue":"здÑавейÑе"}

I used flock for locking/unlocking the file.

Borodin
  • 126,100
  • 9
  • 70
  • 144
bob_saginowski
  • 1,429
  • 2
  • 20
  • 35
  • Which class does object `$message` belongs to? – MarcoS Sep 02 '16 at 15:52
  • what about `binmode` to print – ssr1012 Sep 02 '16 at 15:55
  • *"I have a class that has a string field input which contains utf-8 characters"* Your `input` field shouldn't be encoded: it should consist of unencoded characters. Decoding and encoding should be done on input and output to allow your code to work entirely in characters. – Borodin Sep 02 '16 at 16:30
  • 1
    try use `use utf8; my $a=$message->toString(); utf8::decode($a); print $fh $a;` – Mike Sep 02 '16 at 17:41
  • You have had a number of answers. Do none of them resolve your question? Please take a look at [*What should I do when someone answers my question?*](http://stackoverflow.com/help/someone-answers), as this post should be either pursued further or marked as resolved. – Borodin Sep 07 '16 at 20:46

3 Answers3

1

I suppose you miss
use utf8;
in your code...

This code produces the "output.txt" file you do expect:

#!/usr/bin/perl
use strict;
use utf8;

my $dest = "output.txt";
my $message = "здравейте";

print "\nBefore saving to file\n" . $message . "\n";

open (my $fh, '>>:encoding(UTF-8)', $dest)
    or die "Cannot open $dest : $!";

lock($fh);
print $fh $message;
close $fh;

I did not use toString() method because I'm working on native strings, not real objects, but this does not change the substance...

MarcoS
  • 17,323
  • 24
  • 96
  • 174
  • 1
    `use utf8;` mostly means that your code is in utf8, doesn't really change the input/output characteristics. – Tanktalus Sep 02 '16 at 16:11
  • This is simply wrong. `use utf8` only indicates that the Perl source file is UTF-8-encoded, and since the code is all in 7-bit ASCII it would have no effect at all. – Borodin Sep 07 '16 at 20:52
1

The contents of the string returned by your toString method are already UTF-8 encoded. That works fine when you print it to your terminal because it is expecting UTF-8 data. But when you open your output file with

open (my $fh, '>>:encoding(UTF-8)', $dest) or die "Cannot open $dest : $!"

you are asking that Perl should reencode the data as UTF-8. That converts each byte of the UTF-8-encoded data to a separate UTF-8 sequence, which isn't what you want at all. Unfortunately you don't show your code for the class that $message belongs to, so I can't help you with this

You can fix that by changing your open call to just

open (my $fh, '>>', $dest) or die "Cannot open $dest : $!"

which will avoid the additional encoding step. But you should really be working with unencoded characters throughout your Perl code: removing any encoding from files you are reading from, and encoding output data as necessary when you write to output files.

Borodin
  • 126,100
  • 9
  • 70
  • 144
0

How does your toString method work? I would guess, based on the output you've provided, that the toString method is producing bytes instead of characters, and then perl is getting confused when trying to convert it.

Try binmode STDOUT, ':encoding(UTF-8)' before your print to see if it produces the same output as the file - otherwise your test is apples and oranges.

If it's already bytes instead of characters, you can open your $dest without any encoding(...) layer and it'll work.

In general, I find it quite painful to work in characters over bytes, but since it resolves more corner cases that I don't have to think about anymore, the extra work becomes worth it, but it is extra work.

Tanktalus
  • 21,664
  • 5
  • 41
  • 68
  • 2
    *"In general, I find it quite painful to work in characters over bytes"*. That is strange. Using a simple byte string you have to be aware of what encoding it uses and allow for multi-byte characters. Surely, when you're processing strings you want a character to be just a character, independent of its encoding. – Borodin Sep 02 '16 at 16:27
  • @Borodin - 99% of the time, I don't have to worry about encodings. Things Just Work (TM). When I have to worry about encodings at all, things get harried and difficult, and then I have to pay a lot of attention to the details about what encoding I need where, and then it's painful. However, once I get the encodings lined up between perl and all the other processes / files, then it works nicely. – Tanktalus Sep 07 '16 at 19:24
  • I understand your frustration, and things mostly work because the majority of data is simple 7-bit ASCII, and designers of character encodings have made sure that those 128 characters are mostly unchanged. But I don't get your preference for working in bytes over characters, as it exposes you to the internals of whatever encodings are thrown at you. Consider the small Greek mu `μ` used for "micro". If you insist on working in bytes then that will be a value of 0x3F if your source is encoded in ISO-8859-1, or the two bytes 0xCE 0xBC if the encoding is UTF-8. – Borodin Sep 07 '16 at 20:36
  • On the other hand, if you inform Perl how your sources are encoded, the result will be the 32-bit Unicode character 0x03BC regardless of the original encoding. If you meant "I wish all my data was ASCII" then I understand completely, but 128 characters just aren't enough for many purposes. If you hold on to the paradigm of decoding and encoding all input and output (preferably using the appropriate Perl IO layers on the file handles) then your code normally needs to deal only in a consistent set of Unicode characters. Surely that's a more attractive proposition? – Borodin Sep 07 '16 at 20:41
  • 99% of the time, I don't have to worry about mu, either :P Most of the time, and quite often even when dealing with non-English text, I can get away with reading in a file, doing whatever with it, and writing it back out. Rarely do I need to manipulate anything that isn't in English text. And then it doesn't matter. All my one-liners, almost all of my tooling, etc., I don't need to worry. I've been our local locale expert, I can do it, but, thankfully, most of the time, I don't have to. – Tanktalus Sep 07 '16 at 21:11
  • That's what I'm saying. Data is usually 7-bit ASCII, but sometimes you need to process non-ASCII data, and then it seems perverse to me to insist on working with the byte-level data instead of characters. There are some quite common non-ASCII characters: The *no-break space* for HTML perhaps, or various currencies like the UK pound `£`, the yen `¥` or the cent `¢`, as well as the copyright `©` and registered trade mark `®` symbols. If you can pick your jobs so that you never have to work in non-ASCII then great, but always working in bytes will leave you frustrated. – Borodin Sep 07 '16 at 21:25