问题
How do I remove all tags below a certain node except for some elements using Nokogiri? For example, using this setup:
src = <<EOS
<html>
<body>
<p>
Hello <i>world</i>!
This is <em>another</em> line.
<p><h3>And a paragraph <em>with</em> a heading.</h3></p>
<b>Third line.</b>
</p>
</body>
</html>
EOS
doc = Nokogiri::HTML(src)
para = doc.at('//p')
How can I remove all elements in the paragraph (while preserving their content) except <i> and <b> elements? So the result would be:
<html>
<body>
<p>
Hello <i>world</i>!
This is another line.
And a paragraph with a heading.
<b>Third line.</b>
</p>
</body>
</html>
回答1:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="em | p/p | h3">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Applied to your sample, result will be:
<html>
<body>
<p>
Hello
<i>world</i>!
This is another line.
And a paragraph with a heading.
<b>Third line.</b>
</p>
</body>
</html>
EDIT as requested in comments.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p//*[not(self::i or self::b)]">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
This will remove all elements (markup, not string-values) in p
, except of i
and b
elements.
回答2:
Just to round out the examples, here's one using Nokogiri without XSLT:
require 'nokogiri'
src = <<EOS
<html>
<body>
<p>
Hello <i>world</i>!
This is <em>another</em> line.
<p><h3>And a paragraph <em>with</em> a heading.</h3></p>
<b>Third line.</b>
</p>
</body>
</html>
EOS
doc = Nokogiri::HTML(src)
if (doc.errors.any?)
puts "doc.errors:"
doc.errors.each do |e|
puts "#{ e.line }: #{ e.to_s }"
end
puts
end
doc.search('//p/*').each do |n|
n.replace(n.content) unless (%w[i b].include?(n.name))
end
puts doc.to_html
# >> doc.errors:
# >> 6: Unexpected end tag : p
# >> 8: Unexpected end tag : p
# >>
# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html><body>
# >> <p>
# >> Hello <i>world</i>!
# >> This is another line.
# >> </p>
# >> <p></p>
# >> <h3>And a paragraph <em>with</em> a heading.</h3>
# >> <b>Third line.</b>
# >>
# >> </body></html>
Notice that Nokogiri is not happy with the markup and did some fix-up. And, that the actual code to strip the tags was only three lines and could have been written on one.
回答3:
Flack gave the correct answer using an XSLT template, I provide a full Nokogiri based example here:
xslt = <<EOS
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<output method="html" indent="yes"/>
<template match="node() | @*">
<copy>
<apply-templates select="node() | @*"/>
</copy>
</template>
<template match="p//*[not(self::i or self::b)]">
<apply-templates/>
</template>
</stylesheet>
EOS
src = <<EOS
<html>
<body>
<p>
Hello <i>world</i>!
This is <em>another</em> line.
<p><h3>And a paragraph <em>with</em> a heading.</h3></p>
<b>Third line.</b>
</p>
</body>
</html>
EOS
doc = Nokogiri::XML(src)
paragraph = doc.at('p')
xslt = Nokogiri::XSLT(xslt)
transformed_paragraph = xslt.transform(paragraph)
paragraph.replace transformed_paragraph.children
puts doc
Output:
<?xml version="1.0"?>
<html>
<body>
<p>
Hello <i>world</i>!
This is another line.
And a paragraph with a heading.
<b>Third line.</b>
</p>
</body>
</html>
来源:https://stackoverflow.com/questions/4885300/how-to-remove-all-tags-except-for-some-using-nokogiri