format stuff

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
17 messages Options
Reply | Threaded
Open this post in threaded view
|

format stuff

Ripcat UK
I would like to know if there is a simple means of formatting in columns the
output of data to a file in Smalltalk. ie so that you get this....

a          1           z
ab        12         z
abc      123       z    (actually that doesnt work exactly in this post!)

instead of :-

a         1            z
ab          12          z
abc             123          z
 
etc
in C there is a setwidth() or something. Is there a strightforward way ofoding
this in ST? The code I am  doing it with is ad hoc and is becoming awfully
complicated.


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ian Bartholomew-18
> I would like to know if there is a simple means of formatting in columns
t> he output of data to a file in Smalltalk. ie so that you get this....
[]
> in C there is a setwidth() or something. Is there a strightforward way
> ofoding this in ST? The code I am  doing it with is ad hoc and is
> becoming awfully complicated.

To some extent it depends on how you will be viewing the output file after
it has been created.

If you will be viewing the file using a fixed width font you can use the
Dolphin version of the #sprintf function.  So

'%6d' sprintfWith: 123

will output 123 right justified in a field of 6 characters.

%-6s' sprintfWith: 'abc'

will output "abc" left justified in a 6 character field.  This only works
for Integer, Character and String arguments though - Floats are not
supported.

In a similar vein, Dolphin also support a set of #formatWith: methods.

If you are viewing with a variable width font then the easiest way is just
to separate the fields using tabs (which will obviously also work with fixed
pitch fonts).  This will align the starts of the fields and, if the viewing
editor supports it, will also allow variable width tabs with centre or right
justification (and even line up the decimal points) within each tab field.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Chris Uppal-3
Ian, Ripcat

A quick word of warning: do *NOT* use String>>sprintfWith:with: without *FIRST*
fixing the buffer bug.  The buffer should be allocated inside the loop (as it
is in String>>sprintfWith:)

 ...
 crt := CRTLibrary default.
 size := self size + 128.
 [
      buf := String new: size.
      n := crt _snprintf: buf count: size format: self with: arg1 with: arg2.
      ...

Without the fix you will be at risk of destroying your image.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Christopher J. Demers
In reply to this post by Ripcat UK
Ripcat UK <[hidden email]> wrote in message
news:[hidden email]...
>
> I would like to know if there is a simple means of formatting in columns
the
> output of data to a file in Smalltalk. ie so that you get this....
>
> a          1           z
> ab        12         z
> abc      123       z    (actually that doesnt work exactly in this post!)
...
> in C there is a setwidth() or something. Is there a strightforward way
ofoding
> this in ST? The code I am  doing it with is ad hoc and is becoming awfully
> complicated.

I don't think this is exactly what you want unless you can use RTF, and want
to invest in a little framework overhead.  If not of use, perhaps it will be
of interest.  The RTF "decoration" could be built into an RTF report stream.
The generated RTF files can be opened in just about any decent document
editor (even WordPad), and Ian has a great goodie for RTF printing.
Unfortunately I think my code is going to be mangled a bit.

=============
strm := String writeStream.
"RTF Header."
strm nextPutAll: '{\rtf1\ansi\deff0{\fonttbl{\f0\froman Times
New;}}{\colortbl\red0\green0\blue0;}{\stylesheet{\fs20\snext0
Normal;}}\margl1440\margr720\ftnbj\ftnrestart\sectd\sbknone\endnhere\pard\sl
0\fs22'.
"Set Tab Stops."
strm nextPutAll: '{\tx100\tx200\tx300 '.
data := #(('a' 1 'z')('ab' 12 'z')('abc' 123 'z')).
data do: [:eachRow |
 eachRow do: [:eachColumn |
  strm nextPutAll: eachColumn displayString] separatedBy: [strm tab].
 strm nextPutAll: '\par '].
"Close tab section and document."
strm nextPutAll: '}}'.
"Display the results."
rte := RichTextEdit show.
rte rtfText: strm contents.
=============

Chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ian Bartholomew-18
In reply to this post by Chris Uppal-3
Chris,

> Without the fix you will be at risk of destroying your image.

Slight moment of panic there :-)

On checking I see that after your earlier warning I had added the fix to my
image setup script and just forgotten about it.

Thanks for the heads-up though

Regards
    Ian


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Chris Uppal-3
In reply to this post by Christopher J. Demers
Chris, Ripcat,

> I don't think this is exactly what you want unless you can use RTF, and want
> to invest in a little framework overhead.

Just curious: does this example actually work for you -- on my machine (W2K) I
have to change the tabstops to something like:
    strm nextPutAll: '{\tx600\tx1200\tx1800 '

Or they are too small to affect the layout.

Also, as I mentioned in the "reports" thread a little while ago, there's a
package at:

    http://www.smalltalking.net/Goodies/Dolphin/

that *seems* to provide a comprehensive (I haven't used it) framework for RTF
generation.  May be overkill, though...

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ripcat UK
Hi. Thanks for the replies. Perhaps I should rephrase this, as I am a beginner
at all this. What is the "simplest" way of achieving this....can you send
things like strings out to a stream in columns, right justified to avoid my
problem. What class would these methods be found in (I only have the dolphin
free download)


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ripcat UK
hmm, I am also not sure why sprintf with a letter, ie 'z', gives me a big long
number as a reply...


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ripcat UK
'%6d' sprintfWith: 'tom'

gives me the following answer  !!

'37089900'

not only that, but everything ELSE I do it with afterwards gives me the same
answer too. It seemed to work for a few minutes and then that started
happening. Wazzatallabowt?


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Chris Uppal-3
In reply to this post by Ripcat UK
Ripcat,

[I hope you don't mind me calling you by your first name ;-) ]

> Hi. Thanks for the replies. Perhaps I should rephrase this, as I am a
beginner

Ah, right.  Sorry.

> at all this. What is the "simplest" way of achieving this....can you send
> things like strings out to a stream in columns, right justified to avoid my
> problem.

Well, the *really* simple way -- that might be good enough, but often isn't --
is just to use hard tabs for the column separators in your file. Something
like:

    (FileStream write: 'C:\temp\somethiing.txt')
        display: 'a'; tab;
        display: 12; tab;
        display: $z; tab;
        cr;
        ... and so on...
        close.

Otherwise it depends heavily on whether you are a C programmer (or familiar
with one of the other languages where the C "printf" format is used).  If so
then printf() is probably second nature to you and that is the place to start
in Dolphin. More below.

If not, then "printf" format is rather arcane and you'd probably be better off
sticking with your ad-hoc solution, since there's not much else pre-packaged
for doing formatted output.  There are a few helper methods on Integer and
Float classes, look in the 'printing' category, but they won't help you line up
your columns.

(It may help, if you haven't noticed it, to mention the #position method on
OutputStream -- if you keep track of the #position at the start of every line,
then you can use that and #position when you come to write each "field" to know
how many spaces-worth of padding to use.  Actually it'd be reasonably easy to
write a subclass of OutputStream that did that kind of thing for you, but I'd
better stop talking about it because my coding fingers are already starting to
twitch.)

Anyway, if you *are* conversant with printf() then here's an example.  Say you
have a list of objects which have a String #name, integer #age, and floating
point #height.  Say you want to write them to a file with the name
right-aligned in a field of width 40, the age left-aligned in a field of width
4, and the height left-aligned in  a field of width 10.  Here's one way to
write it:

    stream := FileStream write: 'C:\temp\somethiing.txt'.
    aList do:
        [:each | stream
                        nextPutAll: ('%-40s' sprintfWith: each name);
                        nextPutAll: ('%4d' sprintfWith: each age);
                        nextPutAll: ('%10s' sprintfWith: each height
displayString);
                        cr].
    stream close.

#sprintfWith: is a method on String that assumes that the String is in C's
"printf" format and contains one format specifier (the %d or %s bit) and
answers a new String that is the argument formatted according to the specifier.
Try a "print it" of:

    '%12d' sprintfWith: 400

in a workspace.

As Ian has mentioned, you can't use this technique to format floating point
numbers, but a workaround is to convert the float into a String (by sending it
#displayString, or one of the other similar methods on Float), and then print
that String with the desired padding.  That's what I've done in the third
#nextPutAll: of my example.

I hope this hasn't just been confusing.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ian Bartholomew-18
In reply to this post by Ripcat UK
> Hi. Thanks for the replies. Perhaps I should rephrase this, as I am a
> beginner at all this. What is the "simplest" way of achieving this....can
> you send things like strings out to a stream in columns, right justified
> to avoid my problem. What class would these methods be found in
> (I only have the dolphin free download)

Some demos...

Copy the following into a workspace and evaluate it all.

data := #(('Col1' 99 'Col3') ('Col11' 100 'Col31') ('Col12' 1000 'Col32')).
stream := String writeStream.
"stream := FileStream write: 'filename.txt'."
data do: [:each |
    stream
        tab;
        nextPutAll: (each at: 1);
        tab;
        print: (each at: 2);
        tab;
        nextPutAll: (each at: 3);
        cr].
"stream close."
textP := TextPresenter show: 'Multiline text'.
textP view font: (Font name: 'Arial' pointSize: 12).
textP value: stream contents

The first line just sets up an 3x3 array of the data that we are going to
display.

Line 2 sets up a stream that we will use for collecting the textual output.
I've used an internal stream on a String for simplicity  but you could just
as easily create a FileStream and output to a file (the commented out code)

Lines 4 to 12 read the data array, a line at a time, and write the each of
the items to the stream.  Each of the three items on the line is preceded
by a tab stop to make them align on the left.

Lines 14 - 16 open up a TextPresenter, set a proportional font and display
the contents of the stream.

The above should display in a proportional font with the left hand edges
aligned



The second example does the same again but this time #sprintf: is used to
display each of the fields with a width of 12 characters.  The idea now is
to make the right hand sides align.

data := #(('Col1' 99 'Col3') ('Col11' 100 'Col31') ('Col12' 1000 'Col32')).
stream := String writeStream.
data do: [:each |
    stream
        nextPutAll: ('%12s' sprintfWith: (each at: 1));
        nextPutAll: ('%12d' sprintfWith: (each at: 2));
        nextPutAll: ('%12s' sprintfWith: (each at: 3));
        cr].
textP := TextPresenter show: 'Multiline text'.
textP view font: (Font name: 'Arial' pointSize: 12).
textP value: stream contents

You will notice that this doesn't work.  Because Arial is a proportional
font the characters (including the spaces) all have different widths and the
right hand edges are therefore out of line.

You can also left justify this example my changing the format specifiers to
'%-12....  It still results in a mess, just now it's a left hand justified
mess.



Now try replacing the lines in the above examples that set the font. Change
them to

textP view font: (Font name: 'Courier new' pointSize: 12).

Courier is a fixed width font so when you evaluate the example code all of
the characters have exactly the same width.  This does not make a lot of
difference to the first example but cures the jagged edge problem in the
second.

I hope this demonstrates why it is not only the format that you create the
file with that is important but also the way you display it.



> hmm, I am also not sure why sprintf with a letter, ie 'z', gives me a big
> long number as a reply...

You mentioned C in an earlier post so I just assumed you would be familiar
with sprintf.  The first String is a format specifier

'%d'  means the argument is a decimal integer
'%s' means the argument is a string
'%12s' means format the string with a field width of 12 characters and right
justify it
'%-12s' means the same but left justified

In Dolphin you pass the arguments after the selector so

'%12d' sprintfWith: 1234

would answer as String containing 1234 right justified in a field of 12
characters.  You can have two arguments (bearing in mind Chris' earlier
warning about a Dolphin bug)

'%d to %d' sprintfWith: 12 with: 34

would answer "12 to 34"

There's more to it, but that should be enough to get you going.

Regards
    Ian


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Christopher J. Demers
In reply to this post by Chris Uppal-3
Chris Uppal <[hidden email]> wrote in message
news:3dddfbc4$0$116$[hidden email]...
>
> Just curious: does this example actually work for you -- on my machine
(W2K) I
> have to change the tabstops to something like:
>     strm nextPutAll: '{\tx600\tx1200\tx1800 '
> Or they are too small to affect the layout.

Actually it seems that it only _appears_ to work for me. ;)  On Windows NT
with Dolphin 5 apparently my code does not actually use the tab stops I set,
but just uses the defaults.  My stops look good, your stops look the same.
Actually you just reminded me of an RTF quirk, on NT and Win 98 the tab stop
set code has to start with \pard or it has no effect, Windows 2000 is less
picky.

This line should make it look good on all versions of windows:
strm nextPutAll: '{\pard\tx6000\tx12000\tx18000 '.

> Also, as I mentioned in the "reports" thread a little while ago, there's a
> package at:
>
>     http://www.smalltalking.net/Goodies/Dolphin/
>
> that *seems* to provide a comprehensive (I haven't used it) framework for
RTF
> generation.  May be overkill, though...

I looked at that, and I appreciated the link, there is some interesting
stuff there.  I am currently using my own RTFStream class (it is actually an
enhanced version of an example from John Pletzke's book, Advanced
Smalltalk).  My class does what I need for now, but I did look at the RTF
goody above, hopping it might be even better.  It may well be better, but it
is rather huge (326 classes), for now I will stick to my simpler approach
until I need more out of RTF.

Chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Chris Uppal-3
Chris,

> This line should make it look good on all versions of windows:
> strm nextPutAll: '{\pard\tx6000\tx12000\tx18000 '.

RichText is just Too Wierd (tm), changing that line to:

    strm nextPutAll: '{\pard\tx600\tx1200\tx1800 '.

has no effect whatever on my machine.  I give up.

> >     http://www.smalltalking.net/Goodies/Dolphin/
> [...] is rather huge (326 classes)

I think you've just secured the Understatement of the Week Award.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Chris Uppal-3
In reply to this post by Ripcat UK
Ripcat,

> '%6d' sprintfWith: 'tom'

In case you haven't already worked it our from Ian's examples.  You need to use
'%s' for Strings, '%d' for Integers, and '%c' for Characters.  If you use '%d'
for a String then I think you'll end up printing out its address in memory.

There's a fair number of other options, google for "printf format" or ask here
for more info.

    -- chris


P.S.  For printf() aficionados everwhere, you may like (or be horrified by):

    b := (ByteArray new: 8)
            doubleAtOffset: 0 put: Float pi;
            yourself.
    '%12.4lf'
        sprintfWith: (b sdwordAtOffset: 0)
        with: (b sdwordAtOffset: 4).

Enjoy!

    -- c


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Ian Bartholomew-18
In reply to this post by Chris Uppal-3
Chris,

> RichText is just Too Wierd (tm),

Oooh, that's a bit unfair.   I've been getting a lot of enjoyment from the
RichEdit control in the last week or so (or does that just make me wierd as
well!).  The secret for inner happiness is to avoid mucking about with the
generated text but instead to muck about with something that generates the
text for you - it makes it a lot easier.

>  changing that line to:
>
>     strm nextPutAll: '{\pard\tx600\tx1200\tx1800 '.
>
> has no effect whatever on my machine.

That's not surprising as the arguments are in twips.  You are setting the
tab stops to 0.4, 0.8 and 1.25 inches which doesn't leave a lot of room for
displaying characters :-)

self align: #centre while: [
    self boldWhile: [
        self underline: #wave while: [
            self font: #header while: [
                self appendTextCrCr: 'Ian']]]]

(you'll have to picture the rtf output for yourself)


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Chris Uppal-3
Ian,

> Oooh, that's a bit unfair.

I frequently am :-)  Especially where software from a certain mega-corporation
(or indeed any mega-corporation) is concerned.

> >     strm nextPutAll: '{\pard\tx600\tx1200\tx1800 '.
[...]
> That's not surprising as the arguments are in twips.  You are setting the
> tab stops to 0.4, 0.8 and 1.25 inches

What I don't understand is why multiplying all the tabstops by ten makes no
perceptible difference at all.  A bit of experimenting suggests that it might
be doing some sort of sanity checking and ignoring tabstops that it
considers unreasonable.

Things have come to a pretty pass if a mere text edit control thinks it can
second-guess *me*.  Pah!

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: format stuff

Blair McGlashan
In reply to this post by Chris Uppal-3
"Chris Uppal" <[hidden email]> wrote in message
news:3ddd0501$0$122$[hidden email]...
> Ian, Ripcat
>
> A quick word of warning: do *NOT* use String>>sprintfWith:with: without
*FIRST*
> fixing the buffer bug.  The buffer should be allocated inside the loop (as
it
> is in String>>sprintfWith:)
>

For the benefit of anyone else concerned by this bug, please find below our
patch for it. It is relevant if you expect to use #sprintfWith:with: to
format up strings of greater than approximately 128 characters.

I should note that sprintf(), while convenient, is a fundamentally dangerous
function, in that there is some risk of causing an access violation should
the format string not match the actual arguments passed. The '...' variable
argument mechanism of C/C++ is fundamentally non-typesafe in a statically
typed language with intrinsic value types. Of course in
everything-is-an-object dynamically-typed Smalltalk we could implement a
typesafe version of it :-).

Regards

Blair
------------------------------

!String methodsFor!

sprintfWith: arg1 with: arg2
 "Answer a String which is a message formatted from the receiver (assumed to
be a C-printf
 format String) with substituations from the arguments.
 Note: This is much faster than formatWith:with:."

 | written lib buf size |
 lib := CRTLibrary default.
 size := self size + 128.

 [buf := String new: size.
 written := lib
    _snprintf: buf
    count: size
    format: self
    with: arg1
    with: arg2.
 written < 0]
   whileTrue: [size := size * 2].
 ^buf copyFrom: 1 to: written! !
!String categoriesFor: #sprintfWith:with:!printing!public! !