22class DLNAControlPoint;
91 const char* path =
"")
override {
93 snprintf(buffer,
sizeof(buffer),
"http://%s:%d%s", url.toString().c_str(),
111 std::function<
void(
const char* sid,
const char* varName,
114 void* ref =
nullptr)
override {
127 void onResultNode(std::function<
void(
const char* nodeName,
const char* text,
128 const char* attributes)>
133 bool begin(
const char* searchTarget =
"ssdp:all", uint32_t minWaitMs = 3000,
134 uint32_t maxWaitMs = 60000)
override {
137 "DLNAControlPoint::begin: transports not configured");
156 const char* searchTarget =
"ssdp:all", uint32_t minWaitMs = 3000,
157 uint32_t maxWaitMs = 60000)
override {
175 if (minWaitMs > maxWaitMs) minWaitMs = maxWaitMs;
176 search->
end_time = millis() + maxWaitMs;
184 uint64_t start = millis();
185 uint64_t minEnd = start + minWaitMs;
186 uint64_t maxEnd = start + maxWaitMs;
187 while (millis() < maxEnd) {
191 if (
devices.size() > 0 && millis() >= minEnd)
break;
198 if (
devices.size() > 0 && search !=
nullptr) {
203 "Control Point started with %d devices found",
217 if (
devices.size() == 0)
return false;
236 "No local URL and no HttpServer for subscriptions");
262 for (
auto& device :
devices) device.clear();
329 if (schedule !=
nullptr) {
366 if (result)
return result;
374 if (default_device_idx < 0 || default_device_idx >=
devices.size())
382 if (idx < 0 || idx >=
devices.size()) {
393 for (
auto& srv : dev.getServices()) {
394 if (srv == service)
return dev;
404 if (dev.getDeviceURL() == location) {
406 "DLNAControlPointMgr::getDevice: Found device %s",
424 return getUrlImpl(device, suffix, buffer, len);
430 for (
auto& existing_device :
devices) {
431 if (dev.
getUDN() == existing_device.getUDN()) {
470 device_parser.
begin();
472 int len =
p_http->
read(buffer,
sizeof(buffer));
474 device_parser.parse(buffer, len);
476 device_parser.end(new_device);
484 for (
auto& existing_device :
devices) {
485 if (existing_device.getUDN() == new_device.
getUDN()) {
487 "Device '%s' already exists (skipping add)",
490 existing_device.setActive(
true);
542 std::function<void(
const char* nodeName,
const char* text,
543 const char* attributes)>
557 if (!usn_c || *usn_c ==
'\0')
return false;
558 const char* sep = strstr(usn_c,
"::");
559 int udn_len = sep ? (int)(sep - usn_c) : (int)strlen(usn_c);
561 const char* known_udn = dev.getUDN();
562 if (known_udn && strncmp(known_udn, usn_c, udn_len) == 0 &&
563 (int)strlen(known_udn) == udn_len) {
574 select ?
"added" :
"filtered");
575 if (!select)
return false;
580 "Device '%s' already known (skip GET)",
581 existing ? existing->
getUDN() :
"<unknown>");
590 "Device '%s' filtered by netmask (LOCATION %s)",
607 const char* peerHost = url.
host();
612 bool peerOK = (peerHost && *peerHost) ? peerIP.fromString(peerHost) :
false;
614 (localHost && *localHost) ? localIP.fromString(localHost) :
false;
617 if (!peerOK || !localOK)
return true;
619 for (
int i = 0; i < 4; i++) {
620 if ((localIP[i] & netmask[i]) != (peerIP[i] & netmask[i])) {
622 "Discovery filtered: local=%d.%d.%d.%d peer=%d.%d.%d.%d "
624 localIP[0], localIP[1], localIP[2], localIP[3],
625 peerIP[0], peerIP[1], peerIP[2], peerIP[3], netmask[0],
626 netmask[1], netmask[2], netmask[3]);
632 "Discovery accepted: local=%d.%d.%d.%d peer=%d.%d.%d.%d",
633 localIP[0], localIP[1], localIP[2], localIP[3], peerIP[0],
634 peerIP[1], peerIP[2], peerIP[3]);
641 if (nts.
equals(
"ssdp:byebye")) {
644 if (nts.
equals(
"ssdp:alive")) {
653 "DLNAControlPointMgr::processMSearchReply");
657 const char* usn_c = data.
usn.
c_str();
658 if (usn_c && *usn_c) {
659 const char* sep = strstr(usn_c,
"::");
660 int udn_len = sep ? (int)(sep - usn_c) : (int)strlen(usn_c);
662 const char* known_udn = dev.getUDN();
663 if (known_udn && strncmp(known_udn, usn_c, udn_len) == 0 &&
664 (int)strlen(known_udn) == udn_len) {
666 "MSearchReply: device '%s' already known (skip GET)",
692 for (
auto& srv : dev.getServices()) {
693 srv.is_active =
false;
694 if (usn.
endsWith(srv.service_type)) {
697 srv.is_active =
false;
727 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
728 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"",
733 StrView namespace_str(ns, 200);
734 namespace_str =
"xmlns:u=\"%1\"";
740 namespace_str.
c_str(),
"u");
742 const char* n = arg.name.c_str();
743 const char* v = arg.value.c_str();
779 action_str.add(
"\"");
785 "Service control_url: %s, device base: %s",
808 const char* soapAction,
811 "DLNAControlPointMgr::processActionHttpPost");
816 auto printXML = [
this, &action, &xmlLen](Print& out,
void* ref) ->
size_t {
824 int rc =
p_http->
post(post_url, xmlLen, printXML,
"text/xml");
831 StrView(soapAction).c_str(), rc);
843 if (c) xmlProcessor(*c,
reply);
870 xml_parser.
write(buffer, len);
871 while (xml_parser.
parse(outNodeName, outPath, outText, outAttributes)) {
878 outNodeName.
equals(
"Result")) {
891 StrView(outAttributes).c_str());
894 StrView(outNodeName ? outNodeName :
"").c_str(),
895 StrView(outText ? outText :
"").c_str(),
896 StrView(outAttributes ? outAttributes :
"").c_str());
912 const char* buffer,
int len) {
914 StrView url_str{(
char*)buffer, len};
917 if (base !=
nullptr && *base !=
'\0') {
928 if (suffix !=
nullptr && suffix[0] !=
'\0') {
929 int base_len = url_str.length();
930 const char* tmpc = url_str.c_str();
931 bool base_ends_slash =
932 tmpc !=
nullptr && base_len > 0 && tmpc[base_len - 1] ==
'/';
933 bool suf_starts_slash = suffix[0] ==
'/';
934 if (base_ends_slash && suf_starts_slash) {
936 url_str.add(suffix + 1);
937 }
else if (!base_ends_slash && !suf_starts_slash) {
Represents the result of invoking a DLNA service Action.
Definition: Action.h:48
int size()
Definition: Action.h:79
void clear()
Definition: Action.h:81
void setValid(bool flag)
Definition: Action.h:52
void addArgument(Argument arg)
Definition: Action.h:59
void logArguments()
Definition: Action.h:83
Represents a request to invoke a remote DLNA service action.
Definition: Action.h:104
DLNAServiceInfo * getService()
Definition: Action.h:163
const char * getAction()
Definition: Action.h:157
Vector< Argument > & getArguments()
Definition: Action.h:165
DLNA Service: Action Argument.
Definition: Action.h:17
Str value
Definition: Action.h:25
Str name
Definition: Action.h:24
Translates DLNA UDP Requests to Schedule so that we can schedule a reply.
Definition: DLNAControlPointRequestParser.h:15
Schedule * parse(RequestData &req)
Definition: DLNAControlPointRequestParser.h:17
Lightweight DLNA control point manager.
Definition: DLNAControlPoint.h:61
static bool handleNotifyByebye(Str &usn)
Definition: DLNAControlPoint.h:548
void * reference
Definition: DLNAControlPoint.h:541
void setHttpServer(IHttpServer &server) override
Set HttpServer instance and register the notify handler.
Definition: DLNAControlPoint.h:119
void end() override
Stops the processing and releases the resources.
Definition: DLNAControlPoint.h:246
DLNAControlPoint(IHttpServer &server)
Constructor supporting Notifications.
Definition: DLNAControlPoint.h:79
size_t createSoapXML(ActionRequest &action, Print &out)
Build the SOAP XML request body for the action into out
Definition: DLNAControlPoint.h:800
bool matches(const char *usn)
checks if the usn contains the search target
Definition: DLNAControlPoint.h:680
size_t createXML(ActionRequest &action)
Definition: DLNAControlPoint.h:721
static bool processMSearchReply(MSearchReplyCP &data)
Processes an M-SEARCH HTTP 200 reply and attempts to add the device.
Definition: DLNAControlPoint.h:651
int default_device_idx
Definition: DLNAControlPoint.h:533
static bool handleNotifyAlive(NotifyReplyCP &data)
Definition: DLNAControlPoint.h:571
bool addDevice(Url url) override
Adds the device from the device xml url if it does not already exist.
Definition: DLNAControlPoint.h:444
bool is_parse_device
Definition: DLNAControlPoint.h:536
Vector< DLNADeviceInfo > devices
Definition: DLNAControlPoint.h:529
const char * getUrlImpl(DLNADeviceInfo &device, const char *suffix, const char *buffer, int len)
Definition: DLNAControlPoint.h:911
Url local_url
Definition: DLNAControlPoint.h:538
bool addDevice(DLNADeviceInfo dev) override
Adds a new device.
Definition: DLNAControlPoint.h:428
bool allow_localhost
Definition: DLNAControlPoint.h:539
IHttpServer * p_http_server
Definition: DLNAControlPoint.h:540
Scheduler scheduler
Definition: DLNAControlPoint.h:525
~DLNAControlPoint() override=default
bool is_active
Definition: DLNAControlPoint.h:535
void setLocalURL(Url url) override
Defines the local url (needed for subscriptions)
Definition: DLNAControlPoint.h:89
void setTransports(IHttpRequest &http, IUDPService &udp)
Definition: DLNAControlPoint.h:209
SubscriptionMgrControlPoint * getSubscriptionMgr() override
Provides the subscription manager.
Definition: DLNAControlPoint.h:514
DLNADeviceInfo NO_DEVICE
Definition: DLNAControlPoint.h:524
void parseResult()
Parses the XML response from the HTTP client and populates reply arguments.
Definition: DLNAControlPoint.h:859
const char * getUrl(DLNADeviceInfo &device, const char *suffix, char *buffer, int len) override
Definition: DLNAControlPoint.h:422
DLNAServiceInfo & getService(const char *id) override
Provide addess to the service information.
Definition: DLNAControlPoint.h:361
void setDeviceIndex(int idx) override
Selects the default device by index.
Definition: DLNAControlPoint.h:107
void setSearchRepeatMs(int repeatMs) override
Sets the repeat interval for M-SEARCH requests (define before begin)
Definition: DLNAControlPoint.h:99
int msearch_repeat_ms
Definition: DLNAControlPoint.h:534
void setActive(bool flag) override
We can activate/deactivate the scheduler.
Definition: DLNAControlPoint.h:502
void setEventSubscriptionCallback(std::function< void(const char *sid, const char *varName, const char *newValue, void *reference)> cb, void *ref=nullptr) override
Register a callback that will be invoked for incoming event notification.
Definition: DLNAControlPoint.h:110
ActionReply & processActionHttpPost(ActionRequest &action, Url &post_url, const char *soapAction, XMLCallback xmlProcessor=nullptr)
Processes an HTTP POST request for the given action and URL.
Definition: DLNAControlPoint.h:807
void setLocalURL(IPAddress url, int port=9001, const char *path="") override
Set the local callback URL for event subscriptions.
Definition: DLNAControlPoint.h:90
Vector< ActionRequest > actions
Definition: DLNAControlPoint.h:530
bool isActive() override
Checks if the scheduler is active.
Definition: DLNAControlPoint.h:505
bool loop() override
Definition: DLNAControlPoint.h:318
static bool isUdnKnown(const char *usn_c, DLNADeviceInfo *&outDev)
Definition: DLNAControlPoint.h:555
ActionReply & getLastReply() override
Provides the last reply.
Definition: DLNAControlPoint.h:511
const char * search_target
Definition: DLNAControlPoint.h:537
void onResultNode(std::function< void(const char *nodeName, const char *text, const char *attributes)> cb) override
Definition: DLNAControlPoint.h:127
DLNADeviceInfo & getDevice() override
Provides the device information of the actually selected device.
Definition: DLNAControlPoint.h:372
static bool processDevice(NotifyReplyCP &data)
Definition: DLNAControlPoint.h:638
std::function< void(const char *nodeName, const char *text, const char *attributes)> result_callback
Definition: DLNAControlPoint.h:544
DLNADeviceInfo & getDevice(int idx) override
Provides the device information by index.
Definition: DLNAControlPoint.h:380
bool processBye(Str &usn)
processes a bye-bye message
Definition: DLNAControlPoint.h:688
DLNADeviceInfo & getDevice(Url location) override
Get a device for a Url.
Definition: DLNAControlPoint.h:401
ActionReply & postAction(ActionRequest &action, XMLCallback xmlProcessor=nullptr)
Definition: DLNAControlPoint.h:764
ActionReply reply
Definition: DLNAControlPoint.h:531
void setParseDevice(bool flag) override
Requests the parsing of the device information.
Definition: DLNAControlPoint.h:86
Vector< DLNADeviceInfo > & getDevices() override
Get list of all discovered devices.
Definition: DLNAControlPoint.h:414
bool begin(IHttpRequest &http, IUDPService &udp, const char *searchTarget="ssdp:all", uint32_t minWaitMs=3000, uint32_t maxWaitMs=60000) override
Start discovery by sending M-SEARCH requests and process replies.
Definition: DLNAControlPoint.h:155
void setNotificationsActive(bool flag) override
Activate/deactivate subscription notifications.
Definition: DLNAControlPoint.h:519
IHttpRequest * p_http
Definition: DLNAControlPoint.h:526
bool begin(const char *searchTarget="ssdp:all", uint32_t minWaitMs=3000, uint32_t maxWaitMs=60000) override
Start discovery with default HTTP/UDP services.
Definition: DLNAControlPoint.h:133
DLNADeviceInfo & getDevice(DLNAServiceInfo &service) override
Provides the device for a service.
Definition: DLNAControlPoint.h:390
ActionReply & postAllActions(XMLCallback xmlProcessor=nullptr)
Definition: DLNAControlPoint.h:755
ActionReply & executeActions(XMLCallback xmlProcessor=nullptr) override
Executes action and parses the reply xml to collect the reply entries. If an XML processor is provide...
Definition: DLNAControlPoint.h:304
DLNAControlPoint(IHttpRequest &http, IUDPService &udp, IHttpServer &server)
Constructor wiring HTTP and UDP dependencies; notifications disabled.
Definition: DLNAControlPoint.h:72
ActionRequest & addAction(ActionRequest act) override
Registers a method that will be called.
Definition: DLNAControlPoint.h:290
bool subscribe()
Subscribes to event notifications for all services of the selected device.
Definition: DLNAControlPoint.h:216
XMLPrinter xml_printer
Definition: DLNAControlPoint.h:532
bool isDiscoveryAllowed(Url &url)
Definition: DLNAControlPoint.h:604
DLNAControlPoint(IHttpRequest &http, IUDPService &udp)
Constructor wiring HTTP and UDP dependencies; notifications disabled.
Definition: DLNAControlPoint.h:67
DLNAControlPoint()
Default constructor w/o Notifications.
Definition: DLNAControlPoint.h:64
void setReference(void *ref) override
Attach an opaque reference pointer (optional, for caller context)
Definition: DLNAControlPoint.h:104
SubscriptionMgrControlPoint subscription_mgr
Definition: DLNAControlPoint.h:528
void unEscapeStr(Str &str)
Definition: DLNAControlPoint.h:905
void setAllowLocalhost(bool flag) override
Defines if localhost devices should be allowed.
Definition: DLNAControlPoint.h:508
IUDPService * p_udp
Definition: DLNAControlPoint.h:527
Device Attributes and generation of XML using urn:schemas-upnp-org:device-1-0. We could just return a...
Definition: DLNADeviceInfo.h:28
Url device_url
Definition: DLNADeviceInfo.h:284
Str base_url
Definition: DLNADeviceInfo.h:288
const char * getUDN()
Provide the udn uuid.
Definition: DLNADeviceInfo.h:90
const char * getBaseURL()
Provides the base url.
Definition: DLNADeviceInfo.h:120
virtual bool begin()
Override to initialize the device.
Definition: DLNADeviceInfo.h:61
Url & getDeviceURL()
This method returns base url/device.xml.
Definition: DLNADeviceInfo.h:131
void setActive(bool flag)
Sets the server to inactive.
Definition: DLNADeviceInfo.h:267
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:18
Str service_type
Definition: DLNAServiceInfo.h:37
Str control_url
Definition: DLNAServiceInfo.h:40
Abstract interface for DLNA Control Point functionality.
Definition: IControlPoint.h:43
Abstract interface for HTTP client request functionality.
Definition: IHttpRequest.h:21
virtual void setTimeout(int ms)=0
Set request timeout in milliseconds.
virtual Client * client()=0
Get pointer to client.
virtual int read(uint8_t *str, int len)=0
Read data from response.
virtual HttpRequestHeader & request()=0
Get reference to request header.
virtual int post(Url &url, const char *mime, const char *data, int len=-1)=0
Send POST request with string data.
virtual void stop()=0
Stop the connection.
virtual int get(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)=0
Send GET request.
virtual void setConnection(const char *connection)=0
Set Connection header.
virtual bool isKeepAlive()=0
Do not close the connection after request.
Abstract interface for HTTP server functionality.
Definition: IHttpServer.h:50
virtual bool doLoop()=0
Process server loop.
virtual bool isActive()=0
Abstract Interface for UDP API.
Definition: IUDPService.h:33
virtual RequestData receive()=0
Receive incoming UDP data and peer information.
virtual bool begin(int port)=0
Initialize UDP service on specified port.
Processing at control point to handle a MSearchReply from the device.
Definition: Schedule.h:207
Str usn
Definition: Schedule.h:211
Str location
Definition: Schedule.h:210
Send MSearch request.
Definition: Schedule.h:46
Represents a notification/notify reply scheduled for control-point processing.
Definition: Schedule.h:248
Str nts
Definition: Schedule.h:251
Class with does not do any output: it can be used to determine the length of the output.
Definition: NullPrint.h:12
Scheduler which processes all due Schedules (to send out UDP replies)
Definition: Scheduler.h:15
int execute(IUDPService &udp)
Execute all due schedules.
Definition: Scheduler.h:30
void setActive(bool flag)
Definition: Scheduler.h:81
int size()
Number of queued schedules.
Definition: Scheduler.h:79
bool isMSearchActive()
Returns true if there is any active schedule with name "MSearch".
Definition: Scheduler.h:69
void add(Schedule *schedule)
Add a schedule to the scheduler.
Definition: Scheduler.h:18
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:18
virtual void add(int value)
adds a int value
Definition: StrView.h:128
virtual const char * c_str()
provides the string value as const char*
Definition: StrView.h:376
virtual bool replace(const char *toReplace, const int replaced)
Replaces the first instance of toReplace with replaced.
Definition: StrView.h:395
virtual bool equals(const char *str)
checks if the string equals indicated parameter string
Definition: StrView.h:177
virtual bool contains(const char *str)
checks if the string contains a substring
Definition: StrView.h:284
Heap-backed string utility used throughout tiny_dlna.
Definition: Str.h:27
bool equals(const char *other) const
Exact string equality with C-string.
Definition: Str.h:167
bool isEmpty() const
True if empty.
Definition: Str.h:54
bool endsWith(const char *suffix) const
True if ends with suffix (case-sensitive)
Definition: Str.h:175
bool startsWith(const char *prefix) const
True if starts with prefix (case-sensitive)
Definition: Str.h:206
int replaceAll(const char *toReplace, const char *replaced)
Replace all occurrences of toReplace with replaced; returns count.
Definition: Str.h:279
void clear()
Clear contents (size -> 0)
Definition: Str.h:93
const char * c_str() const
C-string pointer to internal buffer.
Definition: Str.h:88
Standalone manager for UPnP/DLNA event subscriptions used by control points and devices.
Definition: SubscriptionMgrControlPoint.h:44
void setEventSubscriptionCallback(std::function< void(const char *sid, const char *varName, const char *newValue, void *reference)> callback, void *ref=nullptr)
Register a callback invoked when event properties change.
Definition: SubscriptionMgrControlPoint.h:108
bool loop()
Periodic work to manage subscription renewals and retries.
Definition: SubscriptionMgrControlPoint.h:198
void setHttpServer(IHttpServer &server)
Attach an HttpServer instance and register the internal NOTIFY handler.
Definition: SubscriptionMgrControlPoint.h:85
void setNotificationsActive(bool active)
Turn automatic event subscription on or off.
Definition: SubscriptionMgrControlPoint.h:172
void setup(IHttpRequest &http, IUDPService &udp, Url &localCallbackUrl, DLNADeviceInfo &device)
Inject required dependencies and initialize the manager.
Definition: SubscriptionMgrControlPoint.h:62
URL parser which breaks a full url string up into its individual parts.
Definition: Url.h:18
int port()
Definition: Url.h:46
const char * host()
Definition: Url.h:41
const char * urlRoot()
Definition: Url.h:43
const char * protocol()
Definition: Url.h:42
void setUrl(const char *url)
Definition: Url.h:48
const char * url()
Definition: Url.h:39
void clear()
Definition: Url.h:63
Lightweight wrapper around std::vector with Arduino-friendly helpers and a pluggable allocator.
Definition: Vector.h:39
Incremental XML device parser using XMLParserPrint.
Definition: XMLDeviceParser.h:20
Helper class that implements a Print interface to accumulate XML data and then parse it using XMLPars...
Definition: XMLParserPrint.h:16
size_t write(uint8_t ch) override
Writes a single byte to the buffer (Print interface)
Definition: XMLParserPrint.h:32
void setExpandEncoded(bool flag)
Forwards expand-entities setting to the underlying XMLParser.
Definition: XMLParserPrint.h:48
void end()
Resets the internal buffer.
Definition: XMLParserPrint.h:87
bool parse(Str &outNodeName, Vector< Str > &outPath, Str &outText, Str &outAttributes)
Parses the accumulated XML data and returns results via output parameters.
Definition: XMLParserPrint.h:59
#define DLNA_REQUEST_KEEP_ALIVE
Keep the session open for multiple requests.
Definition: dlna_config.h:30
#define DLNA_MAX_URL_LEN
app-wide max URL length
Definition: dlna_config.h:55
#define XML_PARSER_BUFFER_SIZE
Define XML parse buffer size.
Definition: dlna_config.h:35
#define DLNA_HTTP_REQUEST_TIMEOUT_MS
Define the default http request timeout.
Definition: dlna_config.h:25
#define DLNA_DISCOVERY_NETMASK
Define the netmask for discovery filtering.
Definition: dlna_config.h:197
Definition: Allocator.h:13
const char * CON_KEEP_ALIVE
Definition: HttpHeader.h:20
const char * CON_CLOSE
Definition: HttpHeader.h:19
DLNAControlPoint * selfDLNAControlPoint
Definition: DLNAControlPoint.h:23
std::function< void(Client &client, ActionReply &reply)> XMLCallback
Definition: IControlPoint.h:25
Provides information of the received UDP which consists of the (xml) data and the peer address and po...
Definition: IUDPService.h:22
An individual Schedule (to send out UDP messages)
Definition: Schedule.h:18
uint64_t end_time
Definition: Schedule.h:25
bool active
Definition: Schedule.h:27
virtual const char * name()
Definition: Schedule.h:35
uint32_t repeat_ms
Definition: Schedule.h:23
Functions to efficiently output XML. XML data contains a lot of redundancy so it is more memory effic...
Definition: XMLPrinter.h:56
size_t printNodeBegin(const char *node, const char *attributes=nullptr, const char *ns=nullptr)
Prints the beginning of an XML node.
Definition: XMLPrinter.h:314
size_t printNode(XMLNode node)
Prints an XML node from XMLNode struct.
Definition: XMLPrinter.h:89
size_t printNodeEnd(const char *node, const char *ns=nullptr)
Prints the end of an XML node.
Definition: XMLPrinter.h:352
void setOutput(Print &output)
Defines the output Print object.
Definition: XMLPrinter.h:73
size_t printXMLHeader()
Prints the XML header.
Definition: XMLPrinter.h:79
void clear()
Definition: XMLPrinter.h:363