123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 导航一致性检查工具
- 版本: 1.0
- 描述: 检查所有前端页面的导航元素一致性,确保导航结构统一
- """
- import os
- import sys
- import json
- import re
- from pathlib import Path
- import html.parser
- import difflib
- class SimpleHTMLParser(html.parser.HTMLParser):
- """简单的HTML解析器,用于提取导航结构"""
-
- def __init__(self):
- super().__init__()
- self.current_tag = None
- self.nav_elements = []
- self.current_element = None
- self.nav_tags = {'header', 'nav', 'aside', 'div'}
- self.nav_classes = {'header', 'navigation', 'nav', 'sidebar', 'breadcrumb', 'menu'}
-
- def handle_starttag(self, tag, attrs):
- attrs_dict = dict(attrs)
- classes = attrs_dict.get('class', '').split()
- element_id = attrs_dict.get('id', '')
-
- # 检查是否是导航相关元素
- if (tag in self.nav_tags or
- any(nav_class in classes for nav_class in self.nav_classes) or
- any(nav_class in element_id.lower() for nav_class in self.nav_classes)):
-
- self.current_element = {
- 'tag': tag,
- 'classes': classes,
- 'id': element_id,
- 'attrs': attrs_dict
- }
-
- def handle_endtag(self, tag):
- if self.current_element and self.current_element['tag'] == tag:
- self.nav_elements.append(self.current_element)
- self.current_element = None
- # 添加项目根目录到Python路径
- project_root = Path(__file__).parent.parent.parent
- sys.path.append(str(project_root))
- class Colors:
- """终端颜色定义"""
- RED = '\033[91m'
- GREEN = '\033[92m'
- YELLOW = '\033[93m'
- BLUE = '\033[94m'
- MAGENTA = '\033[95m'
- CYAN = '\033[96m'
- WHITE = '\033[97m'
- BOLD = '\033[1m'
- UNDERLINE = '\033[4m'
- RESET = '\033[0m'
- def print_message(color, message):
- """打印带颜色的消息"""
- print(f"{color}{message}{Colors.RESET}")
- def print_success(message):
- print_message(Colors.GREEN, f"✓ {message}")
- def print_info(message):
- print_message(Colors.BLUE, f"ℹ {message}")
- def print_warning(message):
- print_message(Colors.YELLOW, f"⚠ {message}")
- def print_error(message):
- print_message(Colors.RED, f"✗ {message}")
- class NavigationConsistencyChecker:
- """导航一致性检查器"""
-
- def __init__(self):
- self.project_root = project_root
- self.front_dir = self.project_root / "output_sourcecode" / "front"
- self.navigation_issues = []
- self.pages_data = {}
-
- def check_all(self):
- """执行所有导航一致性检查"""
- print_message(Colors.BOLD + Colors.CYAN, "🔍 导航一致性检查工具")
- print_message(Colors.CYAN, "=" * 60)
-
- if not self.front_dir.exists():
- print_error(f"前端页面目录不存在: {self.front_dir}")
- return False
-
- # 获取所有HTML文件
- html_files = list(self.front_dir.glob("*.html"))
- if not html_files:
- print_error("未找到任何HTML页面文件")
- return False
-
- print_info(f"找到 {len(html_files)} 个HTML页面文件")
-
- # 解析所有页面
- self.parse_all_pages(html_files)
-
- # 执行各项检查
- all_passed = True
- all_passed &= self.check_header_consistency()
- all_passed &= self.check_sidebar_consistency()
- all_passed &= self.check_breadcrumb_consistency()
- all_passed &= self.check_navigation_links()
- all_passed &= self.check_css_class_consistency()
- all_passed &= self.check_javascript_consistency()
-
- # 输出检查结果
- self.print_summary(all_passed)
- return all_passed
-
- def parse_all_pages(self, html_files):
- """解析所有页面的导航结构"""
- print_info("解析页面导航结构...")
-
- for html_file in html_files:
- try:
- with open(html_file, 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 使用简单的正则表达式和字符串匹配来提取导航结构
- page_data = {
- 'file': html_file.name,
- 'path': str(html_file),
- 'header': self.extract_header_structure(content),
- 'sidebar': self.extract_sidebar_structure(content),
- 'breadcrumb': self.extract_breadcrumb_structure(content),
- 'navigation_links': self.extract_navigation_links(content),
- 'css_classes': self.extract_navigation_css_classes(content),
- 'javascript': self.extract_navigation_javascript(content)
- }
-
- self.pages_data[html_file.name] = page_data
-
- except Exception as e:
- print_error(f"解析页面失败 {html_file.name}: {e}")
-
- def extract_header_structure(self, soup):
- """提取头部导航结构"""
- header_selectors = ['header', '.header', '.app-header', '#header', '#app-header']
-
- for selector in header_selectors:
- header = soup.select_one(selector)
- if header:
- return {
- 'tag': header.name,
- 'classes': header.get('class', []),
- 'id': header.get('id', ''),
- 'structure': self.get_element_structure(header),
- 'menu_items': self.extract_menu_items(header)
- }
- return None
-
- def extract_sidebar_structure(self, soup):
- """提取侧边栏导航结构"""
- sidebar_selectors = ['.sidebar', '.app-sidebar', '#sidebar', '#app-sidebar', 'aside']
-
- for selector in sidebar_selectors:
- sidebar = soup.select_one(selector)
- if sidebar:
- return {
- 'tag': sidebar.name,
- 'classes': sidebar.get('class', []),
- 'id': sidebar.get('id', ''),
- 'structure': self.get_element_structure(sidebar),
- 'menu_items': self.extract_menu_items(sidebar)
- }
- return None
-
- def extract_breadcrumb_structure(self, soup):
- """提取面包屑导航结构"""
- breadcrumb_selectors = ['.breadcrumb', '.breadcrumb-nav', '#breadcrumb', 'nav[aria-label*="breadcrumb"]']
-
- for selector in breadcrumb_selectors:
- breadcrumb = soup.select_one(selector)
- if breadcrumb:
- return {
- 'tag': breadcrumb.name,
- 'classes': breadcrumb.get('class', []),
- 'id': breadcrumb.get('id', ''),
- 'structure': self.get_element_structure(breadcrumb),
- 'items': self.extract_breadcrumb_items(breadcrumb)
- }
- return None
-
- def extract_navigation_links(self, soup):
- """提取导航链接"""
- links = []
- nav_areas = soup.select('header a, .sidebar a, .breadcrumb a, nav a')
-
- for link in nav_areas:
- href = link.get('href', '')
- if href and not href.startswith('#') and not href.startswith('http'):
- links.append({
- 'href': href,
- 'text': link.get_text(strip=True),
- 'classes': link.get('class', [])
- })
- return links
-
- def extract_navigation_css_classes(self, soup):
- """提取导航相关的CSS类名"""
- nav_classes = set()
- nav_elements = soup.select('header, header *, .sidebar, .sidebar *, .breadcrumb, .breadcrumb *, nav, nav *')
-
- for element in nav_elements:
- classes = element.get('class', [])
- nav_classes.update(classes)
-
- return sorted(list(nav_classes))
-
- def extract_navigation_javascript(self, content):
- """提取导航相关的JavaScript"""
- # 查找导航相关的JavaScript代码
- js_patterns = [
- r'navigation|Navigation',
- r'sidebar|Sidebar',
- r'menu|Menu',
- r'breadcrumb|Breadcrumb'
- ]
-
- js_found = []
- for pattern in js_patterns:
- matches = re.findall(rf'{pattern}[^;]*', content, re.IGNORECASE)
- js_found.extend(matches)
-
- return js_found
-
- def get_element_structure(self, element):
- """获取元素的结构特征"""
- if not element:
- return None
-
- return {
- 'tag': element.name,
- 'classes': element.get('class', []),
- 'id': element.get('id', ''),
- 'children_count': len(element.find_all()),
- 'children_tags': [child.name for child in element.children if hasattr(child, 'name') and child.name]
- }
-
- def extract_menu_items(self, nav_element):
- """提取菜单项"""
- if not nav_element:
- return []
-
- menu_items = []
- links = nav_element.find_all('a')
-
- for link in links:
- menu_items.append({
- 'href': link.get('href', ''),
- 'text': link.get_text(strip=True),
- 'classes': link.get('class', [])
- })
-
- return menu_items
-
- def extract_breadcrumb_items(self, breadcrumb_element):
- """提取面包屑项"""
- if not breadcrumb_element:
- return []
-
- items = []
- links = breadcrumb_element.find_all(['a', 'span', 'li'])
-
- for item in links:
- items.append({
- 'tag': item.name,
- 'href': item.get('href', '') if item.name == 'a' else '',
- 'text': item.get_text(strip=True),
- 'classes': item.get('class', [])
- })
-
- return items
-
- def check_header_consistency(self):
- """检查头部导航一致性"""
- print_info("检查头部导航一致性...")
-
- headers = [page['header'] for page in self.pages_data.values() if page['header']]
-
- if not headers:
- print_error("未找到任何头部导航结构")
- return False
-
- # 检查结构一致性
- reference_header = headers[0]
- issues = []
-
- for page_name, page_data in self.pages_data.items():
- header = page_data['header']
- if not header:
- issues.append(f"{page_name}: 缺少头部导航")
- continue
-
- # 检查基本结构
- if header['tag'] != reference_header['tag']:
- issues.append(f"{page_name}: 头部标签不一致 ({header['tag']} vs {reference_header['tag']})")
-
- if set(header['classes']) != set(reference_header['classes']):
- issues.append(f"{page_name}: 头部CSS类不一致")
-
- # 检查菜单项数量
- if len(header['menu_items']) != len(reference_header['menu_items']):
- issues.append(f"{page_name}: 头部菜单项数量不一致")
-
- if issues:
- for issue in issues:
- print_error(f" {issue}")
- return False
- else:
- print_success("头部导航结构一致")
- return True
-
- def check_sidebar_consistency(self):
- """检查侧边栏一致性"""
- print_info("检查侧边栏导航一致性...")
-
- sidebars = [page['sidebar'] for page in self.pages_data.values() if page['sidebar']]
-
- if not sidebars:
- print_warning("未找到侧边栏导航结构")
- return True # 如果没有侧边栏,不算错误
-
- # 检查结构一致性
- reference_sidebar = sidebars[0]
- issues = []
-
- for page_name, page_data in self.pages_data.items():
- sidebar = page_data['sidebar']
- if not sidebar and sidebars:
- issues.append(f"{page_name}: 缺少侧边栏导航")
- continue
-
- if sidebar:
- # 检查基本结构
- if sidebar['tag'] != reference_sidebar['tag']:
- issues.append(f"{page_name}: 侧边栏标签不一致")
-
- if set(sidebar['classes']) != set(reference_sidebar['classes']):
- issues.append(f"{page_name}: 侧边栏CSS类不一致")
-
- if issues:
- for issue in issues:
- print_error(f" {issue}")
- return False
- else:
- print_success("侧边栏导航结构一致")
- return True
-
- def check_breadcrumb_consistency(self):
- """检查面包屑一致性"""
- print_info("检查面包屑导航一致性...")
-
- breadcrumbs = [page['breadcrumb'] for page in self.pages_data.values() if page['breadcrumb']]
-
- if not breadcrumbs:
- print_warning("未找到面包屑导航结构")
- return True # 如果没有面包屑,不算错误
-
- # 检查结构一致性
- reference_breadcrumb = breadcrumbs[0]
- issues = []
-
- for page_name, page_data in self.pages_data.items():
- breadcrumb = page_data['breadcrumb']
- if not breadcrumb and breadcrumbs:
- issues.append(f"{page_name}: 缺少面包屑导航")
- continue
-
- if breadcrumb:
- # 检查基本结构
- if breadcrumb['tag'] != reference_breadcrumb['tag']:
- issues.append(f"{page_name}: 面包屑标签不一致")
-
- if set(breadcrumb['classes']) != set(reference_breadcrumb['classes']):
- issues.append(f"{page_name}: 面包屑CSS类不一致")
-
- if issues:
- for issue in issues:
- print_error(f" {issue}")
- return False
- else:
- print_success("面包屑导航结构一致")
- return True
-
- def check_navigation_links(self):
- """检查导航链接有效性"""
- print_info("检查导航链接有效性...")
-
- issues = []
- all_files = set(page_data['file'] for page_data in self.pages_data.values())
-
- for page_name, page_data in self.pages_data.items():
- for link in page_data['navigation_links']:
- href = link['href']
- if href.endswith('.html'):
- # 检查相对路径
- target_file = href.split('/')[-1] # 获取文件名
- if target_file not in all_files:
- issues.append(f"{page_name}: 导航链接指向不存在的文件 '{href}'")
-
- if issues:
- for issue in issues:
- print_error(f" {issue}")
- return False
- else:
- print_success("所有导航链接有效")
- return True
-
- def check_css_class_consistency(self):
- """检查CSS类名一致性"""
- print_info("检查导航CSS类名一致性...")
-
- # 收集所有页面的导航CSS类
- all_nav_classes = set()
- for page_data in self.pages_data.values():
- all_nav_classes.update(page_data['css_classes'])
-
- # 检查每个页面是否包含核心导航类
- core_nav_classes = {
- 'header', 'app-header', 'nav', 'navigation',
- 'sidebar', 'app-sidebar', 'menu',
- 'breadcrumb', 'breadcrumb-nav'
- }
-
- issues = []
- for page_name, page_data in self.pages_data.items():
- page_classes = set(page_data['css_classes'])
- missing_core_classes = core_nav_classes - page_classes
-
- # 如果页面缺少太多核心导航类,可能存在问题
- if len(missing_core_classes) > len(core_nav_classes) * 0.7:
- issues.append(f"{page_name}: 可能缺少导航相关的CSS类")
-
- if issues:
- for issue in issues:
- print_warning(f" {issue}")
- return True # 这是警告,不是错误
- else:
- print_success("导航CSS类名使用合理")
- return True
-
- def check_javascript_consistency(self):
- """检查JavaScript一致性"""
- print_info("检查导航JavaScript一致性...")
-
- # 这是一个简化的检查,主要确保页面包含导航相关的JavaScript
- issues = []
-
- for page_name, page_data in self.pages_data.items():
- js_code = page_data['javascript']
- if not js_code:
- issues.append(f"{page_name}: 可能缺少导航相关的JavaScript代码")
-
- if issues:
- for issue in issues:
- print_warning(f" {issue}")
- return True # 这是警告,不是错误
- else:
- print_success("导航JavaScript代码存在")
- return True
-
- def print_summary(self, all_passed):
- """打印检查结果摘要"""
- print_message(Colors.CYAN, "=" * 60)
-
- if all_passed:
- print_success("🎉 所有导航一致性检查通过!")
- print_info("页面导航结构完全一致,符合软著申请质量要求")
- else:
- print_error("❌ 发现导航一致性问题")
- print_warning("请修复上述问题以确保导航的一致性")
- print_info("建议:")
- print(" 1. 确保所有页面使用相同的导航组件模板")
- print(" 2. 检查CSS类名和ID的一致性")
- print(" 3. 验证所有导航链接的有效性")
- print(" 4. 确认当前页面在导航中的正确标识")
-
- print_message(Colors.CYAN, "=" * 60)
- def main():
- """主函数"""
- try:
- checker = NavigationConsistencyChecker()
- success = checker.check_all()
-
- # 返回适当的退出码
- sys.exit(0 if success else 1)
-
- except Exception as e:
- print_error(f"检查过程中发生错误: {e}")
- import traceback
- traceback.print_exc()
- sys.exit(1)
- if __name__ == "__main__":
- main()
|