diff --git a/Makefile.am b/Makefile.am index 429a7ee046..4fbb1c67ea 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,6 +21,11 @@ MAIN_SRCS += $(REGEX_SRCS) MAIN_HEADS += $(REGEX_HEADS) endif +if HAVE_LIBXML +PARSER_SRCS += $(XML_SRCS) +PARSER_HEADS += $(XML_HEADS) +endif + ctags_CPPFLAGS = -I. -I$(srcdir) -I$(srcdir)/main if ENABLE_DEBUGGING ctags_CPPFLAGS+= $(DEBUG_CPPFLAGS) @@ -34,6 +39,10 @@ ctags_CFLAGS += $(EXTRA_CFLAGS) ctags_CFLAGS += $(WARNING_CFLAGS) ctags_CFLAGS += $(COVERAGE_CFLAGS) ctags_CFLAGS += $(CGCC_CFLAGS) +ctags_CFLAGS += $(LIBXML_CFLAGS) + +ctags_LDADD = +ctags_LDADD += $(LIBXML_LIBS) nodist_ctags_SOURCES = $(REPOINFO_HEADS) BUILT_SOURCES = $(REPOINFO_HEADS) diff --git a/configure.ac b/configure.ac index 9e4584f9cf..7e2cfbb018 100644 --- a/configure.ac +++ b/configure.ac @@ -584,6 +584,15 @@ AC_CHECK_FUNCS(scandir) AC_CHECK_FUNCS(fork waitpid execv pipe,[enable_xcmd=yes],[enable_xcmd=no]) AM_CONDITIONAL([ENABLE_XCMD], [test "xyes" = "x$enable_xcmd"]) +AH_TEMPLATE([HAVE_LIBXML], + [Define this value if libxml is available.]) +PKG_CHECK_MODULES(LIBXML, libxml-2.0, + [have_libxml=yes + AC_DEFINE(HAVE_LIBXML)], + [have_libxml=no]) +AM_CONDITIONAL(HAVE_LIBXML, test "x$have_libxml" = xyes) + + # Checks for missing prototypes # ----------------------------- AC_MSG_NOTICE(checking for new missing prototypes) diff --git a/main/lxpath.c b/main/lxpath.c new file mode 100644 index 0000000000..56d4693bad --- /dev/null +++ b/main/lxpath.c @@ -0,0 +1,183 @@ +/* +* Copyright (c) 2015, Masatake YAMATO +* Copyright (c) 2015, Red Hat, Inc. +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for applying regular expression matching. +* +* The code for utilizing the Gnu regex package with regards to processing the +* regex option and checking for regex matches was adapted from routines in +* Gnu etags. +*/ + +#include "general.h" /* must always come first */ +#include "debug.h" +#include "entry.h" +#include "options.h" +#include "parse.h" +#include "read.h" +#include "routines.h" +#include "xtag.h" + +#ifdef HAVE_LIBXML +#include +#include + +static void simpleXpathMakeTag (xmlNode *node, + const tagXpathMakeTagSpec *spec, + const kindOption* const kinds, + void *userData) +{ + tagEntryInfo tag; + xmlChar* str; + const kindOption *kind; + + str = xmlNodeGetContent(node); + if (str == NULL) + return; + + kind = kinds + spec->kind; + + if (spec->role == ROLE_INDEX_DEFINITION) + initTagEntry (&tag, (char *)str, kind); + else if (isXtagEnabled(XTAG_REFERENCE_TAGS)) + initRefTagEntry (&tag, (char *)str, + kind, + spec->role); + else + goto out; + + + tag.lineNumber = xmlGetLineNo (node); + tag.filePosition = getInputFilePositionForLine (tag.lineNumber); + + if (spec->make) + spec->make (node, spec, &tag, userData); + else + makeTagEntry (&tag); + +out: + xmlFree (str); +} + +extern void addTagXpath (const langType language, tagXpathTable *xpathTable) +{ + Assert (xpathTable->xpath); + Assert (!xpathTable->xpathCompiled); + + verbose ("compile a xpath expression: %s\n", (xmlChar *)xpathTable->xpath); + xpathTable->xpathCompiled = xmlXPathCompile ((xmlChar *)xpathTable->xpath); + if (!xpathTable->xpathCompiled) + error (WARNING, "Failed to compile the Xpath expression: %s", xpathTable->xpath); +} + +static void findXMLTagsCore (xmlXPathContext *ctx, xmlNode *root, + const tagXpathTableTable *xpathTableTable, + const kindOption* const kinds,void *userData) +{ + unsigned int i, j; + xmlNode * node; + + Assert (root); + Assert (xpathTableTable); + + for (i = 0; i < xpathTableTable->count; ++i) + { + xmlXPathObject *object; + xmlNodeSet *set; + const tagXpathTable *elt = xpathTableTable->table + i; + + if (! elt->xpathCompiled) + continue; + +#if 0 + /* Older version of libxml2 doesn't have xmlXPathSetContextNode. */ + if (xmlXPathSetContextNode (root, ctx) != 0) + { + error (WARNING, "Failed to set node to XpathContext"); + return; + } +#else + ctx->node = root; +#endif + + object = xmlXPathCompiledEval (elt->xpathCompiled, ctx); + if (!object) + continue; + + set = object->nodesetval; + + if (set) + { + for (j = 0; j < set->nodeNr; ++j) + { + node = set->nodeTab[j]; + if (elt->specType == LXPATH_TABLE_DO_MAKE) + simpleXpathMakeTag (node, &(elt->makeTagSpec), kinds, userData); + else + elt->recurSpec.enter (node, &(elt->recurSpec), ctx, userData); + } + } + xmlXPathFreeObject (object); + } +} + +extern void findXMLTags (xmlXPathContext *ctx, xmlNode *root, + const tagXpathTableTable *xpathTableTable, + const kindOption* const kinds,void *userData) +{ + boolean usedAsEnterPoint = FALSE; + xmlDocPtr doc = NULL; + + if (ctx == NULL) + { + usedAsEnterPoint = TRUE; + + findRegexTags (); + doc = xmlParseFile(getInputFileName()); + if (doc == NULL) + { + verbose ("could not parse %s as a XML file\n", getInputFileName()); + return; + } + + ctx = xmlXPathNewContext (doc); + if (ctx == NULL) + error (FATAL, "failed to make a new xpath context for %s", getInputFileName()); + + root = xmlDocGetRootElement(doc); + if (root == NULL) + { + verbose ("could not get the root node for %s\n", getInputFileName()); + goto out; + } + } + + findXMLTagsCore (ctx, root, xpathTableTable, kinds, userData); + +out: + if (usedAsEnterPoint) + { + xmlXPathFreeContext (ctx); + xmlFreeDoc (doc); + } +} + +#else + +extern void addTagXpath (const langType language, tagXpathTable *xpathTable) +{ + xpathTable->xpathCompiled = NULL; +} + +extern void findXMLTags (xmlXPathContext *ctx, xmlNode *root, + const tagXpathTableTable *xpathTableTable, + const kindOption* const kinds, void *userData) +{ +} + +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/main/options.c b/main/options.c index 01a3e1330c..5f2ce902d1 100644 --- a/main/options.c +++ b/main/options.c @@ -431,6 +431,9 @@ static const char *const Features [] = { #endif #ifdef HAVE_COPROC "coproc", +#endif +#ifdef HAVE_LIBXML + "xpath", #endif NULL }; diff --git a/main/parse.c b/main/parse.c index b15eb10d49..452af2e330 100644 --- a/main/parse.c +++ b/main/parse.c @@ -42,7 +42,8 @@ static void initializeParser (langType lang); static parserDefinition *CTagsSelfTestParser (void); static parserDefinitionFunc* BuiltInParsers[] = { CTagsSelfTestParser, - PARSER_LIST + PARSER_LIST, + XML_PARSER_LIST }; static parserDefinition** LanguageTable = NULL; static unsigned int LanguageCount = 0; @@ -112,7 +113,8 @@ extern boolean isLanguageEnabled (const langType language) if ((lang->method & METHOD_XCMD) && (!(lang->method & METHOD_XCMD_AVAILABLE)) && (lang->kinds == NULL) && - (!(lang->method & METHOD_REGEX))) + (!(lang->method & METHOD_REGEX)) && + (!(lang->method & METHOD_XPATH))) return FALSE; else return TRUE; @@ -1104,6 +1106,7 @@ static void initializeParser (langType lang) installKeywordTable (lang); installTagRegexTable (lang); + installTagXpathTable (lang); if (hasScopeActionInRegex (lang)) parser->useCork = TRUE; @@ -1979,6 +1982,15 @@ extern void useXcmdMethod (const langType language) lang->method |= METHOD_XCMD; } +extern void useXpathMethod (const langType language) +{ + parserDefinition* lang; + + Assert (0 <= language && language < (int) LanguageCount); + lang = LanguageTable [language]; + lang->method |= METHOD_XPATH; +} + extern void notifyAvailabilityXcmdMethod (const langType language) { parserDefinition* lang; @@ -2027,6 +2039,23 @@ extern void installKeywordTable (const langType language) } } +extern void installTagXpathTable (const langType language) +{ + parserDefinition* lang; + unsigned int i, j; + + Assert (0 <= language && language < (int) LanguageCount); + lang = LanguageTable [language]; + + if ((lang->tagXpathTableTable != NULL) && (lang->tagXpathInstalled == FALSE)) + { + for (i = 0; i < lang->tagXpathTableCount; ++i) + for (j = 0; j < lang->tagXpathTableTable[i].count; ++j) + addTagXpath (language, lang->tagXpathTableTable[i].table + j); + lang->tagXpathInstalled = TRUE; + } +} + /* * A parser for CTagsSelfTest (CTST) */ diff --git a/main/parse.h b/main/parse.h index b828e41954..891e51e9f2 100644 --- a/main/parse.h +++ b/main/parse.h @@ -17,6 +17,15 @@ #include "parsers.h" /* contains list of parsers */ #include "strlist.h" +#ifdef HAVE_LIBXML +#include +#include +#else +#define xmlNode void +#define xmlXPathCompExpr void +#define xmlXPathContext void +#endif + /* * MACROS */ @@ -57,6 +66,7 @@ typedef enum { METHOD_REGEX = 1 << 1, METHOD_XCMD = 1 << 2, METHOD_XCMD_AVAILABLE = 1 << 3, + METHOD_XPATH = 1 << 4, } parsingMethod; typedef struct { @@ -66,6 +76,41 @@ typedef struct { const char *const flags; } tagRegexTable; +struct sTagEntryInfo; +typedef struct sTagXpathMakeTagSpec { + int kind; + int role; + /* If make is NULL, just makeTagEntry is used instead. */ + void (*make) (xmlNode *node, + const struct sTagXpathMakeTagSpec *spec, + struct sTagEntryInfo *tag, + void *userData); +} tagXpathMakeTagSpec; + +typedef struct sTagXpathRecurSpec { + void (*enter) (xmlNode *node, + const struct sTagXpathRecurSpec *spec, + xmlXPathContext *ctx, + void *userData); +} tagXpathRecurSpec; + +typedef struct sTagXpathTable +{ + const char *const xpath; + enum { LXPATH_TABLE_DO_MAKE, LXPATH_TABLE_DO_RECUR } specType; + union { + tagXpathMakeTagSpec makeTagSpec; + tagXpathRecurSpec recurSpec; + }; + xmlXPathCompExpr* xpathCompiled; +} tagXpathTable; + +typedef struct sTagXpathTableTable { + tagXpathTable *table; + unsigned int count; +} tagXpathTableTable; + + typedef struct { const char *name; const int id; @@ -92,6 +137,8 @@ typedef struct { unsigned int tagRegexCount; const keywordTable *keywordTable; unsigned int keywordCount; + tagXpathTableTable *tagXpathTableTable; + unsigned int tagXpathTableCount; boolean invisible; /* used internally */ @@ -100,6 +147,7 @@ typedef struct { unsigned int initialized:1; /* initialize() is called or not */ unsigned int tagRegexInstalled:1; /* tagRegexTable is installed or not. */ unsigned int keywordInstalled:1; /* keywordTable is installed or not. */ + unsigned int tagXpathInstalled:1; /* tagXpathTable is installed or not. */ stringList* currentPatterns; /* current list of file name patterns */ stringList* currentExtensions; /* current list of extensions */ @@ -124,6 +172,9 @@ typedef void (*regexCallback) (const char *line, const regexMatch *matches, unsi * at minimum, set the `parser' field. */ extern parserDefinitionFunc PARSER_LIST; +#ifdef HAVE_LIBXML +extern parserDefinitionFunc XML_PARSER_LIST; +#endif /* Language processing and parsing */ extern int makeSimpleTag (const vString* const name, kindOption* const kinds, const int kind); @@ -205,6 +256,13 @@ extern void freeXcmdResources (void); extern void useXcmdMethod (const langType language); extern void notifyAvailabilityXcmdMethod (const langType language); +/* Xpath interface */ +extern void findXMLTags (xmlXPathContext *ctx, xmlNode *root, + const tagXpathTableTable *xpathTableTable, + const kindOption* const kinds, void *userData); +extern void installTagXpathTable (const langType language); +extern void addTagXpath (const langType language, tagXpathTable *xpathTable); + #endif /* CTAGS_MAIN_PARSE_H */ /* vi:set tabstop=4 shiftwidth=4: */ diff --git a/main/parsers.h b/main/parsers.h index 1b87f8c0c9..438183f564 100644 --- a/main/parsers.h +++ b/main/parsers.h @@ -12,6 +12,12 @@ #ifndef CTAGS_MAIN_PARSERS_H #define CTAGS_MAIN_PARSERS_H +#ifdef HAVE_LIBXML +#define XML_PARSER_LIST +#else +#define XML_PARSER_LIST +#endif + /* Add the name of any new parser definition function here */ #define PARSER_LIST \ AdaParser, \ diff --git a/main/read.h b/main/read.h index 3ba30f7294..1a46a07249 100644 --- a/main/read.h +++ b/main/read.h @@ -34,6 +34,10 @@ #define getInputLineNumber() File.input.lineNumber #define getInputFileName() vStringValue (File.input.name) #define getInputFilePosition() File.filePosition +#define getInputFilePositionForLine(line) \ + File.lineFposMap.pos[(((File.lineFposMap.count > (line - 1)) \ + && (File.lineFposMap.count >= 0) \ + && (line > 0))? (line - 1): 0)] #define getInputLanguage() File.input.language #define getInputLanguageName() getLanguageName (File.input.language) #define getInputFileTagPath() vStringValue (File.input.tagPath) diff --git a/source.mak b/source.mak index 447178658c..acd5773d72 100644 --- a/source.mak +++ b/source.mak @@ -47,6 +47,7 @@ MAIN_SRCS = \ main/kind.c \ main/lregex.c \ main/lxcmd.c \ + main/lxpath.c \ main/main.c \ main/mbcs.c \ main/nestlevel.c \ @@ -126,6 +127,11 @@ PARSER_SRCS = \ \ $(NULL) +XML_HEADS = +XML_SRCS = \ + \ + $(NULL) + DEBUG_HEADS = main/debug.h DEBUG_SRCS = main/debug.c diff --git a/win32/ctags_vs2013.vcxproj b/win32/ctags_vs2013.vcxproj index f4b2e3a6e3..8ff768e5f0 100644 --- a/win32/ctags_vs2013.vcxproj +++ b/win32/ctags_vs2013.vcxproj @@ -134,6 +134,7 @@ + diff --git a/win32/ctags_vs2013.vcxproj.filters b/win32/ctags_vs2013.vcxproj.filters index 9a13fa524f..487d37b2fb 100644 --- a/win32/ctags_vs2013.vcxproj.filters +++ b/win32/ctags_vs2013.vcxproj.filters @@ -220,6 +220,9 @@ Source Files\Main + + Source Files\Main + Source Files\Main