Reputation: 1666
I need to find and replace xml tags inside html string which is not complete xml that's why I can not use xml parser to deal with it. So I need to manually find the xml tags and replace them with content inside these html strings.
Example of html string containing the xml tags:
some text<p>hello p</p>
<vars type="text" name="fname" age="64" style="<b>color='red'</b>
Class::SubClass->color" /> other text or html open tags like <p><table><tr>
So I need to find the xml "vars" tags with their variable number of optional attributes and replace them with contents.
Upvotes: 1
Views: 1333
Reputation: 1666
Looking at some Perl parsers for XML and HTML like Mojo::DOM as pointed by Miller answer above and also looking at XML::TreePP, I found they are using regex to parse the entire contents, so I tried their regex and got good results just may need some optimizations.
Here is what I did:
my $text =<<'XHTML';
some text
<p>hello p</p>
<vars type="text" name= "fname" single='single quoted' unqouted=noquotes hastags=" <b>color='red'</b> Class::SubClass->color"/>
other text or html open tags like
<vars type="text" name= "lname" single1='single quoted' unqouted1=noquotes hastags1=" <b>bgcolor='red'</b> Class::SubClass->bgcolor">
<table><tr>
<vars name="mname" />
XHTML
while ( $text =~ m{(<vars\s+([^\!\?\s<>](?:"[^"]*"|'[^']*'|[^"'<>\/])*)/?>)}sxgi ) {
my $match = $1;
my $args = $2;
#print "[[$match]] \n{{$args}}\n\n";
#parse name=value attributes, values may be double or single quoted or unquoted
while ( $args =~ m/([^<>=\s\/]+|\/)(?:\s*=\s*(?:"([^"]*?)"|'([^']*?)'|([^>\s\/]*)))?\s*/sxgi ) {
my $name = $1;
#any better solution with regex above to just get $2
my $value = $2? $2: ($3? $3 : $4);
print "$name=$value\n";
}
print "\n";
}
and here is the output as expected:
type=text
name=fname
single=single quoted
unqouted=noquotes
hastags= <b>color='red'</b> Class::SubClass->color
type=text
name=lname
single1=single quoted
unqouted1=noquotes
hastags1= <b>bgcolor='red'</b> Class::SubClass->bgcolor
name=mname
of course the variable $match in the code has the entire match so I can replace it with my contents.
the second regex that matches the attributes needs optimizations, I am not satisfied with this line :
my $value = $2? $2: ($3? $3 : $4);
can the regex be modified to just get the attribute value in $2.
The regex as used in Mojo::Dom is
my $ATTR_RE = qr/
([^<>=\s\/]+|\/) # Key
(?:
\s*=\s*
(?:
"([^"]*?)" # Quotation marks
|
'([^']*?)' # Apostrophes
|
([^>\s\/]*) # Unquoted
)
)?
\s*
/x;
my $END_RE = qr!^\s*/\s*(.+)!;
my $TOKEN_RE = qr/
([^<]+)? # Text
(?:
<\?(.*?)\?> # Processing Instruction
|
<!--(.*?)--\s*> # Comment
|
<!\[CDATA\[(.*?)\]\]> # CDATA
|
<!DOCTYPE(
\s+\w+
(?:(?:\s+\w+)?(?:\s+(?:"[^"]*"|'[^']*'))+)? # External ID
(?:\s+\[.+?\])? # Int Subset
\s*
)>
|
<(
\s*
[^<>\s]+ # Tag
\s*
(?:$ATTR_RE)* # Attributes
)>
|
(<) # Runaway "<"
)??
/xis;
I just messed up with it to match if closing tag with or without slash > or />.
Upvotes: 0
Reputation: 35208
Do not use regular expressions for parsing HTML. Instead use an actual HTML Parser like Mojo::DOM
. There's a nice 8 minute video about using this module at mojocast episode 5
.
The following takes your html, and translates your special vars tag into some new text.
use strict;
use warnings;
use Mojo::DOM;
# Parse
my $dom = Mojo::DOM->new(do {local $/; <DATA>});
for my $var ($dom->find('vars')->each) {
my $type = $var->{type};
my $name = $var->{name};
$var->replace("<b>name is $name</b> + <i>type is $type</i>");
}
print $dom;
__DATA__
<html>
<head>
<title>Always use a parser, not a regex</title>
</head>
<body>
some text<p>hello p</p>
<vars type="text" name="fname" age="64" style="<b>color='red'</b>
Class::SubClass->color" /> other text or html open tags like <p><table><tr><td></td></tr></table>
</body></html>
Outputs:
<html>
<head>
<title>Always use a parser, not a regex</title>
</head>
<body>
some text<p>hello p</p>
<b>name is fname</b> + <i>type is text</i> other text or html open tags like <p></p><table><tr><td></td></tr></table>
</body></html>
Upvotes: 2