34

Suppose that I would like to censor a curse word <word> using grawlixes, e.g. "What the #@$! are you doing?!" How can I define a command \censor such that \censor{<word>} results in a string of these symbols? I am not entirely sure which or how many symbols should be used given the word <word>. I suppose that the number of symbols used should approximately equal the number of characters in <word>.

Thanks for your input!

27

A random symbol is taken from a list and appended to a token list; when the width of the accumulated symbols is more than the width of the word minus 2pt, the symbols are printed, otherwise another symbol is appended.

\documentclass{article}
\usepackage{xparse,pgf}

\ExplSyntaxOn
\NewDocumentCommand{\censor}{m}
 {
  \pointer_censor:n { #1 }
 }

\seq_new:N \g_pointer_grawlixes_seq
\tl_map_inline:nn { @ * \# ! \$ \% ? ! \# @ \% *}
 {
  \seq_gput_right:Nn \g_pointer_grawlixes_seq { #1 }
 }
\int_const:Nn \c_pointer_grawlix_list_int { \seq_count:N \g_pointer_grawlixes_seq }

\dim_new:N \l_pointer_censor_dim
\dim_new:N \l_pointer_try_dim
\box_new:N \l_pointer_censor_box
\tl_new:N \l_pointer_grawlixes_tl


\cs_new_protected:Npn \pointer_censor:n #1
 {
  \tl_clear:N \l_pointer_grawlixes_tl
  \hbox_set:Nn \l_pointer_censor_box { #1 }
  \dim_set:Nn \l_pointer_censor_dim { \box_wd:N \l_pointer_censor_box }
  \pointer_add_grawlix:
 }
\cs_new_protected:Npn \pointer_add_grawlix:
 {
  \hbox_set:Nn \l_pointer_censor_box { \l_pointer_grawlixes_tl }
  \dim_compare:nTF
   { \l_pointer_censor_dim - 2pt < \box_wd:N \l_pointer_censor_box }
   {
    \tl_use:N \l_pointer_grawlixes_tl
   }
   {
    \pgfmathparse{random(1,\int_eval:n {\c_pointer_grawlix_list_int})}
    \tl_put_right:Nx \l_pointer_grawlixes_tl
     { \seq_item:Nn \g_pointer_grawlixes_seq { \pgfmathresult } }
    \pointer_add_grawlix:
   }
 }
\ExplSyntaxOff

\begin{document}

Censored

\censor{Censored}

\end{document}

enter image description here

17

A humble attempt with lualatex:

The replacement code is very naive, we would need something more robust.

\documentclass{article}

\directlua{

% my list of bad words
bad_words = { "fish", "cat", "dog", "horse", "alligator" }

% the replacement string
replacement = "duck"

% a replacement function which returns
% both the altered line and the number
% of occurrences
function replace(line)
    for _, element in pairs(bad_words) do
        if string.find(line, element) then
            return string.gsub(line, element, replacement)
        end
    end
    return line, 0
end

% my "naive" censor function, it simply
% replaces any occurrences of the
% list of bad words by the replacement
% string
function censor(line)
    occurrences = 0
    repeat
        line, occurrences = replace(line)
    until occurrences == 0
    return line
end

% add the hook
callback.register('process_input_buffer', censor)}

\begin{document}

Once upon a time, there was a little cat who lived inside an igloo. Don't ask me what he was doing there.

One day, the cat was visited by his two other friends, the dog and the alligator!

--- ``What are you guys doing here?'', said the cat.

--- ``We came to visit you, mr.\ cat!'', said the dog.

--- ``Our friend horse will be late, he went to the store to buy some frozen fish for you``, replied the alligator.

\end{document}

The output:

More ducks

Moral of the story: I'm terrible at telling stories. :)

Now, let's add the grawlixes. Since I need a better Lua code, let's create an external file censor.lua and call it from out .tex code:

\begin{filecontents*}{censor.lua}
-- a list of symbols to represent the
-- grawlixe symbols
-- note that we need to escape
-- some chars
grawlixe_symbols = { "\\$", "\\#", "@", "!", "*", "\\&" }

-- generate a grawlixe of length s
-- note that the seed is not so random, so
-- same values of s might get the same
-- grawlixe pattern (I could add another seed
-- mid code, but I'm lazy)
function grawlixe(s)
    math.randomseed(os.time())
    local u = table.getn(grawlixe_symbols)
    local i = math.random(u)
    local r = grawlixe_symbols[i]
    local current
    local w = 1
    repeat
        current = math.random(u)
        while current == i do
            current = math.random(u)
        end
        i = current
        r = r .. grawlixe_symbols[i]
        w = w + 1
    until w == s
    return r
end

-- a list of bad words to be censored
bad_words = { "fish", "cat", "dog", "horse", "alligator" }

-- our replacement function, it returns
-- the new line and the number of
-- replacements made
-- note that this is a very naive replacement
-- function, there's a lot of room for
-- improvement
function replace(line)
    for _, element in pairs(bad_words) do
        if string.find(line, element) then
        return string.gsub(line, element, grawlixe(string.len(element)))
        end
    end
    return line, 0
end

-- the censor function, it repeats
-- ad nauseam until the line has
-- nothing more to be censored
function censor(line)
    local occurrences = 0
    repeat
        line, occurrences = replace(line)
    until occurrences == 0
    return line
end

-- register the callback
callback.register('process_input_buffer', censor)
\end{filecontents*}

\documentclass{article}

\directlua{dofile('censor.lua')}

\begin{document}

Once upon a time, there was a little cat who lived inside an igloo. Don't ask me what he was doing there.

One day, the cat was visited by his two other friends, the dog and the alligator!

--- ``What are you guys doing here?'', said the cat.

--- ``We came to visit you, mr.\ cat!'', said the dog.

--- ``Our friend horse will be late, he went to the store to buy some frozen fish for you``, replied the alligator.

\end{document}

The output:

Grawlixes

Moral of the new story: adding grawlixes to a text makes it look naughty. :)

  • 2
    +1 (even though I hoped to see a duck picture, perhaps it was censored?) – David Carlisle Jun 6 '13 at 11:21
13

Leaving aside the pros and cons of how and why to do this, I find it a nice little exercise to do with xstring. So here's my take on it:

\documentclass{article}

\usepackage{xstring}

\def\grawlix{{\makeatletter@\makeatother}\textdollar{$\sharp$}*?!}

\newcommand{\censor}[1]{\StrLen{#1}[\result]%
\expandarg\StrLeft{\grawlix}{\result}[]}

\begin{document}
What the \censor{word} is this???
\end{document}

As you can see, the limitation is that the grawlix will be composed of the same set of characters, in the same order. (You may redefine the character set or find a way to randomize them at each invocation of the macro... but that's a different story! ;)) But your grawlix will be as long as the censored word.

What happens is that xstring has his own ways of expanding arguments and the macros cannot be nested. So the result from the macro that finds the length of the censored word is returned in a different macro (imaginatively called here \result) that will be (re)used to split the predefined \grawlix at the right spot.

The \expandarg macro makes sure the expansion is done properly. According to xstring documentation, it will allow all arguments passed to be expanded exactly once. Therefore, care must be taken to protect groups that represent a single character with braces (e.g. the \sharp symbol lives in mathmode, but we don't want the dollar signs delimiting it to be seen as separate tokens).

  • I don't think \makeatletter and \makeatother are necessary around the @... It can be used as the symbol it is right away. – clemens Jun 6 '13 at 8:13
  • @cgnieder It's a good job they are not necessary as they have no effect at all if used inside the definition as here:-) – David Carlisle Jun 6 '13 at 8:43
  • @DavidCarlisle true, I hadn't thought about that... I need more coffee – clemens Jun 6 '13 at 8:46
12

A plain TeX solution without additional packages, based on ideas of Kees van der Laan.

\documentclass{article}

\begin{document}

\newcount\cur
\newcount\cura

\def\fifo#1{\ifx\ofif#1\ofif\fi
\process#1\fifo}
\def\ofif#1\fifo{\fi}

\def\mynum#1{\cur\the\lccode`#1\relax\the\cur }

\def\process#1{\cur\the\lccode`#1\relax\cura\cur \divide\cura by7 \multiply\cura by7
    \advance\cur by-\cura\ifcase\cur!\or@\or\#\or\$\or\%\or\&\or*\else  ERROR\fi }

\fifo censored \ofif

\bigskip

\def\censor#1{\fifo #1\ofif}

\censor{fuck}
\censor{WORD}
it is
\censor{shit}

\end{document}

You probably want to have the result connected with the word used. In this solution it is \lccode of a character modulo 7. You can write either

    \fifo censored \ofif

or, as suggested (sorry for my language),

\censor{fuck}
\censor{WORD}
it is
\censor{shit}

obtaining

enter image description here

Some explanations. The main tool is the implemantation of FIFO (First-In-First-Out) queue suggested by Kees van der Laan:

\def\fifo#1{\ifx\ofif#1\ofif\fi
    \process#1\fifo}
    \def\ofif#1\fifo{\fi}

The \fifo command calls a macro \process that handles the individual arguments. Every token is processed until \ofif.

The rest is simple. Manipulating on the counters \cur and \cura we obtain a number between 0 and 6 and \ifcase takes different censor characters for different values.

Remark. I am very interested of the real rules (if they exist) of replacing curse words by strings of @-like symbols.

  • Very nice. Could you perhaps expand (pardon the pun) a bit on how the code actually works? – Alan Munn Jun 6 '13 at 3:47
  • 1
    @AlanMunn Yes, but after returning home (at late evening). – Przemysław Scherwentke Jun 6 '13 at 3:53
  • 1
    \censor{MSWORD} – marczellm Jun 6 '13 at 13:05
7

A stable way to exchange symbols is to change the fontencoding or to reencode the font. A simple example

\documentclass{article}

\begin{document}
\newcommand\censor[1]{\fontencoding{OMS}\selectfont #1}
\censor{Censored}
\end{document}

With lualatex it will in the future (when the interface is more stable) be probably be possible to create virtual reencoded fonts on-the-fly, currently it should be already possible to do it with a feature file.

2

This is an extension to @egreg's answer using another answer by @egreg.

With this code you can enter a whole sentence in \censor and all words initially added to a list using \addcensor are replaced by grawlixes. The argument to \addcensor can be either a single word or a comma separated list.

\documentclass{article}
\usepackage{xparse,pgf}
\ExplSyntaxOn
\NewDocumentCommand{\censor}{m}
 {
  \pointer_badseq:n { #1 }
 }

\tl_new:N \g_pointer_badwords_tl
\NewDocumentCommand{\addcensor}{m}
 {
  \clist_map_inline:nn { #1 }
   {
    \tl_gput_right:Nn \g_pointer_badwords_tl { {##1}{} }
   }
 }

\cs_generate_variant:Nn \str_case:nnTF { nV }
\cs_new_protected:Npn \pointer_badseq:n #1
 {
  \seq_set_split:Nnn \l_tmpa_seq { ~ } { #1 }
  \seq_map_inline:Nn \l_tmpa_seq
   {
    \str_case:nVTF { ##1 } \g_pointer_badwords_tl
     { \pointer_censor:n { ##1 } } { ##1 } ~ % Readd space
   }
  \tex_unskip:D % Remove the trailing space
 }

% From @egreg's answer
\seq_new:N \g_pointer_grawlixes_seq
\tl_map_inline:nn { @ * \# ! \$ \% ? ! \# @ \% *}
 {
  \seq_gput_right:Nn \g_pointer_grawlixes_seq { #1 }
 }
\int_const:Nn \c_pointer_grawlix_list_int { \seq_count:N \g_pointer_grawlixes_seq }

\dim_new:N \l_pointer_censor_dim
\dim_new:N \l_pointer_try_dim
\box_new:N \l_pointer_censor_box
\tl_new:N \l_pointer_grawlixes_tl


\cs_new_protected:Npn \pointer_censor:n #1
 {
  \tl_clear:N \l_pointer_grawlixes_tl
  \hbox_set:Nn \l_pointer_censor_box { #1 }
  \dim_set:Nn \l_pointer_censor_dim { \box_wd:N \l_pointer_censor_box }
  \pointer_add_grawlix:
 }
\cs_new_protected:Npn \pointer_add_grawlix:
 {
  \hbox_set:Nn \l_pointer_censor_box { \l_pointer_grawlixes_tl }
  \dim_compare:nTF
   { \l_pointer_censor_dim - 2pt < \box_wd:N \l_pointer_censor_box }
   {
    \tl_use:N \l_pointer_grawlixes_tl
   }
   {
    \pgfmathparse{random(1,\int_eval:n {\c_pointer_grawlix_list_int})}
    \tl_put_right:Nx \l_pointer_grawlixes_tl
     { \seq_item:Nn \g_pointer_grawlixes_seq { \pgfmathresult } }
    \pointer_add_grawlix:
   }
 }
\ExplSyntaxOff

\addcensor{censored,duck}
\addcensor{street}

\begin{document}
A censored man walk down the street together with his duck.

\censor{A censored man walk down the street together with his duck}.
\end{document}

enter image description here

  • It annoys me when you use traditional placing of open and closing brace (open brace adjacent to the function, then new line, then the code, and then the closing brace alone in a new line —without indent—), mixing it with the recommended LaTeX3 way (both alone on a new line, indented the same amount of space). – Manuel Sep 13 '14 at 13:48
  • @Manuel Sorry, I hope my updated answer fits the recommended style. – Henri Menke Sep 13 '14 at 17:00
  • Definetly better (IMO). I like this way, even now that I'm reading ConTeXt source code and it writes completely different (at least they don't use the opening brace, new line, code, new line closing brace without indentation too much, but something more like %, new line, opening brace and code, more code, and closing brace next to the code). – Manuel Sep 13 '14 at 18:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.