How to replace HTML tags throughout file with string based on tag ID

Viewed 282

I have a bunch of HTML files with empty <a> tags tied to unique page IDs. I'd like to replace each one with the same link but including the text of the ID visibly in the line (i.e. displayed within the <p>...</p> tags within which the <a> tags occur) and changing the class of the tag so I can format it in the CSS.

So what I have currently is tags like this occurring throughout text:

<a id="page_123" class="garbage1"></a>some text<a id="page_124" class="garbage2"></a>

And I want to replace it with:

<a id="page_123" class="pagenum"> 123 </a>some text<a id="page_124" class="pagenum"> 124 </a>

So that the resulting display is:

some text 123 some text 124 some text.

The class of each of these tags is not always the same but I want to change all of them to pagenum. The tag id is always of the form page_####, anywhere from 1 to 4 digits.

I'm way over my head on this. I've gotten as far as constructing a horrifying regex (I know, I know) to pick the pattern out of the files, which seems to work when I test via cat file1.html | grep -o "\Wa\sid..page.\d*.\sclass..\w*....a."—that returns every instance of the pattern and nothing else. I'm totally stuck trying to go from there to making the replacement happen.

4 Answers

First handle the greedy sed by inserting newlines.

sed -r 's#</a>#&\n#g' file1.html

Next determine how strict your match should be

sed -r 's#</a>#&\n#g' file1.html |
   sed -r 's#(<a id="page_)([^"]*)(" class=")([^"]*).*#\1\2\3pagenum">\2</a>#'

You can use:

sed -r 's/(id="page_([0-9]+)" +class=)"[^"]*">/\1"pagenum"> \2 /g' file1.html

This is finding the following expression:

(id="page_([0-9]+)" +class=)"[^"]*">

replacing it with:

\1"pagenum"> \2 

See a demo here.

If perl is your option, would you please try:

perl -0777 -pe 's/(<a\s+id="page_(\d+)"\s+class=").+?">/$1pagenum"> $2 /g' file.html
  • The -0777 option tells perl to slurp whole file to allow included line breaks.
  • The regex .+? is used as a non-greedy match.

Please don't parse HTML with regex, but use a parser like or instead.

Assuming 'input.htm':

<html>
<body>
  <p><a id="page_123" class="garbage1"></a>some text<a id="page_124" class="garbage2"></a></p>
  <p><a id="page_125" class="garbage1"></a>some text<a id="page_126" class="garbage2"></a></p>
  <p><a id="page_127" class="garbage1"></a>some text<a id="page_128" class="garbage2"></a></p>
</body>
</html>

With xmlstarlet:

$ xmlstarlet ed -O -P \
  -u '//a' -x 'concat(" ",substring(@id,6)," ")' \
  input.htm
<html>
<body>
  <p><a id="page_123" class="garbage1"> 123 </a>some text<a id="page_124" class="garbage2"> 124 </a></p>
  <p><a id="page_125" class="garbage1"> 125 </a>some text<a id="page_126" class="garbage2"> 126 </a></p>
  <p><a id="page_127" class="garbage1"> 127 </a>some text<a id="page_128" class="garbage2"> 128 </a></p>
</body>
</html>

With xidel:

$ xidel -s --input-format=xml input.htm -e '
  x:replace-nodes(
    //a,
    function($x){
      element a {$x/@*,x" {substring($x/@id,6)} "}
    }
  )
' --printed-node-format=xml
<html>
<body>
  <p><a id="page_123" class="garbage1"> 123 </a>some text<a id="page_124" class="garbage2"> 124 </a></p>
  <p><a id="page_125" class="garbage1"> 125 </a>some text<a id="page_126" class="garbage2"> 126 </a></p>
  <p><a id="page_127" class="garbage1"> 127 </a>some text<a id="page_128" class="garbage2"> 128 </a></p>
</body>
</html>
Related