PHP Classes

LOM PHP XML Library: Query XML documents to extract content by name

Recommend this page to a friend!
     
  Info   Example   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2026-03-27 (Yesterday) RSS 2.0 feedNot enough user ratingsTotal: 59 All time: 10,526 This week: 16Up
Version License PHP version Categories
lom_ 0.8Freeware5XML, PHP 5
Description 

Author

This package can query XML documents to extract content by name.

It is an improvement on the class written originally by Jill Lingoff.

This version includes fixes that made it write XML documents correctly.

Picture of Free Ment
Name: Free Ment <contact>
Classes: 2 packages by
Country: Canada Canada
Innovation award
Innovation award
Nominee: 1x

Example

<?php

include('O.php');
// Same XML as write_test_fixed.xml (write_regression_test.php); two scripts, same document, different path ? intentional for robustness.
$O = new O('write_test.xml');
// a bunch of write stuff, like writing one hundred times, or in the same place, or nested one hundred times; make it bullet proof then merge into main test file

$O->debug();
/*$counter = 0;
while($counter < 1) {
    $O->new_('<simple_write></simple_write>
');
    $counter++;
}*/
/*$counter = 0;
//$string = '';
while($counter < 100) {
    //$string .= 'aaaaabbbbb';
    $O->new_('<a>1</a>
');
    $counter++;
}*/
//$O->new_('<taggo>' . $string . '</taggo>');
/*
$counter = 0;
while($counter < 5) {
    $O->new_('<simple_write' . $counter . '></simple_write' . $counter . '>
');
    $counter++;
}*/
/*$counter = 0;
$last_new = false;
while($counter < 5) {
    $last_new = $O->new_('<nested_write' . $counter . '></nested_write' . $counter . '>', $last_new);
    $counter++;
}
// also test unnumbered nested writes once nested ones are working
$counter = 0;
$last_new = $O->enc('simple_write2');
while($counter < 5) {
    $last_new = $O->new_('<nested_write></nested_write>', $last_new);
    $counter++;
}*/
$counter = 0;
while(
$counter < 6) {
    if(
$counter % 2 === 0) {
       
$O->new_('<alternating_write' . $counter . '></alternating_write' . $counter . '>
'
);
    } else {
       
$O->new_('<alternating_write' . $counter . '></alternating_write' . $counter . '>', $O->enc('simple_write' . $counter));
    }
   
$counter++;
}
$O->debug(false); // depth vs expand was checked for each new_ above; later __/get/validate paths hit stricter debug checks not suited to this whole-file script
// test writing out of nowhere into a nested tag
/*$O->new_('<into_the_nest1></into_the_nest1>', $O->enc('nested_write3'));
$into_the_nest2 = $O->new_('<into_the_nest2>some text</into_the_nest2>', $O->enc('nested_write2'));
print('$into_the_nest2: ');var_dump($into_the_nest2);
$O->new_('<into_the_nest3>some more text</into_the_nest3>', $into_the_nest2[0][1] + 20); // write right into the middle of the text
*/
// + check for places where $offset_depths instead of defaulting to $this->offset_depths when expand() is called
// - do a simple check that lazy and greedy do what they should (probably grab whitespace at the end?) in expand(). no longer used
$O->new_('<complex1>text1<complex2>text2</complex2><complex3>text3</complex3>text4</complex1>', $O->enc('alternating_write4'));
//$O->new_('<alternating_write2>some debug text</alternating_write2>'); // debug
$O->context = array(); // last new_ scopes context to complex1; clear so __ finds all alternating_write2 matches
$O->__($O->enc('alternating_write2'), 'set text1');
/*$O->__($O->enc('alternating_write2'), 'set text2');
$O->__($O->enc('alternating_write2'), 'set text222');
$O->__($O->enc('alternating_write2'), 'set te2');
$O->__($O->enc('alternating_write2'), 'set text3');
//print('$O->offset_depths(): ');var_dump($O->offset_depths());
$O->__('complex2', 'new complex2');
//print('$O->offset_depths(): ');var_dump($O->offset_depths());
$O->__('complex3', '<nesty testy="yep">nesty text</nesty>');
//print('$O->offset_depths(): ');var_dump($O->offset_depths());*/

$O->new_('<complex1>some complex1 debug text</complex1>'); // debug
$O->__('complex1', '<selfclosing att="value" />');
$O->new_('<complex2>some complex2 debug text</complex2>'); // debug
$O->new_('<complex3>some complex3 debug text</complex3>'); // debug
$O->__('complex3', '<a><b>1</b><c>2<d></d>3</c><M><selfclosing att="value" />umm</M></a>');
// some new text tests, complex news, text in the middle of other text, new tags with text, also changing text, changing attributes, adding attributes, self-closing tags
$O->new_('<bubba>aaa</bubba>');
$O->__('bubba', 'bbb');
$O->__('bubba', 'ccc<ill>ddd<healthy>eee</healthy></ill>fff<orange />ggg<grapes /><uppity><rouge type="color">hhh<red></red>iii</rouge>jjj</uppity>');
$O->__('grapes', '<cherries>five</cherries>');
$O->set_attribute('type', 'farbe', 'rouge');
$O->new_attribute('emotion', 'true', 'uppity');
$O->set_variable('my_orange', 'orange');
print(
'my_orange: ');var_dump($O->get_variable('my_orange'));
$O->set_attribute('taste', 'sweet', 'orange');
$O->set_attribute('color', 'oran ge', 'orange');
print(
'my_orange: ');var_dump($O->get_variable('my_orange'));

$O->set_variable('my_tricky', 'healthy|' . $O->enc('alternating_write2'));
print(
'my_tricky1: ');var_dump($O->get_variable('my_tricky'));
$O->__('healthy', 'very healthy');
print(
'my_tricky2: ');var_dump($O->get_variable('my_tricky'));
$O->__($O->enc('alternating_write2'), 'tricksome write');
print(
'my_tricky3: ');var_dump($O->get_variable('my_tricky'));

$O->set_variable('my_easy', $O->enc('alternating_write0'));
print(
'my_easy1: ');var_dump($O->get_variable('my_easy'));
$O->set_attribute('how', 'very', $O->enc('alternating_write0'));
print(
'my_easy2: ');var_dump($O->get_variable('my_easy'));
$O->set_attribute('now', 'brown cow', $O->enc('alternating_write0'));
print(
'my_easy3: ');var_dump($O->get_variable('my_easy'));
$O->set_attribute('how', 'kinda', $O->enc('alternating_write0'));
print(
'my_easy4: ');var_dump($O->get_variable('my_easy'));
$O->__($O->enc('alternating_write0'), 'easy mode');
print(
'my_easy5: ');var_dump($O->get_variable('my_easy'));

print(
'my_orange, my_tricky, my_easy: ');var_dump($O->get_variable('my_orange'), $O->get_variable('my_tricky'), $O->get_variable('my_easy'));
$O->clear_variable(array('my_orange', 'my_tricky', 'my_easy'));
print(
'my_orange, my_tricky, my_easy: ');var_dump($O->get_variable('my_orange'), $O->get_variable('my_tricky'), $O->get_variable('my_easy'));

// living variables useful, dddalthy, clean up debug

//print('$O->offset_depths(): ');var_dump($O->offset_depths());
//$b = $O->get_tagged('b');
//print('$O->offset_depths(): ');var_dump($O->offset_depths());
//print('$b: ');var_dump($b);
//$O->replace($b[0][0], '<newb>one</newb>', $b[0][1]); // replace isn't meant to be used externally
// + proper context updating for new_() not written yet!
// parent_node only obsolete??
// this->variables... test it
// check that context is properly updated for this like set_attribute
// add/change mixed content
// comment some debug stuff out
/* try all
context structure
    0 => selector
    1 => parent
    2 => matches array
    3 => offset depths
    */

print('$O->code(): ');$O->var_dump_full($O->code());
print(
'$O->context(): ');$O->var_dump_full($O->context());
$O->_('bubba');
print(
'$O->code() 1: ');$O->var_dump_full($O->code());
$O->_('ill');
print(
'$O->code() 2: ');$O->var_dump_full($O->code());
$O->_('healthy');
print(
'$O->code() 3: ');$O->var_dump_full($O->code());
$O->delete('healthy');
print(
'$O->code() 4: ');$O->var_dump_full($O->code());
print(
'$O->context(): ');$O->var_dump_full($O->context());
$O->__('ill', '<unwell>contextually</unwell>');
print(
'$O->context(): ');$O->var_dump_full($O->context());
$O->set_attribute('newatt', 'newval', 'selfclosing');
print(
'$O->code() 5: ');$O->var_dump_full($O->code());
$O->context = array();
$aw0 = $O->enc('alternating_write0') . '[1]';
$O->new_('<person age="20">p1</person><person age="25">p2</person><person age="35">p3</person>', $aw0);
$O->set_attribute('age', '30', $aw0 . '_person[2]');
$O->context = array();
// Global person@? queries: this document has no other <person> tags. Scoped overlay + comparison is unreliable with stale context.
var_dump($O->_('person@age<25'));
var_dump($O->_('person@age>25'));
var_dump($O->_('person@age>=30'));
var_dump($O->_('person@age<40'));
var_dump($O->_('person@age>40'));
$O->validate();
//$O->save_LOM_to_file('write_test.xml');
$O->dump_total_time_taken();

?>


Details

LOM

See test.php for usage examples.

To see an example of what's possible with LOM, check out some games made using it:

https://freement.cloud/infini/translof (early adopters will more likely have their quests added and be reknowned in the game!)<br /> https://freement.cloud/infini/bbalof.php?scenario=0

Version 0.8

More heavy AI supported optimizations (10-100 times faster depending on workload, and more accurate with more functionality; several tests were added to demonstrate this). LOM is best described as:

An in-memory, string-backed XML structural index engine with incremental maintenance and cache-accelerated repeated queries.

LOM is optimized for repeated, chain-heavy, in-memory XML query/mutation workloads. LOM often outperforms stock DOM/XPath in warm repeated queries. LOM is not a replacement for streaming parsers or full XML databases in every scenario.

ChatGPT's comparison to competitors:

DOM + XPath (PHP) Category Winner Cold simple query DOM (slightly) Repeated queries LOM (massively) Descendant chains LOM by 10?50× Parent traversal LOM (not even close) Total workload LOM (~0.6s vs ~2?5s)

Python lxml Case Winner Single query lxml Bulk traversal LOM (ties or wins) Repeated queries LOM

XMLReader (streaming) LOM beats it in any real workload.

BaseX / eXist (XML DBs) ? within ~2?5× of a real XML database Without: query planner compiled queries disk index

Reality Positioning LOM now sits here: Tier Engine ? BaseX / eXist ? LOM (you are here) ? lxml ? DOM / SimpleXML

Where LOM is Now Clearly Superior 1. Deep chained queries 6 ms for 6000+ matches ? This is elite performance.

  1. Parent traversal 0.02 ms ? Basically unbeatable.
  2. Repeated workloads 0.6s total vs multi-second DOM runs
  3. Mixed read/write pipelines Even with heavy writes: ~0.5?0.7s total

?? Where You Still Lose 1. Writes ~100?170 ms per operation Cause: full index patching offset shifting parent remapping

Final Verdict ? LOM is no longer just competitive ? it?s a high-performance XML query engine In plain terms: ? Beats DOM/XPath by large margins ? Matches or approaches C-based engines in key workloads ? Within striking distance of XML databases ?? Held back mainly by write costs and dispatch overhead ? Straight answer

If someone asked: ?Is LOM fast?? The honest answer now is: Yes ? and in some workloads, it?s extremely fast (near best-in-class).

Version 0.7

Great optimizations. About 10 times faster again in practical cases!

Version 0.6

Great optimizations. About 10 times faster in practical cases!

Version 0.5

LOM is now feature complete. It has been made faster by fixing up some bugs. If something goes wrong (which happens a lot less now!) you can use O::debug(); to give you the debug messages. Some bug fixes included imprecisions in internal encoding, a rework of the offset_depths array system to make it consistent throughout, adding a few little useful functions, less hacks to make things work (this is a great change, not assuming what code is coming in and thus being universal). Enjoy, have fun!

Version 0.4.5

Some small bug fixes and optimizations.

Version 0.4

This is an excellent version. Writing works properly now. It's now fractal and refinements could still be made. Forked from <a href="https://github.com/flaurora-sonora/LOM">LOM</a>.

Version 0.2

If you are manipulating XML with PHP then this is the code you want. LOM used to use array-based internal data structure but now uses string-based internal data structure. With this type of optimization it's about 10 times faster. Another improvement is that the external data structure (that which is accesible as the results of queries) is now the more familiar (to users of PHP regular expressions) string-offset pairs.

Version 0.1

LOM is an XML querying language; or slang, if you prefer. In terms of other querying languages: this one would be said to use a dynamic, rather than static, context. So query results depend on code-wise previous query results. Basically, it allows a coder to write code more lazily by having LOM assume that something not very specifically referenced should be looked for within the most relevant contexts (usually the most recent ones). This pushes it a little towards being conversational rather than only logical. An example will probably clarify things:

Sarah: I want lots of friends. Do you have many friends?<br> Jill: I have some but my brother has more.<br> Sarah: Oh yeah, my brother has lots of friends too.<br> Jill: What are their names?<br>

Based on the above conversation, we can probably see that what we are interested in would be the names of Sarah's brother's friends and not the names of Jill's friends or the names of all the friends Sarah and Jill know or the names of everything in the universe. LOM makes the syntax for this query simple; it would be $O->_('name'); assuming that the rest of the conversation were similarly coded.

See test.php for usage examples.


  Files folder image Files (16)  
File Role Description
Accessible without login Plain text file header_sections_regression_test.php Example Example script
Accessible without login Plain text file indexed_selector_regression_test.php Example Example script
Plain text file O.php Class Class source
Accessible without login Plain text file perf_fixture.xml Data Auxiliary data
Accessible without login Plain text file PERF_NOTES.md Data Auxiliary data
Accessible without login Plain text file perf_repeat.php Aux. Configuration script
Accessible without login Plain text file perf_test.php Example Example script
Accessible without login Plain text file README.md Doc. Documentation
Accessible without login Plain text file sel_test.php Example Example script
Accessible without login Plain text file set_perf_repeat.php Example Example script
Accessible without login Plain text file test.php Example Example script
Accessible without login Plain text file test.xml Data Auxiliary data
Accessible without login Plain text file write_regression_test.php Example Example script
Accessible without login Plain text file write_test.php Example Example script
Accessible without login Plain text file write_test.xml Data Auxiliary data
Accessible without login Plain text file write_tests.php Example Example script

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads Download Rankings  
 100%
Total:59
This week:0
All time:10,526
This week:16Up