8000 XmlParseError now supports multiple parsing errors, as well as detailed information of the error, such as line, column etc by jameslan · Pull Request #25 · jameslan/libxml2-wasm · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

XmlParseError now supports multiple parsing errors, as well as detailed information of the error, such as line, column etc #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion binding/exported-functions.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
_free
_malloc
_xmlCtxtReadMemory
_xmlCtxtSetErrorHandler
_xmlDocGetRootElement
_xmlFreeDoc
_xmlFreeParserCtxt
_xmlGetLastError
_xmlGetNsList
_xmlHasNsProp
_xmlNewDoc
_xmlNewDocComment
_xmlNewDocText
_xmlNewParserCtxt
_xmlNodeGetContent
_xmlReadMemory
_xmlRelaxNGFree
_xmlRelaxNGFreeParserCtxt
_xmlRelaxNGFreeValidCtxt
Expand Down
1 change: 1 addition & 0 deletions binding/exported-runtime-functions.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
UTF8ToString
addFunction
getValue
lengthBytesUTF8
stringToUTF8
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"init": "mkdir -p out && mkdir -p lib",
"config": "cd out && emconfigure ../libxml2/autogen.sh --without-python --without-http --without-sax1 --without-modules --without-html --without-threads --without-zlib --without-lzma --disable-shared --enable-static",
"compile": "cd out && emmake make",
"bind": "cd out && emcc -L.libs -lxml2 -o libxml2raw.js --no-entry -s MODULARIZE -s ALLOW_MEMORY_GROWTH -s EXPORTED_RUNTIME_METHODS=@../binding/exported-runtime-functions.txt -s EXPORTED_FUNCTIONS=@../binding/exported-functions.txt -s SINGLE_FILE",
"bind": "cd out && emcc -L.libs -lxml2 -o libxml2raw.js --no-entry -s MODULARIZE -s ALLOW_MEMORY_GROWTH -s ALLOW_TABLE_GROWTH -s EXPORTED_RUNTIME_METHODS=@../binding/exported-runtime-functions.txt -s EXPORTED_FUNCTIONS=@../binding/exported-functions.txt -s SINGLE_FILE",
"dist": "cp out/libxml2raw.* src/libxml2raw.* lib",
"libxml": "npm run init && npm run config && npm run compile",
"link": "npm run bind && npm run dist",
Expand Down
61 changes: 43 additions & 18 deletions src/document.mts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import {
XmlError,
error,
xmlCtxtSetErrorHandler,
xmlDocGetRootElement,
XmlError,
xmlFreeDoc,
xmlFreeParserCtxt,
XmlLibError,
xmlNewDoc,
xmlResetLastError,
xmlGetLastError,
XmlErrorStruct, xmlReadString, XmlParseError, xmlReadMemory,
xmlNewParserCtxt,
xmlReadMemory,
xmlReadString,
} from './libxml2.mjs';
import { XmlElement, XmlNode } from './nodes.mjs';
import { XmlXPath, NamespaceMap } from './xpath.mjs';
import type { XmlDocPtr } from './libxml2raw.js';
import { NamespaceMap, XmlXPath } from './xpath.mjs';
import type { XmlDocPtr, XmlParserCtxtPtr } from './libxml2raw.js';
import { disposeBy, XmlDisposable } from './disposable.mjs';

export enum ParseOption {
Expand Down Expand Up @@ -71,6 +75,9 @@ export interface ParseOptions {
option?: ParseOption;
}

export class XmlParseError extends XmlLibError {
}

export class XmlDocument extends XmlDisposable {
/** @internal */
@disposeBy(xmlFreeDoc)
Expand All @@ -82,10 +89,6 @@ export class XmlDocument extends XmlDisposable {
*/
private constructor(xmlDocPtr: XmlDocPtr) {
super();
if (!xmlDocPtr) {
const err = xmlGetLastError();
throw new XmlParseError(XmlErrorStruct.message(err));
}
this._docPtr = xmlDocPtr;
}

Expand All @@ -105,10 +108,7 @@ export class XmlDocument extends XmlDisposable {
source: string,
options: ParseOptions = {},
): XmlDocument {
xmlResetLastError();
return new XmlDocument(
xmlReadString(source, '', '', options.option ?? ParseOption.XML_PARSE_DEFAULT),
);
return XmlDocument.parse(xmlReadString, source, '', options);
}

/**
Expand All @@ -120,10 +120,35 @@ export class XmlDocument extends XmlDisposable {
source: Uint8Array,
options: ParseOptions = {},
): XmlDocument {
xmlResetLastError();
return new XmlDocument(
xmlReadMemory(source, '', '', options.option ?? ParseOption.XML_PARSE_DEFAULT),
);
return XmlDocument.parse(xmlReadMemory, source, '', options);
}

private static parse<Input>(
parser: (
ctxt: XmlParserCtxtPtr,
source: Input,
url: string,
encoding: string,
options: number,
) => XmlDocPtr,
source: Input,
url: string,
options: ParseOptions,
): XmlDocument {
const ctxt = xmlNewParserCtxt();
const errIndex = error.allocErrorInfo();
xmlCtxtSetErrorHandler(ctxt, error.errorCollector, errIndex);
try {
const xml = parser(ctxt, source, url, '', options.option ?? ParseOption.XML_PARSE_DEFAULT);
if (!xml) {
const errDetails = error.getErrorInfo(errIndex);
throw new XmlParseError(errDetails!.map((d) => d.message).join(''), errDetails!);
}
return new XmlDocument(xml);
} finally {
error.freeErrorInfo(errIndex);
xmlFreeParserCtxt(ctxt);
}
}

get(xpath: XmlXPath): XmlNode | null;
Expand Down
9 changes: 7 additions & 2 deletions src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ export {
XmlText,
XmlCData,
} from './nodes.mjs';
export { XmlDocument, ParseOption, ParseOptions } from './document.mjs';
export { XmlParseError, XmlError } from './libxml2.mjs';
export {
ParseOption,
ParseOptions,
XmlDocument,
XmlParseError,
} from './document.mjs';
export { ErrorDetail, XmlError, XmlLibError } from './libxml2.mjs';
export {
RelaxNGValidator,
XsdValidator,
Expand Down
68 changes: 63 additions & 5 deletions src/libxml2.mts
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
import type {
Pointer,
CString,
Pointer,
XmlAttrPtr,
XmlDocPtr,
XmlErrorPtr,
XmlNodePtr,
XmlXPathContextPtr,
XmlNsPtr,
XmlParserCtxtPtr,
XmlXPathCompExprPtr,
XmlXPathContextPtr,
} from './libxml2raw.js';
import moduleLoader from './libxml2raw.js';

const libxml2 = await moduleLoader();

export class XmlError extends Error {}
export class XmlParseError extends XmlError {}

export interface ErrorDetail {
message: string;
line: number;
col: number;
}

export class XmlLibError extends XmlError {
details: ErrorDetail[];

constructor(message: string, details: ErrorDetail[]) {
super(message);
this.details = details;
}
}

function withStringUTF8<R>(str: string | null, process: (buf: number, len: number) => R): R {
if (!str) {
Expand Down Expand Up @@ -47,21 +63,29 @@ function withCString<R>(str: Uint8Array, process: (buf: number, len: number) =>
}

export function xmlReadString(
ctxt: XmlParserCtxtPtr,
xmlString: string,
url: string,
encoding: string,
options: number,
): XmlDocPtr {
return withStringUTF8(xmlString, (buf, len) => libxml2._xmlReadMemory(buf, len, 0, 0, options));
return withStringUTF8(
xmlString,
(buf, len) => libxml2._xmlCtxtReadMemory(ctxt, buf, len, 0, 0, options),
);
}

export function xmlReadMemory(
ctxt: XmlParserCtxtPtr,
xmlBuffer: Uint8Array,
url: string,
encoding: string,
options: number,
): XmlDocPtr {
return withCString(xmlBuffer, (buf, len) => libxml2._xmlReadMemory(buf, len, 0, 0, options));
return withCString(
xmlBuffer,
(buf, len) => libxml2._xmlCtxtReadMemory(ctxt, buf, len, 0, 0, options),
);
}

export function xmlXPathRegisterNs(ctx: XmlXPathContextPtr, prefix: string, uri: string): number {
Expand Down Expand Up @@ -121,6 +145,33 @@ export function xmlXPathCtxtCompile(ctxt: XmlXPathContextPtr, str: string): XmlX
return withStringUTF8(str, (buf) => libxml2._xmlXPathCtxtCompile(ctxt, buf));
}

export namespace error {
const errorStorage = new Map<number, ErrorDetail[]>();
let errIndex = 0;

export function allocErrorInfo(): number {
errIndex += 1;
errorStorage.set(errIndex, []);
return errIndex;
}

export function freeErrorInfo(index: number) {
errorStorage.delete(index);
}

export function getErrorInfo(index: number) {
return errorStorage.get(index);
}

export const errorCollector = libxml2.addFunction((index: number, err: XmlErrorPtr) => {
errorStorage.get(index)!.push({
message: XmlErrorStruct.message(err),
line: XmlErrorStruct.line(err),
col: XmlErrorStruct.col(err),
});
}, 'vii');
}

export class XmlXPathObjectStruct {
static type = getValueFunc(0, 'i32');

Expand Down Expand Up @@ -203,12 +254,19 @@ export class XmlAttrStruct extends XmlTreeCommonStruct {

export class XmlErrorStruct {
static message = getStringValueFunc(8);

static line = getValueFunc(20, 'i32');

static col = getValueFunc(40, 'i32');
}

export const xmlCtxtSetErrorHandler = libxml2._xmlCtxtSetErrorHandler;
export const xmlDocGetRootElement = libxml2._xmlDocGetRootElement;
export const xmlFreeDoc = libxml2._xmlFreeDoc;
export const xmlFreeParserCtxt = libxml2._xmlFreeParserCtxt;
export const xmlGetLastError = libxml2._xmlGetLastError;
export const xmlNewDoc = libxml2._xmlNewDoc;
export const xmlNewParserCtxt = libxml2._xmlNewParserCtxt;
export const xmlRelaxNGFree = libxml2._xmlRelaxNGFree;
export const xmlRelaxNGFreeParserCtxt = libxml2._xmlRelaxNGFreeParserCtxt;
export const xmlRelaxNGFreeValidCtxt = libxml2._xmlRelaxNGFreeValidCtxt;
Expand Down
25 changes: 18 additions & 7 deletions src/libxml2raw.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type Pointer = number;
type CString = Pointer;
type XmlAttrPtr = Pointer;
type XmlParserCtxtPtr = Pointer;
type XmlDocPtr = Pointer;
type XmlErrorPtr = Pointer;
type XmlNodePtr = Pointer;
Expand All @@ -11,6 +12,7 @@ type XmlRelaxNGValidCtxtPtr = Pointer;
type XmlSchemaParserCtxtPtr = Pointer;
type XmlSchemaPtr = Pointer;
type XmlSchemaValidCtxtPtr = Pointer;
type XmlStructuredErrorFunc = Pointer;
type XmlXPathCompExprPtr = Pointer;
type XmlXPathContextPtr = Pointer;
type XmlXPathObjectPtr = Pointer;
Expand All @@ -22,20 +24,29 @@ export class LibXml2 {

_free(memblock: Pointer): void;
_malloc(size: number): Pointer;
addFunction(func: Function, sig: string): Pointer;
_xmlCtxtReadMemory(
ctxt: XmlParserCtxtPtr,
buffer: CString,
length: number,
url: CString,
encoding: CString,
options: number,
): XmlDocPtr;
_xmlCtxtSetErrorHandler(
ctxt: XmlParserCtxtPtr,
handler: XmlStructuredErrorFunc,
data: Pointer,
): void;
_xmlFreeParserCtxt(ctxt: XmlParserCtxtPtr): void;
_xmlDocGetRootElement(doc: XmlDocPtr): XmlNodePtr;
_xmlFreeDoc(Doc: XmlDocPtr): void;
_xmlGetLastError(): XmlErrorPtr;
_xmlGetNsList(doc: XmlDocPtr, node: XmlNodePtr): Pointer;
_xmlHasNsProp(node: XmlNodePtr, name: CString, namespace: CString): XmlAttrPtr;
_xmlNewDoc(): XmlDocPtr;
_xmlNewParserCtxt(): XmlParserCtxtPtr;
_xmlNodeGetContent(node: XmlNodePtr): CString;
_xmlReadMemory(
buffer: CString,
length: number,
url: CString,
encoding: CString,
options: number,
): XmlDocPtr;
_xmlRelaxNGFree(schema: XmlRelaxNGPtr): void;
_xmlRelaxNGFreeParserCtxt(ctxt: XmlRelaxNGParserCtxtPtr): void;
_xmlRelaxNGFreeValidCtxt(ctxt: XmlRelaxNGValidCtxtPtr): void;
Expand Down
56 changes: 54 additions & 2 deletions test/crossplatform/parseXml.spec.mts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,32 @@ describe('parseXmlString', () => {
expect(() => XmlDocument.fromString('<doc>')).to.throw(
XmlParseError,
'Premature end of data in tag doc line 1\n',
);
).with.deep.property('details', [{
message: 'Premature end of data in tag doc line 1\n',
line: 1,
col: 6,
}]);
});

it('should throw exception with all errors on invalid xml', () => {
expect(() => XmlDocument.fromString('<doc><b><book></b>\n<b><book></b></doc>')).to.throw(
XmlParseError,
'Opening and ending tag mismatch: book line 1 and b\n'
+ 'Opening and ending tag mismatch: book line 2 and b\n'
+ 'Opening and ending tag mismatch: b line 2 and doc\n',
).with.deep.property('details', [{
message: 'Opening and ending tag mismatch: book line 1 and b\n',
line: 1,
col: 19,
}, {
message: 'Opening and ending tag mismatch: book line 2 and b\n',
line: 2,
col: 14,
}, {
message: 'Opening and ending tag mismatch: b line 2 and doc\n',
line: 2,
col: 20,
}]);
});

it('should support parse option', () => {
Expand All @@ -46,7 +71,34 @@ describe('parseXmlBuffer', () => {
expect(() => XmlDocument.fromBuffer(new TextEncoder().encode('<doc>'))).to.throw(
XmlParseError,
'Premature end of data in tag doc line 1\n',
);
).with.deep.property('details', [{
message: 'Premature end of data in tag doc line 1\n',
line: 1,
col: 6,
}]);
});

it('should throw exception with all errors on invalid xml', () => {
expect(() => XmlDocument.fromBuffer(
new TextEncoder().encode('<doc><b><book></b>\n<b><book></b></doc>'),
)).to.throw(
XmlParseError,
'Opening and ending tag mismatch: book line 1 and b\n'
+ 'Opening and ending tag mismatch: book line 2 and b\n'
+ 'Opening and ending tag mismatch: b line 2 and doc\n',
).with.deep.property('details', [{
message: 'Opening and ending tag mismatch: book line 1 and b\n',
line: 1,
col: 19,
}, {
message: 'Opening and ending tag mismatch: book line 2 and b\n',
line: 2,
col: 14,
}, {
message: 'Opening and ending tag mismatch: b line 2 and doc\n',
line: 2,
col: 20,
}]);
});

it('should throw if input buffer is null', () => {
Expand Down
0