You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by kw...@apache.org on 2024/01/12 11:46:43 UTC

(maven-doxia) 01/01: [DOXIA-723] Expose document location in Sink

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/locator
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git

commit 99d4bb4d5582397930b139a2fd6758b6d6b4308c
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Jan 12 12:46:36 2024 +0100

    [DOXIA-723] Expose document location in Sink
    
    WIP
---
 .../maven/doxia/parser/AbstractXmlParser.java      | 22 +++++++
 .../maven/doxia/sink/impl/AbstractLocator.java     | 71 ++++++++++++++++++++++
 .../apache/maven/doxia/sink/impl/AbstractSink.java | 17 ++++++
 .../sink/impl/UniqueAnchorNamesValidator.java      |  3 +-
 .../maven/doxia/sink/impl/Xhtml5BaseSink.java      | 29 +++++++--
 .../doxia/module/markdown/MarkdownParser.java      |  1 +
 .../maven/doxia/module/markdown/MarkdownSink.java  | 15 +++--
 .../org/apache/maven/doxia/sink/EmptyLocator.java  | 46 ++++++++++++++
 .../java/org/apache/maven/doxia/sink/Locator.java  | 57 +++++++++++++++++
 .../java/org/apache/maven/doxia/sink/Sink.java     | 16 +++++
 10 files changed, 266 insertions(+), 11 deletions(-)

diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/parser/AbstractXmlParser.java b/doxia-core/src/main/java/org/apache/maven/doxia/parser/AbstractXmlParser.java
index aa60cb52..533b35a2 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/parser/AbstractXmlParser.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/parser/AbstractXmlParser.java
@@ -26,6 +26,7 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.net.URI;
 import java.net.URL;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Hashtable;
@@ -39,6 +40,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.maven.doxia.macro.MacroExecutionException;
 import org.apache.maven.doxia.markup.XmlMarkup;
 import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.impl.AbstractLocator;
 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
 import org.apache.maven.doxia.util.HtmlTools;
 import org.apache.maven.doxia.util.XmlValidator;
@@ -185,6 +187,25 @@ public abstract class AbstractXmlParser extends AbstractParser implements XmlMar
         return atts;
     }
 
+    private static final class XmlPullParserLocator extends AbstractLocator {
+
+        private final XmlPullParser parser;
+
+        XmlPullParserLocator(XmlPullParser parser, Path file) {
+            super(file);
+            this.parser = parser;
+        }
+
+        @Override
+        public int getLineNumber() {
+            return parser.getLineNumber();
+        }
+
+        @Override
+        public int getColumnNumber() {
+            return parser.getColumnNumber() != -1 ? parser.getColumnNumber() + 1 : -1;
+        }
+    }
     /**
      * Parse the model from the XmlPullParser into the given sink.
      *
@@ -194,6 +215,7 @@ public abstract class AbstractXmlParser extends AbstractParser implements XmlMar
      * @throws org.apache.maven.doxia.macro.MacroExecutionException if there's a problem executing a macro
      */
     private void parseXml(XmlPullParser parser, Sink sink) throws XmlPullParserException, MacroExecutionException {
+        sink.setDocumentLocator(new XmlPullParserLocator(parser, null));
         int eventType = parser.getEventType();
 
         while (eventType != XmlPullParser.END_DOCUMENT) {
diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractLocator.java b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractLocator.java
new file mode 100644
index 00000000..6222587c
--- /dev/null
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractLocator.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.sink.impl;
+
+import java.nio.file.Path;
+
+import org.apache.maven.doxia.sink.Locator;
+
+/**
+ * @since 2.0.0
+ */
+public abstract class AbstractLocator implements Locator {
+
+    private Path file;
+
+    protected AbstractLocator(Path file) {
+        super();
+        this.file = file;
+    }
+
+    @Override
+    public Path getSourceFile() {
+        return file;
+    }
+
+    @Override
+    public String getLogPrefix() {
+        return formatLocation(this);
+    }
+
+    /**
+     * Creates a string with line/column information. Inspired by
+     * {@code o.a.m.model.building.ModelProblemUtils.formatLocation(...)}.
+     *
+     * @param locator The locator must not be {@code null}.
+     * @return The formatted location or an empty string if unknown, never {@code null}.
+     */
+    public static String formatLocation(Locator locator) {
+        StringBuilder buffer = new StringBuilder();
+
+        if (locator.getSourceFile() != null) {
+            buffer.append(locator.getSourceFile());
+        }
+        if (locator.getLineNumber() > 0) {
+            buffer.append(", line ").append(locator.getLineNumber());
+        }
+        if (locator.getColumnNumber() > 0) {
+            buffer.append(", column ").append(locator.getLineNumber());
+        }
+        if (buffer.length() > 0) {
+            buffer.append(": ");
+        }
+        return buffer.toString();
+    }
+}
diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractSink.java b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractSink.java
index 0839ad88..7511d6a7 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractSink.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/AbstractSink.java
@@ -19,6 +19,8 @@
 package org.apache.maven.doxia.sink.impl;
 
 import org.apache.maven.doxia.markup.Markup;
+import org.apache.maven.doxia.sink.EmptyLocator;
+import org.apache.maven.doxia.sink.Locator;
 import org.apache.maven.doxia.sink.Sink;
 import org.apache.maven.doxia.sink.SinkEventAttributes;
 
@@ -33,6 +35,8 @@ import org.apache.maven.doxia.sink.SinkEventAttributes;
  */
 public abstract class AbstractSink implements Sink, Markup {
 
+    private Locator locator;
+
     // -- start default implementation for legacy doxia 1.0 methods which have overridden variants in Doxia 1.1 or 2.0,
     // all sink implementation derived from this must only override the Doxia > 1.0 variants
     @Override
@@ -420,4 +424,17 @@ public abstract class AbstractSink implements Sink, Markup {
     protected void init() {
         // nop
     }
+
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+    @Override
+    public Locator getDocumentLocator() {
+        if (locator == null) {
+            return EmptyLocator.INSTANCE;
+        }
+        return locator;
+    }
 }
diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/UniqueAnchorNamesValidator.java b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/UniqueAnchorNamesValidator.java
index b44ab474..f83b95bf 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/UniqueAnchorNamesValidator.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/UniqueAnchorNamesValidator.java
@@ -46,7 +46,8 @@ public class UniqueAnchorNamesValidator extends SinkWrapper {
 
     private void enforceUniqueAnchor(String name) {
         if (!usedAnchorNames.add(name)) {
-            throw new IllegalStateException("Anchor name \"" + name + "\" used more than once");
+            throw new IllegalStateException(
+                    getDocumentLocator().getLogPrefix() + "Anchor name \"" + name + "\" used more than once");
         }
     }
 }
diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java
index 03693d3a..fa1e9393 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java
@@ -927,7 +927,7 @@ public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
         }
 
         if (this.tableContentWriterStack.isEmpty()) {
-            LOGGER.warn("No table content");
+            LOGGER.warn("{}No table content", getDocumentLocator().getLogPrefix());
             return;
         }
 
@@ -1182,7 +1182,11 @@ public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
         if (!DoxiaUtils.isValidId(id)) {
             id = DoxiaUtils.encodeId(name);
 
-            LOGGER.debug("Modified invalid anchor name '{}' to '{}'", name, id);
+            LOGGER.debug(
+                    "{}Modified invalid anchor name '{}' to '{}'",
+                    getDocumentLocator().getLogPrefix(),
+                    name,
+                    id);
         }
 
         MutableAttributeSet att = new SinkEventAttributeSet();
@@ -1448,7 +1452,11 @@ public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
             }
 
             if (!originalComment.equals(comment)) {
-                LOGGER.warn("Modified invalid comment '{}' to '{}'", originalComment, comment);
+                LOGGER.warn(
+                        "{}Modified invalid comment '{}' to '{}'",
+                        getDocumentLocator().getLogPrefix(),
+                        originalComment,
+                        comment);
             }
 
             final StringBuilder buffer = new StringBuilder(comment.length() + 7);
@@ -1505,7 +1513,10 @@ public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
     @Override
     public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) {
         if (requiredParams == null || !(requiredParams[0] instanceof Integer)) {
-            LOGGER.warn("No type information for unknown event '{}', ignoring!", name);
+            LOGGER.warn(
+                    "{}No type information for unknown event '{}', ignoring!",
+                    getDocumentLocator().getLogPrefix(),
+                    name);
 
             return;
         }
@@ -1527,7 +1538,10 @@ public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
         Tag tag = HtmlTools.getHtmlTag(name);
 
         if (tag == null) {
-            LOGGER.warn("No HTML tag found for unknown event '{}', ignoring!", name);
+            LOGGER.warn(
+                    "[]No HTML tag found for unknown event '{}', ignoring!",
+                    getDocumentLocator().getLogPrefix(),
+                    name);
         } else {
             if (tagType == TAG_TYPE_SIMPLE) {
                 writeSimpleTag(tag, escapeAttributeValues(attributes));
@@ -1536,7 +1550,10 @@ public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
             } else if (tagType == TAG_TYPE_END) {
                 writeEndTag(tag);
             } else {
-                LOGGER.warn("No type information for unknown event '{}', ignoring!", name);
+                LOGGER.warn(
+                        "{}No type information for unknown event '{}', ignoring!",
+                        getDocumentLocator().getLogPrefix(),
+                        name);
             }
         }
     }
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java
index 9c229ccf..6100b301 100644
--- a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java
@@ -171,6 +171,7 @@ public class MarkdownParser extends AbstractTextParser implements TextMarkup {
             // Markdown to HTML (using flexmark-java library)
             String html = toHtml(source);
 
+            // TODO: add locator for the markdown source (not the intermediate HTML format)
             // then HTML to Sink API
             parser.parse(html, getWrappedSink(sink));
         } catch (IOException e) {
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
index b9b09089..719171d0 100644
--- a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
@@ -285,7 +285,8 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
         // markdown only supports decimal numbering
         if (numbering != NUMBERING_DECIMAL) {
             LOGGER.warn(
-                    "Markdown only supports numbered item with decimal style ({}) but requested was style {}, falling back to decimal style",
+                    "{}Markdown only supports numbered item with decimal style ({}) but requested was style {}, falling back to decimal style",
+                    getDocumentLocator().getLogPrefix(),
                     NUMBERING_DECIMAL,
                     numbering);
         }
@@ -333,7 +334,9 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
 
     @Override
     public void definitionList(SinkEventAttributes attributes) {
-        LOGGER.warn("Definition list not natively supported in Markdown, rendering HTML instead");
+        LOGGER.warn(
+                "{}Definition list not natively supported in Markdown, rendering HTML instead",
+                getDocumentLocator().getLogPrefix());
         write("<dl>" + EOL);
     }
 
@@ -734,7 +737,9 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
         }
         if (tableCaptionFlag) {
             // table caption cannot even be emitted via XHTML in markdown as there is no suitable location
-            LOGGER.warn("Ignoring unsupported table caption in Markdown");
+            LOGGER.warn(
+                    "{}Ignoring unsupported table caption in Markdown",
+                    getDocumentLocator().getLogPrefix());
         } else if (headerFlag || bufferFlag) {
             buffer.append(escapeMarkdown(text));
         } else if (verbatimFlag) {
@@ -765,7 +770,9 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
      */
     @Override
     public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) {
-        LOGGER.warn("Unknown Sink event '" + name + "', ignoring!");
+        LOGGER.warn(
+                "{}Unknown Sink event '" + name + "', ignoring!",
+                getDocumentLocator().getLogPrefix());
     }
 
     /**
diff --git a/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/EmptyLocator.java b/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/EmptyLocator.java
new file mode 100644
index 00000000..05a372b5
--- /dev/null
+++ b/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/EmptyLocator.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.sink;
+
+import java.nio.file.Path;
+
+public final class EmptyLocator implements Locator {
+
+    public static final Locator INSTANCE = new EmptyLocator();
+
+    @Override
+    public int getLineNumber() {
+        return -1;
+    }
+
+    @Override
+    public int getColumnNumber() {
+        return -1;
+    }
+
+    @Override
+    public Path getSourceFile() {
+        return null;
+    }
+
+    @Override
+    public String getLogPrefix() {
+        return "";
+    }
+}
diff --git a/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Locator.java b/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Locator.java
new file mode 100644
index 00000000..2a75a579
--- /dev/null
+++ b/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Locator.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.sink;
+
+import java.nio.file.Path;
+
+/**
+ * Interface for associating a {@link Sink} event with a document location.
+ * @since 2.0.0
+ */
+public interface Locator {
+
+    /**
+     * Returns the line number for the sink event (starting from 1).
+     * If it is not known returns {@code -1}.
+     *
+     * @return the line number for the sink event
+     */
+    int getLineNumber();
+
+    /**
+     * Returns the column number for the sink event (starting from 1).
+     * If it is not known returns {@code -1}.
+     *
+     * @return the column number for the sink event
+     */
+    int getColumnNumber();
+
+    /**
+     * Returns the underlying source file path (usually a relative path).
+     * @return the source for the sink event (may be {@code null})
+     */
+    Path getSourceFile();
+
+    /**
+     * Returns a prefix containing location information. This is supposed to be used with logging inside Sink events
+     * to ease tracing the root issue.
+     * @return a prefix to be used for logging (may be empty if no location information is available)
+     */
+    String getLogPrefix();
+}
diff --git a/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Sink.java b/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Sink.java
index 2739eec4..ec4d2833 100644
--- a/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Sink.java
+++ b/doxia-sink-api/src/main/java/org/apache/maven/doxia/sink/Sink.java
@@ -1749,4 +1749,20 @@ public interface Sink extends AutoCloseable {
      * Closing a previously-closed Sink has no effect.
      */
     void close();
+
+    /**
+     * Sets the locator which exposes location information for a particular Sink event.
+     * @param locator the locator (never {@code null}).
+     * @since 2.0.0
+     */
+    default void setDocumentLocator(Locator locator) {}
+
+    /**
+     * Returns the locator which exposes location information for a particular Sink event.
+     * @return the locator (never {@code null}).
+     * @since 2.0.0
+     */
+    default Locator getDocumentLocator() {
+        return EmptyLocator.INSTANCE;
+    }
 }