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

使用XML::LibXML删除XML命名空间,xml,perl,xml-namespaces,libxml2,xml-libxml,Xml,Perl,Xml Namespaces,Libxml2,Xml Libxml,我正在将XML文档转换为HTML。需要做的事情之一是删除名称空间,除非根标记中是XHTML名称空间,否则不能在HTML中合法声明名称空间。我在5-10年前的文章中发现,使用XML::LibXML和LibXML2实现这一点有多么困难,但最近没有这么多。下面是一个例子: use XML::LibXML; use XML::LibXML::XPathContext; use feature 'say'; my $xml = <<'__EOI__'; <myDoc> <

我正在将XML文档转换为HTML。需要做的事情之一是删除名称空间,除非根标记中是XHTML名称空间,否则不能在HTML中合法声明名称空间。我在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,但这也不起作用。最后,我在文档中查找了一种删除名称空间的方法。没有这样的运气。最后的输出字符串仍然包含我想要删除的所有名称空间声明和前缀


是否有人有办法删除名称空间,将元素和属性设置为空名称空间?

以下是我自己的答案。如果没有更好的办法,那就行了。我真希望有更好的方法

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;

这是我自己的体操答案。如果没有更好的办法,那就行了。我真希望有更好的方法

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;

下面是一个使用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);

请注意,如果要复制注释或处理说明,则需要进一步调整。

这里有一个使用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);

请注意,如果要复制注释或处理说明,则需要进行进一步调整。

您试图更改元素的名称空间,但没有迭代元素?@ikegami我会,但我希望看到它首先在其中一个元素上工作。您试图更改元素的名称空间,但是你不迭代元素吗?@ikegami我会的,但我想先看看它在其中一个元素上的效果。对不起,我不记得了。这是很久以前的事了;要么我不知道该怎么做,要么它避免了我在使用这个模块时遇到的一个内存问题。我记得在试图阻止释放仍在使用的内容时遇到问题。@ikegami unbindNode也会在删除最后一个引用时释放节点的内存。@ikegami否,即使原始文档仍然存在,也可以释放使用unbindNode或removeChild删除的节点。它们被移动到一个带有内部refcount的隐藏文档片段。此文档片段引用了原始文档,但如果其refcount降至零,则文档片段将被删除。对不起,我不记得了。这是很久以前的事了;要么我不知道该怎么做,要么它避免了我在使用这个模块时遇到的一个内存问题。我记得在试图阻止释放仍在使用的内容时遇到问题。@ikegami unbindNode也会在删除最后一个引用时释放节点的内存。@ikegami否,即使原始文档仍然存在,也可以释放使用unbindNode或removeChild删除的节点。它们被移动到一个带有内部refcount的隐藏文档片段。此文档片段引用原始文档,但如果其refcount降至零,则文档片段将被删除。