Project

Profile

Help

RE: Tracing change since Saxon 10 ? ยป XSLTCoverageTraceListener.java

Rewrite of supplied trace listener - Michael Kay, 2023-04-02 17:33

 
/****************************************************************************/
/* File: XSLTCoverageTraceListener.java */
/* Author: Jeni Tennison */
/* URI: https://github.com/xspec/xspec/ */
/* Tags: */
/* Copyright (c) 2008-2016 (see end of file.) */
/* ------------------------------------------------------------------------ */


package com.saxonica;

import net.sf.saxon.Controller;
import net.sf.saxon.event.StreamWriterToReceiver;
import net.sf.saxon.expr.LetExpression;
import net.sf.saxon.expr.UserFunctionCall;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.functions.ResolveURI;
import net.sf.saxon.lib.Logger;
import net.sf.saxon.lib.TraceListener;
import net.sf.saxon.om.Item;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.style.ExtensionInstruction;
import net.sf.saxon.sxpath.XPathExpression;
import net.sf.saxon.trace.Traceable;

import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
* A Simple trace listener for XSLT that writes messages to XML file
*/

public class XSLTCoverageTraceListener implements TraceListener {

private int openCount = 0;
private String xspecStylesheet = null;
private HashMap<String, Integer> utils = new HashMap<String, Integer>();
private HashMap<String, Integer> modules = new HashMap<String, Integer>();
private HashSet<Class<? extends Traceable>> constructs = new HashSet<>();
private int utilsCount = 0;
private int moduleCount = 0;
private StreamWriterToReceiver writer = null;
private String srcDir = null;
private String ignoreDir = null;
private boolean debugPrintEnabled = (System.getenv("DEBUG_XSLT_COVERAGE_TRACE_LISTENER") != null);

private void debugPrintf(String format, Object... args) {
if (debugPrintEnabled) {
System.err.printf(format, args);
}
}

private URI filePathToUri(String filePath) {
File f = new File(filePath);
return f.toURI();
}

public XSLTCoverageTraceListener() {
System.out.println("****************************************");
}

/**
* Method called at the start of execution, that is, when the run-time transformation starts
*
* @param c Controller used
*/

public void open(Controller c) {
System.out.println("controller=" + c);

openCount++;
if (openCount >= 2) {
// Re-entered. Maybe by transform().
return;
}

// Get the URI of XSpec home
URI xspecHomeUri;
String xspecHomeUriProp = System.getProperty("xspec.home.uri");
if (xspecHomeUriProp == null) {
String xspecHomeProp = System.getProperty("xspec.home");
debugPrintf("%-17s: %s%n", "xspec.home", xspecHomeProp);

xspecHomeUri = filePathToUri(xspecHomeProp + File.separator);
} else {
debugPrintf("%-17s: %s%n", "xspec.home.uri", xspecHomeUriProp);
try {
xspecHomeUri = new URI(xspecHomeUriProp);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
debugPrintf("%-17s: %s%n", "xspecHomeUri", xspecHomeUri);

// Get 'src/' URI and normalize it
try {
// Use net.sf.saxon.functions.ResolveURI#makeAbsolute, because
// java.net.URI#resolve cannot handle "jar:" scheme.
srcDir = ResolveURI.makeAbsolute("src/", xspecHomeUri.toString()).normalize().toString();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
debugPrintf("%-17s: %s%n", "srcDir", srcDir);

// Get the directory path to ignore
ignoreDir = System.getProperty("xspec.coverage.ignore");
debugPrintf("%-17s: %s%n", "ignoreDir (raw)", ignoreDir);

// Normalize the directory as URI
ignoreDir = filePathToUri(ignoreDir + File.separator).normalize().toString();
debugPrintf("%-17s: %s%n", "ignoreDir (norm)", ignoreDir);

// Coverage XML file
String outPath = System.getProperty("xspec.coverage.xml");
File outFile = new File(outPath);

// XSpec file
String xspecPath = System.getProperty("xspec.xspecfile");
xspecPath = filePathToUri(xspecPath).toString();

Serializer serializer = new Processor(false).newSerializer(outFile);
serializer.setOutputProperty(Serializer.Property.INDENT, "yes");

try {
writer = serializer.getXMLStreamWriter();
} catch (SaxonApiException e) {
throw new RuntimeException(e);
}

try {
writer.writeStartDocument();
writer.writeStartElement("trace");
writer.writeAttribute("xspec", xspecPath);
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}

/**
* Method that implements the output destination for SaxonEE/PE 9.7
*/
public void setOutputDestination(Logger logger) {
}

/**
* Method called at the end of execution, that is, when the run-time execution ends
*/
public void close() {
openCount--;
if (openCount >= 1) {
return;
}

try {
writer.writeEndElement(); // </trace>
writer.writeEndDocument();
writer.close();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}

/**
* Method that is called when an instruction in the stylesheet gets processed.
*
* @param info Instruction gives information about the instruction being
* executed, and about the context in which it is executed. This object is mutable,
* so if information from the InstructionInfo is to be retained, it must be copied.
* @param context XPath context used
*/

@Override
public void enter(Traceable info, Map<String, Object> properties, XPathContext context) {
int lineNumber = info.getLocation().getLineNumber();
int columnNumber = info.getLocation().getColumnNumber();

// Get the current file URI
String systemId = info.getLocation().getSystemId();
debugPrintf("%-17s: %s:%d:%d%n", "enter()", systemId, lineNumber, columnNumber);

// Normalize the current file URI
URI systemIdUri;
try {
systemIdUri = new URI(systemId);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
systemId = systemIdUri.normalize().toString();
debugPrintf("%-17s: %s%n", "systemId (norm)", systemId);

boolean isUtil = false;

if (systemId.startsWith(srcDir)) {
isUtil = true;

if (!utils.containsKey(systemId)) {
Integer utilId = Integer.valueOf(utilsCount);
debugPrintf(
"%-17s: %s%n",
"util[" + utilId + "]",
systemId
);

utilsCount++;
utils.put(systemId, utilId);

try {
writer.writeStartElement("util");
writer.writeAttribute("utilId", String.valueOf(utilId));
writer.writeAttribute("uri", systemId);
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
} else if (xspecStylesheet == null &&
systemId.startsWith(ignoreDir)) {
xspecStylesheet = systemId;
debugPrintf("%-17s: %s%n", "xspecStylesheet", xspecStylesheet);

try {
writer.writeStartElement("compiled");
writer.writeAttribute("uri", systemId);
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}

if (systemId != xspecStylesheet && !isUtil) {
Integer moduleId;
if (modules.containsKey(systemId)) {
moduleId = (Integer) modules.get(systemId);
} else {
moduleId = Integer.valueOf(moduleCount);
moduleCount += 1;
modules.put(systemId, moduleId);

try {
writer.writeStartElement("module");
writer.writeAttribute("moduleId", String.valueOf(moduleId));
writer.writeAttribute("uri", systemId);
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}

Class<? extends Traceable> constructType = info.getClass();

if (!constructs.contains(constructType)) {
String construct;
if (info.getObjectName() != null) {
construct = info.getObjectName().getEQName();
} else {
if (info instanceof FixedElement) {
construct = "LITERAL_RESULT_ELEMENT";
} else if (info instanceof FixedAttribute) {
construct = "LITERAL_RESULT_ATTRIBUTE";
} else if (info instanceof ExtensionInstruction) {
construct = "EXTENSION_INSTRUCTION";
} else if (info instanceof TemplateRule || info instanceof NamedTemplate) {
construct = "TEMPLATE";
} else if (info instanceof UserFunctionCall) {
construct = "FUNCTION_CALL";
} else if (info instanceof LetExpression) {
construct = "LET_EXPRESSION";
} else if (info instanceof TraceExpression) {
construct = "TRACE_CALL";
} else if (info instanceof EvaluateInstr) {
construct = "SAXON_EVALUATE";
} else if (info instanceof UserFunction) {
construct = "FUNCTION";
} else if (info instanceof XPathExpression) {
construct = "XPATH_EXPRESSION";
} else {
construct = "Other";
}
}
constructs.add(constructType);

try {
writer.writeStartElement("construct");
writer.writeAttribute("constructType", String.valueOf(constructType));
writer.writeAttribute("name", construct);
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}

try {
writer.writeStartElement("hit");
writer.writeAttribute("lineNumber", String.valueOf(lineNumber));
writer.writeAttribute("columnNumber", String.valueOf(columnNumber));
writer.writeAttribute("moduleId", String.valueOf(moduleId));
writer.writeAttribute("constructType", String.valueOf(constructType));
writer.writeEndElement();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
}

/**
* Method that is called after processing an instruction of the stylesheet,
* that is, after any child instructions have been processed.
*
* @param instruction gives the same information that was supplied to the
* enter method, though it is not necessarily the same object. Note that the
* line number of the instruction is that of the start tag in the source stylesheet,
* not the line number of the end tag.
*/

@Override
public void leave(Traceable instruction) {
// Do nothing
}

/**
* Method that is called by an instruction that changes the current item
* in the source document: that is, xsl:for-each, xsl:apply-templates, xsl:for-each-group.
* The method is called after the enter method for the relevant instruction, and is called
* once for each item processed.
*
* @param currentItem the new current item. Item objects are not mutable; it is safe to retain
* a reference to the Item for later use.
*/

public void startCurrentItem(Item currentItem) {
// Do nothing
}

/**
* Method that is called when an instruction has finished processing a new current item
* and is ready to select a new current item or revert to the previous current item.
* The method will be called before the leave() method for the instruction that made this
* item current.
*
* @param currentItem the item that was current, whose processing is now complete. This will represent
* the same underlying item as the corresponding startCurrentItem() call, though it will
* not necessarily be the same actual object.
*/

public void endCurrentItem(Item currentItem) {
// Do nothing
}

}


//
// The contents of this file are subject to the Mozilla Public License
// Version 1.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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and
// limitations under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is Edwin Glaser
// (edwin@pannenleiter.de)
//
// Portions created by Jeni Tennison are Copyright (C) Jeni Tennison.
// All Rights Reserved.
//
// Contributor(s): Heavily modified by Michael Kay
// Methods implemented by Jeni Tennison
// Extended for Saxon 9.7 by Sandro Cirulli, github.com/cirulls
    (1-1/1)