【问题标题】:Matching selected option of specific html tag in java Regex匹配java Regex中特定html标签的选定选项
【发布时间】: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选项标签中唯一的其他量词吗?
  • 如果有人想重温过去:stackoverflow.com/a/1732454/821786

标签: java regex


【解决方案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 regex101RegexPlanet (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。

  • \sshort 用于空格 [ \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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-05
      • 2015-06-17
      • 1970-01-01
      • 1970-01-01
      • 2014-12-07
      • 2023-03-24
      • 1970-01-01
      相关资源
      最近更新 更多