regex 正则表达式匹配到模式的最后一次出现(例如< /div>)在另一个匹配模式之前(例如< /div-container>)

h6my8fg2  于 2022-12-19  发布在  其他
关注(0)|答案(5)|浏览(204)

换句话说,在匹配结束和第二个模式之间不能出现其他模式,这需要在单个正则表达式中实现。
在我的具体案例中,我有一个HTML页面,需要提取其中的所有内容

<w-block-content><span><div>

以及

</div></span></w-block-content>

其中

  • 元素可能具有属性
  • HTML可能已格式化或未格式化-可能有额外白色和换行符
  • 在上述任何标记之间可能存在其他内容,包括上述外部div中的内部div元素。
  • 仅包含一个直接子项<span>子项(即,它可以包含其他非跨范围子项)
  • 它只包含一个直接<div>子级
  • 它 Package 了必须提取的内容
  • 🚩匹配必须一直延伸到X1 M7 N1 X内的X1 M6 N1 X内的最后一个X1 M5 N1 X,即使它与开始的X1 M8 N1 X不匹配。
  • 解决方案必须是纯ECMAScript规范正则表达式。不能使用Javascript代码

因此,问题在顶部的问题中陈述。
只要没有内部</div>标记,* 以下正则表达式就能成功匹配:*

(?:<w-block-content.*[\s\S]*?<div>)([\s\S]*?)(?:<\/div>[\s\S]*?<\/span>[\s\S]*?<\/w-block-content>)

但是如果有额外的</div>标签,匹配会提前结束,而不包括整个块。
我使用[\s\S]*?来匹配任意内容,包括多余的空格和换行符。
以下是示例测试数据:

</tr>
          <td>
            <div draggable="true" class="source master draggable-box wysiwyg-block" data-block-type="master" data-block-id="96e80afb-afa0-4e46-bfb7-34b80da76112" style="opacity: 1;">
              <w-block-content data-block-content-id="96e80afb-afa0-4e46-bfb7-34b80da76112"><span class="source-block-tooltip">
                  <div>

Další master<br><div><b>Master č. 2</b>                  </div><br>

                  </div>
                </span></w-block-content>
            </div>
          </td>
        </tr>
</tr>
          <td>
            <div draggable="true" class="source master draggable-box wysiwyg-block" data-block-type="master" data-block-id="96e80afb-afa0-4e46-bfb7-34b80da76112" style="opacity: 1;">
              <w-block-content data-block-content-id="96e80afb-afa0-4e46-bfb7-34b80da76112"><span class="source-block-tooltip">
                  <div>

Další master<br><b>Master č. 2</b><br>
                  
                   </div>
                </span></w-block-content>
            </div>
          </td>
        </tr>

我一直在测试(https://regex101.com/r/jekZhr/3
第一个提取的块应为:

Další master<br><div><b>Master č. 2</b>                  </div><br>

我知道regex不是处理XML/HTML的最佳工具,但我需要知道这样的regex是否可行,或者我是否需要更改数据的结构。

rkttyhzu

rkttyhzu1#

正如已经评论过的,regex不是一个通用的工具--事实上它是一个匹配字符串中模式的特定工具。已经说过,这里有一个regex解决方案,它将匹配第一个<div>之后直到</w-block-content>的所有内容。从那里找到</div>.slice()的最后一个索引。

    • 正则表达式**
/(?<=<w-block-content[\s\S]*?<div[\s\S]*?>)
[\s\S]*?
(?=<\/w-block-content>)/g
    • 一个
    • 说明**

后面一看:(?<= ... )必须在匹配之前,但不会包含在匹配本身中。
展望未来:(?= ... )必须继续进行匹配,但不会包含在匹配本身中。
| 节段|说明|
| - ------| - ------|
|

(?<=<w-block-content[\s\S]*?<div[\s\S]*?>)

|找出文字"<w-block-content",然后是任何内容,然后是文字"<div",然后是任何内容,然后是文字">"是否在匹配的内容之前。不要将其包括在匹配中。|
|

[\s\S]*?

|匹配任何内容|
|

(?=<\/w-block-content>)

|查找文字"</w-block-content>"是否在匹配的文字之后。不要将其包含在匹配中。|

    • 示例**
const rgx = /(?<=<w-block-content[\s\S]*?<div[\s\S]*?>)[\s\S]*?(?=<\/w-block-content>)/g;

const str = document.querySelector("main").innerHTML;

const A = str.match(rgx)[0];

const idx = A.lastIndexOf("</div>");

const X = A.slice(0, idx);

console.log(X);
<main>
  <w-block-content id="A">
    CONTENT OF #A
    <span id="B">
      CONTENT OF #B
      <div id="C">
        <div>CONTENT OF #C</div>
        <div>CONTENT OF #C</div>
      </div>
      CONTENT OF #B
    </span>
    CONTENT OF #A
  </w-block-content>
</main>
1hdlvixo

1hdlvixo2#

在你的模式中,你使用[\s\S]*?匹配任何字符,尽可能少的匹配,但是当你在元素之间使用那个部分时,模式可以回溯并允许匹配第一个</div>
如果您想要提取匹配的部分,并且您已经有了使用捕获组的模式 "只要没有内部标记",则不需要任何查找。
您可以使您的模式更具体,并匹配开始和结束标记,仅在它们之间使用可选的空白字符。

<w-block-content[^<>]*>\s*<span[^<>]*>\s*<div[^<>]*>([^]*?)<\/div>\s*<\/span>\s*<\/w-block-content>
    • 说明**
  • <w-block-content[^<>]*>\s*匹配w-block-content元素,其中[^<>]*是一个取反的字符类,它匹配<>以外的可选字符,\s*匹配可选的空白字符(包括换行符)
  • <span[^<>]*>\s*span相同
  • <div[^<>]*>div相同
  • ([^]*?)捕获组1,匹配包括换行符在内的任何字符,尽可能少
  • <\/div>\s*<\/span>\s*<\/w-block-content>匹配结束部分,结束标记之间可以有可选的空白字符。

参见regex demo
See why parsing HTML with a regex is not advisable

ccrfmcuu

ccrfmcuu3#

纯正则表达式解决方案,接受比问题中提供的示例数据更复杂的输入。

底部的代码和数据片段包含了这样的复杂输入,例如,它在匹配元素中包含了额外的(意外的)非空白,而这些元素不是提取数据的一部分,在本例中是HTML注解。
🚩 我从问题中提供的原始正则表达式推断出这是一个要求。
在撰写本文时,没有其他答案可以处理此输入。
⚠️ It also accepts some illegal input, but that's what you get by requiring the use of regular expressions and disallowing a true HTML parser.
另一方面,HTML解析器将使处理问题中给出的示例输入中格式错误的HTML变得困难。符合规范的解析器将通过强制将标记与树中更靠上的打开的div匹配来处理这种"标记汤",从而过早地关闭沿途的任何中间父元素。因此,它不仅将对数据记录使用第一个而不是最后一个</div>,它可能关闭更高的容器元素,并对如何解析文件的其余部分造成严重破坏。

正则表达式

<w-block-content[^>]*>[\s\S]*?<span[^>]*>[\s\S]*?<div[^>]*>([\s\S]*?)<\/div\s*>(?:(?!<\/div\s*>)[\s\S])*?<\/span\s*>[\s\S]*?<\/w-block-content\s*>/g

正则表达式满足问题中所述的所有要求:

  • 它是纯Regexp,除了调用它所需的标准代码外,不需要任何Javascript。
  • 可以通过String.matchAll()在一次调用中调用它(返回匹配的数组)
  • 或者你可以通过Regexp.exec()迭代地调用它来迭代地解析记录,Regexp.exec()在每次调用时返回连续的匹配,自动跟踪它停止的位置。
  • 使用正则表达式分组,以便解析和使用整个外部"记录",但其中的"数据"仍然可以单独使用。否则,解析连续记录将需要额外的Javascript代码,以便在下一次解析之前设置指向记录末尾的指针。这不仅违反要求,而且还会导致冗余和低效的解析。
  • 完整记录可作为每次匹配的 * 组0 *
  • 其中的数据可用作每个匹配的 * 组1 *
  • 它处理标记中所有合法的多余空格
  • 它处理元素之间的空白和合法的非空白(如上所述)。

此外:

  • 它可以在较旧的浏览器中工作,不依赖于lookabehind或dotall
  • ECMAScript 2018中添加了Lookbehind,但正如您在上面的链接和here中所看到的,即使是最新的浏览器,也并非所有浏览器都支持它。
  • dotall also has backward compatibility limits

正则表达式解释了

| /||
| - ------| - ------|
| <w-block-content[^>]*>|使用任意属性和空格打开w-block-content "record"标记|
| [\s\S]*?| w-block-contentspan之前的任意空白和非空白|
| x1米11米1x|应为具有任意属性和空白的嵌套span|
| [\s\S]*?| spandiv之前的任意空白和非空白|
| <div[^>]*>|应为具有任意属性和空白的嵌套div。此div Package 数据。|
| ([\s\S]*?)|所述数据|
| x1米20英寸1x|带有任意合法空格的结束div标记。|
| (?:(?!<\/div\s*>)[\s\S])*? | arbitrary whitespace and non-whitespace within span after div🌶 except that it guarantees that </div> matched by the preceding pattern is the last one within the span element. |
| <\/span\s*>|带有任意合法空格的结束span标记。|
| [\s\S]*?| span之后w-block-content内的任意空白和非空白|
| <\/w-block-content\s*>|带有任意合法空格的结束w-block-content标记。|
| /g| global标志,允许从输入中提取多个匹配项。影响String.matchAllRegExp.exec的工作方式。|

棘手的测试数据和示例用法/测试代码

'use strict'
const input = `<tr>
          <td>
            <div draggable="true" class="source master draggable-box wysiwyg-block" data-block-type="master" data-block-id="96e80afb-afa0-4e46-bfb7-34b80da76112" style="opacity: 1;">
              <w-block-content data-block-content-id="96e80afb-afa0-4e46-bfb7-34b80da76112">
                <span class="source-block-tooltip">
                  <div>SIMPLE CASE DATA STARTS HERE

Další master<br><b>Master č. 2</b><br>

                  SIMPLE CASE DATA ENDS HERE</div>
                </span>
              </w-block-content>
            </div>
          </td>
</tr><tr>
          <td>
            <div draggable="true" class="source master draggable-box wysiwyg-block" data-block-type="master" data-block-id="96e80afb-afa0-4e46-bfb7-34b80da76112" style="opacity: 1;">
              <w-block-content class="tricky" 
                   data-block-content-id="96e80afb-afa0-4e46-bfb7-34b80da76112"  >
                       <!-- TRICKY: whitespace within expected tags above and below,
                        and also this comment inserted between the tags -->
                <span class="source-block-tooltip"
                      color="burgandy"
                      > <!-- TRICKY: some more non-whitespace
                       between expected tags --> 
                  <div
                     >TRICKY CASE DATA STARTS HERE
                     <div> TRICKY inner div

Další master<br><b>Master č. 2</b><br>
                     </div>
                     TRICKY unmatched closing div tags
                     </div> Per the requirements, THIS closing div tag should be ignored and
                     the one below (the last one before the closing outer tags) should be 
                     treated as the closing tag.
                  TRICKY CASE DATA ENDS HERE</div> TRICKY closing tags can have whitespace including newlines
                  <!-- TRICKY more stuff between closing tags -->
                </span
                   >
                <!-- TRICKY more stuff between closing tags -->
              </w-block-content
                 >
            </div>
          </td>
</tr>
`

const regex = /<w-block-content[^>]*>[\s\S]*?<span[^>]*>[\s\S]*?<div[^>]*>([\s\S]*?)<\/div\s*>((?:(?!<\/div\s*>)[\s\S])*?)<\/span\s*>[\s\S]*?<\/w-block-content\s*>/g

function extractNextRecord() {
    const match = regex.exec(input)
    if (match) {
        return {record: match[0], data: match[1]}
    } else {
        return null
    }
}

let output = '', result, count = 0
while (result = extractNextRecord()) {
    count++
    console.log(`-------------------- RECORD ${count} -----------------------\n${result.record}\n---------------------------------------------------\n\n`)    
    output += `<hr><pre>${result.data.replaceAll('<', '&lt;')}</pre>`
}
output += '<hr>'
output = `<p>Extracted ${count} records:</p>` + output
document.documentElement.innerHTML = output
ehxuflar

ehxuflar4#

下面是适用于the example you provided的正则表达式;为了清晰起见,我把它分成了三行,也许你会把它们合并成一行:

(?<=<w-block-content[^>]*>\s*<span[^>]*>\s*<div[^>]*>)
[\s\S]*?
(?=<\/div>\s*<\/span>\s*<\/w-block-content>)

我认为在这种情况下不需要使用捕获组(),如果使用look-behind (?<=)和look-ahead (?=)进行边界查找(两者都是非捕获的),那么可以让整个匹配成为您想要查找的内容。
我添加这个答案是因为我没有看到其他答案使用[^>](= negated character class)来允许标签字符串在接受附加属性时是开放式的,而不完全跳过标签闭包的任何强制,我认为这是一种更干净、更安全的方法。
我承认我不是一个JavaScript爱好者,所以:今天我了解到JavaScript正则表达式匹配不支持单行模式(/s),因此您必须将[\s\S]作为一种解决方案,而不仅仅是.

tquggr8v

tquggr8v5#

下面的解决方案假设在目标</div></span>之间只能有空格和/或换行符,这是从OP的声明中得出的,即<span>只有一个直接子对象,这就是我们要查找其内容的 Package 器<div>

/(?:<w-block-content.*[\s\S]*?<div>)([\s\S]*?)(?:<\/div>((?!<\/div>))*[\s]+<\/span>[\s]*?<\/w-block-content>)/gm

https://regex101.com/r/sn0frx/1
EDIT:explanation.这基本上是OP的正则表达式,但有以下更改:
1.在模式的<\/div>之后插入负前瞻((?!<\/div>))*以忽略任何较早的</div>

  1. OP的字符类现在跟随着这个插入,已经删除了\S,所以现在是[\s]*?,基于上述假设。
    1.类似地,对<\/span>后面的字符类进行了相同的编辑,这是基于我们正在查找的</span></w-block-content>前面的字符类的假设,尽管有空格和换行符,如问题所示。

相关问题