KO GmbH에 의해서 시작된 WebODFOpenDocument Format(ODF)를 웹사이트에서 사용할 수 있도록 CSS와 HTML을 이용하여 문서를 출력하거나 생성할 수 있게 하는 자바스크립트 라이브러리입니다. 실시간 협업 편집 서비스인 ownCloud와 문서를 손쉽게 웹페이지에 추가할 수 있게 하는 ViewerJS 등에서 WebODF를 사용하고 있습니다.

ODF는 여러 업무용 애플리케이션에서 사용할 수 있도록 고안되었습니다. XML기반의 파일 포맷이고 스프레드시트, 차트, 프리젠테이션 그리고 워드 프로세싱 등의 사무용 문서를 저장할 수 있습니다. 이 파일 포맷은 Microsoft의 오피스에서 지원되며, 선 마이크로스시템즈OpenOffice에서 사용었고 지금은 순수 웹 브라우저만으로도 다룰 수 있게 된 것입니다.

이 라이브러리는 뷰어와 에디터로 구분하여 사용할 수 있는데, 단순히 ODF 파일을 보여주기만 할 것이라면 webodf.js를 로드하고 다음과 같이 코드를 작성하면 됩니다.

<html>
 <head>
  <script src="webodf.js" type="text/javascript" charset="utf-8"></script>
  <script type="text/javascript" charset="utf-8">
function init() {
  var odfelement = document.getElementById("odf"),
      odfcanvas = new odf.OdfCanvas(odfelement);
  odfcanvas.load("myfile.odt");
}
window.setTimeout(init, 0);
  </script>
 </head>
 <body>
  <div id="odf"></div>
 </body>
</html>

webodf.js는 여러 개의 자바스크립트가 하나로 합쳐져 만들어진 것입니다. 소스는 GitHub 리파지토리에서 확인하거나 다운로드 할 수 있으며, CMake를 이용하여 빌드하는 방법은 다음과 같습니다.

git clone https://github.com/kogmbh/WebODF.git webodf
mkdir build
cd build
cmake ../webodf
make webodf.js-target

에디터는 일반 편집과 협업 편집모드를 제공하며, Wodo.TextEditor라는 컴포넌트를 필요로 합니다. Wodo.TextEditor는 Dojo를 기반으로 작성되었으며, webodf.js를 포함하고 있습니다. 다음과 같은 코드로 페이지에 추가할 수 있습니다.

<head>
<!-- ... -->

<script src="wodotexteditor/wodotexteditor.js" type="text/javascript" charset="utf-8"></script>

<!-- ... -->
</head>
<body>
<!-- ... -->

<div id="editorContainer" style="width: 600px;height: 600px;"></div>

<!-- ... -->

<script type="text/javascript">
// ...

Wodo.createTextEditor('editorContainer', {
    allFeaturesEnabled: true,
    userData: {
        fullName: "Tim Lee",
        color:    "blue"
    }
}, function (err, editor) {
    if (err) {
        // something failed unexpectedly, deal with it (here just a simple alert)
        alert(err);
        return;
    }
    editor.openDocumentFromUrl("document.odt", function(err) {
        if (err) {
            // something failed unexpectedly, deal with it (here just a simple alert)
            alert("There was an error on opening the document: " + err);
        }
    });
});

// ...
</script>

<!-- ... -->

</body>

Node.js용 프로젝트인 Etherpad에서 제공하는 것과 같은 협업모드 편집은 웹소켓을 이용한 것으로 서버 컴포넌트가 필요하지만 서버-사이드의 소스코드는 공개되어 있지 않습니다. 단, "programs/editor/backend/pullbox"에 클라이언트-사이드에서 데이터 송/수신을 위한 구현한 코드가 포함했으니 참고하라고 하네요. 이 과정은100% 클라이언트(브라우저)에서 발생하며, 데이터를 서버로 전송하지는 않는다고 합니다.

Comments

일반적으로 C언어에서 struct에 의해 구조화된 바이너리 데이터를 자바스크립트에서 사용할 수 있게 변환하고 이를 다시 조작하여 바이너리로 생성하는 과정이 엘레강스(?)하지가 않아서 C의 struct와 유사하게 자바스크립트에서도 구조체를 사용할 수 있도록 작은 유틸리티를 만들었습니다. 만들고 보니, 다른 곳에서도 유용하게 사용될 수 있을 것 같아 공개합니다.

우선, 자바스크립트만으로 ArrayBuffer를 다루어 보겠습니다. 예제에 사용되는 바이너리 데이터는 0번째 번지에 Uint8 유형의 정수, 1번째 번지에 Int8 유형의 정수, 3번째 번지에는 Uint16 유형의 2바이트짜리 정수이며, 이를 쓰고 다시 읽어내는 것입니다:

// define struct
var struct = {
  foo: 255,
  bar: 127,
  baz: {
    qux: 65535
  }
};

// write arraybuffer from javascript object
var ab = new ArrayBuffer(4);
var dv = new DataView(ab);
dv.setUint8(0, struct.foo);
dv.setInt8(1, struct.bar);
dv.setUint16(2, struct.baz.qux, true);

console.log(dv.buffer);
// => ArrayBuffer {byteLength: 4, slice: function}

// read data from arraybuffer
var dv2 = new DataView(dv.buffer);
var data = {
  foo: dv2.getUint8(0),
  bar: dv2.getInt8(1),
  baz: {
    qux: dv2.getUint16(2, true)
  }
};

console.log(data);
// => Obejct {foo: 255, bar: 127, baz: {qux: 65535}}}

옵셋을 손으로 패딩해야하며 형식이 동일한 구조의 데이터를 읽고 생성하려 했지만 도저히 같다고는 느껴지지 않습니다. 그리고 버퍼의 크기가 크면 클수록 사용성이 떨어지는 문제도 있습니다. struct.js를 사용하면 다음과 같이 코드를 작성할 수 있습니다:

// define struct
var struct = new Struct({
  foo: ['uint8', 255],
  bar: ['int8', 127], 
  baz: {
    qux: ['uint16', 65535]
  }
}, 0, true);

// write arraybuffer from javascript object
var ab = struct.write();
console.log(ab);
// => ArrayBuffer {byteLength: 4, slice: function}

// read data from arraybuffer
var data = struct.read(ab);
console.log(data);
// => Obejct {foo: 255, bar: 127, baz: {qux: 65535}}

옵셋을 자동으로 카운트하고, 자바스크립트 형식으로 작성한 데이터 구조체를 재활용하여 새로운 arraybuffer를 생성하거나 반대로 자바스립트에서 읽을 수 있는 데이터로 만들어 사용하기가 수월합니다. 이는 마치 C에서 생성하는 구조체를 사용하는 느낌입니다.

속성(키)/[유형(타입), 값(밸류)]로 구조를 작성해야 하며 '속성/유형'만 지정하면 버퍼를 작성하는 경우 기본값이 할당됩니다. 즉, '밸류'는 write 메서드를 이용하여 ArrayBuffer를 생성하는 곳에만 사용되며, 단순히 read 메서드로 데이터를 읽기만 한다거나, 속성마다 특정한 값을 설정할 필요가 없는 경우라면 타입만 지정해도 된다는 의미입니다. 다음은 read 메서드의 두 번째 인자에 사용자 지정 옵셋을 입력하여 동일한 형식의 데이터가 복수로 담긴 청크를 처리하는 모습입니다.

/**
 * read multiple data with custom offset
 */

var struct = new Struct({
  sig: 'uint8',
  mimeType: 'uint8',
  id: 'uint16',
  byteLength: 'uint32'
});

...

function parseBinary(chunk, count, callback) {
  var offset = 0;
  for (var index = 0; index < count; index++) {
    var meta = struct.read(chunk, offset)
      , buffer = chunk.slice(
        offset += struct.byteLength,
        offset += meta.byteLength
      );

    callback(meta, new Uint8Array(buffer));
  }
}

write 메서드에 변경할 내용이 담긴 객체를 인자로 전달하여 복수의 값을 갱신할 수 있도 있습니다. 입력 객체는 하위 구조의 값까지 모두 비교하여 값을 할당하기 때문에 다음과 같이 작성해도 무방합니다.(jquery의 $.extend와는 개념이 다름)

// define struct
var struct = new Struct({
  foo: ['uint8', 255],
  bar: 'int8', 
  baz: {
    qux: ['uint16', 65535],
    quux: ['uint32', 0]
  }
}, 1, true);

// update values and write arraybuffer
var ab = struct.write({
  foo: 0,
  baz: {
    quux: 4294967295
  }
});
// write => ArrayBuffer {byteLength: 8, slice: function}
// read => Obejct {foo: 0, bar: 1, baz: {qux: 65535, quux: 4294967295}}

끝으로, 하나의 속성에 멀티-바이트 타입 배열을 지정할 수 있도록 했습니다. 각각의 번지마다 연속된 값(문자열 또는 배열)을 지정할 수 있으며, 속성에 지정되는 배열의 마지막에 버퍼의 크기를 지정하거나 생략한 경우 그 크기를 자동으로 계산하합니다. 그리고 문자열 형식으로 선언된 속성은 버퍼에서 값을 읽어 올 때 정수들을 모두 문자열로 자동 변환하여 반환합니다.

/**
 * create struct with multi-byte value
 */

var struct = new Struct({
  foo: ['uint16', [0xffff, 4095]],
  bar: ['uint8', 'firejune', 8]
});

var ab = struct.write();
// => ArrayBuffer {byteLength: 12, slice: function}

var obj = struct.read(ab);
// => Object {bar: [65535, 4095], qux: "firejune"}

처음 구성한 구조의 크기(유형)는 변경될 수 없으며, 값만 갱신할 수 있는 규칙을 가집니다. 이 작은 유틸리티의 이름은 거창하게도 struct.js이며, MIT 라이센스를 따릅니다. 대략적인 사용법과 소스코드를 GitHub에 올려 두었으니 필요하신 분은 맘껏 사용하세요.

Comments

자바스크립트에서 버퍼를 읽거나 쓰는 예문들을 볼때 C에서 사용되는 'signed'와 'unsigned'라는 키워드에 비유하는 내용을 자주 접하게 됩니다. 이게 무엇이고 왜 구분을 해야 하는 것인지를 몰라서 우리 팀장님께 커피 한 잔 사드리고 특강을 받았습니다. 제가 이해하기 쉽게 '음수를 표현하느냐 안 하느냐의 차이'라고 알려 주셨고, 메모리에 비트를 기록하는 방식이 다르다고 했습니다. "더 자세히 설명해 주세요~"했더니 CPU가 어쩌니 어셈블리가 저쩌니 한 귀로 듣고 흘려 버릴수 밖에 없는 내용이어서... 나중을 대비해 나름 이해한 내용을 정리합니다.

예를 들어 C언어에서는 다음과 같이 8비트 정수 타입을 선언할 수 있습니다.

signed char
unsigned char

unsigned char는 비트를 투명하게 볼 수 있는 특성이 있으며, 임의의 메모리에 바이트 단위로 접근해서 값을 다룰 수 있습디다. 이 경우에는 unsigned char를 사용하는 것이 강제됩니다. 그리고 signed char는 unsigned char와 값이 같아도 같지 않은 경우가 발생할 수도 있습니다. 왜냐하면 signed는 음수 표현을 위해 2의 보수 체계를 사용하고 부호 비트(MSB)가 필요하기 때문입니다.(0 이면 양수 1 이면 음수) 그래서 부호 비트가 없는 unsigned는 양수 범위를 두 배로 늘리는 역활을 한답디다. 즉, char 형식은 8비트이므로 signed char은 -128~127의 범위를 표현할 수 있고 unsigned chare은 0~255의 표현범위를 가지는 것입니다.

조금더 이해하기 쉽게 그림으로 예를 들어봅시다. 정수 3인 1바이트(8비트)를 2진수로 기록하면 00000011이 됩니다. Unsigned에서는 다음과 같이 부호가 할당되겠죠.

0 0 0 0 0 0 1 1

Signed는 음수를 표현하기 위해 제일 앞 하나의 비트를 소비한다고 했습니다. 그럼 정수 3은 이렇게 되겠군요.

0 0 0 0 0 0 1 1

온라인 이진수-정수 변환기를 사용해 보면 금방 이해할 수 있습니다.

(나도 언젠가는 CPU의 마음과 메모리의 정신을 이해할 수 있겠...)

Comments