匹配java Regex中特定html标记的选定选项

时间:2015-12-04 11:06:56

标签: java regex

我必须解析一些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>

我做了一些可怜的尝试,但考虑到变量的数量(例如,&#39;值&#39;以及&#39;选择&#39;标签的顺序)我的大多数解决方案要么不工作或真的很慢。

我结束的最后一个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()) {
   ....
}

它非常慢,并且在属性顺序更改时不起作用。我的正则表达式知识不足以写出有效的模式

4 个答案:

答案 0 :(得分:4)

您可以使用XPath表达式来检索问题中的HTML的所有值属性,而不是使用正则表达式:

//select[@name="quantity"]/option[@selected="selected"]/@value

用语言说:

  • 查找属性<select>等于name的XML中的所有quantity元素,其中子元素<option>的属性selected等于{{1} }}
  • 检索selected属性。

我真的会考虑尝试使用XQuery / XPath,这是它的用途。请阅读问题this answerHow to read XML using XPath in Java,了解如何检索值。关于XPath表达式here的介绍。

考虑以下情况,您将来只需要找到属性value 的选项,例如selected="selected"。 XPath表达式将简单地变为:

status="accepted"

XPath表达式易于扩展,易于查看,易于证明它正在做什么。

现在你需要为增加的条件创建什么样的RegEx怪物?很难写,甚至更难维护。代码审查员如何判断正则表达式的复杂性(cf bobble bubble&answer)?你如何证明正则表达式实际上正在做它应该做的事情?

您当然可以记录正则表达式,这是您应该对正则表达式执行的操作。但这并没有证明什么。

我的建议:远离正则表达式,除非绝对没有办法。

对于体育运动,我制作了一个片段,展示了这种工作方式的基础知识:

//select[@name="quantity"]/option[@selected="selected" and @status="accepted"]/@value

输出结果:

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();
        }
    }
}

答案 1 :(得分:3)

当然取决于您的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。

  • \s是空格[ \t\r\n\f]
  • short
  • \d是数字[0-9]
  • 的缩写
  • \b匹配word boundary
  • (?:打开non capturing group
  • [^>]>的{​​{3}}(匹配字符,不是>
  • (?=[^>]*?\bselected)使用negation完成检查,以便独立于订单
  • (\d[.,\d]*)部分来捕获数字。必填项是一位数字,带有任意可选[.,\d]

匹配将在group(1)第一个lookahead(带括号的组)中。

答案 2 :(得分:2)

让我们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}

我希望这可以帮到你!

答案 3 :(得分:0)

我认为正则表达式并不是最好的,因为复杂性使得难以通读和诊断代码。我们仍然可以使用正则表达式,但要打破逻辑,以便更容易阅读和改进:

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