레이블이 JavaScript인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JavaScript인 게시물을 표시합니다. 모든 게시물 표시

2016년 7월 28일 목요일

크롬 앱으로 멀티플랫폼에서 시리얼 통신하기 (Serial communication through Chrome App)

전자회로 개발 프로젝트를 진행하는 동안 때로 내가 만드는 디바이스와 시리얼로 통신을 하는 GUI를 개발할 필요가 있다.

과거에는 .Net Framework과 C#을 사용해 인터페이스를 만들었었다. Rapid protyping을 가능하게 하는 프레임웍을 사용하면 인터페이스를 커스텀화 하고 OS의 다양한 그래픽 요소를 쉽게 접근할 수 있게 해 준다.



하지만 문제는 그렇게 개발한 앱은 특정 환경에서만 사용할 수 있다는 것이다. 어떻게 하면 진정한 크로스 플랫폼, 즉 맥이나 리눅스에서도 실행될 수 있는가를 찾아 보았다. 거의 모든 운영체제에서 사용할 수 있는 Java나 Python 같은 언어를 사용할 수도 있다. 하지만 이 언어들로 GUI를 개발하는 것은 전용 프레임웍을 사용해야 한다. 추가적으로 SunOracle은 Java의 시리얼 통신 API(javax.comm)의 스펙만을 정의해 놓고 구현을 하지 않았다. 그러므로 이 API를 구현해 놓은 외부 라이브러리(가장 유명한 것이 RxTx이다)를 사용해야만 한다.

여기서 소개하는 방법은 외부 라이브러리를 사용하지 않고 웹 사이트를 개발할 때 사용하는 것과 동일한 기술로 크로스 플랫폼 앱을 개발하는 방법이다.

Chrome App


Chrome App은 기본적으로 어플리케이션이다. 하지만 공통적인 웹 기술(HTML, CSS, javascript등)을 사용해 개발되고 Chrome 브라우져 엔진에 의해 실행된다.
 
표준 웹 사이트와 비교했을 때 가장 큰 장점은 다음과 같다.

* Seamless desktop integration (no address bar...)
* Grouped in a dedicated launcher
* Ability to interface with hardware devices


Anatomy of a Chrome App


Chrome App은 크게 3가지 요소로 구성된다.

* manifest file: 앱의 메타 데이터 (이름, 버젼, 설명 등)를 가지고 있다.
* background script: 앱의 life cycle을 관리하기 위한 이벤트 페이지를 만든다.
* main interface: HTML 페이지, javascript 스크립트, CSS(Cascade Style Sheet) 등으로 만들어진다.

아두이노 IDE에 포함되어 있는 시리얼 모니터를 에뮬레이트하는 데모 앱을 준비해 보았다. 이 앱은 chrome web store에서 구할 수 있고, 소스코드는 github repository에서 다운받을 수 있다.

코드를 좀 상세히 보도록 하자.

manifest.json 파일은 메타 데이터를 포함하고 있다. 가장 중요한 것은 시리얼 포트를 억세스하기를 원한다는 것을 선언하는 것과 백그라운드 스크립트의 이름을 지정하는 것이다.



백그라운드 스크립트는 window.html 파일을 디스플레이 하기 위한 사이즈를 조정할 수 없는 윈도우 (600x500 픽셀)를 만든다.



메인 어플리케이션은 3개의 파일로 만들어 진다.

* window.html
* main.js
* main.css

또한 다양한 메시지 다이얼로그를 디스플레이 하기 위해 jQuery와, 플러그인인 jQuery simplemodal을 사용했다.

Serial port access


Javascript를 사용해 PC의 시리얼포트를 제어하기 위해 Chrome은 chrome.serial API를 제공한다.

먼저 사용 가능한 포트 목록을 찾아 콤보박스에 추가해서, 사용자가 원하는 포트를 선택할 수 있게 해 준다.

chrome.serial.getDevices(function(ports) {
  for (var i = 0; i < ports.length; i++) {
    var portName = ports[i].path;
    var newOption = '' + portName + '';
    var newOption = ' + portName + '">' + portName + '';
    $("#serial_ports_combobox").append(newOption);
  }
});




선택한 시리얼 포트에 연결하기 위해 connect 메소드를 사용한다.

chrome.serial.connect(selectedPort, connectionOptions, onConnect);

이 때 시리얼 포트 설정을 가지고 있는 배열(connectionOptions)을 넘겨줘야 한다.

var connectionOptions = {
  "bitrate": 9600,
  "dataBits": "eight",
  "parityBit": "no",
  "stopBits": "one",
  "receiveTimeout": 500,
  "sendTimeout": 500
};


연결되고 나면 send 메소드로 데이터를 보낼 수 있다.

chrome.serial.send(connectionId, convertStringToArrayBuffer(textToSend), function(sendInfo) {
  if(sendInfo.error) $.modal('Unable to send data: ' + sendInfo.error + '
')
});


에러가 발생하면 sendInfo.error가 null이 아닌 값을 가지게 된다. 위의 예제에서는 에러 메지지가 표시된다.

Javascript 언어의 주요 특징이 비동기성인데 이것이 바로 시리얼 포트로부터 데이터를 수신하는 패러다임이다. 먼저 onReceive 이벤트에 대한 listener(커스텀 함수)를 추가해 줘야만 한다.

chrome.serial.onReceive.addListener(onReceive);

시리얼 포트에 새 데이터가 수신될 때 마다 onReceive() 함수가 호출된다.

function onReceive(info) {
  if (info.connectionId == connectionId &amp;&amp; info.data) {
    var str = convertArrayBufferToString(info.data);
    $("#receive_textarea").append(str);
    $("#receive_textarea").scrollTop($("#receive_textarea")[0].scrollHeight);
  }
}


함수는 먼저 원하는 시리얼 포트에 유효한 데이터가 수신되었는지 확인한다. 유효한 데이터가 수신되면 ArrayBuffer 오브젝트를 string 으로 변환해 textArea에 추가한다.

Test


Chrome 브라우져 설정에서 'Developer mode'를 활성화 한 다음 'Load unpacked extension...'을 클릭하면 Chrome app을 테스트 할 수 있다.


앱을 포함하고 있는 폴더를 선택하면 거기에 있는 앱이 Chrome에 로드되어 실행할 수 있게 된다.

어플리케이션을 배포하고 싶으면 'Pack extension...' 버튼으로 installable package를 만들 수 있다. 또는 Chrome Developer program에 가입한 후 chrome web store에 업로드 할 수도 있다.




2015년 3월 17일 화요일

NodeJS에서 시리얼 포트 사용하기

하드웨어 디바이스와 통신에 가장 쉽고 많이 사용되는것이 시리얼포트이다. NodeJS에서 시리얼 포트를 사용하려면 Chris Williams가 만든 serialport 모듈을 사용하면 된다.

먼저 serialport 모듈을 설치한다.

$ npm install --save-dev serialport

> serialport@1.6.2 install /home/test/node_modules/serialport
> node-pre-gyp install --fallback-to-build

[serialport] Success: "/home/test/node_modules/serialport/build/serialport/v1.6.2/Release/node-v11-darwin-x64/serialport.node" is installed via remote
serialport@1.6.2 ../node_modules/serialport
├── bindings@1.2.1
├── sf@0.1.7
├── nan@1.7.0
├── optimist@0.6.1 (wordwrap@0.0.2, minimist@0.0.10)
└── debug@2.1.3 (ms@0.7.0)


설치가 완료되면 시리얼 포트를 열어준다.

$ node
> var serialport = require("serialport");
undefined
> SerialPort = serialport.SerialPort;
{ [Function: SerialPort]
  super_:
   { [Function: Stream]
     super_: { [Function: EventEmitter] listenerCount: [Function] },
     Readable:
      { [Function: Readable]
        ReadableState: [Function: ReadableState],
        super_: [Circular],
        _fromList: [Function: fromList] },
     Writable:
      { [Function: Writable]
        WritableState: [Function: WritableState],
        super_: [Circular] },
     Duplex: { [Function: Duplex] super_: [Object] },
     Transform: { [Function: Transform] super_: [Object] },
     PassThrough: { [Function: PassThrough] super_: [Object] },
     Stream: [Circular] } }
> var serialPort = new SerialPort("/dev/cu.usbmodem14131", {
      baudrate: 9600,
      parser: serialport.parsers.readline("\n")
});
> serialPort = new SerialPort("/dev/cu.PL2303-00003014", {
... baudrate: 9600,
... parser: serialport.parsers.readline("\n")
... });
{ domain: null,
  _events: {},
  _maxListeners: 10,
  fd: null,
  paused: true,
  bufferSize: 256,
  readable: true,
  reading: false,
  options:
   { baudrate: 9600,
     parser: [Function],
     baudRate: 9600,
     dataBits: 8,
     stopBits: 1,
     parity: 'none',
     rtscts: false,
     xon: false,
     xoff: false,
     xany: false,
     bufferSize: 256,
     platformOptions: { vmin: 1, vtime: 0 },
     dataCallback: [Function],
     disconnectedCallback: [Function] },
  path: '/dev/cu.PL2303-00003014' }


위에서 '/dev/cu.PL2303-00003014' 부분은 시스템에 맞게 바꿔주면 된다. 리눅스라면 보통 '/dev/ttyUSB0' 같은 이름이고 윈도우라면 'COM1' 같은 이름이 된다.

테스트하기 위해 아두이노에 0.1초마다 숫자를 하나씩 증가시켜 시리얼포트로 출력하도록 하는 스케치를 업로드 해 준다.

setup() { 
  Serial.begin(9600); 


int i=0; 
void loop() { 
  Serial.println(i++); 
  delay(100); // poll every 100ms 
}

Reading from the port

시리얼포트에서 값을 읽어 화면에 표시하기 위해 다음의 코드를 작성해 'dump.js'라는 이름으로 저장해준다. 

var serialport = require("serialport"); 
var SerialPort = serialport.SerialPort; 

var serialPort = new SerialPort("cu.PL2303-00003014", { 
  baudrate: 9600, 
  parser: serialport.parsers.readline("\n") 
}); 
serialPort.on("open", function () { 
  console.log('open'); 
  serialPort.on('data', function(data) { 
    console.log(data); 
  }); 
});

이제 아두이노를 USB-to-Serial 케이블에 연결하고 dump.js를 실행한다.

$ node jump.js
...
5
6
7
....
^C


좀 더 상세한 정보와 예제 코드는 https://www.npmjs.com/package/serialport 를 참조하면 된다.

2015년 2월 1일 일요일

아이폰이나 안드로이드폰으로 원하는 푸쉬메시지를 보내는 방법....Pushetta

아이폰이나 안드로이드 폰에 푸쉬를 통해 리얼타임으로 원하는 notification message을 보낼 수 있는 방법으로 pushetta 가 있다.


먼저 pushetta에 가입을 해야 한다. (http://www.pushetta.com)

이메일 주소만 있으면 무료로 쉽게 가입할 수 있다.


가입을 한 후 로그인 하면 나오는 초기화면이다.


Dashboard로 가 보면 자신의 API 키를 확인할 수 있다. 이 API 키를 알아야 추후 푸쉬 메시지를 보낼 수 있다.


이제 'New Channel' 버튼을 눌러 새로운 채널을 생성한다.


'Add a Channel' 버튼을 눌러준다.


새로 만들 채널에 대한 정보를 넣어준다. 채널 사진은 꼭 256x256 픽셀 크기여야만 한다. Channel Name은 나중에 이 채널을 검색할 때 사용하기 때문에 정확하게 넣어줘야 한다. 자신의 채널 이름이 다른 사람에게는 보이지 않게 하려면 'Hidden'을 체크해주면 된다.


'Create' 버튼을 누르면 채널이 생성되었고 위와 같은 채널에 대한 화면이 나오게 된다.

이제 스마트폰에 Pushetta 앱을 설치한다.



앱을 실행하면 아래와 같은 초기화면이 나타난다.


화면 좌측 상단의 '+' 버튼을 클릭해 채널을 등록해준다.


아까 만든 채널 이름을 넣어주면 된다.


채널을 선택하면 바로 메시지를 받을 수 있는게 아니고 채널 오너에게 요청메일이 날라가게 되고 오너가 허가를 해 줘야 메시지를 받을 수 있게 된다.

가입할 때 기입한 이메일로 요청이 왔다는 메일이 날라오게 된다. 메일의 링크를 클릭하면 pushetta 페이지의 채널 정보 페이지로 가게 되고 pending request가 있다고 표시가 되어 있다.


빨간색 'Pending requests' 버튼을 클릭하면 아래 화면이 나오게 된다.


디바이스 정보를 확인하고 녹색 체크 표시를 클릭해주면 허가를 해 주는 것이다.


오른쪽 아래에 방금 허가해 준 장치가 표시되어 있는걸 확인할 수 있다.

이제 스마트폰에서도 앱의 My Channel 탭으로 가 보면 채널이 등록된걸 볼 수 있다.


이제 실제로 푸쉬 메시지를 보내보도록 하겠다. 웹페이지의 'Push a message' 부분에 자신이 원하는 메시지를 써 넣고 'Send' 버튼을 누르면 된다. 버튼을 누르면 스마트폰에 곧바로 푸쉬 메시지가 전송된다.



앱의 'Notification' 탭으로 가 보면 방금 보낸 메시지의 내용을 확인할 수 있다.



이렇게 간단하게 자신이 원하는 메시지를 푸쉬를 통해 스마트폰에 리얼타임으로 바로 보낼 수 있다. 여기서는 테스트를 위해 홈페이지에서 메시지를 보냈지만 표준 web API를 사용하고 있기 때문에 다양한 언어(python, javascript 등등)및 여러 디바이스(아두이노, Raspberry Pi, Edison 등등)에서 스마트폰으로 푸쉬 메시지를 보낼 수 있어 매우 다양한 방법으로 사용할 수 있다.

다음 포스트에서는 에디슨에 센서를 연결해 사람이 문으로 들어오면 스마트폰으로 알려주는 데모를 만들어 보도록 하겠다.

2010년 6월 6일 일요일

SAX를 사용한 XML 파싱 (Parsing XML document using SAX)



SAX를 사용해서 XML 문서를 파싱하려면 먼저 DefaultHandler를 확장한 클래스가 필요하다.

import org.xml.sax.helpers.DefaultHandler;

public class MySAXApp extends DefaultHandler
{
  public MySAXApp()
  {
    super();
  }
}

또한 JAVA 어플리케이션에서 동적으로 SAX드라이버를 사용하기 위해 XMLReaderFactory 클래스의 createXMLReader 메소드를 사용해서 XML reader를 만들어 줘야 한다.

public static void main(String args[])
{
  XMLReader xr = XMLReaderFactory.createXMLReader();
}

여기서 만든 오브젝트를 사용하면 XML문서를 파싱할 수 있는데, 먼저 XMLReader 인터페이스에서 setContentHandler와 setErrorHandler 메소드를 사용해 파싱결과와 에러가 발생했을 때 리포트하는데 사용되는 핸들러를 등록해 줘야 한다. 일반적으로 실제 복잡한 어플리케이션에서는 핸들러를 별도의 오브젝트로 만들지만 여기서는 간단한 데모를 위해 부모 클래스에 포함시켰기 때문에 단순히 클래스를 인스턴스화 해서 XML reader에 등록하면 된다.

public static void main(String args[])
{
  XMLReader xr = XMLReaderFactory.createXMLReader();
  MySAXApp handler = new MySAXApp();
  xr.setContentHandler(handler);
  xr.setErrorHandler(handler);
}

위의 예제 코드는 파싱 이벤트를 처리할 MySAXApp 인스턴스를 만들어 그 인스턴스를 XML reader의 정상적인 컨텐트 이벤트와 에러 이벤트 핸들러로 등록한다.

public static void main(String args[])
{
  XMLReader xr = XMLReaderFactory.createXMLReader();
  MySAXApp handler = new MySAXApp();
  xr.setContentHandler(handler);
  xr.setErrorHandler(handler);

  for (int i=0; i<args.length;i++) {
    FileReader r = new FileReader(args[i]);
    xr.parse(new InputSource(r));
  }
}

지금까지 설명한 예제의 전체 코드는 다음과 같다.

import java.io.FileReader;

import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;

public class MySAXApp extends DefaultHandler
{
  public static void main (String args[])
    throws Exception {
      XMLReader xr = XMLReaderFactory.createXMLReader();
      MySAXApp handler = new MySAXApp();
      xr.setContentHandler(handler);
      xr.setErrorHandler(handler);

      // Parse each file provided on the
      // command line.
      for (int i = 0; i < args.length; i++) {
        FileReader r = new FileReader(args[i]);
        xr.parse(new InputSource(r));
      }
    }

  public MySAXApp ()
  {
    super();
  }
}

예제 코드를 컴파일해서 실행시키면 XML문서를 파싱하지만 그에 따라 발생하는 이벤트를 처리하는 코드가 빠져있기 때문에 XML문서의 포맷이 틀려 에러가 발생하지 않으면 아무 출력도 없이 실행이 종료된다.


이벤트 처리

XML 문서를 처리하려면 파싱하면서 발생하는 이벤트를 처리해주는 핸들러를 만들어 줘야 한다. 먼저 가장 중요한 이벤트로 문서의 시작과 끝, 엘리먼트의 시작과 끝, 문자데이터가 있다.
클라이언트 어플리케이션이 문서의 시작과 끝을 발견하려면 startDocument/endDocument 메소드를 구현해줘야 한다.

public void startDocument()
{
  System.out.println("Start document");
}

public void endDocument()
{
  System.out.println("End document");
}

startDocument/endDocument 핸들러는 별도 argument를 가지지 않는다. SAX드라이버가 문서의 시작을 발견하면 startDocument 메소드를 호출한다. 또한 문서의 끝을 발견하면 (심지어 문서 내에 에러가 있더라도) endDocument 메소드를 호출한다.

예제 코드에서는 단순히 stdout에 메시지를 출력하지만 실제 어플리케이션에서는 핸들러 내에 어떤 코드도 넣어줄 수 있다. 가장 빈번히 볼 수 있는 코드는 메모리 상주 트리 생성, 내용 출력, 데이터베이스 생성/추가, 정보추출등을 수행한다.

SAX드라이버는 엘리먼트의 시작과 끝도 동일한 방법을 사용해 알려준다. 다만 startElement와 endElement 메소드는 몇개의 argument를 같이 넘겨준다.

public void startElement(String uri, String name, String qName, Attributes atts)
{
  if ("".equals(uri)) System.out.println("Start elements: " + qName);
  else System.out.println("Start elements: {" + uri + "}" + name);
}

public void endElement(String uri, String name, String qName)
{
  if ("".equals(uri)) System.out.println("End elements: " + qName);
  else System.out.println("End elements: {" + uri + "}" + name);
}

이 메소드들은 엘리먼트의 시작과 끝을 알려주는데 엘리먼트에 Namespace URI가 정의되어 있으면 '{URI namespace}이름' 형태로 출력하고 그렇지 않은 경우는 단순히 '이름' 형태로 출력해준다. qName에 XML 1.0 이름이 들어가 있기 때문에 namespace URI가 없는 엘리먼트의 경우 이 이름을 사용해줘야만 한다.

마지막으로 SAX2는 일반 문자데이터는 characters 메소드를 통해 리포트한다. 아래의 코드는 모든 문자데이터를 화면에 출력하는데 특수문자를 escape-character를 사용해 표현하기 때문에 보기좋게 출력하기 위해 코드가 좀 길다.

public void characters (char ch[], int start, int length)
{
  System.out.print("Characters:    \"");
  for (int i = start; i < start + length; i++) {
    switch (ch[i]) {
      case '\\':
        System.out.print("\\\\");
        break;
      case '"':
        System.out.print("\\\"");
        break;
      case '\n':
        System.out.print("\\n");
        break;
      case '\r':
        System.out.print("\\r");
        break;
      case '\t':
        System.out.print("\\t");
        break;
      default:
        System.out.print(ch[i]);
        break;
    }
  }
  System.out.print("\"\n");
}

지금까지 설명된 내용을 모두 추가한 예제 프로그램이다.

import java.io.FileReader;

import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;

public class MySAXApp extends DefaultHandler
{
    public static void main (String args[])
        throws Exception
    {
        XMLReader xr = XMLReaderFactory.createXMLReader();
        MySAXApp handler = new MySAXApp();
        xr.setContentHandler(handler);
        xr.setErrorHandler(handler);

        // Parse each file provided on the command line.
        for (int i = 0; i < args.length; i++) {
            FileReader r = new FileReader(args[i]);
            xr.parse(new InputSource(r));
        }
    }

    public MySAXApp ()
    {
        super();
    }

    ////////////////////////////////////////////////////////////////////
    // Event handlers.
    ////////////////////////////////////////////////////////////////////

    public void startDocument ()
    {
        System.out.println("Start document");
    }

    public void endDocument ()
    {
        System.out.println("End document");
    }

    public void startElement (String uri, String name,
                              String qName, Attributes atts)
    {
        if ("".equals (uri))
            System.out.println("Start element: " + qName);
        else
            System.out.println("Start element: {" + uri + "}" + name);
    }

    public void endElement (String uri, String name, String qName)
    {
        if ("".equals (uri))
            System.out.println("End element: " + qName);
        else
            System.out.println("End element:   {" + uri + "}" + name);
    }

    public void characters (char ch[], int start, int length)
    {
        System.out.print("Characters:    \"");
        for (int i = start; i < start + length; i++) {
            switch (ch[i]) {
            case '\\':
                System.out.print("\\\\");
                break;
            case '"':
                System.out.print("\\\"");
                break;
            case '\n':
                System.out.print("\\n");
                break;
            case '\r':
                System.out.print("\\r");
                break;
            case '\t':
                System.out.print("\\t");
                break;
            default:
                System.out.print(ch[i]);
                break;
            }
        }
        System.out.print("\"\n");
    }
}

샘플 출력

예제에 사용할 XML파일이다.

<?xml version="1.0"?>

<poem xmlns="http://www.megginson.com/ns/exp/poetry">
<title>Roses are Red</title>
<l>Roses are red,</l>
<l>Violets are blue;</l>
<l>Sugar is sweet,</l>
<l>And I love you.</l>
</poem>

이 문서파일 이름을 roses.xml이라고 하고 com.example.xml.SAXDriver 클래스 패스에 SAX2 드라이버가 있으면 샘플 어플리케이션은 다음과 같이 실행하면 된다.

java -Dorg.xml.sax.driver=com.example.xml.SAXDriver MySAXApp roses.xml

실행 결과는 다음과 같다.

Start documentStart element: {http://www.megginson.com/ns/exp/poetry}poem
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}title
Characters:    "Roses are Red"
End element:   {http://www.megginson.com/ns/exp/poetry}title
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "Roses are red,"
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "Violets are blue;"
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "Sugar is sweet,"
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "And I love you."
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
End element:   {http://www.megginson.com/ns/exp/poetry}poem
End document

이렇게 짧은 문서라도 (최소) 25개의 이벤트가 발생하는걸 볼 수 있다. 문서의 시작과 끝에 각각 하나씩, 6개의 엘리먼트마다 시작과 끝, 11개의 문자데이터(엘리먼트 사이의 공백을 포함)모든 엘리먼트의 네임스페이스를 선언하기 위한 xmlns="http://www.magginson.com/ns/exp/poetry" 속성이 포함되어 있으면 위와 같은 결과가 나오지만 xmlns 속성이 정의되어 있지 않았을때의 출력은 다음과 같다.

Start document
Start element: poem
Characters:    "\n"
Start element: title
Characters:    "Roses are Red"
End element:   title
Characters:    "\n"
Start element: l
Characters:    "Roses are red,"
End element:   l
Characters:    "\n"
Start element: l
Characters:    "Violets are blue;"
End element:   l
Characters:    "\n"
Start element: l
Characters:    "Sugar is sweet,"
End element:   l
Characters:    "\n"
Start element: l
Characters:    "And I love you."
End element:   l
Characters:    "\n"
End element:   poem
End document

어플리케이션은 XML 네임스페이스가 있는 경우와 없는 경우 두가지 타입의 문서를 모두 사용하게 될 수 있다. 또한 문서 내 일부 엘리먼트는 네임스페이스를 가지고 있고 나머지는 그렇지 않은 문서를 다룰수도 있기 때문에 항상 네임스페이스가 있거나 또는 없다고 가정하지 말고 코드내에서 실제로 네임스페이스 URI를 확인해야만 한다.