blob: dcbbd80c8fd9d9eef922541cb7cd9d32a8248fdb [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.
*/
#include "XalanFileUtility.hpp"
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <vector>
#include <climits>
#include <cstring>
#if defined(WIN32)
#include <direct.h>
#define PATH_MAX _MAX_PATH
#define chdir _chdir
#define getcwd _getcwd
#define mkdir _mkdir
#else
#if !defined(PATH_MAX)
#define PATH_MAX 2000
#endif
#define DIR_MODE_BITS 509
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
#if defined(XALAN_CLASSIC_IOSTREAMS)
#include <iostream.h>
#include <strstream.h>
#else
#include <iostream>
#include <strstream>
#endif
#if !defined(NDEBUG) && defined(_MSC_VER)
#include <crtdbg.h>
#endif
#include "xercesc/sax/SAXException.hpp"
#include "xalanc/PlatformSupport/DirectoryEnumerator.hpp"
#include "xalanc/PlatformSupport/DOMStringHelper.hpp"
#include "xalanc/PlatformSupport/XalanOutputStreamPrintWriter.hpp"
#include "xalanc/PlatformSupport/XalanFileOutputStream.hpp"
#include "xalanc/PlatformSupport/XalanUnicode.hpp"
#include "xalanc/XMLSupport/FormatterToXML.hpp"
#include "xalanc/XMLSupport/FormatterTreeWalker.hpp"
#include "xalanc/XalanSourceTree/XalanSourceTreeDOMSupport.hpp"
#include "xalanc/XalanSourceTree/XalanSourceTreeParserLiaison.hpp"
#include "xalanc/XalanSourceTree/XalanSourceTreeDocument.hpp"
#include "xalanc/XSLT/StylesheetRoot.hpp"
#include "xalanc/XalanTransformer/XalanCompiledStylesheet.hpp"
#include "xalanc/XalanTransformer/XalanTransformer.hpp"
#include "XalanXMLFileReporter.hpp"
XALAN_CPP_NAMESPACE_BEGIN
const char* const xalanNodeTypes[] =
{
"UNKNOWN_NODE",
"ELEMENT_NODE",
"ATTRIBUTE_NODE",
"TEXT_NODE",
"CDATA_SECTION_NODE",
"ENTITY_REFERENCE_NODE",
"ENTITY_NODE",
"PROCESSING_INSTRUCTION_NODE",
"COMMENT_NODE",
"DOCUMENT_NODE",
"DOCUMENT_TYPE_NODE",
"DOCUMENT_FRAGMENT_NODE",
"NOTATION_NODE"
};
XALAN_USING_STD(cerr)
XALAN_USING_STD(cout)
XALAN_USING_STD(endl)
const XalanDOMString XalanFileUtility::s_emptyString;
XalanFileUtility::reportStruct::reportStruct() :
theDrive(),
testOrFile(),
xmlFileURL(),
xslFileURL(),
xmlFormat(),
msg(0),
currentNode(),
actual(),
expected(),
pass(0),
fail(0),
nogold(0)
{
}
void
XalanFileUtility::reportStruct::reset()
{
clear(testOrFile);
msg = "";
clear(currentNode);
clear(actual);
clear(expected);
}
XalanFileUtility::cmdParams::cmdParams() :
help(),
base(),
output(),
gold(),
sub(),
source(0),
skip(false),
iters(0)
{
}
const char*
XalanFileUtility::cmdParams::getHelpMessage()
{
help << '\0';
const char* const data = help.str();
#if defined(HPUX)
help.rdbuf() -> freeze(false);
#else
help.freeze(false);
#endif
return data;
}
XalanFileUtility::XalanFileUtility() :
data(),
args()
{
cout << endl
<< "Using Xalan version "
<< XALAN_FULLVERSIONDOT
<< endl
<< "Using Xerces version "
<< XERCES_FULLVERSIONDOT
<< endl
<< endl;
}
XalanFileUtility::~XalanFileUtility()
{
}
#if !defined(WIN32)
XalanDOMString
XalanFileUtility::getDrive()
{
return XalanDOMString();
}
#else
XalanDOMString
XalanFileUtility::getDrive()
{
const char temp[] =
{
char(_getdrive() + 'A' - 1),
':',
'\0'
};
return XalanDOMString(temp, sizeof(temp) - 1);
}
#endif
bool
XalanFileUtility::getParams(
int argc,
char* argv[],
const char* outDir,
bool fsetGold)
{
bool fSuccess = true; // Used to continue argument loop
bool fsetOut = true; // Set default output directory, set to false if data is provided
args.skip = true; // Default values for performance testing parameters.
args.iters = 3;
// Insure that required "-base" argument is there.
//
if (argc == 1 || argv[1][0] == '-')
{
cout << args.getHelpMessage();
return false;
}
else
{
if (checkDir(XalanDOMString(argv[1])))
{
assign(args.base, XalanDOMString(argv[1]));
}
else
{
cout << endl << "Given base directory \"" << argv[1] << "\" does not exist" << endl;
cout << args.getHelpMessage();
return false;
}
}
// Get the rest of the arguments.
//
for (int i = 2; i < argc && fSuccess == true; ++i)
{
if(!stricmp("-out", argv[i]))
{
++i;
if(i < argc && argv[i][0] != '-')
{
assign(args.output, XalanDOMString(argv[i]));
append(args.output, s_pathSep);
checkAndCreateDir(args.output);
fsetOut = false;
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
}
else if(!stricmp("-gold", argv[i]))
{
++i;
if(i < argc && argv[i][0] != '-')
{
assign(args.gold, XalanDOMString(argv[i]));
if ( !checkDir(args.gold) )
{
cout << "Given Gold dir - " << c_str(TranscodeToLocalCodePage(args.gold)) << " - does not exist" << endl;
fSuccess = false;
}
append(args.gold, s_pathSep);
fsetGold = false;
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
}
else if(!stricmp("-source", argv[i]))
{
++i;
if(i < argc && argv[i][0] != '-')
{
if (stricmp(argv[i],"XPL") == 0)
{
args.source = 1;
outDir = "DOM-XALAN";
}
else if (stricmp(argv[i], "DOM") == 0)
{
args.source = 2;
outDir = "DOM-XERCES";
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
}
else if(!stricmp("-sub", argv[i]))
{
++i;
if(i < argc && argv[i][0] != '-')
{
assign(args.sub, XalanDOMString(argv[i]));
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
}
else if(!stricmp("-i", argv[i]))
{
args.skip = false;
}
else if(!stricmp("-iter", argv[i]))
{
++i;
// Make sure number is there and is greater then zero
if(i < argc && atol(argv[i]) > 0)
{
args.iters = atol(argv[i]);
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
}
else
{
cout << args.getHelpMessage();
fSuccess = false;
}
} // End of for-loop
// Do we need to set the default output directory??
//
if (fsetOut)
{
unsigned int ii = lastIndexOf(args.base, s_pathSep[0]);
if (ii < length(args.base))
{
args.output.assign(args.base, 0, ii + 1);
}
append(args.output,XalanDOMString(outDir));
checkAndCreateDir(args.output);
append(args.output,s_pathSep);
}
// Do we need to set the default gold directory??
//
if (fsetGold)
{
args.gold = args.base;
append(args.gold,XalanDOMString("-gold"));
if ( !checkDir(args.gold) )
{
cout << "Assumed Gold dir - " << c_str(TranscodeToLocalCodePage(args.gold)) << " - does not exist" << endl;
fSuccess = false;
}
append(args.gold,s_pathSep);
}
// Add the path seperator to the end of the base directory
// here after we've finished using it for all directory creation.
//
append(args.base,s_pathSep);
return fSuccess;
}
// This routine retrieves test file names from specified directories.
// Inputs: baseDir: typically "conf" or "perf"
// relDir: sub-directory to search.
//
// Notes: It builds the searchSpecification by concatenating all the
// necessary components.
//
XalanFileUtility::FileNameVectorType
XalanFileUtility::getTestFileNames(
const XalanDOMString& baseDir,
const XalanDOMString& relDir,
bool useDirPrefix)
{
char buffer3[PATH_MAX];
getcwd(buffer3, PATH_MAX);
const XalanDOMString searchSuffix(XALAN_STATIC_UCODE_STRING("*.xsl"));
XalanDOMString searchSpecification;
// Allow directory search w/o mandating files start with directory name. Required for files
// garnered from XSLTMARK performance directory exm.
if (useDirPrefix)
{
assign(searchSpecification, baseDir + relDir + s_pathSep + relDir + searchSuffix);
}
else
{
assign(searchSpecification, baseDir + relDir + s_pathSep + searchSuffix);
}
DirectoryEnumeratorFunctor<FileNameVectorType, XalanDOMString> theEnumerator;
FileNameVectorType theFiles;
theEnumerator(searchSpecification, theFiles);
chdir(buffer3);
return theFiles;
}
/* This routine retrieves all sub-directories from the specified directories.
// Inputs: rootDirectory: typically "conf" or "perf"
//
// Notes: The searchSpecification in this case is just "*".
// */
XalanFileUtility::FileNameVectorType
XalanFileUtility::getDirectoryNames(const XalanDOMString& rootDirectory)
{
char buffer2[PATH_MAX];
getcwd(buffer2, PATH_MAX);
const XalanDOMString dirSpec(XALAN_STATIC_UCODE_STRING("*"));
DirectoryEnumeratorFunctor<FileNameVectorType, XalanDOMString, DirectoryFilterPredicate> theEnumerator;
FileNameVectorType theFiles;
theEnumerator(XalanDOMString(rootDirectory), XalanDOMString(dirSpec), theFiles);
chdir(buffer2);
return theFiles;
}
bool XalanFileUtility::checkDir(const XalanDOMString& directory )
{
char buffer[PATH_MAX];
getcwd(buffer, PATH_MAX);
bool fResult = false;
if ( !chdir(c_str(TranscodeToLocalCodePage(directory))) )
{
fResult = true;
}
chdir(buffer);
return fResult;
}
void XalanFileUtility::checkAndCreateDir(const XalanDOMString& directory)
{
char buffer[PATH_MAX];
getcwd(buffer, PATH_MAX);
if ( (chdir(c_str(TranscodeToLocalCodePage(directory)))) )
{
//cout << "Couldn't change to " << directory << ", will create it." << endl;
#if defined(WIN32)
if ( !mkdir(c_str(TranscodeToLocalCodePage(directory))))
#else
if ( !mkdir(c_str(TranscodeToLocalCodePage(directory)), DIR_MODE_BITS))
#endif
{
cout << directory << " created." << endl;
}
else
{
cout << directory << " NOT created." << endl;
}
}
chdir(buffer);
}
/* This routine generates file names based on the provide suffix
// Inputs: theXMLFileName: typically "conf" or "perf"
// suffix: typically "xsl" or "out".
//
// Notes:
*/
XalanDOMString
XalanFileUtility::generateFileName(
const XalanDOMString& theXMLFileName,
const char* suffix,
bool* status)
{
XalanDOMString targetFile;
int thePeriodIndex = -1;
const int theLength = length(theXMLFileName);
for (int i = theLength - 1; i > 0; i--)
{
if (charAt(theXMLFileName, i) == XalanUnicode::charFullStop)
{
thePeriodIndex = i; // charFullStop is the dot (x2E)
break;
}
}
if (thePeriodIndex != -1)
{
targetFile.assign(theXMLFileName, 0, thePeriodIndex + 1);
targetFile += XalanDOMString(suffix);
}
// Check the .xml file exists.
if (!strcmp(suffix,"xml"))
{
FILE* fileHandle = fopen(c_str(TranscodeToLocalCodePage(targetFile)), "r");
if (fileHandle == 0)
{
cout << "TEST ERROR: File Missing: " << targetFile << endl;
if (status != 0)
{
*status = false;
}
}
else
{
fclose(fileHandle);
}
}
return targetFile;
}
/* This routine generates a Unique Runid.
// Inputs: None
//
// Notes: The format is mmddhhmm. For example
// 03151046 is "Mar 15 10:46"
*/
XalanDOMString
XalanFileUtility::generateUniqRunid()
{
#if defined(XALAN_STRICT_ANSI_HEADERS)
using std::tm;
using std::time;
using std::localtime;
using std::strftime;
#endif
struct tm *newtime;
time_t long_time;
char tmpbuf[10];
time( &long_time ); /* Get time as long integer. */
newtime = localtime( &long_time ); /* Convert to local time. */
strftime( tmpbuf, 10,"%m%d%H%M",newtime );
return XalanDOMString(tmpbuf);
}
// This routine gets Xerces Version number. It's used to put the Xerces Version
// into the output xml results file as an attribute of 'PerfData' element.
// Inputs: None
//
XalanDOMString
XalanFileUtility::getXercesVersion()
{
return XalanDOMString(gXercesFullVersionStr);
}
/* This routine creates a FormatterToXML FormatterListener. This is used to format
// the output DOM so a comparision can be done with the expected GOLD file.
// Inputs: None
//
*/
FormatterListener*
XalanFileUtility::getXMLFormatter(
PrintWriter& resultWriter,
int indentAmount,
const XalanDOMString& mimeEncoding,
const StylesheetRoot* stylesheet)
{
XalanDOMString version;
bool outputIndent= 0;
XalanDOMString mediatype;
XalanDOMString doctypeSystem;
XalanDOMString doctypePublic;
XalanDOMString standalone;
if (stylesheet != 0)
{
version = stylesheet->getOutputVersion();
mediatype = stylesheet->getOutputMediaType();
doctypeSystem = stylesheet->getOutputDoctypeSystem();
doctypePublic = stylesheet->getOutputDoctypePublic();
standalone = stylesheet->getOutputStandalone();
outputIndent = stylesheet->getOutputIndent();
}
return new FormatterToXML(
resultWriter,
version,
outputIndent,
indentAmount,
mimeEncoding,
mediatype,
doctypeSystem,
doctypePublic,
true, // xmlDecl
standalone);
}
/* This routine is used to compares the results of a transform and report the results.
// When a failure is detected the 'data' structure used to report detailed info about
// a failure is filled in.
// Inputs:
// goldFile - Name of gold file
// outputFile - Name of result file.
// logfile - Name of log file reporter.
//
// Returns:
// Void
*/
void
XalanFileUtility::checkResults(
const XalanDOMString& outputFile,
const XalanDOMString& goldFile,
XalanXMLFileReporter& logfile)
{
int ambgFlag = data.nogold; // get the current number of tests w/o gold files.
// Compare the results, report success if compareSerializedResults returns true.
if(compareSerializedResults(outputFile, goldFile))
{
cout << "Passed: " << data.testOrFile << endl;
logfile.logCheckPass(data.testOrFile);
data.pass += 1;
}
else
{
typedef XalanXMLFileReporter::Hashtable Hashtable;
// if the compairson fails gather up the failure data and determine if it failed
// due to bad output or missing Gold file. Lastly, log the failure.
Hashtable attrs;
Hashtable actexp;
reportError();
attrs.insert(Hashtable::value_type(XalanDOMString("reason"), XalanDOMString(data.msg)));
attrs.insert(Hashtable::value_type(XalanDOMString("atNode"), data.currentNode));
actexp.insert(Hashtable::value_type(XalanDOMString("exp"), data.expected));
actexp.insert(Hashtable::value_type(XalanDOMString("act"), data.actual));
actexp.insert(Hashtable::value_type(XalanDOMString("xsl"), data.xslFileURL));
actexp.insert(Hashtable::value_type(XalanDOMString("xml"), data.xmlFileURL));
actexp.insert(Hashtable::value_type(XalanDOMString("result"), outputFile));
actexp.insert(Hashtable::value_type(XalanDOMString("gold"), goldFile));
if (ambgFlag < data.nogold)
{
logfile.logCheckAmbiguous(data.testOrFile);
}
else
{
logfile.logCheckFail(data.testOrFile, attrs, actexp);
}
}
}
void
XalanFileUtility::checkAPIResults(
const XalanDOMString& actual,
const XalanDOMString& expected,
const char* msg,
XalanXMLFileReporter& logfile,
const XalanDOMString& outputFile,
const XalanDOMString& goldFile,
bool containsOnly)
{
if(actual == expected ||
(containsOnly == true && indexOf(actual, expected) != XalanDOMString::npos))
{
data.pass += 1;
cout << "Passed: " << data.testOrFile << endl;
logfile.logCheckPass(data.testOrFile);
}
else
{ data.actual = actual;
data.expected = expected;
data.currentNode = "API Test";
data.msg = msg;
data.fail += 1;
reportError();
typedef XalanXMLFileReporter::Hashtable Hashtable;
Hashtable actexp;
actexp.insert(Hashtable::value_type(XalanDOMString("exp"), expected));
actexp.insert(Hashtable::value_type(XalanDOMString("act"), actual));
actexp.insert(Hashtable::value_type(XalanDOMString("xsl"), data.xslFileURL));
actexp.insert(Hashtable::value_type(XalanDOMString("xml"), data.xmlFileURL));
actexp.insert(Hashtable::value_type(XalanDOMString("result"), outputFile));
actexp.insert(Hashtable::value_type(XalanDOMString("gold"), goldFile));
// Todo: Need to determine if I should check for missing gold in these cases.
logfile.logCheckFail(data.testOrFile, actexp);
}
}
/* This routine compares the results of a transform with the gold file.
// It in turn call the domCompare routine to do the actual comparision.
// Inputs:
// gold - Dom tree for the expected results
// doc - Dom tree created during transformation
// filename - Current filename
//
// Returns:
// Void
//
*/
void
XalanFileUtility::checkDOMResults(
const XalanDOMString& theOutputFile,
const XalanCompiledStylesheet* compiledSS,
const XalanSourceTreeDocument* dom,
const XSLTInputSource& goldInputSource,
XalanXMLFileReporter& logfile)
{
const int ambgFlag = data.nogold;
const XalanDOMString mimeEncoding("");
XalanFileOutputStream myOutput(theOutputFile);
XalanOutputStreamPrintWriter myResultWriter(myOutput);
FormatterListener* const theFormatter =
getXMLFormatter(
myResultWriter,
0,
mimeEncoding,
compiledSS->getStylesheetRoot());
FormatterTreeWalker theTreeWalker(*theFormatter);
theTreeWalker.traverse(dom);
delete theFormatter;
XalanSourceTreeDOMSupport domSupport;
XalanSourceTreeParserLiaison parserLiaison(domSupport);
domSupport.setParserLiaison(&parserLiaison);
const XalanDocument* const goldDom =
parserLiaison.parseXMLStream(goldInputSource);
if(domCompare(*goldDom, *dom))
{
cout << "Passed: " << data.testOrFile << endl;
logfile.logCheckPass(data.testOrFile);
data.pass += 1;
}
else
{
typedef XalanXMLFileReporter::Hashtable Hashtable;
// if the compairson fails gather up the failure data and determine if it failed
// due to bad output or missing Gold file. Lastly, log the failure.
Hashtable attrs;
Hashtable actexp;
reportError();
attrs.insert(Hashtable::value_type(XalanDOMString("reason"), XalanDOMString(data.msg)));
attrs.insert(Hashtable::value_type(XalanDOMString("atNode"), data.currentNode));
actexp.insert(Hashtable::value_type(XalanDOMString("exp"), data.expected));
actexp.insert(Hashtable::value_type(XalanDOMString("act"), data.actual));
if (ambgFlag < data.nogold)
{
logfile.logCheckAmbiguous(data.testOrFile);
}
else
{
logfile.logCheckFail(data.testOrFile, attrs, actexp);
}
}
}
/* This routine takes the result file and gold file and parses them.
// If either of the files fails to parse and a SAXException is throw,
// then the files are compared using a char by char file compare,
// otherwise the domCompare routine is used.
// Inputs:
// outputFile: Name of result file
// goldFile: Name of gold file
//
// Returns:
// True or False
//
*/
bool
XalanFileUtility::compareSerializedResults(
const XalanDOMString& outputFile,
const XalanDOMString& goldFile)
{
const XSLTInputSource resultInputSource(outputFile);
const XSLTInputSource goldInputSource(goldFile);
XalanSourceTreeDOMSupport domSupport;
XalanSourceTreeParserLiaison parserLiaison(domSupport);
domSupport.setParserLiaison(&parserLiaison);
try
{
const XalanDocument* const transformDom =
parserLiaison.parseXMLStream(resultInputSource);
assert(transformDom != 0);
const XalanDocument* const goldDom =
parserLiaison.parseXMLStream(goldInputSource);
assert(goldDom != 0);
return domCompare(*goldDom, *transformDom);
}
// This exception is being reported prior to this Catch, however, however, I clarify that it's a SAX exception.
// It's a good indication that the Gold file is not a valid XML. When this happens the transform result needs
// to be compared with the Gold, with a character by character basis, not via the DOM compair.
catch (const XERCES_CPP_NAMESPACE_QUALIFIER SAXException&)
{
cout << "SAXException: Using fileCompare to check output.\n";
return fileCompare(c_str(TranscodeToLocalCodePage(goldFile)), c_str(TranscodeToLocalCodePage(outputFile)));
}
}
static void
replaceNonAsciiCharacters(
char* theBuffer,
char theReplacementChar)
{
while(*theBuffer)
{
if (unsigned(*theBuffer) > 127)
{
*theBuffer = theReplacementChar;
}
++theBuffer;
}
}
/* This routine is used to compare the results against the gold when one or both of
// fails to parse without throwing a SAXException. When a failure is detected the 'data'
// structure used to report detailed info about a failure is filled in.
// Inputs:
// outputFile: Name of result file
// goldFile: Name of gold file
//
// Returns:
// True or False
//
*/
bool
XalanFileUtility::fileCompare(
const char* goldFile,
const char* outputFile)
{
const unsigned long maxBuffer = 132;
char rline[maxBuffer] = {'0'}; // declare buffers to hold single line from file
char gline[maxBuffer] = {'0'};
char temp[10]; // buffer to hold line number
char lineNum = 1;
// Set fail data incase there are i/o problems with the files to compare.
data.expected = XalanDOMString(" ");
data.actual = XalanDOMString(" ");
data.currentNode = XalanDOMString("Line: 0");
// Attempt to open the files.
FILE* const result = fopen(outputFile, "r");
FILE* const gold = fopen(goldFile, "r");
// If the result file fails to open report this as a failure.
if (!result)
{
data.msg = "No Result (Transform failed)";
data.fail += 1;
return false;
}
// If the gold file fails to open report this as ambiguous.
if (!gold)
{
data.msg = "No Gold file";
data.nogold += 1;
return false;
}
// Start file comparison, line by line..
while(!feof(result) && !feof(gold))
{
fgets(gline, sizeof(gline), gold );
fgets(rline, sizeof(rline), result );
sprintf(temp,"%d",lineNum);
if (ferror(gold) || ferror(result))
{
data.msg = "Read Error - Gold/Result file";
data.currentNode = XalanDOMString("Line: ") + XalanDOMString(temp);
return false;
}
// Compare the lines character by charcter ....
unsigned int i = 0;
while(i < strlen(gline))
{
if (gline[i] == rline[i])
{
i++;
}
else
{ // If there is a mismatch collect up the fail data and return false. To ensure that
// the results can be seen in the browser enclose the actual/expected in CDATA Sections.
// Replace any non-ASCII characters. Otherwise, we would have to encode them
// in UTF-8, which is a huge pain.
replaceNonAsciiCharacters(gline, '?');
replaceNonAsciiCharacters(rline, '?');
data.msg = "Text based comparison failure";
data.expected = XalanDOMString("<![CDATA[") + XalanDOMString(gline) + XalanDOMString("]]>");
data.actual = XalanDOMString("<![CDATA[") + XalanDOMString(rline) + XalanDOMString("]]>");
data.currentNode = XalanDOMString("Line: ") + XalanDOMString(temp);
data.fail += 1;
fclose(result); fclose(gold);
return false;
}
}
lineNum += 1;
}
fclose(result); fclose(gold);
return true;
}
/* This routine performs a DOM Comparision.
// Inputs:
// gold - Dom tree for the expected results
// doc - Dom tree created during transformation
// filename - Current filename
//
// Returns:
// True or False
//
*/
bool
XalanFileUtility::domCompare(
const XalanNode& gold,
const XalanNode& doc)
{
const XalanNode::NodeType docNodeType = doc.getNodeType();
const XalanNode::NodeType goldNodeType = gold.getNodeType();
const XalanDOMString& docNodeName = doc.getNodeName();
if (goldNodeType != docNodeType)
{
collectData("NodeType mismatch.",
docNodeName,
XalanDOMString(xalanNodeTypes[docNodeType]),
XalanDOMString(xalanNodeTypes[goldNodeType]));
return false;
}
switch (goldNodeType)
{
case XalanNode::ELEMENT_NODE: // ATTRIBUTE_NODEs are processed with diffElement().
{
if (diffElement(gold, doc) == false)
{
return false;
}
}
break;
case XalanNode::CDATA_SECTION_NODE:
case XalanNode::TEXT_NODE:
{
const XalanDOMString& docNodeValue = doc.getNodeValue();
const XalanDOMString& goldNodeValue = gold.getNodeValue();
//debugNodeData(docNodeName, docNodeValue);
if(goldNodeValue != docNodeValue)
{
collectData("Text node mismatch. ",
docNodeName,
goldNodeValue,
docNodeValue);
return false;
}
}
break;
case XalanNode::PROCESSING_INSTRUCTION_NODE:
{
const XalanDOMString& goldNodeName = gold.getNodeName();
if (goldNodeName != docNodeName)
{
collectData("processing-instruction target mismatch. ",
docNodeName,
goldNodeName,
docNodeName);
return false;
}
else
{
const XalanDOMString& docNodeValue = doc.getNodeValue();
const XalanDOMString& goldNodeValue = gold.getNodeValue();
if (goldNodeValue != docNodeValue)
{
collectData("processing-instruction data mismatch. ",
docNodeName,
goldNodeValue,
docNodeValue);
return false;
}
}
}
break;
case XalanNode::COMMENT_NODE:
{
const XalanDOMString& docNodeValue = doc.getNodeValue();
const XalanDOMString& goldNodeValue = gold.getNodeValue();
if (goldNodeValue != docNodeValue)
{
collectData("comment data mismatch. ",
docNodeName,
goldNodeValue,
docNodeValue);
return false;
}
}
break;
case XalanNode::DOCUMENT_NODE:
{
//debugNodeData(docNodeName);
const XalanNode *goldNextNode;
const XalanNode *domNextNode;
goldNextNode = gold.getFirstChild();
domNextNode = doc.getFirstChild();
if (0 != goldNextNode)
{
if(domCompare(*goldNextNode,*domNextNode) == false)
{
return false;
}
}
}
break;
case XalanNode::ENTITY_REFERENCE_NODE:
case XalanNode::ENTITY_NODE:
case XalanNode::DOCUMENT_TYPE_NODE:
case XalanNode::DOCUMENT_FRAGMENT_NODE:
case XalanNode::NOTATION_NODE:
default:
cerr << "Unexpected node type: " << goldNodeType << endl;
return false;
}
// Need to process siblings. Children are processed in diffElement, since
// only they can have children in the XPath data model.
const XalanNode* const goldNextNode = gold.getNextSibling();
const XalanNode* const domNextNode = doc.getNextSibling();
if (0 != goldNextNode)
{
if (0 != domNextNode)
{
if (domCompare(*goldNextNode, *domNextNode) == false)
{
return false;
}
}
else
{
collectData("Missing sibling node. ",
docNodeName,
goldNextNode->getNodeName(),
goldNextNode->getNodeName());
return false;
}
}
else if (0 != domNextNode)
{
collectData("Extra sibling node. ",
docNodeName,
domNextNode->getNodeName(),
domNextNode->getNodeName());
return false;
}
return true;
}
bool
XalanFileUtility::domCompare(
const XalanDocument& gold,
const XalanDocument& doc)
{
const XalanNode* theGoldPos = &gold;
const XalanNode* theDocPos = &doc;
bool fEqual = true;
do
{
fEqual = diffNode(theGoldPos, theDocPos);
if (fEqual == true)
{
assert(theGoldPos != 0 && theDocPos != 0);
const XalanNode* nextGoldNode = theGoldPos->getFirstChild();
const XalanNode* nextDocNode = theDocPos->getFirstChild();
bool fBreak = false;
while(
nextGoldNode == 0 &&
nextDocNode == 0 &&
fBreak == false)
{
// Move to the next sibling of each node,
// since we would get here only if both have
// no children.
nextGoldNode = theGoldPos->getNextSibling();
nextDocNode = theDocPos->getNextSibling();
// If there is no next sibling, move up to the
// parent. If one, but not both, has a sibling,
// we'll end up back at the top of the do/while
// loop and the difference will be reported.
if(0 == nextGoldNode && 0 == nextDocNode)
{
theGoldPos = theGoldPos->getParentNode();
theDocPos = theDocPos->getParentNode();
// If the parent is null, then we've reached
// the end of the document. Note that if we
// got here, then there must also be a parent
// node in the document we're verifying, so we
// could simply assert that theDocPos is either
// null if theGoldPos is null, or it is not-null
// if theGoldPos is not-null.
if(0 == theGoldPos)
{
nextGoldNode = theGoldPos;
fBreak = true;
}
if(0 == theDocPos)
{
nextDocNode = theDocPos;
fBreak = true;
}
}
}
theGoldPos = nextGoldNode;
theDocPos = nextDocNode;
}
} while((theGoldPos != 0 || theDocPos != 0) && fEqual == true);
return fEqual;
}
bool
XalanFileUtility::diffNode(
const XalanNode& gold,
const XalanNode& doc)
{
const XalanNode::NodeType docNodeType = doc.getNodeType();
const XalanNode::NodeType goldNodeType = gold.getNodeType();
const XalanDOMString& docNodeName = doc.getNodeName();
if (goldNodeType != docNodeType)
{
collectData("NodeType mismatch.",
docNodeName,
XalanDOMString(xalanNodeTypes[docNodeType]),
XalanDOMString(xalanNodeTypes[goldNodeType]));
return false;
}
switch (goldNodeType)
{
case XalanNode::ELEMENT_NODE: // ATTRIBUTE_NODEs are processed with diffElement().
return diffElement2(gold, doc);
break;
case XalanNode::CDATA_SECTION_NODE:
case XalanNode::TEXT_NODE:
{
const XalanDOMString& docNodeValue = doc.getNodeValue();
const XalanDOMString& goldNodeValue = gold.getNodeValue();
//debugNodeData(docNodeName, docNodeValue);
if(goldNodeValue != docNodeValue)
{
collectData("Text node mismatch. ",
docNodeName,
goldNodeValue,
docNodeValue);
return false;
}
}
break;
case XalanNode::PROCESSING_INSTRUCTION_NODE:
{
const XalanDOMString& goldNodeName = gold.getNodeName();
if (goldNodeName != docNodeName)
{
collectData("processing-instruction target mismatch. ",
docNodeName,
goldNodeName,
docNodeName);
return false;
}
else
{
const XalanDOMString& docNodeValue = doc.getNodeValue();
const XalanDOMString& goldNodeValue = gold.getNodeValue();
if (goldNodeValue != docNodeValue)
{
collectData("processing-instruction data mismatch. ",
docNodeName,
goldNodeValue,
docNodeValue);
return false;
}
}
}
break;
case XalanNode::COMMENT_NODE:
{
const XalanDOMString& docNodeValue = doc.getNodeValue();
const XalanDOMString& goldNodeValue = gold.getNodeValue();
if (goldNodeValue != docNodeValue)
{
collectData("comment data mismatch. ",
docNodeName,
goldNodeValue,
docNodeValue);
return false;
}
}
break;
case XalanNode::DOCUMENT_NODE:
break;
case XalanNode::ENTITY_REFERENCE_NODE:
case XalanNode::ENTITY_NODE:
case XalanNode::DOCUMENT_TYPE_NODE:
case XalanNode::DOCUMENT_FRAGMENT_NODE:
case XalanNode::NOTATION_NODE:
default:
cerr << "Unexpected node type: " << goldNodeType << endl;
return false;
}
return true;
}
bool
XalanFileUtility::diffNode(
const XalanNode* gold,
const XalanNode* doc)
{
if (gold != 0 && doc != 0)
{
return diffNode(*gold, *doc);
}
else if (gold != 0)
{
const XalanNode* const parent =
gold->getParentNode();
collectData(
"Missing sibling node. ",
parent == 0 ? s_emptyString : parent->getNodeName(),
s_emptyString,
gold->getNodeName());
return false;
}
else
{
assert(doc != 0 && gold == 0);
const XalanNode* const parent =
doc->getParentNode();
collectData(
"Extra sibling node. ",
parent == 0 ? s_emptyString : parent->getNodeName(),
doc->getNodeName(),
s_emptyString);
return false;
}
}
/* This routine compares two element nodes.
// Inputs:
// gold - Dom tree for the expected results
// doc - Dom tree created during transformation
// filename - Current filenam
//
// Returns:
// True or False
//
*/
bool
XalanFileUtility::diffElement(
const XalanNode& gold,
const XalanNode& doc)
{
assert(gold.getNodeType() == XalanNode::ELEMENT_NODE);
assert(gold.getNodeType() == XalanNode::ELEMENT_NODE);
const XalanDOMString& docNodeName = doc.getNodeName();
const XalanDOMString& goldNodeName = gold.getNodeName();
const XalanDOMString& docNsUri = doc.getNamespaceURI();
const XalanDOMString& goldNsUri = gold.getNamespaceURI();
//debugNodeData(docNodeName);
// This essentially checks 2 things, that the prefix and localname are the
// same. So specific checks of these items are not necessary.
if (goldNodeName != docNodeName)
{
collectData("Element mismatch. ",
docNodeName,
goldNodeName,
docNodeName);
return false;
}
if ( goldNsUri != docNsUri)
{
collectData("Element NamespaceURI mismatch. ",
docNodeName,
goldNsUri,
docNsUri);
return false;
}
// Get Attributes for each Element Node.
const XalanNamedNodeMap* const goldAttrs = gold.getAttributes();
const XalanNamedNodeMap* const docAttrs = doc.getAttributes();
// Get number of Attributes
const unsigned int numGoldAttr = goldAttrs->getLength();
const unsigned int numDomAttr = docAttrs ->getLength();
/*
// This needs to be uncommented if 'compare.exe' is to work.
// If this is the 'root' element strip off the xmlns:xml namespace attribute,
// that is lurking around on the gold file, but not the dom. This is necessary
// only for the 'compare' test, that uses a pure DOM, that has not been serialized.
//if (goldNodeName == XalanDOMString("root"))
{
numGoldAttr -= 1;
XalanNode *gXMLAttr = goldAttrs->item(1);
}
*/
// Check that each Element has same number of Attributes. If they don't report error
if ( numGoldAttr == numDomAttr )
{
// Compare Attributes one at a time.
//for (int i=1; i < numGoldAttr; i++) // To be used with 'compare'
for (unsigned int i = 0; i < numGoldAttr; ++i)
{
// Attribute order is irrelvant, so comparision is base on Attribute name.
const XalanNode* const gAttr = goldAttrs->item(i);
const XalanDOMString& goldAttrName = gAttr->getNodeName();
const XalanNode* const dAttr = docAttrs->getNamedItem(goldAttrName);
if (dAttr != 0)
{
if( ! (diffAttr(gAttr, dAttr)) )
return false;
}
else
{
collectData("Element missing named Attribute. ",
docNodeName,
goldAttrName,
XalanDOMString("NOTHING"));
return false;
}
}
}
else
{
char buf1[2], buf2[2];
sprintf(buf1, "%u", numGoldAttr);
sprintf(buf2, "%u", numDomAttr);
collectData("Wrong number of attributes. ",
docNodeName,
XalanDOMString(buf1),
XalanDOMString(buf2));
return false;
}
const XalanNode* goldNextNode = gold.getFirstChild();
const XalanNode* domNextNode = doc.getFirstChild();
if (0 != goldNextNode)
{
if (0 != domNextNode)
{
if ( ! domCompare(*goldNextNode, *domNextNode) )
return false;
}
else
{
collectData("Element missing ChildNode. ",
docNodeName,
XalanDOMString(goldNextNode->getNodeName()),
XalanDOMString("NOTHING"));
return false;
}
}
else if (domNextNode != 0)
{
// The result doc has additional Children. If the additional node is a text node
// then gather up the text and print it out.
if ( domNextNode->getNodeType() == XalanNode::TEXT_NODE)
{
collectData("Result has additional Child node: ",
docNodeName,
XalanDOMString("NOTHING"),
XalanDOMString(domNextNode->getNodeName()) + XalanDOMString(" \"") +
XalanDOMString(domNextNode->getNodeValue()) + XalanDOMString("\""));
}
// Additional node is NOT text, so just print it's Name.
else
{
collectData("Result has additional Child node: ",
docNodeName,
XalanDOMString("NOTHING"),
XalanDOMString(domNextNode->getNodeName()));
}
return false;
}
return true;
}
bool
XalanFileUtility::diffElement2(
const XalanNode& gold,
const XalanNode& doc)
{
assert(gold.getNodeType() == XalanNode::ELEMENT_NODE);
assert(gold.getNodeType() == XalanNode::ELEMENT_NODE);
const XalanDOMString& docNodeName = doc.getNodeName();
const XalanDOMString& goldNodeName = gold.getNodeName();
const XalanDOMString& docNsUri = doc.getNamespaceURI();
const XalanDOMString& goldNsUri = gold.getNamespaceURI();
//debugNodeData(docNodeName);
// This essentially checks 2 things, that the prefix and localname are the
// same. So specific checks of these items are not necessary.
if (goldNodeName != docNodeName)
{
collectData("Element mismatch. ",
docNodeName,
goldNodeName,
docNodeName);
return false;
}
if ( goldNsUri != docNsUri)
{
collectData("Element NamespaceURI mismatch. ",
docNodeName,
goldNsUri,
docNsUri);
return false;
}
// Get Attributes for each Element Node.
const XalanNamedNodeMap* const goldAttrs = gold.getAttributes();
assert(goldAttrs != 0);
const XalanNamedNodeMap* const docAttrs = doc.getAttributes();
assert(docAttrs != 0);
// Get number of Attributes
const unsigned int numGoldAttr = goldAttrs->getLength();
const unsigned int numDomAttr = docAttrs ->getLength();
// Check that each Element has same number of Attributes. If they don't report error
if ( numGoldAttr == numDomAttr )
{
// Compare Attributes one at a time.
//for (int i=1; i < numGoldAttr; i++) // To be used with 'compare'
for (unsigned int i = 0; i < numGoldAttr; ++i)
{
// Attribute order is irrelvant, so comparision is base on Attribute name.
const XalanNode* const gAttr = goldAttrs->item(i);
const XalanDOMString& goldAttrName = gAttr->getNodeName();
const XalanNode* const dAttr = docAttrs->getNamedItem(goldAttrName);
if (dAttr != 0)
{
if( ! (diffAttr(gAttr, dAttr)) )
return false;
}
else
{
collectData("Element missing named Attribute. ",
docNodeName,
goldAttrName,
XalanDOMString("NOTHING"));
return false;
}
}
}
else
{
char buf1[2], buf2[2];
sprintf(buf1, "%u", numGoldAttr);
sprintf(buf2, "%u", numDomAttr);
collectData("Wrong number of attributes. ",
docNodeName,
XalanDOMString(buf1),
XalanDOMString(buf2));
return false;
}
return true;
}
/* This routine compares two attribute nodes.
// Inputs:
// gAttr - attribute from Gold dom tree
// dAttr - attribute from Dom tree created during transformation
// fileName - Current filenam
//
// Returns:
// True or False
//
*/
bool XalanFileUtility::diffAttr(const XalanNode* gAttr, const XalanNode* dAttr)
{
const XalanDOMString& docAttrName = dAttr->getNodeName();
//debugAttributeData(goldAttrName);
const XalanDOMString& goldAttrValue = gAttr->getNodeValue();
const XalanDOMString& docAttrValue = dAttr->getNodeValue();
if (goldAttrValue != docAttrValue)
{
collectData(
"Attribute Value mismatch. ",
docAttrName,
goldAttrValue,
docAttrValue);
return false;
}
const XalanDOMString& goldAttrNsUri = gAttr->getNamespaceURI();
const XalanDOMString& docAttrNsUri = dAttr->getNamespaceURI();
if (goldAttrNsUri != docAttrNsUri)
{
collectData(
"Attribute NamespaceURI mismatch. ",
docAttrName,
goldAttrNsUri,
docAttrNsUri);
return false;
}
return true;
}
/* This routine reports DOM comparison errors.
// Inputs:
// file - Name of current file
// node - Current node that fails
// msg - Failure message
//
*/
void
XalanFileUtility::reportError()
{
cout << endl
<< "* Failed "
<< data.testOrFile
<< " Error: "
<< data.msg
<< endl
<< " "
<< "Processing Node: "
<< data.currentNode
<< endl
<< " Expected: "
<< data.expected
<< endl
<< " Actual: "
<< data.actual
<< endl
<< endl;
}
#if !defined(NDEBUG)
void
XalanFileUtility::debugNodeData(const XalanDOMString& value) const
{
cout << "Node is: " << c_str(TranscodeToLocalCodePage(value)) << endl;
}
void
XalanFileUtility::debugNodeData(
const XalanDOMString& node,
const XalanDOMString& value) const
{
cout << "Node is: " << c_str(TranscodeToLocalCodePage(node)) << " "
<< "Value is: \"" << c_str(TranscodeToLocalCodePage(value)) << "\"\n";
}
void
XalanFileUtility::debugAttributeData(const XalanDOMString& value) const
{
cout << "Attribute is: " << c_str(TranscodeToLocalCodePage(value)) << endl;
}
#endif
/* This routine collects up data pertinent to a dom comparison failure.
// Inputs:
// errmsg: Reason for the failure.
// currentnode: Node in the dom tree where the mismatch occured
// expdata: Expected data based on the Gold file.
// actdata: Actual data returned in the result file.
// Returns: Void
*/
void
XalanFileUtility::collectData(
const char* errmsg,
const XalanDOMString& currentnode,
const XalanDOMString& expdata,
const XalanDOMString& actdata)
{
data.msg = errmsg;
data.currentNode = currentnode;
data.expected = expdata;
data.actual = actdata;
data.fail += 1;
}
/* Routine prints the result to the console, as well as adds summary info into the logfile.
// Inputs:
// logfile: Current log file
// runid: Unique runid
// Returns: Void
*/
void
XalanFileUtility::reportPassFail(
XalanXMLFileReporter& logfile,
const XalanDOMString& runid)
{
typedef XalanXMLFileReporter::Hashtable Hashtable;
Hashtable runResults;
char temp[5];
// Create entrys that contain runid, xerces version, and numbers for Pass, Fail and No Gold.
runResults.insert(Hashtable::value_type(XalanDOMString("UniqRunid"), runid));
runResults.insert(Hashtable::value_type(XalanDOMString("Xerces-Version "), getXercesVersion()));
runResults.insert(Hashtable::value_type(XalanDOMString("BaseDrive "), XalanDOMString(getDrive())));
runResults.insert(Hashtable::value_type(XalanDOMString("TestBase "), XalanDOMString(args.base)));
runResults.insert(Hashtable::value_type(XalanDOMString("xmlFormat "), data.xmlFormat));
sprintf(temp, "%ld", args.iters);
runResults.insert(Hashtable::value_type(XalanDOMString("Iters "), XalanDOMString(temp)));
sprintf(temp, "%d", data.pass);
runResults.insert(Hashtable::value_type(XalanDOMString("Passed"), XalanDOMString(temp)));
sprintf(temp, "%d", data.fail);
runResults.insert(Hashtable::value_type(XalanDOMString("Failed"), XalanDOMString(temp)));
sprintf(temp, "%d", data.nogold);
runResults.insert(Hashtable::value_type(XalanDOMString("No_Gold_Files"), XalanDOMString(temp)));
logfile.logElementWAttrs(10, "RunResults", runResults, "xxx");
cout << "\nPassed " << data.pass;
cout << "\nFailed " << data.fail;
cout << "\nMissing Gold " << data.nogold << endl;
}
/* Routine runs a stylesheet on the log file and displays the results in HTML.
// Inputs:
// xalan: An instance of the transformer
// resultsFile: logfile
// Returns: Void
*/
void
XalanFileUtility::analyzeResults(XalanTransformer& xalan, const XalanDOMString& resultsFile)
{
XalanDOMString paramValue;
bool fileStatus;
#if defined(AIX) || defined(SOLARIS) || defined(LINUX) || defined(HPUX)
bool pathStatus;
CharVectorType withPath;
TranscodeToLocalCodePage(resultsFile, withPath, false);
if (withPath[0] == '/')
pathStatus=true;
else
pathStatus=false;
char buffer5[PATH_MAX];
XalanDOMString resultPath= XalanDOMString(getcwd(buffer5, PATH_MAX));
append(resultPath, s_pathSep);
#endif
// Pass the results .xml file as a parameter to the stylesheet. It must be wrapped in single
// quotes so that it is not considered an expression.
//
#if defined (AIX) || defined(SOLARIS) || defined(LINUX) || defined(HPUX)
assign(paramValue, XalanDOMString("\'"));
if ( !pathStatus )
append(paramValue, resultPath);
append(paramValue, resultsFile);
append(paramValue, XalanDOMString("\'"));
#else
assign(paramValue, XalanDOMString("'"));
append(paramValue, resultsFile);
append(paramValue, XalanDOMString("'"));
#endif
// Set the parameter
//
xalan.setStylesheetParam(XalanDOMString("testfile"), paramValue);
// Generate the input and output file names.
//
const XalanDOMString theHTMLFile = generateFileName(resultsFile,"html", &fileStatus);
const XalanDOMString theStylesheet = args.base + XalanDOMString("cconf.xsl");
const XalanDOMString theXMLSource = args.base + XalanDOMString("cconf.xml");
// Check that we can find the stylesheet to analyze the results.
//
FILE* fileHandle = fopen(c_str(TranscodeToLocalCodePage(theStylesheet)), "r");
if (fileHandle == 0)
{
cout << "ANALYSIS ERROR: File Missing: " << c_str(TranscodeToLocalCodePage(theStylesheet)) << endl;
return;
}
else
{
fclose(fileHandle);
}
// Create the InputSources and ResultTarget.
const XSLTInputSource xslInputSource(theStylesheet);
const XSLTInputSource xmlInputSource(theXMLSource);
const XSLTResultTarget resultFile(theHTMLFile);
// Do the transform, display the output HTML, or report any failure.
const int result = xalan.transform(xmlInputSource, xslInputSource, resultFile);
if (result == 0)
{
#if defined(_MSC_VER)
system(c_str(TranscodeToLocalCodePage(theHTMLFile)));
#else
cout << "The HTML output: " << theHTMLFile << " was created" << endl;
#endif
}
else
{
cout << "Analysis failed due to following error: "
<< xalan.getLastError()
<< endl;
}
}
const XalanDOMChar XalanFileUtility::s_xmlSuffix[] =
{
XalanUnicode::charFullStop,
XalanUnicode::charLetter_x,
XalanUnicode::charLetter_m,
XalanUnicode::charLetter_l,
0
};
const XalanDOMChar XalanFileUtility::s_pathSep[] =
{
#if defined(WIN32)
XalanUnicode::charReverseSolidus,
#else
XalanUnicode::charSolidus,
#endif
0
};
XALAN_CPP_NAMESPACE_END