@@ -28,6 +28,10 @@ shconfparser is a vendor independent library where you can parse the following f
2828- Table structure * ` i.e. show ip interface ` *
2929- Data * ` i.e. show version ` *
3030
31+ YAML Format Output
32+
33+ ![ show run to YAML structure] ( https://raw.githubusercontent.com/kirankotari/shconfparser/master/asserts/img/sh_run_yaml.png )
34+
3135Tree Structure
3236
3337![ show run to tree structure] ( https://raw.githubusercontent.com/kirankotari/shconfparser/master/asserts/img/sh_run.png )
@@ -43,7 +47,8 @@ Table Structure
4347🔒 ** Type Safe** - Full type hints and py.typed marker
4448🎯 ** Vendor Independent** - Works with any network device configuration
4549📊 ** Multiple Formats** - Parse trees, tables, and unstructured data
46- 🔍 ** XPath Queries** - Search configs with XPath-style syntax (NEW!)
50+ 📄 ** Format Flexibility** - Output as JSON or YAML structures
51+ 🔍 ** XPath Queries** - NSO-style queries with context tracking (NEW!)
4752🧪 ** Well Tested** - 80%+ code coverage, tested on Python 3.8-3.13
4853
4954## Quick Start
@@ -62,27 +67,30 @@ uv pip install shconfparser
6267
6368### Basic Usage
6469
65- ** Single show command:**
70+ ** Single show command with YAML format (recommended) :**
6671``` python
6772from shconfparser.parser import Parser
6873
69- p = Parser()
74+ # Use YAML format for cleaner output and XPath support
75+ p = Parser(output_format = ' yaml' )
7076data = p.read(' running_config.txt' )
7177
7278# Parse directly (no split needed for single show running command)
7379tree = p.parse_tree(data)
7480print (p.dump(tree, indent = 2 ))
81+
82+ # Query with XPath
83+ result = p.xpath(' /hostname' )
84+ print (result.data) # 'R1'
7585```
7686
7787<details >
78- <summary >Alternative: Access internal properties </summary >
88+ <summary >Alternative: JSON format (backward compatible) </summary >
7989
8090``` python
81- p = Parser()
82- p.read(' running_config.txt' )
83-
84- # Access reader data directly
85- tree = p.parse_tree(p.r.data)
91+ p = Parser() # Default is JSON format (OrderedDict)
92+ data = p.read(' running_config.txt' )
93+ tree = p.parse_tree(data)
8694print (p.dump(tree, indent = 4 ))
8795```
8896</details >
@@ -91,7 +99,7 @@ print(p.dump(tree, indent=4))
9199``` python
92100from shconfparser.parser import Parser
93101
94- p = Parser()
102+ p = Parser(output_format = ' yaml ' ) # YAML format recommended
95103data = p.read(' multiple_commands.txt' ) # Contains multiple show outputs
96104data = p.split(data) # Split into separate commands
97105data.keys()
@@ -208,60 +216,157 @@ print(match)
208216# {'Device ID': 'R2', 'Local Intrfce': 'Fas 0/0', ...}
209217```
210218
211- ### XPath-Style Queries (New in 3.0!)
219+ ### Output Format Selection (New in 3.0!)
212220
213- Query parsed configuration trees using XPath-style syntax :
221+ Parse configurations to JSON (OrderedDict) or YAML-friendly dict structures :
214222
215223``` python
216224from shconfparser.parser import Parser
217225
226+ # Default: JSON format (OrderedDict - backward compatible)
218227p = Parser()
219- data = p.read(' multiple_commands.txt' )
220- data = p.split(data)
228+ data = p.read(' running_config.txt' )
229+ tree = p.parse_tree(data) # Returns OrderedDict
230+ print (type (tree)) # <class 'collections.OrderedDict'>
221231
222- # Parse the running config into a tree
223- data[' running' ] = p.parse_tree(data[' running' ])
232+ # YAML format: cleaner hierarchical structure
233+ p = Parser(output_format = ' yaml' )
234+ data = p.read(' running_config.txt' )
235+ tree_yaml = p.parse_tree(data) # Returns dict with nested structure
236+ print (type (tree_yaml)) # <class 'dict'>
237+
238+ # Override format per call
239+ p = Parser() # Default is JSON
240+ tree_json = p.parse_tree(data) # OrderedDict
241+ tree_yaml = p.parse_tree(data, format = ' yaml' ) # dict
242+
243+ # YAML structure example:
244+ # Input: "interface FastEthernet0/0" with nested config
245+ # JSON: {"interface FastEthernet0/0": {...}}
246+ # YAML: {"interface": {"FastEthernet0/0": {...}}}
247+ ```
224248
225- # Now use XPath queries on the parsed tree
226- # Find all IP addresses anywhere in config
227- result = p.xpath(' //ip/address' , tree = data[' running' ])
228- print (f " Found { result.count} IP addresses " )
249+ ** Format Comparison:**
229250
230- # Get specific interface configuration
231- result = p.xpath(' /interface/FastEthernet0-0/ip/address' , tree = data[' running' ])
232- if result:
233- print (f " IP: { result.data} " )
251+ ``` python
252+ # JSON format (default) - preserves exact CLI structure
253+ {
254+ " interface FastEthernet0/0" : {
255+ " ip address 1.1.1.1 255.255.255.0" : " " ,
256+ " duplex auto" : " "
257+ }
258+ }
259+
260+ # YAML format - hierarchical and human-readable
261+ {
262+ " interface" : {
263+ " FastEthernet0/0" : {
264+ " ip" : {
265+ " address" : " 1.1.1.1 255.255.255.0"
266+ },
267+ " duplex" : " auto"
268+ }
269+ }
270+ }
271+ ```
234272
235- # List all interface names using wildcard
236- result = p.xpath(' /interface/*' , tree = data[' running' ])
237- print (f " Interfaces: { ' , ' .join(result.matches)} " )
273+ ** Benefits of YAML format:**
274+ - Cleaner hierarchy for nested configurations
275+ - Better for programmatic access
276+ - Easier to convert to actual YAML files
277+ - Natural structure for complex configs
278+ - Required for XPath queries
238279
239- # Filter interfaces by pattern
240- result = p.xpath(' /interface[FastEthernet*]/description' , tree = data[' running' ])
241- for desc in result.matches:
242- print (f " - { desc} " )
243- ```
280+ ### XPath Queries (New in 3.0!)
244281
245- ** Supported XPath Features:**
246- - ** Absolute paths** : ` /interface/FastEthernet0-0/ip/address `
247- - ** Recursive search** : ` //ip/address ` (find anywhere)
248- - ** Wildcards** : ` /interface/*/description ` (all interfaces)
249- - ** Predicates** : ` /interface[GigabitEthernet*] ` (pattern matching)
250- - ** Root access** : ` / ` (entire tree)
282+ Query YAML-formatted configurations using NSO-style XPath with optional context tracking:
251283
252- ** Or use stored tree directly:**
253284``` python
254- p = Parser()
285+ from shconfparser.parser import Parser
286+
287+ # XPath requires YAML format
288+ p = Parser(output_format = ' yaml' )
255289data = p.read(' running_config.txt' )
256- p.parse_tree(data) # Stores in p.data
290+ tree = p.parse_tree(data)
291+
292+ # Simple queries
293+ result = p.xpath(' /hostname' )
294+ print (result.data) # 'R1'
257295
258- # Query uses stored tree automatically
259- result = p.xpath(' //ip/address' )
260- for addr in result.matches:
261- print (addr)
296+ # Wildcards - find all interface duplex settings
297+ result = p.xpath(' /interface/*/duplex' )
298+ print (result.matches) # ['auto', 'auto']
299+ print (result.count) # 2
300+
301+ # Predicates with slashes (network interface names)
302+ result = p.xpath(' /interface[FastEthernet0/0]/duplex' )
303+ print (result.data) # 'auto'
304+
305+ # Recursive search - find anywhere in tree
306+ result = p.xpath(' //duplex' )
307+ print (result.matches) # ['auto', 'auto']
308+
309+ # Predicate wildcards
310+ result = p.xpath(' /interface[FastEthernet*]/ip/address' )
311+ print (result.data) # '1.1.1.1 255.255.255.0'
312+ ```
313+
314+ ** Context Options** - Solve the "which interface?" problem:
315+
316+ ``` python
317+ # Problem: Can't identify source with wildcards
318+ result = p.xpath(' /interface/*/duplex' )
319+ print (result.matches) # ['auto', 'auto'] - Which interface?
320+
321+ # Solution 1: context='none' (default - just values)
322+ result = p.xpath(' /interface/*/duplex' , context = ' none' )
323+ print (result.matches) # ['auto', 'auto']
324+
325+ # Solution 2: context='partial' (from wildcard match point)
326+ result = p.xpath(' /interface/*/duplex' , context = ' partial' )
327+ print (result.matches)
328+ # [{'FastEthernet0/0': {'duplex': 'auto'}},
329+ # {'FastEthernet0/1': {'duplex': 'auto'}}]
330+
331+ # Solution 3: context='full' (complete tree hierarchy)
332+ result = p.xpath(' /interface/*/duplex' , context = ' full' )
333+ print (result.matches)
334+ # [{'interface': {'FastEthernet0/0': {'duplex': 'auto'}}},
335+ # {'interface': {'FastEthernet0/1': {'duplex': 'auto'}}}]
336+
337+ # Path tracking (always available)
338+ result = p.xpath(' /interface/*/speed' )
339+ print (result.paths)
340+ # [['interface', 'FastEthernet0/0', 'speed'],
341+ # ['interface', 'FastEthernet0/1', 'speed']]
342+ ```
343+
344+ ** XPath Features:**
345+ - ✅ Absolute paths: ` /interface/FastEthernet0/0/duplex `
346+ - ✅ Recursive search: ` //duplex ` (find anywhere)
347+ - ✅ Wildcards: ` /interface/*/duplex `
348+ - ✅ Predicates: ` /interface[FastEthernet0/0] `
349+ - ✅ Predicate wildcards: ` /interface[FastEthernet*] `
350+ - ✅ Context tracking: See which match came from where
351+ - ✅ Path tracking: ` result.paths ` shows path components
352+
353+ ** XPathResult Structure:**
354+ ``` python
355+ result = p.xpath(' //duplex' )
356+ print (result.success) # True
357+ print (result.data) # First match: 'auto'
358+ print (result.matches) # All matches: ['auto', 'auto']
359+ print (result.count) # Number of matches: 2
360+ print (result.query) # Original query: '//duplex'
361+ print (result.paths) # Path to each match
362+ print (result.error) # Error message if failed
363+
364+ # Boolean check
365+ if result:
366+ print (f " Found { result.count} matches " )
262367```
263368
264- See [ docs/XPATH_GUIDE.md ] ( docs/XPATH_GUIDE.md ) for complete XPath documentation .
369+ ** Note: ** XPath queries only work with ` output_format='yaml' ` . JSON format (OrderedDict) preserves exact CLI structure and should use traditional dict navigation .
265370
266371### Alternative: Using Individual Components
267372
0 commit comments