匹配java Regex中特定html标签的选定选项
Posted
技术标签:
【中文标题】匹配java Regex中特定html标签的选定选项【英文标题】:Matching selected option of specific html tag in java Regex 【发布时间】:2016-03-09 07:16:47 【问题描述】:我必须解析一些 html 以从一些 HTML 中找到一组值,这些值并不总是格式正确,我无法控制(所以 Scanner 似乎不是一个选项)
这是一个购物车,购物车中有 n 行,每行包含一个数量下拉菜单。现在我希望能够获得购物车中产品的总和。
鉴于这个 html,我想匹配值 2 和 5
...
<select attr="other stuff" name="quantity">
<option value="1" />
<option value="2" selected="selected" />
</select>
....
<select name="quantity" attr="other stuff">
<option selected="selected" value="5" />
<option value="6" />
</select>
我做了一些可怜的尝试,但考虑到变量的数量(例如“值”和“选定”标签的顺序),我的大多数解决方案要么不起作用,要么真的很慢。
我最后使用的 Java 代码如下
Pattern pattern = Pattern.compile("select(.*?)name=\"quantity\"([.|\\n|\\r]*?)option(.*?)value=\"(/d)\" selected=\"selected\"", Pattern.DOTALL);
Matcher matcher = pattern.matcher(html);
if (matcher.find())
....
当属性顺序改变时,它很慢并且不起作用。我的正则表达式知识不足以编写有效的模式
【问题讨论】:
你需要使用\\d来匹配数字不是(/d) 在使用正则表达式解析 HTML 时,尽可能避免使用惰性点匹配,因为可能会出现 超时(我了解到,@Casimir:))问题。跨度> 你能建议一种重写它的方法吗?我一直在研究向前/向后查找,但我不确定我是否“得到”它们 是selected="selected
选项标签中唯一的其他量词吗?
如果有人想重温过去:***.com/a/1732454/821786
【参考方案1】:
您可以使用 XPath 表达式来检索问题中的 HTML 的所有值属性,而不是使用正则表达式:
//select[@name="quantity"]/option[@selected="selected"]/@value
一句话:
在 XML 中查找所有<select>
元素,其属性name
等于quantity
,子元素<option>
的属性selected
等于selected
检索value
属性。
我真的会考虑尝试使用 XQuery/XPath,这就是它的用途。阅读this answer 到问题How to read XML using XPath in Java,了解如何检索这些值。 XPath 表达式介绍here.
考虑将来您只需要找到属性为selected="selected"
和的选项,例如status="accepted"
。 XPath 表达式将简单地变成:
//select[@name="quantity"]/option[@selected="selected" and @status="accepted"]/@value
XPath 表达式易于扩展、易于审查、易于证明它在做什么。
现在你必须为添加的条件创建什么样的 RegEx 怪物?很难写,更难维护。代码审阅者如何判断复杂的(cf bobble bubble's answer)正则表达式在做什么?你如何证明正则表达式实际上正在做它应该做的事情?
您当然可以记录正则表达式,这是您应该始终为正则表达式做的事情。但这并不能证明什么。
我的建议:除非绝对没有其他办法,否则不要使用正则表达式。
对于运动,我制作了一个 sn-p,展示了这种工作方式的基础知识:
import java.io.StringReader;
import javax.xml.xpath.*;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class ReadElementsFromHtmlUsingXPath
private static final String html=
"<html>Read more about XPath <a href=\"www.w3schools.com/xsl/xpath_intro.asp\">here</a>..."+
"<select attr=\"other stuff\" name=\"quantity\">"+
"<option value=\"1\" />"+
"<option value=\"2\" selected=\"selected\" />"+
"</select>"+
"<i><b>Oh and here's the second element</b></i>"+
"<select name=\"quantity\" attr=\"other stuff\">"+
"<option selected=\"selected\" value=\"5\" />"+
"<option value=\"6\" />"+
"</select>"+
"And that's all folks</html>";
private static final String xpathExpr =
"//select[@name=\"quantity\"]/option[@selected=\"selected\"]/@value";
public static void main(String[] args)
try
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression expr = xpath.compile(xpathExpr);
NodeList nodeList = (NodeList) expr.evaluate(new InputSource(new StringReader(html)),XPathConstants.NODESET);
for( int i = 0; i != nodeList.getLength(); ++i )
System.out.println(nodeList.item(i).getNodeValue());
catch (XPathExpressionException e)
e.printStackTrace();
输出结果:
2
5
【讨论】:
请注意,您可以缩短研究添加[1]
为具有 selected
属性的第一个(和唯一的)option
标记(并且由于 selected="selected"
只是一个 xhtml 成语,你不需要测试值,一个selected
属性只能有值selected
)://select[@name="quantity"]/option[@selected][1]/@value
。这样,XPath 就不会尝试为同一个 select
父级查找具有 selected
属性的另一个 option
标记。研究停止,立即跳转到下一个select
标签。
@CasimiretHippolyte 感谢您的见解。【参考方案2】:
当然取决于您的 html 格式可能有多错误。 Parser solution 为首选。
符合您要求的正则表达式并不难,只需将它们放在一起即可。
(?xi) # i-flag for caseless, x-flag for comments (free spacing mode)
# 1.) match <select with optional space at the end
<\s*select\s[^>]*?\bname\s*=\s*["']\s*quantity[^>]*>\s*
# 2.) match lazily any amount of options until the "selected"
(?:<\s*option[^>]*>\s*)*?
# 3.) match selected using a lookahead and capture number from value
<\s*option\s(?=[^>]*?\bselected)[^>]*?\bvalue\s*=\s*["']\s*(\d[.,\d]*)
Try pattern at regex101 或 RegexPlanet (Java) 并作为 Java 字符串:
"(?i)<\\s*select\\s[^>]*?\\bname\\s*=\\s*[\"']\\s*quantity[^>]*>\\s*(?:<\\s*option[^>]*>\\s*)*?<\\s*option\\s(?=[^>]*?\\bselected)[^>]*?\\bvalue\\s*=\\s*[\"']\\s*(\\d[.,\\d]*)"
它没有太多的魔力。一个长长的丑陋模式主要是因为解析 html。
\s
是 short 用于空格 [ \t\r\n\f]
\d
是数字 [0-9]
的缩写
\b
匹配 word boundary
(?:
打开non capturing group
[^>]
是>
的negation(匹配字符,不是>
)
(?=[^>]*?\bselected)
检查是通过使用 lookahead 来完成的,因为它与订单无关
(\d[.,\d]*)
部分用于捕获号码。必需是一位数字,带有任何可选的[.,\d]
匹配项将在group(1)
第一个capturing group(带括号的组)中。
【讨论】:
=) 将 XPath 表达式的优雅与您答案中的正则表达式进行比较。尝试调试,如果其中有错误... RegEx101 似乎同意你的看法。 GJ。 我给了这个赏金,因为它是我真正在寻找的答案。不过我会接受 XPath 的答案,因为我认为这才是未来访问者真正应该关注的地方 @NickCardoso 谢谢! :) 是的,TTs 的答案非常详细,当然也更优雅。【参考方案3】:让我们Divide and Conquer。
首先,创建一个名为 Option
的类:
public class Option
private String value;
private boolean selected;
public Option()
public Option(String value, boolean selected)
this.value = value;
this.selected = selected;
public String getValue()
return value;
public void setValue(String value)
this.value = value;
public boolean isSelected()
return selected;
public void setSelected(boolean selected)
this.selected = selected;
@Override
public String toString()
return "" +
"value='" + value + '\'' +
", selected=" + selected +
'';
其次,我们需要一个regex
来查找html标签:
static final Pattern OPTION_TAG_PATTERN = Pattern.compile("<option\\s*(value=\"\\w+\"\\s+(?:selected=\"selected\")?|(?:selected=\"selected\")?\\s+value=\"\\w+\")\\s*/>");
并提取value
的值:
static final Pattern VALUE_PATTERN = Pattern.compile("value=\"(\\w+)\"");
最后:
public class Test
private static final Pattern OPTION_TAG_PATTERN = Pattern.compile("<option\\s*(value=\"\\w+\"\\s+(?:selected=\"selected\")?|(?:selected=\"selected\")?\\s+value=\"\\w+\")\\s*/>");
private static final Pattern VALUE_PATTERN = Pattern.compile("value=\"(\\w+)\"");
public static void main(String[] args)
String html = "...\n" +
"<select attr=\"other stuff\" name=\"quantity\">\n" +
" <option value=\"1\" />\n" +
" <option value=\"2\" selected=\"selected\" />\n" +
"</select>\n" +
"....\n" +
"<select name=\"quantity\" attr=\"other stuff\">\n" +
" <option selected=\"selected\" value=\"5\" />\n" +
" <option value=\"6\" />\n" +
"</select>";
findOptions(html).forEach(System.out::println);
public static List<Option> findOptions(String htmlContent)
List<Option> options = new ArrayList<>();
Matcher optionMatcher = OPTION_TAG_PATTERN.matcher(htmlContent);
while (optionMatcher.find())
options.add(toOption(htmlContent.substring(optionMatcher.start(), optionMatcher.end())));
return options;
private static Option toOption(String htmlTag)
Option option = new Option();
Matcher valueMatcher = VALUE_PATTERN.matcher(htmlTag);
if (valueMatcher.find())
option.setValue(valueMatcher.group(1));
if (htmlTag.contains("selected=\"selected\""))
option.setSelected(true);
return option;
输出:
value='1', selected=false
value='2', selected=true
value='5', selected=true
value='6', selected=false
希望对你有帮助!
【讨论】:
【参考方案4】:我相信正则表达式并不是最适合这种情况,因为它的复杂性使得代码难以阅读和诊断。我们仍然可以使用正则表达式,但要分解逻辑以使其更易于阅读和改进:
String html = "<select attr=\"other stuff\" name=\"quantity\">" +
"<option value=\"1\" /> " +
"<option value=\"2\" selected=\"selected\" /> " +
"</select> " +
"<select name=\"quantity\" attr=\"other stuff\"> " +
"<option selected=\"selected\" value=\"5\" /> " +
"<option value=\"6\" /> " + "</select>";
String options = "(?<=<option).*?(?=/>)";
Pattern pat = Pattern.compile(options, Pattern.DOTALL);
Matcher m = pat.matcher(html);
Pattern values = Pattern.compile("(?<=value=\").*?(?=\")");
Pattern selected = Pattern.compile("selected=\"selected\"");
Integer counter = 0;
while (m.find())
Matcher sel = selected.matcher(m.group());
if (sel.find())
Matcher val = values.matcher(m.group());
if (val.find())
Integer count = Integer.parseInt(val.group());
counter = counter + count;
System.out.println(counter.toString());
打印出所需的 7
【讨论】:
以上是关于匹配java Regex中特定html标签的选定选项的主要内容,如果未能解决你的问题,请参考以下文章