views:

363

answers:

6

First things first: Neither this, this, this nor this answered my question. So I'll open a new one.

Please read

Okay okay. I know that regexes are not the way to parse general HTML. Please take note that the created documents are written using a limited, controlled HTML subset. And people writing the docs know what they're doing. They are all IT professionals!

Given the controlled syntax it is possible to parse the documents I have here using regexes.

I am not trying to download arbitrary documents from the web and parse them!

And if the parsing does fail, the document is edited, so it'll parse. The problem I am addressing here is more general than that (i.e. not replace patterns inside two other patterns).

A little bit of background (you can skip this...)

In our office we are supposed to "pretty print" our documentation. Hence why some came up with putting it all into Word documents. So far we're thankfully not quite there yet. And, if I get this done, we might not need to.

The current state (... and this)

The main part of the docs are stored in a TikiWiki database. I've created a daft PHP script which converts the documents from HTML (via LaTeX) to PDF. One of the must have features of the selected Wiki-System was a WYSIWYG editor. Which, as expected leaves us with documents with a less then formal DOM.

Consequently, I am transliterating the document using "simple" regexes. It all works (mostly) fine so far, but I encountered one problem I haven't figured out on my own yet.

The problem

Some special characters need to replaced by LaTeX markup. For exaple, the \ character should be replaced by $\backslash$ (unless someone knows another solution?).

Except while in a verbatim block!

I do replace <code> tags with verbatim sections. But if this code block contains backslashes (as is the case for Windows folder names), the script still replaces these backslashes.

I reckon I could solve this using negative LookBehinds and/or LookAheads. But my attempts did not work.

Granted, I would be better off with a real parser. In fact, it is something on my "in-brain-roadmap", but it is currently out of the scope. The script works well enough for our limited knowledge domain. Creating a parser would require me to start pretty much from scratch.

My attempt

Example Input

The Hello \ World document is located in:
<code>C:\documents\hello_world.txt</code>

Expected output

The Hello $\backslash$ World document is located in:
\begin{verbatim}C:\documents\hello_world.txt\end{verbatim}

This is the best I could come up with so far:

<?php
$patterns = array(
    "special_chars2" => array( '/(?<!<code[^>]*>.*)\\\\[^$](?!.*<\/code>)/U', '$\\backslash$'),
);

foreach( $patterns as $name => $p ){
    $tex_input = preg_replace( $p[0], $p[1], $tex_input );
}
?>

Note that this is only an excerpt, and the [^$] is another LaTeX requirement.

Another attempt which seemed to work:

<?php
$patterns = array(
    "special_chars2" => array( '/\\\\[^$](?!.*<\/code>)/U', '$\\backslash$'),
);

foreach( $patterns as $name => $p ){
    $tex_input = preg_replace( $p[0], $p[1], $tex_input );
}
?>

... in other words: leaving out the negative lookbehind.

But this looks more error-prone than with both lookbehind and lookahead.

A related question

As you may have noticed, the pattern is ungreedy (/.../U). So will this match only as little possible inside a <code> block? Considering the look-arounds?

+3  A: 

I reckon I could solve this using negative LookBehinds and/or LookAheads.

You reckon wrong. Regular expressions are not a replacement for a parser.

I would suggest that you pipe the html through htmltidy, then read it with a dom-parser and then transform the dom to your target output format. Is there anything preventing your from taking this route?

troelskn
+1: Couldn't agree more. THe pain and suffering OP will suffer mangling that regex to work will still leave dozens of corner cases.
Jed Smith
This is *exactly* what I have in mind! I am very well aware that the current solution is not "beautiful". But it works. The little bug with the code blocks is not a show-stopper. So, given the resoure-constraints, I'd rather prefer fiddling with one line of regex, than restarting from scratch... As I already said in the Q. ;)@JedSmith: Agreed. Which is why the parser is already on the future roadmap.
exhuma
+2  A: 

Parser FTW, ok. But if you can't use a parser, and you can be certain that <code> tags are never nested, you could try the following:

  1. Find <code>.*?</code> sections of your file (probably need to turn on dot-matches-newlines mode).
  2. Replace all backslashes inside that section with something unique like #?#?#?#
  3. Replace the section found in 1 with that new section
  4. Replace all backslashes with $\backslash$
  5. Replace als <code> with \begin{verbatim} and all </code> with \end{verbatim}
  6. Replace #?#?#?# with \

FYI, regexes in PHP don't support variable-length lookbehind. So that makes this conditional matching between two boundaries difficult.

Tim Pietzcker
Thanks. Good idea!
exhuma
... and thanks for actually reading, and taking into account the part that I am already thinking about a real parser *hug* :)
exhuma
A: 

Provided that your <code> blocks are not nested, this regex would find a backslash after ^ start-of-string or </code> with no <code> in between.

((?:^|</code>)(?:(?!<code>).)+?)\\
    |            |              |
    |            |              \-- backslash
    |            \-- least amount of anything not followed by <code>
    \-- start-of-string or </code>

And replace it with:

$1$\backslash$

You'd have to run this regex in "singleline" mode, so . matches newlines. You'd also have to run it multiple times, specifying global replacement is not enough. Each replacement will only replace the first eligible backslash after start-of-string or </code>.

Andomar
A: 

Write a parser based on an HTML or XML parser like DOMDocument. Traverse the parsed DOM and replace the \ on every text node that is not a descendent of a code node with $\backslash$ and every node that is a code node with \begin{verbatim} … \end{verbatim}.

Gumbo
Not answering the question... I now added a quick para explaining why I did not use a parser in the beginning.
exhuma
+6  A: 

If me, I will try to find HTML parser and will do with that.

Another option is will try to chunk the string into <code>.*?</code> and other parts.

and will update other parts, and will recombine it.

$x="The Hello \ World document is located in:\n<br>
<code>C:\documents\hello_world.txt</code>";

$r=preg_split("/(<code>.*?<\/code>)/", $x,-1,PREG_SPLIT_DELIM_CAPTURE);

for($i=0;$i<count($r);$i+=2)
    $r[$i]=str_replace("\\","$\\backslash$",$r[$i]);

$x=implode($r);

echo $x;

Here is the results.

The Hello $\backslash$ World document is located in: 
C:\documents\hello_world.txt

Sorry, If my approach is not suitable for you.

S.Mark
+1 Great, and way more helpful than the usual "write your own parser" :)
Andomar
I see only one problem, will this also work if the input starts with a code tag? You should probably check for this. BTW, when imploding the array, the code tags won't be put back with this solution. You could reuse the "for" loop for this purpose.
dutchflyboy
It is. Thanks! Splitting the string like this seems actually quite suitable!
exhuma
@dutchflyboy: yup. Thought about the same.
exhuma
S.Mark
+1  A: 

Pandoc? Pandoc converts between a bunch of formats. you can also concatenate a bunch of flies together then covert them. Maybe a few shell scripts combined with your php scraping scripts?

With your "expected input" and the command pandoc -o text.tex test.html the output is:

The Hello \textbackslash{} World document is located in:
\verb!C:\documents\hello_world.txt!

pandoc can read from stdin, write to stdout or pipe right into a file.

Mica
Very interesting. I'll have a closer look on that tomorrow.
exhuma
I've had the best results with pandoc than with any other converter or what have you. it installs on win, mac, or linux. The more "vanilla" your latex code, the better it works. For example, I like to make heavy use of the latex package wrapfig when I can, but pandoc doesn't play very nice with it. But for your WYSIWYG editors, it should go well. Don't forget about pandoc's html to markdown converter (then you could go markdown to latex if necessary).
Mica