How do I stop parsing an XML document with IVBSAXXMLReader in Delphi?
为了在Delphi(2007)程序中快速解析一些大型XML文档,我实现了IVBSAXContentHandler接口并使用它如下:
1 2 3 | FXMLReader := CoSAXXMLReader60.Create; FXMLReader.contentHandler := Self; FXMLReader.parseURL(FXmlFile); |
这很好,只要我简单地解析整个文件,但是一旦找到了我要查找的内容,我就想停止。所以我的IVBSAXContentHandler.startElement实现会检查某些条件,当它为true时应该中止进一步的解析。我试过这个:
1 2 3 4 5 | procedure TContentHandler.startElement(var strNamespaceURI, strLocalName, strQName: WideString; const oAttributes: IVBSAXAttributes); begin if SomeCondition then SysUtils.Abort; end; |
不幸的是,这引起了相当无益的EOleException"灾难性的失败"。 (我也尝试使用相同的结果引发自定义异常。)
MSDN说如下:
The ErrorHandler interface essentially allows the XMLReader to signal the ContentHandler implementation that it wants to abort processing. Conversely, ContentHandler implementations can indicate to the XMLReader that it wants to abort processing. This can be accomplished by simply raising an application-specific exception. This is especially useful for aborting processing once the implementation finds what it is looking for:
1 2 3 4 5 | Private Sub IVBSAXContentHandler_characters(ByVal strChars As String) ' I found what I was looking for, abort processing Err.Raise vbObjectError + errDone,"startElement", _ "I got what I want, let's go play!" End Sub |
所以,显然我还需要以某种方式实现IVBSAXErrorHandler接口。该接口需要三种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | procedure TContentHandler.error(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer); begin end; procedure TContentHandler.fatalError(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer); begin end; procedure TContentHandler.ignorableWarning(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer); begin end; |
并且还必须在调用ParseURL方法之前分配:
1 2 3 4 | FXMLReader := CoSAXXMLReader60.Create; FXMLReader.contentHandler := Self; FXMLReader.errorHandler := Self; FXMLReader.parseURL(FXmlFile); |
不幸的是,这没有任何区别,因为现在使用strErrorMessage ='灾难性故障'来调用fatalError处理程序。使用空方法体仍然会导致上述无用的EOleException"灾难性故障"。
所以,现在我没有想法:
- 我是否需要在errorhandler接口中实现一些特殊功能?
- 我是否需要提出一个特殊的异常而不是EAbort?
- 还是我错过了别的什么?
编辑:
根据Ondrej Kelle的回答,这是我最终使用的解决方案:
声明以下常量:
1 2 3 | const // idea taken from Delphi 10.1 unit System.Win.ComObj: EExceptionRaisedHRESULT = HResult(E_UNEXPECTED or (1 shl 29)); // turn on customer bit |
向TContentHandler类添加两个新字段:
1 2 | FExceptObject: TObject; FExceptAddr: Pointer; |
将此代码添加到析构函数:
1 | FreeAndNil(FExceptObject); |
添加一个新方法SafeCallException:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function TContentHandler.SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; var GUID: TGUID; exc: Exception; begin if ExceptObject is Exception then begin exc := Exception(ExceptObject); // Create a copy of the exception object and store it in the FExceptObject field FExceptObject := exc.NewInstance; Exception(FExceptObject).Create(exc.Message); Exception(FExceptObject).HelpContext := exc.HelpContext; // Store the exception address in the FExceptAddr field FExceptAddr := ExceptAddr; // return a custom HRESULT Result := EExceptionRaisedHRESULT; end else begin ZeroMemory(@GUID, SizeOf(GUID)); Result := HandleSafeCallException(ExceptObject, ExceptAddr, GUID, '', ''); end; end; |
向调用代码添加异常处理程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | var exc: Exception; begin try FXMLReader := CoSAXXMLReader60.Create; FXMLReader.contentHandler := Self; // we do not need an errorHandler FXMLReader.parseURL(FXmlFile); FXMLReader := nil; except on e: EOleException do begin // Check for the custom HRESULT if e.ErrorCode = EExceptionRaisedHRESULT then begin // Check that the exception object is assigned if Assigned(FExceptObject) then begin exc := Exception(FExceptObject); // set the pointer to NIL FExceptObject := nil; // raise the exception a the given address raise exc at FExceptAddr; end; end; // fallback: raise the original exception raise; end; end; end; |
虽然这对我有用,但它有一个严重的缺陷:它只复制原始异常的Message和HelpContext属性。因此,如果有更多属性/字段,例如
1 2 3 4 | EInOutError = class(Exception) public ErrorCode: Integer; end; |
在调用代码中重新引发异常时,不会初始化这些内容。
优点是您将在调试器中获得正确的异常地址。请注意,您将无法获得正确的调用堆栈。
只需调用
1 2 3 4 | function TContentHandler.SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HRESULT; begin Result := HandleSafeCallException(ExceptObject, ExceptAddr, TGUID.Empty, '', ''); end; |
或者,您可以引发自己的异常类,覆盖