Formatting Techniques

Once you have query data in the form of a result set, you might want to massage it a bit before outputting it to the browser or to a file. ColdFusion provides several built-in functions for formatting a variety of datatypes. This section covers some of the more popular functions for formatting strings, HTML code, numbers, currency, dates, times, and Boolean values. For more information on all the functions covered in this section, see Appendix B.

Formatting Plain-Text Strings

ColdFusion provides functions for formatting text strings: ParagraphFormat( ), Wrap( ), Ucase( ), Lcase( ), JSStringFormat( ), and XMLFormat( ). Each function is covered in the sections that follow.

Using ParagraphFormat

ParagraphFormat( )takes a string and formats it so that single newline characters are replaced with a space, and double newline characters are replaced with HTML <p> tags. This function is most often used to display data that has been entered into a Textarea HTML form field. The following example shows how the ParagraphFormat( ) function handles single and double newline characters:

<cfset MyText="This is my block of text.
It has both single newline characters in it like this paragraph, and double 
newline characters like in the next paragraph.
   
This is the paragraph with the double newline characters.">
   
<form>
<cfoutput>
<textarea cols="50" rows="10" name="TheText" wrap="Virtual">
#ParagraphFormat(MyText)#
</textarea>
</cfoutput>
</form>

Wrapping text

As of ColdFusion MX 6.1, you can wrap a block of text with the Wrap() function so that no line exceeds a specified number of characters. Wrap() takes two required arguments and one optional argument:

wrap(string, limit [,strip])

string represents the string you want to wrap, limit specifies the maximum number of characters to allow per line in string, and strip indicates whether to remove all existing line breaks before wrapping the string. If a line is longer than the specified number of characters, an attempt is made to break the line by inserting a line break at the nearest whitespace character (space, tab, etc) before the character limit. If string contains no whitespace characters before the maximum number of characters per line, a line break is inserted at the maximum line length. Wrap() uses operating system specific line breaks. On Windows, it uses a carriage return and newline character. On Unix/Linux, it uses a newline. Here is an example that wraps a string at 80 characters:

<cfset myString="This is a string that is so long, I probably would like to 
break it up.  What do you think about that?  Is it a good idea?">

<cfoutput>
<pre>#Wrap(myString, 80, False)#</pre>
</cfoutput>

Changing case

You can change the case of an entire string using the Ucase( ) and Lcase( ) functions. Ucase( ) converts a string to all uppercase characters, while Lcase( ) converts a string to all lowercase characters. The following example demonstrates both functions:

<cfset MyString = "This Is A Mixed Case String!">
   
<h3>UCase/LCase</h3>
<cfoutput>
Original String: #MyString#<br>
After UCase: #UCase(MyString)#<br>
After LCase: #LCase(MyString)#
</cfoutput>

Making strings JavaScript-safe

On occasion, you may need to retrieve a string from a database for use in a JavaScript function within your application. The JSStringFormat( ) function can be used to make the string safe for use in JavaScript statements by automatically escaping special characters that normally cause a problem, such as double quotes, single quotes, and the newline character (\). The following example returns a string that has been formatted for use in a JavaScript statement:

<cfset MyString="""Escape double quotes"". Escape the \ character. 'Escape
     single quotes'">
   
<cfset SafeString=JSStringFormat(MyString)>
   
<cfoutput>
<b>Original String:</b> #MyString#<br>
<b>JavaScript Safe String:</b> #SafeString#
</cfoutput>

Making strings safe for XML

You can make a string safe to use with XML by using the XMLFormat( ) function. XMLFormat( ) takes a string as its only parameter and returns it in a format that is safe to use with XML by escaping the following special characters:

Ampersand (&)
Double quotation mark (")
Greater-than sign (>)
Less-than sign (<)
Single quotation mark (')

The following example takes a string and makes it safe to use with XML:

<cfset MyString="Here's an example of the XMLFormat function: 5+5<20">
   
<cfoutput>
#XMLFormat(MyString)#
</cfoutput>
   
<p>
<i>View the page source to see the escaped text.</i>

Formatting HTML

In order to display literal HTML or CFML code, it is necessary to escape certain special characters so that they don’t cause the browser to interpret the code contained inside them. For example, if you have a string that contains Hello <b>World!</b>, and you want to display the HTML code contained in the string without having it execute, you need to escape the < and > characters. ColdFusion provides you with two functions to do this: HTMLCodeFormat( ) and HTMLEditFormat( ).

HTMLCodeFormat

The HTMLCodeFormat( ) function returns a string enclosed in <pre> and </pre> tags with all carriage returns removed and special characters (< > " &) escaped. The function takes two parameters, a string to format and optionally, the HTML version to use for the character escape sequences. Valid entries for the HTML version are shown in Table 4-4.

Table 4-4. HTML versions

Value

Description

-1

Current HTML version

2.0

HTML v2.0 (default)

3.2

HTML v3.0

The following example demonstrates the HTMLCodeFormat( ) function:

<cfset MyString="<h3>This is an example of the HTMLCodeFormat function.</h3>
View the source of this document to see the escaping of the HTML characters.">
   
<cfoutput>
#HTMLCodeFormat(MyString, "3.2")#
</cfoutput>

HTMLEditFormat

The HTMLEditFormat( ) function is almost identical in functionality to HTMLCodeFormat( ). The only difference is that it doesn’t add <pre></pre> tags to the output returned by the function. The following example demonstrates the HTMLEditFormat( ) function:

<cfset MyString="<h3>This is an example of the HTMLEditFormat function.</h3>
View the source of this document to see the escaping of the HTML characters.">
   
<cfoutput>
#HTMLEditFormat(MyString, "3.2")#
</cfoutput>

Formatting Numbers

ColdFusion provides three functions for formatting numbers: DecimalFormat( ), NumberFormat( ), and LSNumberFormat( ). These functions can format numbers in a variety of ways, as described in the following sections.

Formatting decimal numbers

The DecimalFormat( ) function takes a number and returns it formatted to two decimal places, with thousands separators. The following example formats a variety of numbers using DecimalFormat( ):

<cfoutput>
1:  #DecimalFormat(1)#<br>
10: #DecimalFormat(10)#<br>
100: #DecimalFormat(100)#<br>
1000: #DecimalFormat(1000)#<br>
10000: #DecimalFormat(10000)#<br>
100000: #DecimalFormat(100000)#<br>
1000000: #DecimalFormat(1000000)#<br>
</cfoutput>

General number formatting

The NumberFormat( ) function handles general number formatting in ColdFusion. NumberFormat( ) allows you to format numbers using a variety of masks. The function accepts two parameters, the number you wish to format and a mask to specify the formatting, in the form NumberFormat( Number , ' mask '). If no mask is supplied, NumberFormat( ) returns the number formatted with thousands separators. If for any reason ColdFusion is not able to format the number with the supplied mask, the original value is returned unformatted. Valid entries for the mask are listed in Table 4-5.

Table 4-5. Mask Values for NumberFormat( )

Mask

Description

- (underscore)

Optional digit placeholder

9

Optional digit placeholder; same as _ but better for showing decimal places

.

Decimal point location

Forces padding with zeros

( )

Surrounds negative numbers in parentheses

+

Places a plus sign in front of positive numbers and a minus sign in front of negative numbers

- (hyphen)

Places a space in front of positive numbers and a minus sign in front of negative numbers

,

Separates thousands with commas

L

Left-justifies the number within the width of the mask

C

Centers the number within the width of the mask

$

Places a dollar sign in front of the number

^

Separates left from right formatting

The following example shows the NumberFormat( ) function applied to various numbers:

<!--- Assign a number to a variable to be used throughout the example --->
<cfset MyNumber = 1000.99>
   
<h3>Formatting Numeric Values using NumberFormat</h3>
   
<cfoutput>
<b>MyNumber = #MyNumber#</b>
<p>
NumberFormat(MyNumber, '____'): #NumberFormat(MyNumber, '____')#<br>
NumberFormat(MyNumber, '9999.99'): #NumberFormat(MyNumber, '9999.99')#<br>
NumberFormat(MyNumber, '09999.9900'): #NumberFormat(MyNumber, '09999.9900')#<br>
NumberFormat(-MyNumber, '(9999.99)'): #NumberFormat(-MyNumber, '(9999.99)')#<br>
NumberFormat(MyNumber, '+9999.99'): #NumberFormat(MyNumber, '+9999.99')#<br>
NumberFormat(-MyNumber, '+9999.99'): #NumberFormat(-MyNumber, '+9999.99')#<br>
NumberFormat(MyNumber, '-9999.99'): #NumberFormat(MyNumber, '-9999.99')#<br>
NumberFormat(-MyNumber, '-9999.99'): #NumberFormat(-MyNumber, '-9999.99')#<br>
NumberFormat(MyNumber, '$9,999.99'): #NumberFormat(MyNumber, '$9,999.99')#<br>
NumberFormat(MyNumber, 'L999,999.99'): #NumberFormat(MyNumber, 'L999,999.99')#<br>
NumberFormat(MyNumber, 'C999,999.99'): #NumberFormat(MyNumber, 'C999,999.99')#<br>
NumberFormat(MyNumber, 'C_____(^___)'): #NumberFormat(MyNumber, 'C_____(^___)')#
</cfoutput>

Locale-specific number formatting

In addition to general number formatting using the NumberFormat( ) function, ColdFusion provides another function for formatting numbers in a format that is locale-specific. Locale-specific formatting allows you to format numbers for a specific language or dialect (often country-specific). For example, in many European countries, the period (.) is used as a thousands separator instead of the comma (,) as in the United States. The LSNumberFormat( ) function behaves the same as the NumberFormat( ) function (using the same masks), but it takes into account the formatting used by the default locale. If no formatting mask is supplied, LSNumberFormat( ) returns the number as an integer.

The following example loops through each locale supported by ColdFusion and applies a variety of different number masks for each locale:

<h3>Formatting Locale Specific Numeric Values using LSNumberFormat</h3>
   
<!--- Loop over each locale.  The list of locales is obtained from the server 
      variable Server.ColdFusion.SupportedLocales --->
<cfloop index="locale" list="#Server.Coldfusion.SupportedLocales#">
<!--- This causes the CF server to assume the locale specified by the current 
      iteration of the loop --->
<cfset SetLocale(locale)>
   
<cfoutput>
<p>
<b>#locale#</b><br>
LSNumberFormat(1000.99, '____'): #LSNumberFormat(1000.99, '____')#<br>
LSNumberFormat(1000.99, '9999.99'): #LSNumberFormat(1000.99, '9999.99')#<br>
LSNumberFormat(1000.99, '09999.9900'): #LSNumberFormat(1000.99, '09999.9900')#<br>
LSNumberFormat(-1000.99, '(9999.99)'): #LSNumberFormat(-1000.99, '(9999.99)')#<br>
LSNumberFormat(1000.99, '+9999.99'): #LSNumberFormat(1000.99, '+9999.99')#<br>
LSNumberFormat(-1000.99, '+9999.99'): #LSNumberFormat(-1000.99, '+9999.99')#<br>
LSNumberFormat(1000.99, '-9999.99'): #LSNumberFormat(1000.99, '-9999.99')#<br>
LSNumberFormat(-1000.99, '-9999.99'): #LSNumberFormat(-1000.99, '-9999.99')#<br>
LSNumberFormat(1000.99, '$9,999.99'): #LSNumberFormat(1000.99, '$9,999.99')#<br>
LSNumberFormat(1000.99, 'L999,999.99'): 
  #LSNumberFormat(1000.99, 'L999,999.99')#<br>
LSNumberFormat(1000.99, 'C999,999.99'): 
  #LSNumberFormat(1000.99,'C999,999.99')#<br>
LSNumberFormat(1000.99, 'C____(^___)'): 
  #LSNumberFormat(1000.99, 'C____(^___)')#<br>
</cfoutput>
</cfloop>

Formatting Currency Values

Numeric values can be automatically formatted as currency values with the help of three ColdFusion functions: DollarFormat( ), LSCurrencyFormat( ), and LSEuroCurrencyFormat( ).

Formatting dollars

The DollarFormat( ) function returns a number formatted as U.S. dollars. The returned number is formatted to two decimal places with a dollar sign and thousands separators. If the number is negative, it is returned in parentheses. The following example formats a variety of numbers using DollarFormat( ):

<cfoutput>
-1000: #DollarFormat(-1000)#<br>
-100: #DollarFormat(-100)#<br>
-10: #DollarFormat(-10)#<br>
-1: #DollarFormat(-1)#<br>
1:  #DollarFormat(1)#<br>
10: #DollarFormat(10)#<br>
100: #DollarFormat(100)#<br>
1000: #DollarFormat(1000)#<br>
10000: #DollarFormat(10000)#<br>
100000: #DollarFormat(100000)#<br>
1000000: #DollarFormat(1000000)#<br>
</cfoutput>

Locale-specific currency formatting

In addition to U.S. dollar formatting using the DollarFormat( ) function, ColdFusion provides another function for formatting currency values specific to a particular locale. The LSCurrencyFormat( ) function behaves the same as the DollarFormat( ) function, but it takes into account the currency conventions used by the default locale. The function takes two parameters, a numeric value and an optional locale-specific convention. Table 4-6 lists the valid values for the convention.

Table 4-6. Locale-specific conventions for LSCurrencyFormat( )

Value

Description

None

Returns the amount

Local

Returns the currency amount with locale-specific currency formatting; the default

International

Returns the currency value with its corresponding three-letter international currency prefix

The following example loops through each locale supported by ColdFusion and applies each type of currency mask to a currency value for each locale:

<h3>Formatting Locale Specific Currency Values using LSCurrencyFormat</h3>
   
<!--- Loop over each locale.  The list of locales is obtained from the server 
      variable Server.ColdFusion.SupportedLocales --->
<cfloop index="locale" list="#Server.Coldfusion.SupportedLocales#">
<!--- This causes the CF server to assume the locale specified by the current 
      iteration of the loop --->
<cfset SetLocale(locale)>
   
<cfoutput>
<p>
<b>#locale#</b><br>
None: #LSCurrencyFormat(1000000.99, "None")#<br>
Local: #LSCurrencyFormat(1000000.99, "Local")#<br>
International: #LSCurrencyFormat(1000000.99, "International")#<br>
</cfoutput>
</cfloop>

Locale-specific currency formatting with the euro

The final currency formatting function, LSEuroCurrencyFormat( ), is the same as the LSCurrencyFormat( ) function except it returns the formatted currency with the euro symbol for locales using the euro. The following example displays euro currency formats for each locale:

<h3>Formatting Locale Specific Currency Values with the Euro using 
    LSCurrencyFormat</h3>
   
<!--- Loop over each locale.  The list of locales is obtained from the server 
      variable Server.ColdFusion.SupportedLocales --->
<cfloop index="locale" list="#Server.Coldfusion.SupportedLocales#">
<!--- This causes the CF server to assume the locale specified by the current 
      iteration of the loop --->
<cfset SetLocale(locale)>
   
<cfoutput>
<p>
<b>#locale#</b><br>
None: #LSEuroCurrencyFormat(1000000.99, "None")#<br>
Local: #LSEuroCurrencyFormat(1000000.99, "Local")#<br>
International: #LSEuroCurrencyFormat(1000000.99, "International")#<br>
</cfoutput>
</cfloop>

Formatting Boolean Values

ColdFusion uses the Boolean datatype to store the value generated by a logical operation. Boolean values are stored as either true or false. In numeric operations, Boolean values evaluate to 1 for true and 0 for false. When dealing with strings, Boolean values are set to Yes for true and No for false. Because most users are used to seeing the results of a Boolean operation as either Yes or No, ColdFusion has a function called YesNoFormat( ) you can use to automatically convert any Boolean value to its equivalent Yes/No format (all nonzero values are returned as Yes, while a zero value is returned as No). The following example demonstrates this by applying the YesNoFormat( ) function to a variety of Boolean values:

<h3>Formatting Boolean Values using YesNoFormat</h3>
   
<cfoutput>
-1: #YesNoFormat(-1)#<br>
-1.123: #YesNoFormat(-1.123)#<br>
-0.123: #YesNoFormat(-0.123)#<br>
0: #YesNoFormat(0)#<br>
0.123: #YesNoFormat(0.123)#<br>
1: #YesNoFormat(1)#<br>
1.123: #YesNoFormat(1.123)#
</cfoutput>

Formatting Dates and Times

Depending on the database you use, date and time values returned as part of a query result set can come in a variety of formats. ColdFusion affords a lot of flexibility in formatting date and time values before you output them to the browser.

General date formatting

General date formatting is handled by the DateFormat( ) function. DateFormat( ) allows you to format dates using a variety of masks. The function accepts two parameters, the date you wish to format and a mask to specify the formatting in the format DateFormat( date , " mask "). If no mask is supplied, DateFormat( ) defaults to dd-mmm-yy. Valid entries for the date mask are shown in Table 4-7.

Table 4-7. Mask values for DateFormat( )

Mask

Description

d

Day of the month as a number with no leading zero for single-digit days

dd

Day of the month as a number with a leading zero for single-digit days

ddd

Three-letter abbreviation for day of the week

dddd

Full name of the day of the week

gg

Period/era; this mask is currently ignored

m

Month as a number with no leading zero for single-digit months

mm

Month as a number with a leading zero for single-digit months

mmm

Three-letter abbreviation for the month

mmmm

Full name of the month

y

Last two digits of year with no leading zero for years less than 10

yy

Last two digits of year with a leading zero for years less than 10

yyyy

Four-digit year

gg

Period/era

short

Java Short date format

medium

Java Medium date format

long

Java Long date format

full

Java Full date format

You should note that DateFormat( ) supports U.S. date formats only. To use locale-specific date formats, see the LSDateFormat( ) function in the next section. For more information on how ColdFusion handles dates, see Chapter 2.

The following example demonstrates the DateFormat( ) function utilizing a variety of different date masks:

<cfset TheDate = Now( )>
   
<cfoutput>
TheDate = #DateFormat(TheDate, 'mm/dd/yyyy')#
<p>
m/d/yy: #DateFormat(TheDate, 'm/d/yy')#<br>
mm/dd/yy: #DateFormat(TheDate, 'mm/dd/yy')#<br>
mm/dd/yyyy: #DateFormat(TheDate, 'mm/dd/yyyy')#<br>
dd/mm/yyyy gg: #DateFormat(TheDate, 'dd/mm/yyyy gg')#<br>
dd mmm yy: #DateFormat(TheDate, 'dd mmm yy')#<br>
dddd mmmm dd, yyyy: #DateFormat(TheDate, 'dddd mmmm dd, yyyy')#<br>
<p>
And these formats are new in ColdFusion MX:<br>
short: #DateFormat(TheDate, 'short')#<br>
medium: #DateFormat(TheDate, 'medium')#<br>
long: #DateFormat(TheDate, 'long')#<br>
full: #DateFormat(TheDate, 'full')#<br>
</cfoutput>

Locale-specific date formatting

In addition to U.S. date formatting using the DateFormat( ) function, ColdFusion provides another function for formatting dates specific to a particular locale. The LSDateFormat( ) function behaves the same as the DateFormat( ) function, but it takes into account the formatting used by the default locale. If no formatting mask is supplied, LSDateFormat( ) uses the locale-specific default.

The following example loops through each locale supported by ColdFusion and applies a number of different date masks to the current date for each locale:

<h3>Formatting Locale Specific Date Values using LSDateFormat</h3>
   
<!--- Loop over each locale.  The list of locales is obtained from the server 
      variable Server.ColdFusion.SupportedLocales --->
<cfloop index="locale" list="#Server.Coldfusion.SupportedLocales#">
<!--- This causes the CF server to assume the locale specified by the current 
      iteration of the loop --->
<cfset SetLocale(locale)>
   
<!--- Output formatted dates using a variety of masks --->
<cfoutput>
<p>
<b>#locale#</b><br>
#LSDateFormat(Now( ))#<br> 
#LSDateFormat(Now( ), "d/m/yy")#<br>
#LSDateFormat(Now( ), "d-mmm-yyyy")#<br>
#LSDateFormat(Now( ), "dd mmm yy")#<br>
#LSDateFormat(Now( ), "dddd, mmmm dd, yyyy")#<br>
#LSDateFormat(Now( ), "mm/dd/yyyy")#<br>
#LSDateFormat(Now( ), "mmmm d, yyyy")#<br>
#LSDateFormat(Now( ), "mmm-dd-yyyy")#<br>
</cfoutput>
</cfloop>

General time formatting

You can format times in ColdFusion using the TimeFormat( ) function. TimeFormat( ) is similar to the DateFormat( ) function in that it allows you to use a mask to control the formatting. The function accepts two parameters, the time you wish to format and a mask to specify the formatting in the format TimeFormat( time , " mask "). If no mask is specified, the default hh:mm tt is used. Valid mask values are shown in Table 4-8.

Table 4-8. Mask values for TimeFormat( )

Mask

Description

h

Hours based on a 12-hour clock with no leading zeros for single-digit hours

hh

Hours based on a 12-hour clock with leading zeros for single-digit hours

H

Hours based on a 24-hour clock with no leading zeros for single-digit hours

HH

Hours based on a 24-hour clock with leading zeros for single-digit hours

m

Minutes with no leading zero for single-digit minutes

mm

Minutes with a leading zero for single-digit minutes

s

Seconds with no leading zero for single-digit seconds

ss

Seconds with a leading zero for single-digit seconds

l

Milliseconds with no leading zeros for single or double-digit milliseconds

t

Single character meridian, either A or p

tt

Multicharacter meridian, either AM or PM

short

Java Short time format

medium

Java Medium time format

long

Java Long time format

full

Java Full time format

The following example demonstrates the TimeFormat( ) function using a number of time masks:

<cfset TheTime = Now( )>
   
<cfoutput>
TheTime = #TimeFormat(TheTime,'hh:mm:ss tt')#<p>
   
TimeFormat(TheTime, 'h:m:s'): #TimeFormat(TheTime, 'h:m:s')#<br>
TimeFormat(TheTime, 'h:m:s t'): #TimeFormat(TheTime, 'h:m:s t')#<br>
TimeFormat(TheTime, 'hh:mm:ss'): #TimeFormat(TheTime, 'hh:mm:ss')#<br>
TimeFormat(TheTime, 'hh:mm:ss tt'): #TimeFormat(TheTime, 'hh:mm:ss tt')#<br>
TimeFormat(TheTime, 'H:M:ss'): #TimeFormat(TheTime, 'H:M:s')#<br>
TimeFormat(TheTime, 'HH:MM:ss'): #TimeFormat(TheTime, 'HH:MM:ss')#<br>
<p>
And these formats are new in ColdFusion MX:<br>
short: #TimeFormat(TheTime, 'short')#<br>
medium: #TimeFormat(TheTime, 'medium')#<br>
long: #TimeFormat(TheTime, 'long')#<br>
full: #TimeFormat(TheTime, 'full')#<br>
</cfoutput>

Locale-specific time formatting

In addition to U.S. time formatting using the TimeFormat() function, ColdFusion provides another function for formatting dates specific to a particular locale. The LSTimeFormat() function behaves the same as the TimeFormat() function, but it takes into account the formatting used by the default locale. If no formatting mask is supplied, LSTimeFormat() uses the locale-specific default. The following example loops through each locale supported by ColdFusion and applies a number of different time masks to the current time for each locale:

<cfset theTime = Now()>

<cfloop index="locale" list="#Server.Coldfusion.SupportedLocales#">
  <cfset SetLocale(locale)>
  <cfoutput>
    <p><b>#locale#</b><br>
    TheTime = #LSTimeFormat(TheTime)#<br>
    LSTimeFormat(TheTime, 'h:m:s'): #LSTimeFormat(TheTime, 'h:m:s')#<br>
    LSTimeFormat(TheTime, 'h:m:s t'): #LSTimeFormat(TheTime, 
                 'h:m:s t')#<br>
    LSTimeFormat(TheTime, 'hh:mm:ss'): #LSTimeFormat(TheTime, 
                 'hh:mm:ss')#<br>
    LSTimeFormat(TheTime, 'hh:mm:ss tt'): 
      #LSTimeFormat(TheTime, 'hh:mm:ss tt')#<br>
    LSTimeFormat(TheTime, 'H:M:ss'): #LSTimeFormat(TheTime, 'H:M:s')#<br>
    LSTimeFormat(TheTime, 'HH:MM:ss'): #LSTimeFormat(TheTime, 
                 'HH:MM:ss')#<br>
    <p>
    And these formats are new in ColdFusion MX:<br>
    short: #LSTimeFormat(TheTime, 'short')#<br>
    medium: #LSTimeFormat(TheTime, 'medium')#<br>
    long: #LSTimeFormat(TheTime, 'long')#<br>
    full: #LSTimeFormat(TheTime, 'full')#<br>
  </cfoutput>
</cfloop>

Get Programming ColdFusion MX, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.