Hands On: XML validation in ABAP using XML-schema
Introduction
The following code snippet shows how to validate an XML document present as string in ABAP. Unfortunately there is no possibility to get the whole task done in pure ABAP if XML-schema should be used (validation via DTD is possible). Thus, the following code will only work on MS Windows OS having the Microsoft XML Core Services (MSXML) library installed on the client system. The approach is as follows:
- A MS JScript script is downloaded onto the local client.
- The XML-schema file is downloaded onto the local client.
- The script (1) is executed. Errors during validation are stored into a log file.
- The error log file is processed in order to check if the validation was successful.
Code Snippet(s)
DATA: lv_xml TYPE STRING, "XML document to validate against schema lv_valid TYPE ABAP_BOOL. "Indicator if the given XML is valid * Validation only supported for MS-Win OS XP and above. * Check the platform. DATA: lv_platform TYPE i. cl_gui_frontend_services=>get_platform( RECEIVING platform = lv_platform " Gets platform EXCEPTIONS error_no_gui = 1 cntl_error = 2 not_supported_by_gui = 3 OTHERS = 4 ). IF sy-subrc <> 0 OR lv_platform NE 14. RAISE validation_not_possible. ENDIF. * Build the validation mediation script which is executed on * client side. DATA: lv_validation_script TYPE string, lv_validation_script_filename TYPE string VALUE 'XMLValidation.js', lt_string_tab TYPE STANDARD TABLE OF string, lt_validation_script_tmp TYPE STANDARD TABLE OF string, lv_temp_dir_path TYPE string, lv_filename_separator TYPE c, lv_schema TYPE string, "The actual XML-schema lv_schema_file_path TYPE string, lv_schema_filename TYPE string VALUE '.xsd', lv_target_namespace TYPE string VALUE '', lv_xml_filename TYPE string VALUE 'xmlToValidate.xml', lv_error_log_file_name TYPE string VALUE 'validationErrorLog.log', lv_cr_lf TYPE abap_cr_lf VALUE cl_abap_char_utilities=>cr_lf. * Get the path to the temporary directory in order to store * all necessary files there as well as to perform the validation there. cl_gui_frontend_services=>get_temp_directory( CHANGING temp_dir = lv_temp_dir_path " Temporary Directory EXCEPTIONS cntl_error = 1 error_no_gui = 2 not_supported_by_gui = 3 OTHERS = 4 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. cl_gui_cfw=>flush( ). cl_gui_frontend_services=>get_file_separator( CHANGING file_separator = lv_filename_separator EXCEPTIONS not_supported_by_gui = 1 error_no_gui = 2 cntl_error = 3 OTHERS = 4 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. * Setup filename. lv_xml_filename = lv_temp_dir_path && lv_filename_separator && lv_xml_filename. lv_xml_filename = me->replace_slash( lv_xml_filename ). lv_error_log_file_name = lv_temp_dir_path && lv_filename_separator && lv_error_log_file_name. lv_error_log_file_name = me->replace_slash( lv_error_log_file_name ). lv_schema_file_path = lv_temp_dir_path && lv_filename_separator && lv_schema_filename. lv_schema_file_path = me->replace_slash( lv_schema_file_path ). lv_validation_script_filename = lv_temp_dir_path && lv_filename_separator && lv_validation_script_filename. lv_validation_script_filename = me->replace_slash( lv_validation_script_filename ). * Setup validation script. lv_validation_script = |var FileSysObj = new ActiveXObject("Scripting.FileSystemObject");| && |{ lv_cr_lf }| && |var xmlReader = new ActiveXObject("MSXML2.SAXXMLReader.4.0");| && |{ lv_cr_lf }| && |var xSchema = new ActiveXObject("MSXML2.XMLSchemaCache.4.0");| && |{ lv_cr_lf }| && |var xWriter = new ActiveXObject("MSXML2.MXXMLWriter.4.0");| && |{ lv_cr_lf }| && |var wShell = new ActiveXObject("WScript.Shell");| && |{ lv_cr_lf }| && |var schemaFile = "| && |{ lv_schema_file_path }| && |";| && |{ lv_cr_lf }| && |var xmlFile = "| && |{ lv_xml_filename }| && |";| && |{ lv_cr_lf }| && |xSchema.add("| && |{ lv_target_namespace }| && |", schemaFile);| && |{ lv_cr_lf }| && |xmlReader.putFeature("schema-validation", true);| && |{ lv_cr_lf }| && |xmlReader.putFeature("exhaustive-errors", true);| && |{ lv_cr_lf }| && |xmlReader.putProperty("schemas", xSchema);| && |{ lv_cr_lf }| && |xmlReader.errorHandler = xWriter;| && |{ lv_cr_lf }| && |try \{| && |{ lv_cr_lf }| && |xmlReader.parseURL(xmlFile);| && |{ lv_cr_lf }| && |\} catch (ex) \{| && |{ lv_cr_lf }| && |\}| && |{ lv_cr_lf }| && |str = xmlFile.split(".");| && |{ lv_cr_lf }| && |var errFile = "| && |{ lv_error_log_file_name }| && |";| && |{ lv_cr_lf }| && |var FileObj = FileSysObj.CreateTextFile(errFile,true);| && |{ lv_cr_lf }| && |FileObj.WriteLine(xWriter.output);| && |{ lv_cr_lf }| && |FileObj.Close();|. APPEND lv_validation_script TO lt_validation_script_tmp. * Store the validation script locally. cl_gui_frontend_services=>gui_download( EXPORTING filename = lv_validation_script_filename " Name of file filetype = 'ASC' " File type (ASCII, binary ...) CHANGING data_tab = lt_validation_script_tmp "lt_parse_file " Transfer table EXCEPTIONS file_write_error = 1 OTHERS = 2 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. * Store the XML-schema locally. CLEAR lt_string_tab. APPEND lv_schema TO lt_string_tab. cl_gui_frontend_services=>gui_download( EXPORTING filename = lv_schema_file_path " Name of file filetype = 'ASC' " File type (ASCII, binary ...) CHANGING data_tab = lt_string_tab "lt_parse_file " Transfer table EXCEPTIONS file_write_error = 1 OTHERS = 2 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. * Store the XML locally. CLEAR lt_string_tab. APPEND lv_xml TO lt_string_tab. cl_gui_frontend_services=>gui_download( EXPORTING filename = lv_xml_filename " Name of file filetype = 'ASC' " File type (ASCII, binary ...) CHANGING data_tab = lt_string_tab "lt_parse_file " Transfer table EXCEPTIONS file_write_error = 1 OTHERS = 2 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. cl_gui_frontend_services=>execute( EXPORTING application = lv_validation_script_filename " Path and Name of Application synchronous = 'X' " When 'X': Runs the Application in Synchronous Mode EXCEPTIONS cntl_error = 1 OTHERS = 2 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. * Read errors. CLEAR lt_string_tab. cl_gui_frontend_services=>gui_upload( EXPORTING filename = lv_error_log_file_name " Name of file filetype = 'ASC' " File Type (ASCII, Binary) CHANGING data_tab = lt_string_tab " Transfer table for file contents EXCEPTIONS file_open_error = 1 OTHERS = 2 ). IF sy-subrc <> 0. RAISE validation_not_possible. ENDIF. DATA lv_line TYPE string. LOOP AT lt_string_tab INTO lv_line. CONDENSE lv_line. IF lv_line IS NOT INITIAL . lv_valid = abap_false. RETURN. ENDIF. ENDLOOP. lv_valid = abap_true.
Explanation
At first a temporary directory on the client machine is created. Then all necessary file paths are constructed. All files will be stored in the same directory. There will be…
- one file that is the JScript script file
- one file for the XML-schema
- one file for the XML instance to validate
- one file that contains validation error (after the validation has been performed).
The content of the validation script is constructed just afterwards. Note that the whole processing is XML-namespace aware! I.e. using no or an empty namespace might end up in errors. The first three files are downloaded to the client machine and the validation script is executed synchronously. I.e. the code waits at the call of cl_gui_frontend_services=>execute until the script execution has been finished. Afterwards the error log file is uploaded onto the application server and its content analyzed. The validation script is implemented that way any error will result in a line in the error log file. If there are no entries in the error log, the XML instance is valid with respect to the XML-schema. The following snippet shows the validation script (more readable than in ABAP above):
var FileSysObj = new ActiveXObject("Scripting.FileSystemObject");
var xmlReader = new ActiveXObject("MSXML2.SAXXMLReader.4.0");
var xSchema = new ActiveXObject("MSXML2.XMLSchemaCache.4.0");
var xWriter = new ActiveXObject("MSXML2.MXXMLWriter.4.0");
var wShell = new ActiveXObject("WScript.Shell");
var schemaFile = "C:\\Users\\<user>\\AppData\\Local\\SAP\\SAP GUI\\tmp\\DependencySchema.xsd";
var xmlFile = "C:\\Users\\<user>\\AppData\\Local\\SAP\\SAP GUI\\tmp\\xmlToValidate.xml";
xSchema.add("http://sap.com/XYZ", schemaFile);
xmlReader.putFeature("schema-validation", true);
xmlReader.putFeature("exhaustive-errors", true);
xmlReader.putProperty("schemas", xSchema);
xmlReader.errorHandler = xWriter;
try {
xmlReader.parseURL(xmlFile);
} catch (ex) {
}
str = xmlFile.split(".");
var errFile = "C:\\Users\\<user>\\AppData\\Local\\SAP\\SAP GUI\\tmp\\validationErrorLog.log";
var FileObj = FileSysObj.CreateTextFile(errFile,true);
FileObj.WriteLine(xWriter.output);
FileObj.Close();
Note that the used XML-schema should look like this for the current example:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified"
elementFormDefault="qualified" xmlns="http://sap.com/XYZ"
targetNamespace="http://sap.com/XYZ">
<!-- insert schema details here -->
</xs:schema>
If an error occurs during validation it looks like this (example):
Error: Line Number: 1 Column Number: 34120 SystemId: "file:///C:/Users/<user>/AppData/Local/SAP/SAP%20GUI/tmp/xmlToValidate.xml" PublicId: "" Description: Validate failed because the root element had no associated DTD/schema. Error Code: -2147467259
Availability
Key | Value |
---|---|
Software Component | SAP_BASIS |
Requires Client-Side Software Library | Yes |
Required Client-Side Software Libraries | MSXML 3.0 or later |
Code Snippet is OS dependent | Yes |
Required OS | MS Windows |