blob: b6fadfcb56c1c21a905f163c47f493259521f8ce [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "CSSParser.h"
#include "CSS.h"
#include "DFHTML.h"
#include "DFString.h"
#include "DFCharacterSet.h"
#include "DFCommon.h"
#include "DFPlatform.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
static int matchPast(CSSParser *p, uint16_t endChar, int *invalid);
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// CSSParser //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
struct CSSParser {
char *chars;
size_t length;
size_t pos;
};
static void CSSParserSetError(CSSParser *p, const char *format, ...)
{
// This isn't really needed
}
CSSParser *CSSParserNew(const char *cinput)
{
CSSParser *p = (CSSParser *)xcalloc(1,sizeof(CSSParser));
if (cinput == NULL)
cinput = "";
p->chars = xstrdup(cinput);
p->length = strlen(cinput);
return p;
}
void CSSParserFree(CSSParser *p)
{
free(p->chars);
free(p);
}
static int match(CSSParser *p, uint16_t c)
{
if ((p->pos < p->length) && (p->chars[p->pos] == c)) {
p->pos++;
return 1;
}
else {
return 0;
}
}
static int matchTwo(CSSParser *p, uint16_t c1, uint16_t c2)
{
if ((p->pos+1 < p->length) && (p->chars[p->pos] == c1) && (p->chars[p->pos+1] == c2)) {
p->pos += 2;
return 1;
}
else {
return 0;
}
}
static int matchEscape(CSSParser *p)
{
if (match(p,'\\')) {
int hexCount = 0;
for (; hexCount < 6; hexCount++) {
if ((p->pos < p->length) && DFCharIsHex(p->chars[p->pos])) {
hexCount++;
p->pos++;
}
}
if (hexCount > 0) {
if (matchTwo(p,'\r','\n'))
return 1;
else if (match(p,' '))
return 1;
else if (match(p,'\t'))
return 1;
else if (match(p,'\r'))
return 1;
else if (match(p,'\n'))
return 1;
else if (match(p,'\f'))
return 1;
else
return 1;
}
else {
p->pos++;
return 1;
}
}
else {
return 0;
}
}
static int matchString(CSSParser *p, int *invalid)
{
uint16_t closeChar = 0;
if (match(p,'"'))
closeChar = '"';
else if (match(p,'\''))
closeChar = '\'';
else
return 0;
while (p->pos < p->length) {
if (match(p,'\n')) {
*invalid = 1;
return 1;
}
else if (matchEscape(p)) {
// nothing to do; pos already advanced
}
else if (match(p,closeChar)) {
return 1;
}
else {
p->pos++;
}
}
*invalid = 1;
return 1;
}
static int matchGeneric(CSSParser *p, int *invalid)
{
if (p->pos >= p->length)
return 1;
switch (p->chars[p->pos]) {
case '"':
case '\'':
return matchString(p,invalid);
case '(':
p->pos++;
return matchPast(p,')',invalid);
case '[':
p->pos++;
return matchPast(p,']',invalid);
case '{':
p->pos++;
return matchPast(p,'}',invalid);
default:
p->pos++;
return 1;
}
}
static int matchPast(CSSParser *p, uint16_t endChar, int *invalid)
{
while (p->pos < p->length) {
if (match(p,endChar))
return 1;
if (!matchGeneric(p,invalid))
return 0;
}
CSSParserSetError(p,"Missing %c", (char)endChar);
return 0;
}
static int matchBefore(CSSParser *p, uint16_t terminator, int *invalid)
{
while (p->pos < p->length) {
if (p->chars[p->pos] == terminator) {
return 1;
}
else if (!matchGeneric(p,invalid))
return 0;
}
return 1;
}
static char *trimmedSubstring(CSSParser *p, size_t start, size_t pos)
{
char *untrimmed = DFSubstring(p->chars,start,pos);
char *trimmed = DFStringTrimWhitespace(untrimmed);
free(untrimmed);
return trimmed;
}
DFHashTable *CSSParserRules(CSSParser *p)
{
DFHashTable *result = DFHashTableNew((DFCopyFunction)xstrdup,free);
while (p->pos < p->length) {
size_t start = p->pos;
int invalid = 0;
if (!matchBefore(p,'{',&invalid))
return result;
char *selectors = trimmedSubstring(p,start,p->pos);
if (strlen(selectors) == 0) {
free(selectors);
break;
}
if (!match(p,'{')) {
CSSParserSetError(p,"Expected {");
free(selectors);
return result;
}
start = p->pos;
if (!matchBefore(p,'}',&invalid)) {
free(selectors);
return result;
}
char *ruleBody = trimmedSubstring(p,start,p->pos);
if (!match(p,'}')) {
CSSParserSetError(p,"Expected }");
}
DFHashTableAdd(result,selectors,ruleBody);
free(selectors);
free(ruleBody);
}
return result;
}
DFArray *CSSParserSelectors(CSSParser *p)
{
DFArray *result = DFArrayNew((DFCopyFunction)xstrdup,free);
while (p->pos < p->length) {
size_t start = p->pos;
int invalid = 0;
if (!matchBefore(p,',',&invalid))
return 0;
char *selector = trimmedSubstring(p,start,p->pos);
if (strlen(selector) == 0) {
free(selector);
continue;
}
DFArrayAppend(result,selector);
match(p,',');
free(selector);
}
return result;
}
DFHashTable *CSSParserProperties(CSSParser *p)
{
DFHashTable *result = DFHashTableNew((DFCopyFunction)xstrdup,(DFFreeFunction)free);
while (p->pos < p->length) {
size_t start = p->pos;
int invalid = 0;
if (!matchBefore(p,':',&invalid)) {
DFHashTableRelease(result);
return NULL;
}
char *name = trimmedSubstring(p,start,p->pos);
if (strlen(name) == 0) {
free(name);
break;
}
if (!match(p,':')) {
CSSParserSetError(p,"Missing :");
DFHashTableRelease(result);
free(name);
return NULL;
}
start = p->pos;
if (!matchBefore(p,';',&invalid)) {
DFHashTableRelease(result);
free(name);
return NULL;
}
if (!invalid) {
char *value = trimmedSubstring(p,start,p->pos);
DFHashTableAdd(result,name,value);
free(value);
}
match(p,';');
free(name);
}
return result;
}
static int matchSpecific(CSSParser *p, const char *string)
{
size_t start = p->pos;
size_t stringlen = strlen(string);
for (size_t i = 0; i < stringlen; i++) {
if (p->pos == p->length) {
p->pos = start;
return 0;
}
if ((p->chars[p->pos] != tolower(string[i])) &&
(p->chars[p->pos] != toupper(string[i]))) {
p->pos = start;
return 0;
}
p->pos++;
}
return 1;
}
static int matchIdent(CSSParser *p)
{
size_t start = p->pos;
while (p->pos < p->length) {
if (((p->chars[p->pos] >= 'a') && (p->chars[p->pos] <= 'z')) ||
((p->chars[p->pos] >= 'A') && (p->chars[p->pos] <= 'Z')) ||
(p->chars[p->pos] == '_') ||
((p->pos > start) && (p->chars[p->pos] == '-')) ||
((p->pos > start) && (p->chars[p->pos] >= '0') && (p->chars[p->pos <= '9']))) {
p->pos++;
}
else {
break;
}
}
return (p->pos > start);
}
static int matchWhitespace(CSSParser *p)
{
size_t start = p->pos;
while ((p->pos < p->length) && isspace(p->chars[p->pos]))
p->pos++;
return (p->pos > start);
}
DFArray *CSSParserContent(CSSParser *p)
{
DFArray *result = DFArrayNew((DFCopyFunction)ContentPartRetain,(DFFreeFunction)ContentPartRelease);
matchWhitespace(p);
while (p->pos < p->length) {
size_t start = p->pos;
switch (p->chars[p->pos]) {
case '"':
case '\'': {
int invalid = 0;
if (!matchString(p,&invalid))
return result;
char *quotedValue = trimmedSubstring(p,start,p->pos);
char *unquotedValue = DFUnquote(quotedValue);
ContentPart *part = ContentPartNew(ContentPartString,unquotedValue,NULL);
DFArrayAppend(result,part);
ContentPartRelease(part);
free(quotedValue);
free(unquotedValue);
if ((p->pos < p->length) && !matchWhitespace(p))
return result;
break;
}
case 'C':
case 'c': {
if (matchSpecific(p,"counters(")) {
// Not yet supported
return result;
}
else if (matchSpecific(p,"counter(")) {
size_t nameStart = p->pos;
if (!matchIdent(p))
return result;
char *name = DFSubstring(p->chars,nameStart,p->pos);
char *type = NULL;
if (match(p,',')) {
size_t typeStart = p->pos;
if (!matchIdent(p)) {
free(name);
free(type);
return result;
}
type = DFSubstring(p->chars,typeStart,p->pos);
}
ContentPart *part = ContentPartNew(ContentPartCounter,name,type);
DFArrayAppend(result,part);
ContentPartRelease(part);
if (!match(p,')')) {
free(name);
free(type);
return result;
}
if ((p->pos < p->length) && !matchWhitespace(p)) {
free(name);
free(type);
return result;
}
free(name);
free(type);
}
else {
return result;
}
break;
}
default:
return result;
}
}
return result;
}