XML Schema .xsd中的正则表达式



我有一个xsd模式文件,它使用的模式基本上是RegEx,但我有问题创建正确的RegEx。我的RegEx应该接受这些:

  • @{...},...为以.png/.jpg结尾的任意字符
  • C:/...,...为任何以.png/.jpg结尾的驱动器,C为任何驱动器字符所以我认为所有的大写字符
  • /...,...是任何以.png/.jpg结尾的

我得到了这个RegEx:^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$,它使用Java RegEx匹配器和regex101,但xml解析器抛出异常,说它不匹配。

我也发现,xml正则表达式匹配引擎是使用非确定性有限自动机,但我不知道这对我的regex意味着什么:

使用非确定性有限自动机(NFA)的正则表达式匹配引擎。此引擎不符合POSIX正则表达式。

valid.xml

<?xml version="1.0" encoding="UTF-8"?>
<frameui xmlns="http://www.squidxtv.me/schema/frameui/v1/frameui.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.squidxtv.me/schema/frameui/v1 /frameui.xsd"
id="valid-frame"
width="5"
height="3"
background-image="@{../test-image.png}">
<div id="first-div" width="500" height="500" x="100" y="100" border-color="#FFFFFF" border-width="1">
<text id="first-test" x="100" y="100" underline="true" border-color="#FFFFFF" border-width="1">Test Text</text>
</div>
</frameui>

frameui.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://www.squidxtv.me/schema/frameui/v1/frameui.xsd"
xmlns="http://www.squidxtv.me/schema/frameui/v1/frameui.xsd"
version="1.0">
<xs:element name="frameui">
<xs:complexType>
<xs:all>
<xs:element name="text" type="TextType" minOccurs="0"/>
<xs:element name="image" type="ImgType" minOccurs="0"/>
<xs:element name="div" type="DivType" minOccurs="0"/>
</xs:all>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="width" type="xs:positiveInteger" use="required"/>
<xs:attribute name="height" type="xs:positiveInteger" use="required"/>
<xs:attribute name="background-color" type="hexType" default="#0D0D0D"/>
<xs:attribute name="background-image" type="pathType"/>
<xs:attributeGroup ref="border"/>
<xs:attribute name="click-radius" type="xs:positiveInteger" default="6"/>
<xs:attribute name="scroll-radius" type="xs:positiveInteger" default="6"/>
</xs:complexType>
</xs:element>
<xs:complexType name="TextType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attributeGroup ref="position"/>
<xs:attributeGroup ref="font"/>
<xs:attributeGroup ref="border"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="ImgType">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attributeGroup ref="position"/>
<xs:attribute name="path" type="pathType" use="required"/>
<xs:attributeGroup ref="border"/>
</xs:complexType>
<xs:complexType name="DivType">
<xs:all>
<xs:element name="text" type="TextType" minOccurs="0"/>
<xs:element name="image" type="ImgType" minOccurs="0"/>
<xs:element name="div" type="DivType" minOccurs="0"/>
</xs:all>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attributeGroup ref="position"/>
<xs:attribute name="width" type="xs:positiveInteger" use="required"/>
<xs:attribute name="height" type="xs:positiveInteger" use="required"/>
<xs:attributeGroup ref="border"/>
</xs:complexType>
<xs:attributeGroup name="font">
<xs:attribute name="font" type="xs:string" default="Sans Serif"/>
<xs:attribute name="font-size" type="xs:positiveInteger" default="12"/>
<xs:attribute name="font-color" type="hexType" default="#FFFFFF"/>
<xs:attribute name="bold" type="xs:boolean" default="false"/>
<xs:attribute name="italic" type="xs:boolean" default="false"/>
<xs:attribute name="underline" type="xs:boolean" default="false"/>
</xs:attributeGroup>
<xs:attributeGroup name="border">
<xs:attribute name="border-color" type="hexType" default="#0D0D0D"/>
<xs:attribute name="border-width" type="xs:nonNegativeInteger" default="0"/>
</xs:attributeGroup>
<xs:attributeGroup name="position">
<xs:attribute name="x" type="xs:integer" default="0"/>
<xs:attribute name="y" type="xs:integer" default="0"/>
</xs:attributeGroup>
<xs:simpleType name="hexType">
<xs:restriction base="xs:string">
<xs:pattern value="#[0-9a-fA-F]{6}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="pathType">
<xs:restriction base="xs:string">
<xs:pattern value="^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$"/>
<xs:whiteSpace value="collapse"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

测试
@Test
public void testValidXml() {
ScreenType model = assertDoesNotThrow(() -> parser.parse(Path.of(getClass().getResource("/guis/valid.xml").toURI())));
}

解析器配置:

package me.squidxtv.frameui.core.parser;
import me.squidxtv.frameui.core.content.*;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
/**
* The ScreenParser class provides methods for parsing an XML file into a ScreenType object
* and retrieving information about the child nodes of a given Node.
*/
public class ScreenParser {
/**
* The regular expression pattern for matching Windows-style paths.
*/
private static final @NotNull Predicate<String> WINDOWS_PATH = Pattern.compile("^[A-Z]:\\.+$").asMatchPredicate();
/**
* The regular expression pattern for matching Unix-style paths.
*/
private static final @NotNull Predicate<String> UNIX_PATH = Pattern.compile("^/.+$").asMatchPredicate();
/**
* The regular expression pattern for matching relative paths.
*/
private static final @NotNull Predicate<String> RELATIVE_PATH = Pattern.compile("^@\{.+}$").asMatchPredicate();
/**
* The {@link DocumentBuilder} used for parsing XML files.
*/
private final @NotNull DocumentBuilder documentBuilder;
/**
* Constructs a new instance of {@link ScreenParser} class.
* @param plugin
* @throws SAXException if there is an error in the XML schema.
* @throws ParserConfigurationException if there is a configuration error in the {@link DocumentBuilder}.
*/
public ScreenParser(JavaPlugin plugin) throws SAXException, ParserConfigurationException {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(getClass().getResource("/frameui.xsd"));
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setSchema(schema);
documentBuilderFactory.setNamespaceAware(true);
documentBuilder = documentBuilderFactory.newDocumentBuilder();
ParserErrorHandler errorHandler = new ParserErrorHandler(plugin);
documentBuilder.setErrorHandler(errorHandler);
}
/**
* Parses an XML file and returns a ScreenType object representing the root element of the document.
* @param xmlFile the Path to the XML file to be parsed
* @return a ScreenType object representing the root element of the parsed document
* @throws IOException if an I/O error occurs while reading the file
* @throws SAXException if an error occurs during parsing
*/
public ScreenType parse(Path xmlFile) throws IOException, SAXException {
Document xml = documentBuilder.parse(xmlFile.toFile());
Element root = xml.getDocumentElement();
return ScreenType.of(root, xmlFile);
}
/**
* Returns the {@link DocumentBuilder} used by this {@link ScreenParser} instance.
* @return the {@link DocumentBuilder} used by this {@link ScreenParser} instance
*/
public DocumentBuilder getDocumentBuilder() {
return documentBuilder;
}
/**
* Returns an array of ComplexType objects representing the child nodes of the specified root Node.
*
* @param root the root Node whose children to retrieve
* @param path the path to the XML file containing the root Node
* @return an array of ComplexType objects representing the child nodes of the root Node
*/
public static ComplexType[] getChildren(Node root, Path path) {
NodeList nodes = root.getChildNodes();
if (nodes.getLength() == 0) {
return new ComplexType[0];
}
ComplexType[] sub = new ComplexType[nodes.getLength()];
for (int i = 0; i < nodes.getLength(); i++) {
Node current = nodes.item(i);
String name = current.getNodeName();
if (name.equals("#text")) {
continue;
}
switch (name) {
case "text" -> sub[i] = TextType.of((Element) current);
case "image" -> sub[i] = ImageType.of((Element) current, path);
case "div" -> sub[i] = DivType.of((Element) current, path);
}
}
return Arrays.stream(sub).filter(Objects::nonNull).toArray(ComplexType[]::new);
}
/**
* Converts the given path string to a {@link Path} object.
*
* @param original the path string to convert
* @param xml the path to the XML file used as the base for relative paths
* @return the {@link Path} object corresponding to the input path string
* @throws IllegalArgumentException if the input path string is not in a recognized format
*/
public static Path convert(String original, Path xml) {
if (RELATIVE_PATH.test(original)) {
return xml.resolve(original.substring(2, original.length() - 1));
}
if (WINDOWS_PATH.test(original) || UNIX_PATH.test(original)) {
return Paths.get(original);
}
throw new IllegalArgumentException("Invalid path format: " + original);
}
}
异常:

OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
org.opentest4j.AssertionFailedError: Unexpected exception thrown: org.xml.sax.SAXException: org.xml.sax.SAXParseException; systemId: file:/D:/intellij-workspace/FrameUI/target/test-classes/guis/valid.xml; lineNumber: 8; columnNumber: 50; cvc-pattern-valid: Value '@{../test-image.png}' is not facet-valid with respect to pattern '^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$' for type 'pathType'.
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
at org.junit.jupiter.api.AssertDoesNotThrow.createAssertionFailedError(AssertDoesNotThrow.java:84)
at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:75)
at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:58)
at org.junit.jupiter.api.Assertions.assertDoesNotThrow(Assertions.java:3196)
at me.squidxtv.frameui.core.parser.ScreenParserTest.testValidXml(ScreenParserTest.java:53)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: org.xml.sax.SAXException: org.xml.sax.SAXParseException; systemId: file:/D:/intellij-workspace/FrameUI/target/test-classes/guis/valid.xml; lineNumber: 8; columnNumber: 50; cvc-pattern-valid: Value '@{../test-image.png}' is not facet-valid with respect to pattern '^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$' for type 'pathType'.
org.xml.sax.SAXParseException; systemId: file:/D:/intellij-workspace/FrameUI/target/test-classes/guis/valid.xml; lineNumber: 8; columnNumber: 50; cvc-pattern-valid: Value '@{../test-image.png}' is not facet-valid with respect to pattern '^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$' for type 'pathType'.
at me.squidxtv.frameui.core.parser.ParserErrorHandler.error(ParserErrorHandler.java:40)
at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:138)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:284)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:512)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3600)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processOneAttribute(XMLSchemaValidator.java:3108)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processAttributes(XMLSchemaValidator.java:3052)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2287)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:830)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:374)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDriver.scanRootElementHook(XMLNSDocumentScannerImpl.java:613)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3079)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:836)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at java.xml/com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:247)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:342)
at java.xml/javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:206)
at me.squidxtv.frameui.core.parser.ScreenParser.parse(ScreenParser.java:77)
at me.squidxtv.frameui.core.parser.ScreenParserTest.lambda$testValidXml$1(ScreenParserTest.java:53)
at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:71)
... 73 more
Caused by: org.xml.sax.SAXParseException; systemId: file:/D:/intellij-workspace/FrameUI/target/test-classes/guis/valid.xml; lineNumber: 8; columnNumber: 50; cvc-pattern-valid: Value '@{../test-image.png}' is not facet-valid with respect to pattern '^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$' for type 'pathType'.
at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:135)
... 98 more

Process finished with exit code -1

Regexp匹配看起来像是xsd的有限实现。
删除一些不需要的分组和重构正则表达式部分,以避免重复(jpg|png)模式。

(@{.*|[A-Z]:/.*).(png|jpg)}?

@{../test-image.png}
Z:/test/image.jpg

一个更详细但更简化的模式,更接近w3.org regexp定义。

@{.*.(png|jpg)}|[A-Z]:/.*.(png|jpg)

命令行测试(libxml2实现)

xmllint --schema tmp.xsd --noout tmp.xml 
tmp.xml validates

注意:得到的错误描述与原始regex

tmp.xml:8: element frameui: Schemas validity error : Element '{http://www.squidxtv.me/schema/frameui/v1/frameui.xsd}frameui', attribute 'background-image': [facet 'pattern'] The value '@{../test-image.png}' is not accepted by the pattern '^(@{.*.(png|jpg)}|([A-Z]:)?/.*.(png|jpg))$'.

最新更新