large_xml

a pure dart library for reading, writing large xml

Usage

Sample XML

all example code will use this xml string

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE root>
<root 
  xmlns:r="http://schemas.openxmlformats.org/package/2006/relationships"
  xmlns:x="http://schemas.openxmlformats.org/package/2006/x"
>
    <!-- comment here -->
    <person age="1" r:id="2" x:id="3" ></person>
    <!-- comment here -->
    <x:script>
      <![CDATA[function matchwo(a,b){if (a < b && a < 0) then{return 1;}else{return 0;}}]]>
    </x:script>
    <info>
      <child x:name="child name"/>
    </info>
</root>

Getting Started

first, you need create a XmlDocument Object

this object holding your xml string and dynamicly update mounted XmlNode

then, you can access Xml's root xml node

NOTICE: if you want to cache the xml node

you MUST invoke node.mount(), mount the node to XmlDocument

so that XmlDocument can dynamicly update this XmlNode's pointer when the XML raw string changing.

you should invoke node.unmount() when the node is unnecessary

import 'package:large_xml/large_xml.dart';

var doc = XmlDocument.fromString(xmlstr);
var root = XmlDocument.root;

Finding Node

now we have a root node referece object

if you want to get the child node referece, you can invoke node.into()

var root = XmlDocument.root;

// find first normal start element in root chilren
// return <person/> node
var person = doc.root.into(type: XmlElementType.start);
if(person == null){
  print("node not found");
}

// find first normal start element in root chilren and node name is "info"
// return <info/> node
var info = doc.root.into(selector: (n)=>n.type == XmlElementType.start && n.name == "info");
if(info == null){
  print("node not found");
}

if you want to get the parallel node referece, you can invoke node.next()

var root = XmlDocument.root;
var person = doc.root.into(type: XmlElementType.start)!;

// find first normal start element after person node
// return <x:script/> node
var script = person.next(type: XmlElementType.start);
if(script == null){
  print("node not found");
}

// find first normal start element in root chilren and node name is "info"
// return <info/> node
var info = doc.root.next(selector: (n)=>n.type == XmlElementType.start && n.name == "info");
if(info == null){
  print("node not found");
}

// find first normal start element after person node and node name is "script", ignore namespace
// return <x:script/> node
script = doc.root.next(selector: (n)=>n.type == XmlElementType.start && n.name.removeNamespace() == "script");
if(script == null){
  print("node not found");
}

// find first normal start element after person node and namespace is "x"
// return <x:script/> node
script = doc.root.next(selector: (n)=>n.type == XmlElementType.start && n.name.namespace() == "x");
if(script == null){
  print("node not found");
}

// find first normal start element after person node and specified whole match "x:script"
// return <x:script/> node
script = doc.root.next(selector: (n)=>n.type == XmlElementType.start && n.name == "x:script");
if(script == null){
  print("node not found");
}

if you want to get the ancestor node referece, you can invoke node.findAncestor()

like into() and next(), it also support type and selector

and you can easily get node's parent node by node.parent

Node Attribute

you can use following method to access node's attribute

  • node.containsAttribute
  • node.getAttribute
  • node.getAttributes
  • node.getAttributeNode
  • node.addAttribute

directly get attribute's value like this

var root = XmlDocument.root;
var person = doc.root.into(type: XmlElementType.start)!;

// directly get attribute's value
// parameter key should like these
// "id" or "x:id" or ":id" or "*id"
// - "id" no namespace named "id" attribute
// - "x:id" namespace is "x" and name is "id"
// - ":id" has a namespace and name is "id"
// - "*id" just name is "id"
String? id = person.getAttribute(":id");
if(id != null){
  // should be attribute r:id
  print(id);
}

if you want to update attribute, use node.getAttributeNode, you will get a XmlAttribute object

NOTICE: XmlAttribute should not be cached it should be dropped immediately after any writing operation, all XmlAttribute will point at wrong position

if you make sure that there will be no write action while holding this object, then you can keep it.

tipsattr.setAttribute will update it self, you can still keep it after setAttribute

some method support chain invoke, you can keep the object in a chain otherwise you should re-find it by node.getAttributeNode.

var root = XmlDocument.root;
var person = doc.root.into(type: XmlElementType.start)!;

XmlAttribute id = person.getAttributeNode(":id")!;
print(id.key); // x:id

// id.namepace should be http://schemas.openxmlformats.org/package/2006/relationships
// it will lookup it ancestors and find first matched "xmlns:r" definition
print(id.namespace);

print(value); // 2

id.setAttribute("new value");
print(value); // new value

id.remove();

person.addAttribute("nattr")!;
print(person.containsAttribute("nattr")); // true

// you can get attributes map like this
Map<String,String> attrs = person.getAttributes();

Node Cache

if you want to holding a node

this is a correct usage example

//root is defaultly mounted
var root = XmlDocument.root;

var person = doc.root
  .into(type: XmlElementType.start)!
  .mount(); // mounted
var info = doc.root
  .next(selector: (n)=>n.type == XmlElementType.start && n.name == "info")!
  .mount(); // mounted

person.addAttribute("nattr");
info.addAttribute("nattr");

person.unmount();// release
info.unmount();// release

return;

wrong usage

//root is defaultly mounted
var root = XmlDocument.root;

var person = doc.root
  .into(type: XmlElementType.start)!
  .mount(); // mounted
var info = doc.root
  .next(selector: (n)=>n.type == XmlElementType.start && n.name == "info")!; // unmounted

person.addAttribute("nattr");
// after person node write action
// info node still keep the origin pointer, so it will write the attribute on wrong position
info.addAttribute("nattr");

person.unmount();// release

return;

Node Write

you can use following method to add, remove, and copy a node

  • XmlNode.create
  • node.remove
  • node.copy

and all writing action is depending on XmlNodeInstance object

it has following method:

  • inst.pasteBefore
  • inst.pasteAfter
  • inst.pasteInner

here is a example about how to create a new node

var root = XmlDocument.root;
var person = doc.root
  .into(type: XmlElementType.start)!
  .mount();

XmlNodeInstance inst = XmlNode.create("new");
var newNode = inst.pasteBefore(person).mount();
// xml will be changed like this
// <root>
//    <new/> <-- new node will be add before person node
//    <person/>
//    ...
// </root>

you can copy a node to a XmlNodeInstance object

var root = XmlDocument.root;
var person = doc.root
  .into(type: XmlElementType.start)!
  .mount();
var info = doc.root
  .next(selector: (n)=>n.type == XmlElementType.start && n.name == "info")!
  .mount();

XmlNodeInstance copy = info.copy();
var newNode = inst.pasteInner(person).mount();
// xml will be changed like this
// <root>
//    <person>
//      <info> <-- copy to here
//        <child/>
//      </info>
//    </person>
//    ...
// </root>

Libraries

large_xml