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:

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:

Moral of the new story: adding grawlixes to a text makes it look naughty. :)
\makeatletterT@*(% !%$ . – percusse Jun 06 '13 at 05:52\numberand\char, but I don't know how. This will give "fuck" the new word "#(*(". – Pål GD Jun 06 '13 at 13:28