匹配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 中查找所有&lt;select&gt; 元素,其属性name 等于quantity,子元素&lt;option&gt; 的属性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 [^&gt;]&gt; 的negation(匹配字符,不是&gt;(?=[^&gt;]*?\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标签的选定选项的主要内容,如果未能解决你的问题,请参考以下文章

IE 11不会更新选择元素中选定的选项项标签

RegEx 只查看 HTML 标签内的文本?

java正则表达式过滤html标签(转)

如何转义字符串中的特定 HTML 标签

如何使用jquery在标签中显示多选下拉列表的选定文本?

Java模式与组匹配