How to remove a number after another number with comma outside parentheses using XSLT 3.1 "replace" function

Viewed 94

I am finding numbers more then two with comma but my regex find numbers inside or outside parentheses. How to find numbers not inside parentheses.

Regex used

([0-9]+, ){2,}

String

Albemarle Paper Co. v Moody (1975) 422 US 405, 425, 95 S Ct 2362

Expected Result

Albemarle Paper Co. v Moody (1975) 422 US 405, 95 S Ct 2362

In particular, my XML looks like

<root>
<p><styled-content><italic>Agarwal v Johnson </italic>(1979) 25 C3d 932, 942, overruled on *6 other grounds in <italic>White v Ultramar, Inc.</italic> (1999) 21 C4th 563</styled-content></p>
</root>

Here is the XSL template with the regex and replace function:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>

    <xsl:template match="root">
        <xsl:copy>
            <p><xsl:value-of select="replace(p/styled-content, '[0-9]+(?:, [-0-9]+)+,(?![^()]*\))', '')"/></p>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
3 Answers

NOTE: Now, that you added the XSD tag, note you cannot use a lookahead in XML Schema regex: "Particularly noteworthy is the complete absence of anchors like the caret and dollar, word boundaries, and lookaround.".

XML Schema 1.1 supports xs:assertions. With the following, you can assure 123, 345, 567 text matches and (123, 345, 567) text and (123, 345, 567) 123, 345, 567 text do not:

<xs:element name="your_element">
  <xs:simpleType>
    <xs:restriction base="xs:string">
      <xs:assertion test="not(matches($value, '.*\([^()]*([0-9]+, ){2,}[^()]*\).*'))"/>
      <xs:assertion test="matches($value, '.*([0-9]+, ){2,}.*')"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>

The answer below will work for other engines that work with lookaheads:

[0-9]+(?:, [-0-9]+)+(?![^()]*\))

See proof. It will find sequence of comma-separated numbers not followed with non-parentheses characters up to a close parenthesis.

If comma must come after the second or more numbers, just add it:

[0-9]+(?:, [-0-9]+)+,(?![^()]*\))
                    ^
                    |___ HERE

See updated demo

It appears you are using XSLT 3.1 replace function.

You may use

<xsl:value-of select="replace(styled-content, '(\([^()]*\))|([0-9]+,)\s*[0-9]+,', '$1$2')"/>

This is a demo of how the replace works in this case.

Details

  • (\([^()]*\)) - Capturing group #1 ($1 in the replacement pattern): a (, any 0+ chars other than ) and ( and then )
  • | - or
  • ([0-9]+,) - Capturing group #2 ($2): 1+ digits and a comma
  • \s* - 0+ whitespaces
  • [0-9]+ - 1+ digits
  • , - a comma.

The replacement is the contents of Group 1 and 2.

If the regex engine supports (*SKIP) and (*FAIL), then this regex does a better job of ensuring the numbers are not within parenthesis:

(?x)                    # Verbose flag
    (?:\([^()]*)        # Match '( ....'
    (?:\d+,\ ){2,}      # Our regex in '( ..... )'
    (?:[^()]*\))        # Match  '..... )'
    (*SKIP)(*FAIL)      # Fail the first alternative
|                       # Second alternative
    (?:\d+,\ ){2,}      # Our regex not enclosed in '( ... )'

See Regex Demo

Old Solution

When a regex problem is posed, the OP is supposed to state the language being used because different (1) different languages support different subsets of the regex specification and (2) depending on the complexity of the problem, it may take more take some procedural code to fully solve the problem or at least to solve it in a simple fashion.

The solutions posed so far do not fully solve the problem of determining whether the numbers are enclosed withing parenthesis. They take a simplified approach of looking to see whether the numbers are followed by a right parenthesis, and this causes false results.

The solution is to have a regex that looks for two alternate sub-regex patterns: (1) numbers within parenthesis and (2) numbers and then determine which sub-pattern was matched and to only use matches from the seconds sub-pattern. Here I am using Python:

import re

text = """Albemarle Paper Co. v Moody (1975) 422 US 405, 425, 95 S Ct 2362 (Booboo)
Albemarle Paper Co. v Moody (1975) 422 US 405, 95 S Ct 2362
Aerotek, Inc. v Johnson Group Staffing Co. (July 30, 2013, C067652) 2013 Cal Unpub Lexis 5424:"""

r_ex = re.compile(r"""
        (?:\([^)]*)             # Match '( ....'
        (?P<R1>(\d+,\s){2,})    # Our regex in capture group R1
        (?:[^)]*\))             # Match  '..... )'
    |                           # Second alternative
        (?P<R2>(\d+,\s){2,})    # Our regex not enclosed in '( ... )' in capture group R2
        """, flags=re.X)

for m in r_ex.finditer(text):
    if m.lastgroup == 'R2': # only chose second alternative matches
        print(m.group())

Prints:

405, 425,

Update

While I was writing this it seems that the OP did add a language, xsd, which probably does not provide for procedural code. I will leave this answer anyway for whatever it is worth.

Related