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
15 * The Original Code is the Mork Reader.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
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"
41 #include "nsNetUtil.h"
42 #include "nsVoidArray.h"
44 // A FixedString implementation that can hold 2 80-character lines
45 class nsCLineString
: public nsFixedCString
48 nsCLineString() : fixed_string_type(mStorage
, sizeof(mStorage
), 0) {}
49 explicit nsCLineString(const substring_type
& str
)
50 : fixed_string_type(mStorage
, sizeof(mStorage
), 0)
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.
65 if ('0' <= c1
&& c1
<= '9') {
69 if ('A' <= c1
&& c1
<= 'F') {
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.
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
88 // FIXME: Mork assume there will never be errors
89 if (!EnsureStringLength(aResult
, len
)) {
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
;
100 for (; source
< sourceEnd
; ++source
) {
104 bytes
= source
- startPos
;
105 memcpy(result
, startPos
, bytes
);
109 if (source
< sourceEnd
- 1) {
110 *(result
++) = *(++source
);
112 } else if (c
== '$') {
114 bytes
= source
- startPos
;
115 memcpy(result
, startPos
, bytes
);
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
) {
133 bytes
= source
- startPos
;
134 memcpy(result
, startPos
, bytes
);
137 aResult
.SetLength(result
- aResult
.BeginReading());
143 NS_ENSURE_TRUE(mValueMap
.Init(), NS_ERROR_OUT_OF_MEMORY
);
144 NS_ENSURE_TRUE(mTable
.Init(), NS_ERROR_OUT_OF_MEMORY
);
148 PR_STATIC_CALLBACK(PLDHashOperator
)
149 DeleteStringArray(const nsCSubstring
& aKey
,
150 nsTArray
<nsCString
> *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
;
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
;
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");
203 if (!line
.EqualsLiteral("// <!-- <mdb:mork:z v=\"1.4\"/> -->")) {
204 return NS_ERROR_FAILURE
; // unexpected file format
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
] == ' ') {
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("<("))) {
245 rv
= ParseMap(l
, &mValueMap
);
246 NS_ENSURE_SUCCESS(rv
, rv
);
247 } else if (l
[0] == '{' || l
[0] == '[') {
249 rv
= ParseTable(l
, columnMap
);
250 NS_ENSURE_SUCCESS(rv
, rv
);
252 // Don't know, hopefully don't care
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
,
268 // Parses a key/value map of the form
269 // <(k1=v1)(k2=v2)...>
272 nsMorkReader::ParseMap(const nsCSubstring
&aLine
, StringMap
*aMap
)
274 nsCLineString
line(aLine
);
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)>"))) {
283 for (; NS_SUCCEEDED(rv
); rv
= ReadLine(line
)) {
285 PRUint32 len
= line
.Length();
289 switch (line
[idx
++]) {
291 // Beginning of a key/value pair
292 if (!key
.IsEmpty()) {
293 NS_WARNING("unterminated key/value pair?");
298 while (idx
< len
&& line
[idx
] != '=') {
301 key
= Substring(line
, tokenStart
, idx
- tokenStart
);
305 // Beginning of the value
307 NS_WARNING("stray value");
312 while (idx
< len
&& line
[idx
] != ')') {
313 if (line
[idx
] == '\\') {
314 ++idx
; // skip escaped ')' characters
318 PRUint32 tokenEnd
= PR_MIN(idx
, len
);
322 MorkUnescape(Substring(line
, tokenStart
, tokenEnd
- tokenStart
),
324 aMap
->Put(key
, value
);
330 NS_WARN_IF_FALSE(key
.IsEmpty(),
331 "map terminates inside of key/value pair");
337 // We ran out of lines and the map never terminated. This probably indicates
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.
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
;
361 PRUint32 len
= line
.Length();
362 PRUint32 tokenStart
, tokenEnd
;
365 switch (line
[idx
++]) {
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
;
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.
386 NS_WARNING("unterminated row?");
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.
394 if (idx
< len
&& line
[idx
] == '-') {
395 cutColumns
= PR_TRUE
;
398 cutColumns
= PR_FALSE
;
409 while (idx
< len
&& line
[idx
] != '(' && line
[idx
] != ']') {
414 mMetaRow
= NewVoidStringArray(columnCount
);
415 NS_ENSURE_TRUE(mMetaRow
, NS_ERROR_OUT_OF_MEMORY
);
416 currentRow
= mMetaRow
;
418 const nsCSubstring
& row
= Substring(line
, tokenStart
,
419 tokenEnd
- tokenStart
);
420 if (!mTable
.Get(row
, ¤tRow
)) {
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
);
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
);
438 // We're done with the row
440 inMetaRow
= PR_FALSE
;
445 NS_WARNING("cell value outside of row");
449 NS_WARN_IF_FALSE(columnIndex
== -1, "unterminated cell?");
452 if (line
[idx
] == '^') {
453 columnIsAtom
= PR_TRUE
;
454 ++idx
; // this is not part of the column id, advance past it
456 columnIsAtom
= PR_FALSE
;
459 while (idx
< len
&& line
[idx
] != '^' && line
[idx
] != '=') {
460 if (line
[idx
] == '\\') {
461 ++idx
; // skip escaped characters
466 tokenEnd
= PR_MIN(idx
, len
);
468 nsCAutoString column
;
469 const nsCSubstring
&colValue
=
470 Substring(line
, tokenStart
, tokenEnd
- tokenStart
);
472 column
.Assign(colValue
);
474 MorkUnescape(colValue
, column
);
477 if (!aColumnMap
.Get(colValue
, &columnIndex
)) {
478 NS_WARNING("Column not in column map, discarding it");
486 if (columnIndex
== -1) {
487 NS_WARNING("stray ^ or = marker");
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
499 tokenEnd
= PR_MIN(idx
, len
);
502 const nsCSubstring
&value
=
503 Substring(line
, tokenStart
, tokenEnd
- tokenStart
);
505 (*currentRow
)[columnIndex
] = value
;
507 nsCAutoString value2
;
508 MorkUnescape(value
, value2
);
509 (*currentRow
)[columnIndex
] = value2
;
516 } while (currentRow
&& NS_SUCCEEDED(ReadLine(line
)));
522 nsMorkReader::ReadLine(nsCString
&aLine
)
525 nsresult rv
= mStream
->ReadLine(aLine
, &res
);
526 NS_ENSURE_SUCCESS(rv
, rv
);
528 return NS_ERROR_NOT_AVAILABLE
;
531 while (!aLine
.IsEmpty() && aLine
.Last() == '\\') {
532 // There is a continuation for this line. Read it and append.
534 rv
= mStream
->ReadLine(line2
, &res
);
535 NS_ENSURE_SUCCESS(rv
, rv
);
537 return NS_ERROR_NOT_AVAILABLE
;
539 aLine
.Truncate(aLine
.Length() - 1);
547 nsMorkReader::NormalizeValue(nsCString
&aValue
) const
549 PRUint32 len
= aValue
.Length();
553 const nsCSubstring
&str
= Substring(aValue
, 1, len
- 1);
556 if (!mValueMap
.Get(str
, &aValue
)) {
559 } else if (c
== '=') {
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();