While XML is a text-only format, trying to manipulate it with sed and/or awk is a terrible idea, because the format has so many corner cases and exceptions in the way it is used that you should really think of it as a binary format that just happens to be readable by the naked eye rather than a text format. It seems easy, until you actually try it. The short answer is, just, don't.
Instead, I would suggest the use of a scripting language that has a library for handling XML. There are many such libraries. In Perl, you could do something along these lines:
#!/usr/bin/perl -wCSDA
use strict;
use warnings;
package MyFilter;
use base qw(XML::SAX::Base);
sub new {
my $class = shift;
my @args = @_;
my $self = $class->SUPER::new(@args);
$self->{indesc} = 0;
return $self;
}
sub start_element {
my $self = shift;
my $data = shift;
if ($data->{LocalName} eq "description") {
$self->{indesc} = 1;
}
return $self->SUPER::start_element($data);
}
sub end_element {
my $self = shift;
my $data = shift;
if ($data->{LocalName} eq "description") {
$self->{indesc} = 1;
}
return $self->SUPER::end_element($data);
}
sub characters {
my $self = shift;
my $data = shift;
if(($self->{indesc}) == 1) {
$data->{Data} =~ s/\.[^\.]*<a href/.<a href/;
}
return $self->SUPER::characters($data);
}
package main;
use XML::SAX::ParserFactory;
use XML::SAX::Writer;
my $writer = XML::SAX::Writer->new();
my $filter = MyFilter->new(Handler => $writer);
my $input = XML::SAX::ParserFactory->parser(Handler => $filter);
$input->parse_uri("input.xml");
This works as follows:
- The
package MyFilter;
line signals a class which implements an XML::SAX filter:
sub new
is the constructor, which really only creates the $self->{indesc}
flag.
sub start_element
gets called every time an XML element is opened. We check if the element in question is the <description>
element; if so, we set the flag (and pass on further processing to the superclass).
sub end_element
gets called every time an XML element is closed. We check if the element in question is the <description>
element; if so, we clear the flag (and pass on further processing to the superclass).
sub characters
gets called every time a text or CDATA element is processed. In that sub, we check if the flag is set; if it is, we apply a regular expression to the data passed on so that any incomplete sentence is dropped (only counting from a dot; improvement of this regex is left as an exercise to the reader ;-P)
- The
main
package contains the starting point of the script:
- It sets up an
XML::SAX::Writer
(which simply outputs the parsed XML data that it is passed in XML format again, to standard output by default), hooks that up to our filter (so the XML data passed to the writer contains the XML data that the filter received with the incomplete sentences removed), and hooks the filter up to an XML parser created with XML::SAX::ParserFactory
.
- The whole chain is then passed the input (it assumes it can be found in a file named
input.xml
).
If that seems complicated, that's because it is. If you have a choice, just say no to XML, and use something simpler instead, like JSON or YAML ;-)
Nommée FontCode, cette idée est
, in the original.