使用XML::LibXML删除XML命名空间

4

我正在将一个XML文档转换为HTML。其中需要进行的一个操作是去除命名空间,在HTML中不能合法地声明命名空间(除非是在根标签中使用XHTML命名空间)。我发现5-10年前有关于如何使用XML::LibXML和LibXML2解决这个问题的帖子,但最近没有那么多了。以下是一个例子:

use XML::LibXML;
use XML::LibXML::XPathContext;
use feature 'say';

my $xml = <<'__EOI__';
<myDoc>
  <par xmlns:bar="www.bar.com">
    <bar:foo/>
  </par>
</myDoc>
__EOI__

my $parser = XML::LibXML->new();
my $doc = $parser->parse_string($xml);

my $bar_foo = do{
    my $xpc = XML::LibXML::XPathContext->new($doc);
    $xpc->registerNs('bar', 'www.bar.com');
    ${ $xpc->findnodes('//bar:foo') }[0];
};
$bar_foo->setNodeName('foo');
$bar_foo->setNamespace('','');
say $bar_foo->nodeName; #prints 'bar:foo'. Dang!

my @namespaces = $doc->findnodes('//namespace::*');
for my $ns (@namespaces){
    # $ns->delete; #can't find any such method for namespaces
}
say $doc->toStringHTML;

在这段代码中,我尝试了一些行不通的方法。首先,我试图将bar:foo元素的名称设置为未加前缀的foo(文档说该方法能识别命名空间,但显然不行)。然后我试图将元素的命名空间设置为null,但也没用。最后,我查看文档以寻找删除命名空间的方法,但没有发现。最终输出的字符串仍包含我想要删除的内容(命名空间声明和前缀)。
有人知道如何移除命名空间并将元素和属性设置为null命名空间吗?

你试图更改元素的命名空间,但你没有迭代元素? - ikegami
@ikegami 我会的,但是我想先看看它在其中一个上面能否正常运行。 - Nate Glenn
2个回答

5
这是我的自制体操答案。如果没有更好的方法,它就可以胜任。我真希望有更好的方法... replace_without_ns 方法只是复制没有命名空间的节点。需要命名空间的任何子元素都会在其上声明。下面的代码将整个文档移动到空命名空间中:
use strict;
use warnings;
use XML::LibXML;

my $xml = <<'__EOI__';
<myDoc xmlns="foo">
  <par xmlns:bar="www.bar.com" foo="bar">
    <bar:foo stuff="junk">
      <baz bar:thing="stuff"/>
      fooey
      <boof/>
    </bar:foo>
  </par>
</myDoc>
__EOI__

my $parser = XML::LibXML->new();
my $doc = $parser->parse_string($xml);

# remove namespaces for the whole document
for my $el($doc->findnodes('//*')){
    if($el->getNamespaces){
        replace_without_ns($el);
    }
}

# replaces the given element with an identical one without the namespace
# also does this with attributes
sub replace_without_ns {
    my ($el) = @_;
    # new element has same name, minus namespace
    my $new = XML::LibXML::Element->new( $el->localname );
    #copy attributes (minus namespace namespace)
    for my $att($el->attributes){
        if($att->nodeName !~ /xmlns(?::|$)/){
            $new->setAttribute($att->localname, $att->value);
        }
    }
    #move children
    for my $child($el->childNodes){
        $new->appendChild($child);
    }

    # if working with the root element, we have to set the new element
    # to be the new root
    my $doc = $el->ownerDocument;
    if( $el->isSameNode($doc->documentElement) ){
        $doc->setDocumentElement($new);
        return;
    }
    #otherwise just paste the new element in place of the old element
    $el->parentNode->insertAfter($new, $el);
    $el->unbindNode;
    return;
}

print $doc->toStringHTML;

抱歉,我不记得了。这是很久以前的事了;要么我当时不知道该怎么做,要么这样做可以避免我在使用该模块时遇到的某些内存问题。我记得曾经尝试过让我仍在使用的东西不被释放,但遇到了麻烦。 - Nate Glenn
@ikegami unbindNode方法在最后一个引用被删除时立即释放该节点的内存。 - nwellnhof
@ikegami 不,使用 unbindNoderemoveChild 删除的节点即使原始文档仍存在也可以被释放。它们被移动到一个带有内部引用计数的隐藏文档片段中。该文档片段引用了原始文档,但如果其引用计数降至零,则该文档片段将被释放。 - nwellnhof

1
这是一个使用XSLT样式表的简单解决方案:
use strict;
use warnings;
use XML::LibXML;
use XML::LibXSLT;

my $xml = <<'__EOI__';
<myDoc xmlns="foo">
  <par xmlns:bar="www.bar.com" foo="bar">
    <bar:foo stuff="junk">
      <baz bar:thing="stuff"/>
      fooey
      <boof/>
    </bar:foo>
  </par>
</myDoc>
__EOI__

my $parser = XML::LibXML->new();
my $doc = $parser->parse_string($xml);

my $xslt    = XML::LibXSLT->new();
my $xsl_doc = $parser->parse_string(<<'XSL');
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*">
    <xsl:element name="{local-name()}">
      <xsl:apply-templates select="node()|@*"/>
    </xsl:element>
  </xsl:template>
  <xsl:template match="@*">
    <xsl:attribute name="{local-name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>
</xsl:stylesheet>
XSL

my $stylesheet = $xslt->parse_stylesheet($xsl_doc);
my $result     = $stylesheet->transform($doc);
print $stylesheet->output_as_bytes($result);

请注意,如果您想复制注释或处理指令,则需要进行进一步的调整。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接