PBToolboxAI v5 ← Site

xml — n_pbt_xml #

← Component reference · Guide contents

Write an XML element by element — opens, attributes, text, closes — with the escaping done for you, then read one back with real XPath: of_load, of_get_value, of_get_attr, of_count, and numbers and dates read the same way on every machine (of_get_decimal, of_get_date). PowerBuilder has PBDOM (a DOM without XPath, a PBNI extension to deploy) and XMLParseString; this one gives XPath 1.0 and namespaces with nothing to deploy, relying on the WebView2 engine's DOMParser and document.evaluate, called synchronously.

▶ See it live — Demo application, XML tile: the results, the code behind them and this page, side by side.


At a glance #

The build side is pure PowerScript (assembling a string needs no engine). The read side opens a hidden page on the first query. The XPath is version 1.0, as the browser evaluates it. There is one document at a time: a read on a built document loads it into the page first, it becomes the loaded document, and the next of_start_element starts a new one. A read is refused while an element of the built document is still open (-4, empty or false, and of_get_last_error()): close your elements first — nothing is lost. of_xml and of_pretty, which work on a copy, stay available.

Quick start #

// Local variables
n_pbt_xml  lnv_xml

// Create the object once, free it when done
lnv_xml = create n_pbt_xml

// Read : load, then query with XPath
lnv_xml.of_load(/*xml*/ '<order id="4152"><customer>Ada</customer></order>')
IF lnv_xml.of_is_valid() THEN
	MessageBox("Customer", lnv_xml.of_get_value(/*xpath*/ "/order/customer"))   // Ada
	MessageBox("Id", lnv_xml.of_get_attr(/*xpath*/ "/order", /*name*/ "id"))              // 4152
END IF

// Build : element by element, escaping done for you
lnv_xml.of_clear()
lnv_xml.of_start_element(/*name*/ "order")
lnv_xml.of_element_attr(/*name*/ "id", /*value*/ "4152")
lnv_xml.of_leaf(/*name*/ "customer", /*text*/ "Ada & Co")
lnv_xml.of_end_element()

// lnv_xml.of_xml() -><order id="4152"><customer>Ada &amp; Co</customer></order>

// Free the object
destroy lnv_xml

Properties #

PropertyTypeDefaultRole
ipo_ownerpowerobjectnullThe visual object this helper works for: the licence is checked on its class. Set it before the first read; set later, it is handed over at the next read. Needed only in the demonstration application; a development or runtime key unlocks reading without it. Not unlocked, reading runs in demo mode — documents of 65,536 characters at most, changes included; of_is_valid is false and of_pretty empty for a longer built document. Building does not need it: it is pure PowerScript

Build a document #

MemberRole
of_get_last_error ( ) → stringWhy the last read returned empty, false, 0 or a negative code: a malformed document, an XPath the engine refused, an XPath that matched nothing, nothing loaded, the demo cap, a page that did not answer. Empty after a read that succeeded. The builder writes here too when it refuses an invalid element or attribute name
of_start_element (string as_name)Opens an element (elements nest: a pending start tag is closed first). An invalid name (empty, with a space, a forbidden character, more than one :, a : first or last) is refused: of_get_last_error() says why, and the element is left out of the document with everything it holds; so is a second element at the top once the root is closed: a document has one root
of_element_attr (string as_name, string as_value)Adds an attribute to the open element, its value escaped — line breaks and tabs included, so they read back as they were (before any child or text). An invalid name is refused (of_get_last_error()); the same name given twice keeps ONE attribute, with the second value. Given after a child or a text, or with no element open, it is left out and of_get_last_error() says so. Overloads: decimal, date, datetime, boolean, written as of_leaf writes them
of_element_text (string as_text)The text content of the current element, escaped; the control characters XML forbids are dropped (a single one made the whole document malformed). With no element open the text is left out, and of_get_last_error() says so
of_end_element ( )Closes the current element (empty, it is self-closing <name/>)
of_leaf (string as_name, string as_text)A whole leaf element in one call — <name>text</name>, escaped. Overloads: a decimal, a date, a datetime or a boolean, written as XML Schema writes them whatever the machine — 690.5 (a point), 2026-10-31, 2026-10-31T14:30:00, true; a NULL writes an empty element
of_clear ( ) → longEmpties the document — built or loaded — to start another; a loaded document leaves the page too. Returns 0
of_xml ( ) → stringThe document as it stands: what you built or loaded (of_load), after any change — the single output, to send, store or save back. A built document needs no page (any element left open is closed) ; a loaded one is serialized by the engine. of_pretty is the indented form

Read a document (XPath) #

MemberRole
of_load (string as_xml) → longLoads an XML text (parsed by the engine's DOMParser). Returns 0 when well-formed, -5 when empty, -4 when not valid XML (or, in demo mode, too long once its entities are expanded), -2 when the hidden page did not answer. On a negative code NOTHING stays: neither a previously loaded document nor a half-built one. Each call to the page waits 5 seconds at most: a document of tens of MB, above all in a 32-bit application, may return -2
of_is_valid ( ) → booleanTrue when a well-formed document is held — loaded with of_load, or built (it is then loaded into the page, as any read does). False when there is nothing, or when the built document is malformed or still has an element open — and, in demo mode, for a built document longer than 65,536 characters
of_exists (string as_xpath) → booleanTrue when the XPath matches at least one node. An expression that yields a VALUE (count(//line)) is not a node: read it with of_get_value
of_get_value (string as_xpath) → stringThe text content of the FIRST matching node. Empty when none (of_get_last_error() says so). An XPath that yields a VALUE gives it as text: count(//line) → 3, sum(//@qty), boolean(//paid) → true. / gives the text of the whole document; an expression that is not a number (a sum() over a text) gives empty with its reason, never NaN. A number, a date, a boolean read the same way on every machine with of_get_number, of_get_decimal, of_get_date…
of_get_attr (string as_xpath, string as_name) → stringThe value of attribute as_name on the first matching node. Empty when the node or the attribute is absent — of_get_last_error() then says "no node matched" or "no such attribute"; an attribute written empty (a="") returns empty with no reason. An empty name is refused the same way
of_count (string as_xpath) → longHow many nodes the XPath selects. 0 when none; -4 when nothing is loaded, an element of the built document is still open or the engine refused the XPath, -2 when the page did not answer; of_get_last_error() says which
of_name (string as_xpath) → stringThe tag name of the first matching node. Empty if none
of_pretty ( ) → stringThe document as it stands — built or loaded, what of_xml returns — re-indented for reading. CDATA, comments, processing instructions and the XML declaration are kept, and an element holding text is written on one line, as it is; what is re-indented is the white space BETWEEN elements — except in an element marked xml:space="preserve", written as it is. Empty when there is nothing, when a built document is not well-formed, and in demo mode for a document longer than 65,536 characters

Numbers, dates and booleans #

XML Schema writes a number with a point and a date in ISO 8601, whatever the country. Dec("690.50") or Date("2026-10-31") depend on the regional settings of the machine. These readers check the value against its type and give it back the same way on every machine; an absent, empty or other-typed value gives NULL, and of_get_last_error() says why. An XPath that yields a value (sum(//qty)) works too. To write: of_leaf, of_element_attr and of_set_value take a decimal, a date, a datetime or a boolean.

MemberRole
of_get_number (string as_xpath) → doubleThe number as a double: 690.50, -1.5E3, +.5; blanks around ignored; 12 kg, 1,5, INF are refused
of_get_decimal (string as_xpath) → decimalThe number as a decimal, every digit kept (an amount never loses its cents); refused beyond the 28 digits of a decimal
of_get_longlong (string as_xpath) → longlongThe integer as a longlong, every digit kept (an id above 2^53 too); 2.5 is refused, not cut
of_get_boolean (string as_xpath) → booleantrue or 1 → true, false or 0 → false; anything else (yes, Y) gives NULL
of_get_date (string as_xpath) → date2026-10-31, or the date of a date-time; a time zone (Z, +02:00) is accepted and not applied. Refused: a day that does not exist (2026-02-30), a year outside 1000 to 3000
of_get_datetime (string as_xpath) → datetime2026-10-10T14:30:00, with or without a fraction of second, 2026-10-10 14:30, or a date alone (midnight); the clock time is the one WRITTEN: a time zone is not applied, as n_pbt_json reads it
// Local variables
n_pbt_xml  lnv_xml
decimal    ldc_total
date       ld_due

// Create the object once, free it when done
lnv_xml = create n_pbt_xml

// Write : a point and ISO 8601, whatever the regional settings
lnv_xml.of_start_element(/*name*/ "invoice")
lnv_xml.of_element_attr(/*name*/ "paid", /*value*/ false)
lnv_xml.of_leaf(/*name*/ "total", /*value*/ 690.50)
lnv_xml.of_leaf(/*name*/ "due", /*value*/ Date(2026, 10, 31))
lnv_xml.of_end_element()

// lnv_xml.of_xml() -> <invoice paid="false"><total>690.5</total><due>2026-10-31</due></invoice>

// Read them back as values you can compute with
ldc_total = lnv_xml.of_get_decimal(/*xpath*/ "/invoice/total")
ld_due    = lnv_xml.of_get_date(/*xpath*/ "/invoice/due")

// A value that is not of its type is NULL : is_last_error says why
IF IsNull(ldc_total) THEN MessageBox("Total", lnv_xml.of_get_last_error())

// Free the object
destroy lnv_xml

Several nodes, and the document #

MemberRole
of_get_values (string as_xpath, ref string as_values[]) → longThe text content of EVERY matching node, into as_values; returns the count, 0 when none, -4 when nothing is loaded or the XPath was refused, -2 when the page did not answer. Where of_get_value returns only the first
of_get_attrs (string as_xpath, string as_name, ref string as_values[]) → longThe value of attribute as_name on EVERY matching node (the id of every row), empty for a node without it; returns the count, 0 when none, -5 on an empty name, -4 when nothing is loaded or the XPath was refused, -2 when the page did not answer
of_get_attr_names (string as_xpath, ref string as_names[]) → longThe names of every attribute on the first matching node (enumeration); returns the count, 0 when the node has none or no node matches (of_get_last_error() says so), -4 when nothing is loaded, the XPath was refused or the node is not an element, -2 when the page did not answer

Modify a document #

The write side changes the loaded tree in place; of_xml returns the result. Each targets the FIRST matching node (except of_remove, which takes them all), returns 0 on success, -4 when nothing matched or the change was refused (of_get_last_error() says why), -5 on an empty argument, -2 when the page did not answer. What a change writes follows the builder's rule: the control characters XML forbids are dropped, and a CR is written so that it reads back.

MemberRole
of_set_value (string as_xpath, string as_text) → longSets the text of the first matching node (its children are replaced). Overloads: decimal, date, datetime, boolean, written as of_leaf writes them
of_set_attr (string as_xpath, string as_name, string as_value) → longSets (or adds) an attribute on the first matching node. A prefixed name (xsi:type) goes into the namespace its prefix has at that node (or one bound by of_register_namespace); a prefix bound nowhere is refused (-4, of_get_last_error())
of_remove_attr (string as_xpath, string as_name) → longRemoves an attribute from the first matching node; -4 and "no such attribute" when the node does not have it
of_add_child (string as_xpath, string as_tag, string as_text) → longAppends a child element <as_tag>as_text</as_tag> under the first matching node (the child inherits the parent's namespace and prefix; a prefixed tag p:item takes the namespace of its prefix, or -4 when it is bound nowhere). -4 also when the node cannot have children (an attribute, a text) or the tag is not a valid name
of_add_xml (string as_xpath, string as_fragment) → longGrafts an already-formed XML fragment (one or several sibling elements) under the first matching node — what of_add_child cannot do, taking only a tag and text. The fragment must be well-formed; it inherits the namespaces in force at its target, like of_add_child (<line> under <order xmlns='urn:acme'> is in urn:acme, <s:Item/> under a SOAP s:Body declares nothing); a prefix bound by of_register_namespace, or declared elsewhere in the document, declares nothing either
of_remove (string as_xpath) → longRemoves EVERY matching node (a row, a stale branch, an attribute). Returns how many were really removed (>= 0; on 0, of_get_last_error() says why), -4 when nothing is loaded, -5 on an empty XPath, -2 when the page did not answer. The root element stays: of_clear empties a document

Namespaces #

A prefixed XPath (SOAP, an enterprise API) resolves on its own a prefix the document declares (xmlns:s="…", on any element). ⚠️ The most frequent trap: the DEFAULT namespace. <order xmlns="urn:acme"><customer>… has no prefix, and yet /order/customer matches NOTHING: XPath 1.0 has no default namespace. Bind a prefix of your choice with of_register_namespace("a", "urn:acme") and write /a:order/a:customer. of_clear_namespaces forgets the bindings. There is no XSLT: Chromium is removing it from the engine (transform on the server side, or through MSXML in OLE: Msxml2.DOMDocument.6.0, transformNode). There is no XSD schema validation: the engine has none.

MemberRole
of_register_namespace (string as_prefix, string as_uri) → longBinds a prefix to its namespace URI so a prefixed XPath resolves. Returns 0, -5 on an empty prefix or URI, -2 when the page did not answer. Bindings last until of_clear_namespaces or of_reset — of_close does not lose them
of_clear_namespaces ( ) → longForgets every registered prefix: a prefix the document does not declare matches nothing again until it is bound. Returns 0, -2 when the page did not answer. No page is opened for it
// Local variables
n_pbt_xml  lnv_xml
string     ls_soap, ls_ok, ls_name, ls_out

// ls_soap holds the response of a web service
lnv_xml = create n_pbt_xml

// A SOAP response : the prefix declared by the document resolves on its own
lnv_xml.of_load(/*xml*/ ls_soap)
ls_ok = lnv_xml.of_get_value(/*xpath*/ "//s:Body/result/status")

// A DEFAULT namespace (xmlns="urn:acme") : bind a prefix of your choice
lnv_xml.of_load(/*xml*/ '<order xmlns="urn:acme"><customer>Ada</customer></order>')
lnv_xml.of_register_namespace(/*prefix*/ "a", /*uri*/ "urn:acme")
ls_name = lnv_xml.of_get_value(/*xpath*/ "/a:order/a:customer")   // Ada

// Mutate, then read the result back
lnv_xml.of_set_value(/*xpath*/ "/a:order/a:customer", /*text*/ "Grace")
lnv_xml.of_add_child(/*xpath*/ "/a:order", /*tag*/ "total", /*text*/ "690.50")

// Graft a whole fragment at once (of_add_child adds only one tag)
lnv_xml.of_add_xml(/*xpath*/ "/a:order", /*fragment*/ '<lines xmlns="urn:acme"><line sku="1">A</line><line sku="2">B</line></lines>')
ls_out = lnv_xml.of_xml()

// Free the object
destroy lnv_xml

The hidden page #

MemberRole
of_open ( ) → longCreates the hidden page (read side). Optional — the first read does it. Returns the handle (> 0) or -2 when it could not be created (of_get_last_error() says why)
of_is_open ( ) → booleanTrue once the hidden page exists
of_close ( ) → longReleases the hidden page. The build side keeps working without it, and a loaded document is not lost: it is kept as text, and the next read loads it back, with the prefixes bound by of_register_namespace. Done for you on destruction Returns 0 once done.
of_reset ( ) → longBack to the defaults: the builder emptied, the loaded document dropped, the registered prefixes forgotten, of_get_last_error() emptied. The hidden page stays open; of_close releases it Returns 0 once done.

Scope. XML is read through the engine (DOMParser, XPath): n_pbt_xml opens a hidden page on the first read, unlike json, which parses in pure PowerScript. To display an XML as a collapsible tree, see xmltree.

← Component reference · Guide contents