Back out Bug 450717
[wine-gecko.git] / db / morkreader / nsMorkReader.cpp
blob10d3bb9ed866cd9791213cae481d537e52e2158f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is the Mork Reader.
17 * The Initial Developer of the Original Code is
18 * Google Inc.
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Brian Ryner <bryner@brianryner.com> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsMorkReader.h"
40 #include "prio.h"
41 #include "nsNetUtil.h"
42 #include "nsVoidArray.h"
44 // A FixedString implementation that can hold 2 80-character lines
45 class nsCLineString : public nsFixedCString
47 public:
48 nsCLineString() : fixed_string_type(mStorage, sizeof(mStorage), 0) {}
49 explicit nsCLineString(const substring_type& str)
50 : fixed_string_type(mStorage, sizeof(mStorage), 0)
52 Assign(str);
55 private:
56 char_type mStorage[160];
59 // Convert a hex character (0-9, A-F) to its corresponding byte value.
60 // The character pointed to by 'c' is modified in place.
61 inline PRBool
62 ConvertChar(char *c)
64 char c1 = *c;
65 if ('0' <= c1 && c1 <= '9') {
66 *c = c1 - '0';
67 return PR_TRUE;
69 if ('A' <= c1 && c1 <= 'F') {
70 *c = c1 - 'A' + 10;
71 return PR_TRUE;
73 return PR_FALSE;
76 // Unescape a Mork value. Mork uses $xx escaping to encode non-ASCII
77 // characters. Additionally, '$' and '\' are backslash-escaped.
78 // The result of the unescape is in returned into aResult.
80 static void
81 MorkUnescape(const nsCSubstring &aString, nsCString &aResult)
83 PRUint32 len = aString.Length();
85 // We optimize for speed over space here -- size the result buffer to
86 // the size of the source, which is an upper bound on the size of the
87 // unescaped string.
88 // FIXME: Mork assume there will never be errors
89 if (!EnsureStringLength(aResult, len)) {
90 aResult.Truncate();
91 return; // out of memory.
94 char *result = aResult.BeginWriting();
95 const char *source = aString.BeginReading();
96 const char *sourceEnd = source + len;
98 const char *startPos = nsnull;
99 PRUint32 bytes;
100 for (; source < sourceEnd; ++source) {
101 char c = *source;
102 if (c == '\\') {
103 if (startPos) {
104 bytes = source - startPos;
105 memcpy(result, startPos, bytes);
106 result += bytes;
107 startPos = nsnull;
109 if (source < sourceEnd - 1) {
110 *(result++) = *(++source);
112 } else if (c == '$') {
113 if (startPos) {
114 bytes = source - startPos;
115 memcpy(result, startPos, bytes);
116 result += bytes;
117 startPos = nsnull;
119 if (source < sourceEnd - 2) {
120 // Would be nice to use ToInteger() here, but it currently
121 // requires a null-terminated string.
122 char c2 = *(++source);
123 char c3 = *(++source);
124 if (ConvertChar(&c2) && ConvertChar(&c3)) {
125 *(result++) = ((c2 << 4) | c3);
128 } else if (!startPos) {
129 startPos = source;
132 if (startPos) {
133 bytes = source - startPos;
134 memcpy(result, startPos, bytes);
135 result += bytes;
137 aResult.SetLength(result - aResult.BeginReading());
140 nsresult
141 nsMorkReader::Init()
143 NS_ENSURE_TRUE(mValueMap.Init(), NS_ERROR_OUT_OF_MEMORY);
144 NS_ENSURE_TRUE(mTable.Init(), NS_ERROR_OUT_OF_MEMORY);
145 return NS_OK;
148 PR_STATIC_CALLBACK(PLDHashOperator)
149 DeleteStringArray(const nsCSubstring& aKey,
150 nsTArray<nsCString> *aData,
151 void *aUserArg)
153 delete aData;
154 return PL_DHASH_NEXT;
157 nsMorkReader::~nsMorkReader()
159 mTable.EnumerateRead(DeleteStringArray, nsnull);
162 struct AddColumnClosure
164 AddColumnClosure(nsTArray<nsMorkReader::MorkColumn> *a,
165 nsMorkReader::IndexMap *c)
166 : array(a), columnMap(c), result(NS_OK) {}
168 nsTArray<nsMorkReader::MorkColumn> *array;
169 nsMorkReader::IndexMap *columnMap;
170 nsresult result;
173 PR_STATIC_CALLBACK(PLDHashOperator)
174 AddColumn(const nsCSubstring &id, nsCString name, void *userData)
176 AddColumnClosure *closure = static_cast<AddColumnClosure*>(userData);
177 nsTArray<nsMorkReader::MorkColumn> *array = closure->array;
179 if (!array->AppendElement(nsMorkReader::MorkColumn(id, name)) ||
180 !closure->columnMap->Put(id, array->Length() - 1)) {
181 closure->result = NS_ERROR_OUT_OF_MEMORY;
182 return PL_DHASH_STOP;
185 return PL_DHASH_NEXT;
188 nsresult
189 nsMorkReader::Read(nsIFile *aFile)
191 nsCOMPtr<nsIFileInputStream> stream =
192 do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID);
193 NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
195 nsresult rv = stream->Init(aFile, PR_RDONLY, 0, 0);
196 NS_ENSURE_SUCCESS(rv, rv);
198 mStream = do_QueryInterface(stream);
199 NS_ASSERTION(mStream, "file input stream must impl nsILineInputStream");
201 nsCLineString line;
202 rv = ReadLine(line);
203 if (!line.EqualsLiteral("// <!-- <mdb:mork:z v=\"1.4\"/> -->")) {
204 return NS_ERROR_FAILURE; // unexpected file format
207 IndexMap columnMap;
208 NS_ENSURE_TRUE(columnMap.Init(), NS_ERROR_OUT_OF_MEMORY);
210 while (NS_SUCCEEDED(ReadLine(line))) {
211 // Trim off leading spaces
212 PRUint32 idx = 0, len = line.Length();
213 while (idx < len && line[idx] == ' ') {
214 ++idx;
216 if (idx >= len) {
217 continue;
220 const nsCSubstring &l = Substring(line, idx);
222 // Look at the line to figure out what section type this is
223 if (StringBeginsWith(l, NS_LITERAL_CSTRING("< <(a=c)>"))) {
224 // Column map. We begin by creating a hash of column id to column name.
225 StringMap columnNameMap;
226 NS_ENSURE_TRUE(columnNameMap.Init(), NS_ERROR_OUT_OF_MEMORY);
228 rv = ParseMap(l, &columnNameMap);
229 NS_ENSURE_SUCCESS(rv, rv);
231 // Now that we have the list of columns, we put them into a flat array.
232 // Rows will have value arrays of the same size, with indexes that
233 // correspond to the columns array. As we insert each column into the
234 // array, we also make an entry in columnMap so that we can look up the
235 // index given the column id.
236 mColumns.SetCapacity(columnNameMap.Count());
238 AddColumnClosure closure(&mColumns, &columnMap);
239 columnNameMap.EnumerateRead(AddColumn, &closure);
240 if (NS_FAILED(closure.result)) {
241 return closure.result;
243 } else if (StringBeginsWith(l, NS_LITERAL_CSTRING("<("))) {
244 // Value map
245 rv = ParseMap(l, &mValueMap);
246 NS_ENSURE_SUCCESS(rv, rv);
247 } else if (l[0] == '{' || l[0] == '[') {
248 // Table / table row
249 rv = ParseTable(l, columnMap);
250 NS_ENSURE_SUCCESS(rv, rv);
251 } else {
252 // Don't know, hopefully don't care
256 return NS_OK;
259 void
260 nsMorkReader::EnumerateRows(RowEnumerator aCallback, void *aUserData) const
262 // Constify the table values
263 typedef const nsDataHashtable<IDKey, const nsTArray<nsCString>* > ConstTable;
264 reinterpret_cast<ConstTable*>(&mTable)->EnumerateRead(aCallback,
265 aUserData);
268 // Parses a key/value map of the form
269 // <(k1=v1)(k2=v2)...>
271 nsresult
272 nsMorkReader::ParseMap(const nsCSubstring &aLine, StringMap *aMap)
274 nsCLineString line(aLine);
275 nsCAutoString key;
276 nsresult rv = NS_OK;
278 // If the first line is the a=c line (column map), just skip over it.
279 if (StringBeginsWith(line, NS_LITERAL_CSTRING("< <(a=c)>"))) {
280 rv = ReadLine(line);
283 for (; NS_SUCCEEDED(rv); rv = ReadLine(line)) {
284 PRUint32 idx = 0;
285 PRUint32 len = line.Length();
286 PRUint32 tokenStart;
288 while (idx < len) {
289 switch (line[idx++]) {
290 case '(':
291 // Beginning of a key/value pair
292 if (!key.IsEmpty()) {
293 NS_WARNING("unterminated key/value pair?");
294 key.Truncate(0);
297 tokenStart = idx;
298 while (idx < len && line[idx] != '=') {
299 ++idx;
301 key = Substring(line, tokenStart, idx - tokenStart);
302 break;
303 case '=':
305 // Beginning of the value
306 if (key.IsEmpty()) {
307 NS_WARNING("stray value");
308 break;
311 tokenStart = idx;
312 while (idx < len && line[idx] != ')') {
313 if (line[idx] == '\\') {
314 ++idx; // skip escaped ')' characters
316 ++idx;
318 PRUint32 tokenEnd = PR_MIN(idx, len);
319 ++idx;
321 nsCString value;
322 MorkUnescape(Substring(line, tokenStart, tokenEnd - tokenStart),
323 value);
324 aMap->Put(key, value);
325 key.Truncate(0);
326 break;
328 case '>':
329 // End of the map.
330 NS_WARN_IF_FALSE(key.IsEmpty(),
331 "map terminates inside of key/value pair");
332 return NS_OK;
337 // We ran out of lines and the map never terminated. This probably indicates
338 // a parsing error.
339 NS_WARNING("didn't find end of key/value map");
340 return NS_ERROR_FAILURE;
343 // Parses a table row of the form [123(^45^67)..]
344 // (row id 123 has the value with id 67 for the column with id 45).
345 // A '^' prefix for a column or value references an entry in the column or
346 // value map. '=' is used as the separator when the value is a literal.
348 nsresult
349 nsMorkReader::ParseTable(const nsCSubstring &aLine, const IndexMap &aColumnMap)
351 nsCLineString line(aLine);
352 const PRUint32 columnCount = mColumns.Length(); // total number of columns
354 PRInt32 columnIndex = -1; // column index of the cell we're parsing
355 // value array for the row we're parsing
356 nsTArray<nsCString> *currentRow = nsnull;
357 PRBool inMetaRow = PR_FALSE;
359 do {
360 PRUint32 idx = 0;
361 PRUint32 len = line.Length();
362 PRUint32 tokenStart, tokenEnd;
364 while (idx < len) {
365 switch (line[idx++]) {
366 case '{':
367 // This marks the beginning of a table section. There's a lot of
368 // junk before the first row that looks like cell values but isn't.
369 // Skip to the first '['.
370 while (idx < len && line[idx] != '[') {
371 if (line[idx] == '{') {
372 inMetaRow = PR_TRUE; // the meta row is enclosed in { }
373 } else if (line[idx] == '}') {
374 inMetaRow = PR_FALSE;
376 ++idx;
378 break;
379 case '[':
381 // Start of a new row. Consume the row id, up to the first '('.
382 // Row edits also have a table namespace, separated from the row id
383 // by a colon. We don't make use of the namespace, but we need to
384 // make sure not to consider it part of the row id.
385 if (currentRow) {
386 NS_WARNING("unterminated row?");
387 currentRow = nsnull;
390 // Check for a '-' at the start of the id. This signifies that
391 // if the row already exists, we should delete all columns from it
392 // before adding the new values.
393 PRBool cutColumns;
394 if (idx < len && line[idx] == '-') {
395 cutColumns = PR_TRUE;
396 ++idx;
397 } else {
398 cutColumns = PR_FALSE;
401 tokenStart = idx;
402 while (idx < len &&
403 line[idx] != '(' &&
404 line[idx] != ']' &&
405 line[idx] != ':') {
406 ++idx;
408 tokenEnd = idx;
409 while (idx < len && line[idx] != '(' && line[idx] != ']') {
410 ++idx;
413 if (inMetaRow) {
414 mMetaRow = NewVoidStringArray(columnCount);
415 NS_ENSURE_TRUE(mMetaRow, NS_ERROR_OUT_OF_MEMORY);
416 currentRow = mMetaRow;
417 } else {
418 const nsCSubstring& row = Substring(line, tokenStart,
419 tokenEnd - tokenStart);
420 if (!mTable.Get(row, &currentRow)) {
421 currentRow = NewVoidStringArray(columnCount);
422 NS_ENSURE_TRUE(currentRow, NS_ERROR_OUT_OF_MEMORY);
424 NS_ENSURE_TRUE(mTable.Put(row, currentRow),
425 NS_ERROR_OUT_OF_MEMORY);
428 if (cutColumns) {
429 // Set all of the columns to void
430 // (this differentiates them from columns which are empty strings).
431 for (PRUint32 i = 0; i < columnCount; ++i) {
432 currentRow->ElementAt(i).SetIsVoid(PR_TRUE);
435 break;
437 case ']':
438 // We're done with the row
439 currentRow = nsnull;
440 inMetaRow = PR_FALSE;
441 break;
442 case '(':
444 if (!currentRow) {
445 NS_WARNING("cell value outside of row");
446 break;
449 NS_WARN_IF_FALSE(columnIndex == -1, "unterminated cell?");
451 PRBool columnIsAtom;
452 if (line[idx] == '^') {
453 columnIsAtom = PR_TRUE;
454 ++idx; // this is not part of the column id, advance past it
455 } else {
456 columnIsAtom = PR_FALSE;
458 tokenStart = idx;
459 while (idx < len && line[idx] != '^' && line[idx] != '=') {
460 if (line[idx] == '\\') {
461 ++idx; // skip escaped characters
463 ++idx;
466 tokenEnd = PR_MIN(idx, len);
468 nsCAutoString column;
469 const nsCSubstring &colValue =
470 Substring(line, tokenStart, tokenEnd - tokenStart);
471 if (columnIsAtom) {
472 column.Assign(colValue);
473 } else {
474 MorkUnescape(colValue, column);
477 if (!aColumnMap.Get(colValue, &columnIndex)) {
478 NS_WARNING("Column not in column map, discarding it");
479 columnIndex = -1;
482 break;
483 case '=':
484 case '^':
486 if (columnIndex == -1) {
487 NS_WARNING("stray ^ or = marker");
488 break;
491 PRBool valueIsAtom = (line[idx - 1] == '^');
492 tokenStart = idx - 1; // include the '=' or '^' marker in the value
493 while (idx < len && line[idx] != ')') {
494 if (line[idx] == '\\') {
495 ++idx; // skip escaped characters
497 ++idx;
499 tokenEnd = PR_MIN(idx, len);
500 ++idx;
502 const nsCSubstring &value =
503 Substring(line, tokenStart, tokenEnd - tokenStart);
504 if (valueIsAtom) {
505 (*currentRow)[columnIndex] = value;
506 } else {
507 nsCAutoString value2;
508 MorkUnescape(value, value2);
509 (*currentRow)[columnIndex] = value2;
511 columnIndex = -1;
513 break;
516 } while (currentRow && NS_SUCCEEDED(ReadLine(line)));
518 return NS_OK;
521 nsresult
522 nsMorkReader::ReadLine(nsCString &aLine)
524 PRBool res;
525 nsresult rv = mStream->ReadLine(aLine, &res);
526 NS_ENSURE_SUCCESS(rv, rv);
527 if (!res) {
528 return NS_ERROR_NOT_AVAILABLE;
531 while (!aLine.IsEmpty() && aLine.Last() == '\\') {
532 // There is a continuation for this line. Read it and append.
533 nsCLineString line2;
534 rv = mStream->ReadLine(line2, &res);
535 NS_ENSURE_SUCCESS(rv, rv);
536 if (!res) {
537 return NS_ERROR_NOT_AVAILABLE;
539 aLine.Truncate(aLine.Length() - 1);
540 aLine.Append(line2);
543 return NS_OK;
546 void
547 nsMorkReader::NormalizeValue(nsCString &aValue) const
549 PRUint32 len = aValue.Length();
550 if (len == 0) {
551 return;
553 const nsCSubstring &str = Substring(aValue, 1, len - 1);
554 char c = aValue[0];
555 if (c == '^') {
556 if (!mValueMap.Get(str, &aValue)) {
557 aValue.Truncate(0);
559 } else if (c == '=') {
560 aValue.Assign(str);
561 } else {
562 aValue.Truncate(0);
566 /* static */ nsTArray<nsCString>*
567 nsMorkReader::NewVoidStringArray(PRInt32 aCount)
569 nsAutoPtr< nsTArray<nsCString> > array(new nsTArray<nsCString>(aCount));
570 NS_ENSURE_TRUE(array, nsnull);
572 for (PRInt32 i = 0; i < aCount; ++i) {
573 nsCString *elem = array->AppendElement();
574 NS_ENSURE_TRUE(elem, nsnull);
575 elem->SetIsVoid(PR_TRUE);
578 return array.forget();